Overview

Namespaces

  • TokenReflection
    • Broker
      • Backend
    • Dummy
    • Exception
    • Invalid
    • Php
    • Stream

Classes

  • FileStream
  • StreamBase
  • StringStream
  • Overview
  • Namespace
  • Class
  • Tree
  • Download
  1: <?php
  2: /**
  3:  * PHP Token Reflection
  4:  *
  5:  * Version 1.3.1
  6:  *
  7:  * LICENSE
  8:  *
  9:  * This source file is subject to the new BSD license that is bundled
 10:  * with this library in the file LICENSE.
 11:  *
 12:  * @author Ondřej Nešpor
 13:  * @author Jaroslav Hanslík
 14:  */
 15: 
 16: namespace TokenReflection\Stream;
 17: 
 18: use TokenReflection\Exception;
 19: use SeekableIterator, Countable, ArrayAccess, Serializable;
 20: 
 21: // Ensure that we check if we have a native support of traits
 22: if (!defined('NATIVE_TRAITS')) {
 23:     require_once __DIR__ . '/../Broker.php';
 24: }
 25: 
 26: /**
 27:  * Token stream iterator base class.
 28:  */
 29: abstract class StreamBase implements SeekableIterator, Countable, ArrayAccess, Serializable
 30: {
 31:     /**
 32:      * Token source file name.
 33:      *
 34:      * @var string
 35:      */
 36:     protected $fileName = 'unknown';
 37: 
 38:     /**
 39:      * Tokens storage.
 40:      *
 41:      * @var array
 42:      */
 43:     private $tokens = array();
 44: 
 45:     /**
 46:      * Internal pointer.
 47:      *
 48:      * @var integer
 49:      */
 50:     private $position = 0;
 51: 
 52:     /**
 53:      * Token stream size.
 54:      *
 55:      * @var integer
 56:      */
 57:     private $count = 0;
 58: 
 59:     /**
 60:      * Constructor.
 61:      *
 62:      * Protected to ensure that the concrete implementation will override it.
 63:      *
 64:      * @throws \TokenReflection\Exception\StreamException If tokenizer PHP extension is missing.
 65:      */
 66:     protected function __construct()
 67:     {
 68:         if (!extension_loaded('tokenizer')) {
 69:             throw new Exception\StreamException($this, 'The tokenizer PHP extension is not loaded.', Exception\StreamException::PHP_EXT_MISSING);
 70:         }
 71:     }
 72: 
 73:     /**
 74:      * Extracts tokens from a source code.
 75:      *
 76:      * @param string $source Source code
 77:      */
 78:     protected final function processSource($source)
 79:     {
 80:         $stream = @token_get_all(str_replace(array("\r\n", "\r"), "\n", $source));
 81: 
 82:         static $checkLines = array(T_COMMENT => true, T_WHITESPACE => true, T_DOC_COMMENT => true, T_INLINE_HTML => true, T_ENCAPSED_AND_WHITESPACE => true, T_CONSTANT_ENCAPSED_STRING => true);
 83: 
 84:         foreach ($stream as $position => $token) {
 85:             if (is_array($token)) {
 86:                 if (!NATIVE_TRAITS && T_STRING === $token[0]) {
 87:                     $lValue = strtolower($token[1]);
 88:                     if ('trait' === $lValue) {
 89:                         $token[0] = T_TRAIT;
 90:                     } elseif ('insteadof' === $lValue) {
 91:                         $token[0] = T_INSTEADOF;
 92:                     } elseif ('__TRAIT__' === $token[1]) {
 93:                         $token[0] = T_TRAIT_C;
 94:                     } elseif ('callable' === $lValue) {
 95:                         $token[0] = T_CALLABLE;
 96:                     }
 97:                 }
 98: 
 99:                 $this->tokens[] = $token;
100:             } else {
101:                 $previous = $this->tokens[$position - 1];
102:                 $line = $previous[2];
103:                 if (isset($checkLines[$previous[0]])) {
104:                     $line += substr_count($previous[1], "\n");
105:                 }
106: 
107:                 $this->tokens[] = array($token, $token, $line);
108:             }
109:         }
110: 
111:         $this->count = count($this->tokens);
112:     }
113: 
114:     /**
115:      * Returns the file name this is a part of.
116:      *
117:      * @return string
118:      */
119:     public function getFileName()
120:     {
121:         return $this->fileName;
122:     }
123: 
124:     /**
125:      * Returns the original source code.
126:      *
127:      * @return string
128:      */
129:     public function getSource()
130:     {
131:         return $this->getSourcePart();
132:     }
133: 
134:     /**
135:      * Returns a part of the source code.
136:      *
137:      * @param mixed $start Start offset
138:      * @param mixed $end End offset
139:      * @return string
140:      */
141:     public function getSourcePart($start = null, $end = null)
142:     {
143:         $start = (int) $start;
144:         $end = null === $end ? ($this->count - 1) : (int) $end;
145: 
146:         $source = '';
147:         for ($i = $start; $i <= $end; $i++) {
148:             $source .= $this->tokens[$i][1];
149:         }
150:         return $source;
151:     }
152: 
153:     /**
154:      * Finds the position of the token of the given type.
155:      *
156:      * @param integer|string $type Token type
157:      * @return \TokenReflection\Stream|boolean
158:      */
159:     public function find($type)
160:     {
161:         $actual = $this->position;
162:         while (isset($this->tokens[$this->position])) {
163:             if ($type === $this->tokens[$this->position][0]) {
164:                 return $this;
165:             }
166: 
167:             $this->position++;
168:         }
169: 
170:         $this->position = $actual;
171:         return false;
172:     }
173: 
174:     /**
175:      * Returns the position of the token with the matching bracket.
176:      *
177:      * @return \TokenReflection\Stream
178:      * @throws \TokenReflection\Exception\RuntimeException If out of the token stream.
179:      * @throws \TokenReflection\Exception\RuntimeException If there is no bracket at the current position.
180:      * @throws \TokenReflection\Exception\RuntimeException If the matching bracket could not be found.
181:      */
182:     public function findMatchingBracket()
183:     {
184:         static $brackets = array(
185:             '(' => ')',
186:             '{' => '}',
187:             '[' => ']',
188:             T_CURLY_OPEN => '}',
189:             T_DOLLAR_OPEN_CURLY_BRACES => '}'
190:         );
191: 
192:         if (!$this->valid()) {
193:             throw new Exception\StreamException($this, 'Out of token stream.', Exception\StreamException::READ_BEYOND_EOS);
194:         }
195: 
196:         $position = $this->position;
197: 
198:         $bracket = $this->tokens[$this->position][0];
199: 
200:         if (!isset($brackets[$bracket])) {
201:             throw new Exception\StreamException($this, sprintf('There is no usable bracket at position "%d".', $position), Exception\StreamException::DOES_NOT_EXIST);
202:         }
203: 
204:         $searching = $brackets[$bracket];
205: 
206:         $level = 0;
207:         while (isset($this->tokens[$this->position])) {
208:             $type = $this->tokens[$this->position][0];
209:             if ($searching === $type) {
210:                 $level--;
211:             } elseif ($bracket === $type || ($searching === '}' && ('{' === $type || T_CURLY_OPEN === $type || T_DOLLAR_OPEN_CURLY_BRACES === $type))) {
212:                 $level++;
213:             }
214: 
215:             if (0 === $level) {
216:                 return $this;
217:             }
218: 
219:             $this->position++;
220:         }
221: 
222:         throw new Exception\StreamException($this, sprintf('Could not find the end bracket "%s" of the bracket at position "%d".', $searching, $position), Exception\StreamException::DOES_NOT_EXIST);
223:     }
224: 
225:     /**
226:      * Skips whitespaces and comments next to the current position.
227:      *
228:      * @param boolean $skipDocBlocks Skip docblocks as well
229:      * @return \TokenReflection\Stream\StreamBase
230:      */
231:     public function skipWhitespaces($skipDocBlocks = false)
232:     {
233:         static $skipped = array(T_WHITESPACE => true, T_COMMENT => true, T_DOC_COMMENT => true);
234: 
235:         do {
236:             $this->position++;
237:         } while (isset($this->tokens[$this->position]) && isset($skipped[$this->tokens[$this->position][0]]) && ($skipDocBlocks || $this->tokens[$this->position][0] !== T_DOC_COMMENT));
238: 
239:         return $this;
240:     }
241: 
242:     /**
243:      * Returns if the token stream is at a whitespace position.
244:      *
245:      * @param boolean $docBlock Consider docblocks as whitespaces
246:      * @return boolean
247:      */
248:     public function isWhitespace($docBlock = false)
249:     {
250:         static $skipped = array(T_WHITESPACE => true, T_COMMENT => true, T_DOC_COMMENT => false);
251: 
252:         if (!$this->valid()) {
253:             return false;
254:         }
255: 
256:         return $docBlock ? isset($skipped[$this->getType()]) : !empty($skipped[$this->getType()]);
257:     }
258: 
259:     /**
260:      * Checks if there is a token of the given type at the given position.
261:      *
262:      * @param integer|string $type Token type
263:      * @param integer $position Position; if none given, consider the current iteration position
264:      * @return boolean
265:      */
266:     public function is($type, $position = -1)
267:     {
268:         return $type === $this->getType($position);
269:     }
270: 
271:     /**
272:      * Returns the type of a token.
273:      *
274:      * @param integer $position Token position; if none given, consider the current iteration position
275:      * @return string|integer|null
276:      */
277:     public function getType($position = -1)
278:     {
279:         if (-1 === $position) {
280:             $position = $this->position;
281:         }
282: 
283:         return isset($this->tokens[$position]) ? $this->tokens[$position][0] : null;
284:     }
285: 
286:     /**
287:      * Returns the current token value.
288:      *
289:      * @param integer $position Token position; if none given, consider the current iteration position
290:      * @return stirng
291:      */
292:     public function getTokenValue($position = -1)
293:     {
294:         if (-1 === $position) {
295:             $position = $this->position;
296:         }
297: 
298:         return isset($this->tokens[$position]) ? $this->tokens[$position][1] : null;
299:     }
300: 
301:     /**
302:      * Returns the token type name.
303:      *
304:      * @param integer $position Token position; if none given, consider the current iteration position
305:      * @return string|null
306:      */
307:     public function getTokenName($position = -1)
308:     {
309:         $type = $this->getType($position);
310:         if (is_string($type)) {
311:             return $type;
312:         } elseif (T_TRAIT === $type) {
313:             return 'T_TRAIT';
314:         } elseif (T_INSTEADOF === $type) {
315:             return 'T_INSTEADOF';
316:         } elseif (T_CALLABLE === $type) {
317:             return 'T_CALLABLE';
318:         }
319: 
320:         return token_name($type);
321:     }
322: 
323:     /**
324:      * Stream serialization.
325:      *
326:      * @return string
327:      */
328:     public function serialize()
329:     {
330:         return serialize(array($this->fileName, $this->tokens));
331:     }
332: 
333:     /**
334:      * Restores the stream from the serialized state.
335:      *
336:      * @param string $serialized Serialized form
337:      * @throws \TokenReflection\Exception\StreamException On deserialization error.
338:      */
339:     public function unserialize($serialized)
340:     {
341:         $data = @unserialize($serialized);
342:         if (false === $data) {
343:             throw new Exception\StreamException($this, 'Could not deserialize the serialized data.', Exception\StreamException::SERIALIZATION_ERROR);
344:         }
345:         if (2 !== count($data) || !is_string($data[0]) || !is_array($data[1])) {
346:             throw new Exception\StreamException($this, 'Invalid serialization data.', Exception\StreamException::SERIALIZATION_ERROR);
347:         }
348: 
349:         $this->fileName = $data[0];
350:         $this->tokens = $data[1];
351:         $this->count = count($this->tokens);
352:         $this->position = 0;
353:     }
354: 
355:     /**
356:      * Checks of there is a token with the given index.
357:      *
358:      * @param integer $offset Token index
359:      * @return boolean
360:      */
361:     public function offsetExists($offset)
362:     {
363:         return isset($this->tokens[$offset]);
364:     }
365: 
366:     /**
367:      * Removes a token.
368:      *
369:      * Unsupported.
370:      *
371:      * @param integer $offset Position
372:      * @throws \TokenReflection\Exception\StreamException Unsupported.
373:      */
374:     public function offsetUnset($offset)
375:     {
376:         throw new Exception\StreamException($this, 'Removing of tokens from the stream is not supported.', Exception\StreamException::UNSUPPORTED);
377:     }
378: 
379:     /**
380:      * Returns a token at the given index.
381:      *
382:      * @param integer $offset Token index
383:      * @return mixed
384:      */
385:     public function offsetGet($offset)
386:     {
387:         return isset($this->tokens[$offset]) ? $this->tokens[$offset] : null;
388:     }
389: 
390:     /**
391:      * Sets a value of a particular token.
392:      *
393:      * Unsupported
394:      *
395:      * @param integer $offset Position
396:      * @param mixed $value Value
397:      * @throws \TokenReflection\Exception\StreamException Unsupported.
398:      */
399:     public function offsetSet($offset, $value)
400:     {
401:         throw new Exception\StreamException($this, 'Setting token values is not supported.', Exception\StreamException::UNSUPPORTED);
402:     }
403: 
404:     /**
405:      * Returns the current internal pointer value.
406:      *
407:      * @return integer
408:      */
409:     public function key()
410:     {
411:         return $this->position;
412:     }
413: 
414:     /**
415:      * Advances the internal pointer.
416:      *
417:      * @return \TokenReflection\Stream
418:      */
419:     public function next()
420:     {
421:         $this->position++;
422:         return $this;
423:     }
424: 
425:     /**
426:      * Sets the internal pointer to zero.
427:      *
428:      * @return \TokenReflection\Stream
429:      */
430:     public function rewind()
431:     {
432:         $this->position = 0;
433:         return $this;
434:     }
435: 
436:     /**
437:      * Returns the current token.
438:      *
439:      * @return array|null
440:      */
441:     public function current()
442:     {
443:         return isset($this->tokens[$this->position]) ? $this->tokens[$this->position] : null;
444:     }
445: 
446:     /**
447:      * Checks if there is a token on the current position.
448:      *
449:      * @return boolean
450:      */
451:     public function valid()
452:     {
453:         return isset($this->tokens[$this->position]);
454:     }
455: 
456:     /**
457:      * Returns the number of tokens in the stream.
458:      *
459:      * @return integer
460:      */
461:     public function count()
462:     {
463:         return $this->count;
464:     }
465: 
466:     /**
467:      * Sets the internal pointer to the given value.
468:      *
469:      * @param integer $position New position
470:      * @return \TokenReflection\Stream
471:      */
472:     public function seek($position)
473:     {
474:         $this->position = (int) $position;
475:         return $this;
476:     }
477: 
478:     /**
479:      * Returns the stream source code.
480:      *
481:      * @return string
482:      */
483:     public function __toString()
484:     {
485:         return $this->getSource();
486:     }
487: }
488: 
PHP Token Reflection API documentation generated by ApiGen 2.8.0