1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15:
16: namespace TokenReflection;
17:
18: use TokenReflection\Exception;
19:
20: 21: 22:
23: class ReflectionAnnotation
24: {
25: 26: 27: 28: 29: 30: 31:
32: const SHORT_DESCRIPTION = ' short_description';
33:
34: 35: 36: 37: 38: 39: 40:
41: const LONG_DESCRIPTION = ' long_description';
42:
43: 44: 45: 46: 47: 48: 49:
50: private static $copydocStack = array();
51:
52: 53: 54: 55: 56:
57: private $templates = array();
58:
59: 60: 61: 62: 63:
64: private $annotations;
65:
66: 67: 68: 69: 70: 71: 72:
73: private ;
74:
75: 76: 77: 78: 79:
80: private $reflection;
81:
82: 83: 84: 85: 86: 87:
88: public function __construct(ReflectionBase $reflection, $docComment = false)
89: {
90: $this->reflection = $reflection;
91: $this->docComment = $docComment ?: false;
92: }
93:
94: 95: 96: 97: 98:
99: public function ()
100: {
101: return $this->docComment;
102: }
103:
104: 105: 106: 107: 108: 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: 121: 122: 123: 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: 136: 137: 138:
139: public function getAnnotations()
140: {
141: if (null === $this->annotations) {
142: $this->parse();
143: }
144:
145: return $this->annotations;
146: }
147:
148: 149: 150: 151: 152: 153: 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: 177:
178: private function parse()
179: {
180: $this->annotations = array();
181:
182: if (false !== $this->docComment) {
183:
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:
201: if ('' === $line && self::SHORT_DESCRIPTION === $name) {
202: $name = self::LONG_DESCRIPTION;
203: continue;
204: }
205:
206:
207: if (preg_match('~^\\s*@([\\S]+)\\s*(.*)~', $line, $matches)) {
208: $name = $matches[1];
209: $this->annotations[$name][] = $matches[2];
210: continue;
211: }
212:
213:
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:
227: $value = str_replace('{@*}', '*/', $value);
228: $value = trim($value);
229: });
230: }
231:
232: if ($this->reflection instanceof ReflectionElement) {
233:
234: $this->mergeTemplates();
235:
236:
237: if (!empty($this->annotations['copydoc'])) {
238: $this->copyAnnotation();
239: }
240:
241:
242: if ($this->reflection instanceof ReflectionClass || $this->reflection instanceof ReflectionMethod || $this->reflection instanceof ReflectionProperty) {
243: $this->inheritAnnotations();
244: }
245: }
246: }
247:
248: 249: 250: 251: 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:
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:
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:
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:
304: foreach ($parent->getAnnotations() as $name => $value) {
305:
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:
315: }
316: }
317:
318: array_pop(self::$copydocStack);
319: }
320:
321: 322: 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:
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:
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: 353: 354: 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:
369:
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:
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:
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:
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:
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:
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:
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: