Overview

Namespaces

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

Classes

  • Broker
  • ReflectionAnnotation
  • ReflectionBase
  • ReflectionClass
  • ReflectionConstant
  • ReflectionElement
  • ReflectionFile
  • ReflectionFileNamespace
  • ReflectionFunction
  • ReflectionFunctionBase
  • ReflectionMethod
  • ReflectionNamespace
  • ReflectionParameter
  • ReflectionProperty
  • Resolver

Interfaces

  • IReflection
  • IReflectionClass
  • IReflectionConstant
  • IReflectionExtension
  • IReflectionFunction
  • IReflectionFunctionBase
  • IReflectionMethod
  • IReflectionNamespace
  • IReflectionParameter
  • IReflectionProperty
  • 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;
 17: 
 18: use TokenReflection\Exception;
 19: 
 20: /**
 21:  * Docblock parser.
 22:  */
 23: class ReflectionAnnotation
 24: {
 25:     /**
 26:      * Main description annotation identifier.
 27:      *
 28:      * White space at the beginning on purpose.
 29:      *
 30:      * @var string
 31:      */
 32:     const SHORT_DESCRIPTION = ' short_description';
 33: 
 34:     /**
 35:      * Sub description annotation identifier.
 36:      *
 37:      * White space at the beginning on purpose.
 38:      *
 39:      * @var string
 40:      */
 41:     const LONG_DESCRIPTION = ' long_description';
 42: 
 43:     /**
 44:      * Copydoc recursion stack.
 45:      *
 46:      * Prevents from infinite loops when using the @copydoc annotation.
 47:      *
 48:      * @var array
 49:      */
 50:     private static $copydocStack = array();
 51: 
 52:     /**
 53:      * List of applied templates.
 54:      *
 55:      * @var array
 56:      */
 57:     private $templates = array();
 58: 
 59:     /**
 60:      * Parsed annotations.
 61:      *
 62:      * @var array
 63:      */
 64:     private $annotations;
 65: 
 66:     /**
 67:      * Element docblock.
 68:      *
 69:      * False if none.
 70:      *
 71:      * @var string|boolean
 72:      */
 73:     private $docComment;
 74: 
 75:     /**
 76:      * Parent reflection object.
 77:      *
 78:      * @var \TokenReflection\ReflectionBase
 79:      */
 80:     private $reflection;
 81: 
 82:     /**
 83:      * Constructor.
 84:      *
 85:      * @param \TokenReflection\ReflectionBase $reflection Parent reflection object
 86:      * @param string|boolean $docComment Docblock definition
 87:      */
 88:     public function __construct(ReflectionBase $reflection, $docComment = false)
 89:     {
 90:         $this->reflection = $reflection;
 91:         $this->docComment = $docComment ?: false;
 92:     }
 93: 
 94:     /**
 95:      * Returns the docblock.
 96:      *
 97:      * @return string|boolean
 98:      */
 99:     public function getDocComment()
100:     {
101:         return $this->docComment;
102:     }
103: 
104:     /**
105:      * Returns if the current docblock contains the requrested annotation.
106:      *
107:      * @param string $annotation Annotation name
108:      * @return boolean
109:      */
110:     public function hasAnnotation($annotation)
111:     {
112:         if (null === $this->annotations) {
113:             $this->parse();
114:         }
115: 
116:         return isset($this->annotations[$annotation]);
117:     }
118: 
119:     /**
120:      * Returns a particular annotation value.
121:      *
122:      * @param string $annotation Annotation name
123:      * @return string|array|null
124:      */
125:     public function getAnnotation($annotation)
126:     {
127:         if (null === $this->annotations) {
128:             $this->parse();
129:         }
130: 
131:         return isset($this->annotations[$annotation]) ? $this->annotations[$annotation] : null;
132:     }
133: 
134:     /**
135:      * Returns all parsed annotations.
136:      *
137:      * @return array
138:      */
139:     public function getAnnotations()
140:     {
141:         if (null === $this->annotations) {
142:             $this->parse();
143:         }
144: 
145:         return $this->annotations;
146:     }
147: 
148:     /**
149:      * Sets Docblock templates.
150:      *
151:      * @param array $templates Docblock templates
152:      * @return \TokenReflection\ReflectionAnnotation
153:      * @throws \TokenReflection\Exception\RuntimeException If an invalid annotation template was provided.
154:      */
155:     public function setTemplates(array $templates)
156:     {
157:         foreach ($templates as $template) {
158:             if (!$template instanceof ReflectionAnnotation) {
159:                 throw new Exception\RuntimeException(
160:                     sprintf(
161:                         'All templates have to be instances of \\TokenReflection\\ReflectionAnnotation; %s given.',
162:                         is_object($template) ? get_class($template) : gettype($template)
163:                     ),
164:                     Exception\RuntimeException::INVALID_ARGUMENT,
165:                     $this->reflection
166:                 );
167:             }
168:         }
169: 
170:         $this->templates = $templates;
171: 
172:         return $this;
173:     }
174: 
175:     /**
176:      * Parses reflection object documentation.
177:      */
178:     private function parse()
179:     {
180:         $this->annotations = array();
181: 
182:         if (false !== $this->docComment) {
183:             // Parse docblock
184:             $name = self::SHORT_DESCRIPTION;
185:             $docblock = trim(
186:                 preg_replace(
187:                     array(
188:                         '~^' . preg_quote(ReflectionElement::DOCBLOCK_TEMPLATE_START, '~') . '~',
189:                         '~^' . preg_quote(ReflectionElement::DOCBLOCK_TEMPLATE_END, '~') . '$~',
190:                         '~^/\\*\\*~',
191:                         '~\\*/$~'
192:                     ),
193:                     '',
194:                     $this->docComment
195:                 )
196:             );
197:             foreach (explode("\n", $docblock) as $line) {
198:                 $line = preg_replace('~^\\*\\s?~', '', trim($line));
199: 
200:                 // End of short description
201:                 if ('' === $line && self::SHORT_DESCRIPTION === $name) {
202:                     $name = self::LONG_DESCRIPTION;
203:                     continue;
204:                 }
205: 
206:                 // @annotation
207:                 if (preg_match('~^\\s*@([\\S]+)\\s*(.*)~', $line, $matches)) {
208:                     $name = $matches[1];
209:                     $this->annotations[$name][] = $matches[2];
210:                     continue;
211:                 }
212: 
213:                 // Continuation
214:                 if (self::SHORT_DESCRIPTION === $name || self::LONG_DESCRIPTION === $name) {
215:                     if (!isset($this->annotations[$name])) {
216:                         $this->annotations[$name] = $line;
217:                     } else {
218:                         $this->annotations[$name] .= "\n" . $line;
219:                     }
220:                 } else {
221:                     $this->annotations[$name][count($this->annotations[$name]) - 1] .= "\n" . $line;
222:                 }
223:             }
224: 
225:             array_walk_recursive($this->annotations, function(&$value) {
226:                 // {@*} is a placeholder for */ (phpDocumentor compatibility)
227:                 $value = str_replace('{@*}', '*/', $value);
228:                 $value = trim($value);
229:             });
230:         }
231: 
232:         if ($this->reflection instanceof ReflectionElement) {
233:             // Merge docblock templates
234:             $this->mergeTemplates();
235: 
236:             // Copy annotations if the @copydoc tag is present.
237:             if (!empty($this->annotations['copydoc'])) {
238:                 $this->copyAnnotation();
239:             }
240: 
241:             // Process docblock inheritance for supported reflections
242:             if ($this->reflection instanceof ReflectionClass || $this->reflection instanceof ReflectionMethod || $this->reflection instanceof ReflectionProperty) {
243:                 $this->inheritAnnotations();
244:             }
245:         }
246:     }
247: 
248:     /**
249:      * Copies annotations if the @copydoc tag is present.
250:      *
251:      * @throws \TokenReflection\Exception\RuntimeException When stuck in an infinite loop when resolving the @copydoc tag.
252:      */
253:     private function copyAnnotation()
254:     {
255:         self::$copydocStack[] = $this->reflection;
256:         $broker = $this->reflection->getBroker();
257: 
258:         $parentNames = $this->annotations['copydoc'];
259:         unset($this->annotations['copydoc']);
260: 
261:         foreach ($parentNames as $parentName) {
262:             try {
263:                 if ($this->reflection instanceof ReflectionClass) {
264:                     $parent = $broker->getClass($parentName);
265:                     if ($parent instanceof Dummy\ReflectionClass) {
266:                         // The class to copy from is not usable
267:                         return;
268:                     }
269:                 } elseif ($this->reflection instanceof ReflectionFunction) {
270:                     $parent = $broker->getFunction(rtrim($parentName, '()'));
271:                 } elseif ($this->reflection instanceof ReflectionConstant && null === $this->reflection->getDeclaringClassName()) {
272:                     $parent = $broker->getConstant($parentName);
273:                 } elseif ($this->reflection instanceof ReflectionMethod || $this->reflection instanceof ReflectionProperty || $this->reflection instanceof ReflectionConstant) {
274:                     if (false !== strpos($parentName, '::')) {
275:                         list($className, $parentName) = explode('::', $parentName, 2);
276:                         $class = $broker->getClass($className);
277:                     } else {
278:                         $class = $this->reflection->getDeclaringClass();
279:                     }
280: 
281:                     if ($class instanceof Dummy\ReflectionClass) {
282:                         // The source element class is not usable
283:                         return;
284:                     }
285: 
286:                     if ($this->reflection instanceof ReflectionMethod) {
287:                         $parent = $class->getMethod(rtrim($parentName, '()'));
288:                     } elseif ($this->reflection instanceof ReflectionConstant) {
289:                         $parent = $class->getConstantReflection($parentName);
290:                     } else {
291:                         $parent = $class->getProperty(ltrim($parentName, '$'));
292:                     }
293:                 }
294: 
295:                 if (!empty($parent)) {
296:                     // Don't get into an infinite recursion loop
297:                     if (in_array($parent, self::$copydocStack, true)) {
298:                         throw new Exception\RuntimeException('Infinite loop detected when copying annotations using the @copydoc tag.', Exception\RuntimeException::INVALID_ARGUMENT, $this->reflection);
299:                     }
300: 
301:                     self::$copydocStack[] = $parent;
302: 
303:                     // We can get into an infinite loop here (e.g. when two methods @copydoc from each other)
304:                     foreach ($parent->getAnnotations() as $name => $value) {
305:                         // Add annotations that are not already present
306:                         if (empty($this->annotations[$name])) {
307:                             $this->annotations[$name] = $value;
308:                         }
309:                     }
310: 
311:                     array_pop(self::$copydocStack);
312:                 }
313:             } catch (Exception\BaseException $e) {
314:                 // Ignoring links to non existent elements, ...
315:             }
316:         }
317: 
318:         array_pop(self::$copydocStack);
319:     }
320: 
321:     /**
322:      * Merges templates with the current docblock.
323:      */
324:     private function mergeTemplates()
325:     {
326:         foreach ($this->templates as $index => $template) {
327:             if (0 === $index && $template->getDocComment() === $this->docComment) {
328:                 continue;
329:             }
330: 
331:             foreach ($template->getAnnotations() as $name => $value) {
332:                 if ($name === self::LONG_DESCRIPTION) {
333:                     // Long description
334:                     if (isset($this->annotations[self::LONG_DESCRIPTION])) {
335:                         $this->annotations[self::LONG_DESCRIPTION] = $value . "\n" . $this->annotations[self::LONG_DESCRIPTION];
336:                     } else {
337:                         $this->annotations[self::LONG_DESCRIPTION] = $value;
338:                     }
339:                 } elseif ($name !== self::SHORT_DESCRIPTION) {
340:                     // Tags; short description is not inherited
341:                     if (isset($this->annotations[$name])) {
342:                         $this->annotations[$name] = array_merge($this->annotations[$name], $value);
343:                     } else {
344:                         $this->annotations[$name] = $value;
345:                     }
346:                 }
347:             }
348:         }
349:     }
350: 
351:     /**
352:      * Inherits annotations from parent classes/methods/properties if needed.
353:      *
354:      * @throws \TokenReflection\Exception\RuntimeException If unsupported reflection was used.
355:      */
356:     private function inheritAnnotations()
357:     {
358:         if ($this->reflection instanceof ReflectionClass) {
359:             $declaringClass = $this->reflection;
360:         } elseif ($this->reflection instanceof ReflectionMethod || $this->reflection instanceof ReflectionProperty) {
361:             $declaringClass = $this->reflection->getDeclaringClass();
362:         }
363: 
364:         $parents = array_filter(array_merge(array($declaringClass->getParentClass()), $declaringClass->getOwnInterfaces()), function($class) {
365:             return $class instanceof ReflectionClass;
366:         });
367: 
368:         // In case of properties and methods, look for a property/method of the same name and return
369:         // and array of such members.
370:         $parentDefinitions = array();
371:         if ($this->reflection instanceof ReflectionProperty) {
372:             $name = $this->reflection->getName();
373:             foreach ($parents as $parent) {
374:                 if ($parent->hasProperty($name)) {
375:                     $parentDefinitions[] = $parent->getProperty($name);
376:                 }
377:             }
378: 
379:             $parents = $parentDefinitions;
380:         } elseif ($this->reflection instanceof ReflectionMethod) {
381:             $name = $this->reflection->getName();
382:             foreach ($parents as $parent) {
383:                 if ($parent->hasMethod($name)) {
384:                     $parentDefinitions[] = $parent->getMethod($name);
385:                 }
386:             }
387: 
388:             $parents = $parentDefinitions;
389:         }
390: 
391:         if (false === $this->docComment) {
392:             // Inherit the entire docblock
393:             foreach ($parents as $parent) {
394:                 $annotations = $parent->getAnnotations();
395:                 if (!empty($annotations)) {
396:                     $this->annotations = $annotations;
397:                     break;
398:                 }
399:             }
400:         } else {
401:             if (isset($this->annotations[self::LONG_DESCRIPTION]) && false !== stripos($this->annotations[self::LONG_DESCRIPTION], '{@inheritdoc}')) {
402:                 // Inherit long description
403:                 foreach ($parents as $parent) {
404:                     if ($parent->hasAnnotation(self::LONG_DESCRIPTION)) {
405:                         $this->annotations[self::LONG_DESCRIPTION] = str_ireplace(
406:                             '{@inheritdoc}',
407:                             $parent->getAnnotation(self::LONG_DESCRIPTION),
408:                             $this->annotations[self::LONG_DESCRIPTION]
409:                         );
410:                         break;
411:                     }
412:                 }
413: 
414:                 $this->annotations[self::LONG_DESCRIPTION] = str_ireplace('{@inheritdoc}', '', $this->annotations[self::LONG_DESCRIPTION]);
415:             }
416:             if (isset($this->annotations[self::SHORT_DESCRIPTION]) && false !== stripos($this->annotations[self::SHORT_DESCRIPTION], '{@inheritdoc}')) {
417:                 // Inherit short description
418:                 foreach ($parents as $parent) {
419:                     if ($parent->hasAnnotation(self::SHORT_DESCRIPTION)) {
420:                         $this->annotations[self::SHORT_DESCRIPTION] = str_ireplace(
421:                             '{@inheritdoc}',
422:                             $parent->getAnnotation(self::SHORT_DESCRIPTION),
423:                             $this->annotations[self::SHORT_DESCRIPTION]
424:                         );
425:                         break;
426:                     }
427:                 }
428: 
429:                 $this->annotations[self::SHORT_DESCRIPTION] = str_ireplace('{@inheritdoc}', '', $this->annotations[self::SHORT_DESCRIPTION]);
430:             }
431:         }
432: 
433:         // In case of properties check if we need and can inherit the data type
434:         if ($this->reflection instanceof ReflectionProperty && empty($this->annotations['var'])) {
435:             foreach ($parents as $parent) {
436:                 if ($parent->hasAnnotation('var')) {
437:                     $this->annotations['var'] = $parent->getAnnotation('var');
438:                     break;
439:                 }
440:             }
441:         }
442: 
443:         if ($this->reflection instanceof ReflectionMethod) {
444:             if (0 !== $this->reflection->getNumberOfParameters() && (empty($this->annotations['param']) || count($this->annotations['param']) < $this->reflection->getNumberOfParameters())) {
445:                 // In case of methods check if we need and can inherit parameter descriptions
446:                 $params = isset($this->annotations['param']) ? $this->annotations['param'] : array();
447:                 $complete = false;
448:                 foreach ($parents as $parent) {
449:                     if ($parent->hasAnnotation('param')) {
450:                         $parentParams = array_slice($parent->getAnnotation('param'), count($params));
451: 
452:                         while (!empty($parentParams) && !$complete) {
453:                             array_push($params, array_shift($parentParams));
454: 
455:                             if (count($params) === $this->reflection->getNumberOfParameters()) {
456:                                 $complete = true;
457:                             }
458:                         }
459:                     }
460: 
461:                     if ($complete) {
462:                         break;
463:                     }
464:                 }
465: 
466:                 if (!empty($params)) {
467:                     $this->annotations['param'] = $params;
468:                 }
469:             }
470: 
471:             // And check if we need and can inherit the return and throws value
472:             foreach (array('return', 'throws') as $paramName) {
473:                 if (!isset($this->annotations[$paramName])) {
474:                     foreach ($parents as $parent) {
475:                         if ($parent->hasAnnotation($paramName)) {
476:                             $this->annotations[$paramName] = $parent->getAnnotation($paramName);
477:                             break;
478:                         }
479:                     }
480:                 }
481:             }
482:         }
483:     }
484: }
485: 
PHP Token Reflection API documentation generated by ApiGen 2.8.0