Skip to content

Commit 885a477

Browse files
author
Alexander Obuhovich
committed
Merge pull request php-annotations#102 from benesch/trait-docblocks
Trait docblocks
2 parents 5446fb5 + a26b684 commit 885a477

File tree

7 files changed

+623
-35
lines changed

7 files changed

+623
-35
lines changed

src/annotations/AnnotationFile.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ class AnnotationFile
5959
*/
6060
public $uses;
6161

62+
/**
63+
* @var string[] $traitMethodOverrides hash mapping FQCN to a hash mapping aliased method names to (trait, original method name)
64+
*/
65+
public $traitMethodOverrides;
66+
6267
/**
6368
* @param string $path absolute path to php source-file
6469
* @param array $data annotation data (as provided by AnnotationParser)
@@ -69,6 +74,25 @@ public function __construct($path, array $data)
6974
$this->data = $data;
7075
$this->namespace = $data['#namespace'];
7176
$this->uses = $data['#uses'];
77+
78+
if (isset($data['#traitMethodOverrides'])) {
79+
foreach ($data['#traitMethodOverrides'] as $class => $methods) {
80+
$this->traitMethodOverrides[$class] = array_map(array($this, 'resolveMethod'), $methods);
81+
}
82+
}
83+
}
84+
85+
/**
86+
* Qualify the class name in a method reference like 'Class::method'.
87+
*
88+
* @param string $raw_method Raw method string.
89+
*
90+
* @return string[] of fully-qualified class name, method name
91+
*/
92+
public function resolveMethod($raw_method)
93+
{
94+
list($class, $method) = explode('::', $raw_method, 2);
95+
return array(ltrim($this->resolveType($class), '\\'), $method);
7296
}
7397

7498
/**

src/annotations/AnnotationManager.php

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ class AnnotationManager
134134
*/
135135
private $_cacheSeed = '';
136136

137+
/**
138+
* Whether this version of PHP has support for traits.
139+
*/
140+
private $_traitsSupported;
141+
137142
/**
138143
* Initialize the Annotation Manager
139144
*
@@ -145,6 +150,7 @@ public function __construct($cacheSeed = '')
145150
$this->_usageAnnotation = new UsageAnnotation();
146151
$this->_usageAnnotation->class = true;
147152
$this->_usageAnnotation->inherited = true;
153+
$this->_traitsSupported = version_compare(PHP_VERSION, '5.4.0', '>=');
148154
}
149155

150156
/**
@@ -262,34 +268,52 @@ protected function getAnnotations($class_name, $member_type = self::MEMBER_CLASS
262268

263269
$inherit = true; // inherit parent annotations unless directed not to
264270

265-
if (isset($file) && isset($file->data[$key])) {
266-
foreach ($file->data[$key] as $spec) {
267-
$name = $spec['#name']; // currently unused
268-
$type = $spec['#type'];
271+
if (isset($file)) {
272+
if (isset($file->data[$key])) {
273+
foreach ($file->data[$key] as $spec) {
274+
$name = $spec['#name']; // currently unused
275+
$type = $spec['#type'];
269276

270-
unset($spec['#name'], $spec['#type']);
277+
unset($spec['#name'], $spec['#type']);
271278

272-
if (!class_exists($type, $this->autoload)) {
273-
throw new AnnotationException("Annotation type '{$type}' does not exist");
274-
}
279+
if (!class_exists($type, $this->autoload)) {
280+
throw new AnnotationException("Annotation type '{$type}' does not exist");
281+
}
275282

276-
$annotation = new $type;
283+
$annotation = new $type;
277284

278-
if (!($annotation instanceof IAnnotation)) {
279-
throw new AnnotationException("Annotation type '{$type}' does not implement the mandatory IAnnotation interface");
280-
}
285+
if (!($annotation instanceof IAnnotation)) {
286+
throw new AnnotationException("Annotation type '{$type}' does not implement the mandatory IAnnotation interface");
287+
}
281288

282-
if ($annotation instanceof IAnnotationFileAware) {
283-
$annotation->setAnnotationFile($file);
284-
}
289+
if ($annotation instanceof IAnnotationFileAware) {
290+
$annotation->setAnnotationFile($file);
291+
}
285292

286-
$annotation->initAnnotation($spec);
293+
$annotation->initAnnotation($spec);
287294

288-
$annotations[] = $annotation;
289-
}
295+
$annotations[] = $annotation;
296+
}
297+
298+
if ($member_type === self::MEMBER_CLASS) {
299+
$classAnnotations = $annotations;
300+
}
301+
} else if ($this->_traitsSupported && $member_name !== null) {
302+
$traitAnnotations = array();
303+
304+
if (isset($file->traitMethodOverrides[$class_name][$member_name])) {
305+
list($traitName, $originalMemberName) = $file->traitMethodOverrides[$class_name][$member_name];
306+
$traitAnnotations = $this->getAnnotations($traitName, $member_type, $originalMemberName);
307+
} else {
308+
foreach ($reflection->getTraitNames() as $traitName) {
309+
if ($this->classHasMember($traitName, $member_type, $member_name)) {
310+
$traitAnnotations = $this->getAnnotations($traitName, $member_type, $member_name);
311+
break;
312+
}
313+
}
314+
}
290315

291-
if ($member_type === self::MEMBER_CLASS) {
292-
$classAnnotations = $annotations;
316+
$annotations = array_merge($traitAnnotations, $annotations);
293317
}
294318
}
295319

@@ -321,6 +345,25 @@ protected function getAnnotations($class_name, $member_type = self::MEMBER_CLASS
321345
return $this->annotations[$key];
322346
}
323347

348+
/**
349+
* Determines whether a class or trait has the specified member.
350+
*
351+
* @param string $className The name of the class or trait to check
352+
* @param string $memberType The type of member, e.g. "property" or "method"
353+
* @param string $memberName The member name, e.g. "method" or "$property"
354+
*
355+
* @return bool whether class or trait has the specified member
356+
*/
357+
protected function classHasMember($className, $memberType, $memberName)
358+
{
359+
if ($memberType === self::MEMBER_METHOD) {
360+
return method_exists($className, $memberName);
361+
} else if ($memberType === self::MEMBER_PROPERTY) {
362+
return property_exists($className, ltrim($memberName, '$'));
363+
}
364+
return false;
365+
}
366+
324367
/**
325368
* Validates the constraints (as defined by the UsageAnnotation of each annotation) of a
326369
* list of annotations for a given type of member.
@@ -453,14 +496,14 @@ public function getClassAnnotations($class, $type = null)
453496
$class = ltrim($class, '\\');
454497
}
455498

456-
if (!class_exists($class, $this->autoload)) {
457-
$isTrait = function_exists('trait_exists') ? trait_exists($class, $this->autoload) : false;
458-
459-
if (interface_exists($class, $this->autoload) || $isTrait) {
460-
throw new AnnotationException("Reading annotations from interface/trait '{$class}' is not supported");
499+
if (!class_exists($class, $this->autoload) &&
500+
!(function_exists('trait_exists') && trait_exists($class, $this->autoload))
501+
) {
502+
if (interface_exists($class, $this->autoload)) {
503+
throw new AnnotationException("Reading annotations from interface '{$class}' is not supported");
461504
}
462505

463-
throw new AnnotationException("Unable to read annotations from an undefined class '{$class}'");
506+
throw new AnnotationException("Unable to read annotations from an undefined class/trait '{$class}'");
464507
}
465508

466509
if ($type === null) {

src/annotations/AnnotationParser.php

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
namespace mindplay\annotations;
1515

16+
if (!defined('T_TRAIT')) {
17+
define(__NAMESPACE__ . '\\T_TRAIT', -2);
18+
}
19+
1620
/**
1721
* This class implements a parser for source code annotations
1822
*/
@@ -27,6 +31,10 @@ class AnnotationParser
2731
const NAMESPACE_NAME = 6;
2832
const USE_CLAUSE = 11;
2933
const USE_CLAUSE_AS = 12;
34+
const TRAIT_USE_CLAUSE = 13;
35+
const TRAIT_USE_BLOCK = 14;
36+
const TRAIT_USE_AS = 15;
37+
const TRAIT_USE_INSTEADOF = 16;
3038

3139
const SKIP = 7;
3240
const NAME = 8;
@@ -68,6 +76,7 @@ public function __construct(AnnotationManager $manager)
6876
public function parse($source, $path)
6977
{
7078
$index = array();
79+
$traitMethodOverrides = array();
7180

7281
$docblocks = array();
7382
$state = self::SCAN;
@@ -91,7 +100,7 @@ public function parse($source, $path)
91100

92101
switch ($state) {
93102
case self::SCAN:
94-
if ($type == T_CLASS) {
103+
if ($type == T_CLASS || $type == T_TRAIT) {
95104
$state = self::CLASS_NAME;
96105
}
97106
if ($type == T_NAMESPACE) {
@@ -159,6 +168,7 @@ public function parse($source, $path)
159168
case self::CLASS_NAME:
160169
if ($type == T_STRING) {
161170
$class = ($namespace ? $namespace . '\\' : '') . $str;
171+
$traitMethodOverrides[$class] = array();
162172
$index[$class] = $docblocks;
163173
$docblocks = array();
164174
$state = self::SCAN_CLASS;
@@ -172,6 +182,59 @@ public function parse($source, $path)
172182
if ($type == T_FUNCTION) {
173183
$state = self::METHOD_NAME;
174184
}
185+
if ($type == T_USE) {
186+
$state = self::TRAIT_USE_CLAUSE;
187+
$use = '';
188+
}
189+
break;
190+
191+
case self::TRAIT_USE_CLAUSE:
192+
if ($type === self::CHAR) {
193+
if ($str === '{') {
194+
$state = self::TRAIT_USE_BLOCK;
195+
$use = '';
196+
} elseif ($str === ';') {
197+
$state = self::SCAN_CLASS;
198+
}
199+
}
200+
break;
201+
202+
case self::TRAIT_USE_BLOCK:
203+
if ($type == T_STRING || $type == T_NS_SEPARATOR || $type == T_DOUBLE_COLON) {
204+
$use .= $str;
205+
} elseif ($type === T_INSTEADOF) {
206+
$state = self::TRAIT_USE_INSTEADOF;
207+
} elseif ($type === T_AS) {
208+
$state = self::TRAIT_USE_AS;
209+
$use_as = '';
210+
} elseif ($type === self::CHAR) {
211+
if ($str === ';') {
212+
$use = '';
213+
} elseif ($str === '}') {
214+
$state = self::SCAN_CLASS;
215+
}
216+
}
217+
break;
218+
219+
case self::TRAIT_USE_INSTEADOF:
220+
if ($type === self::CHAR && $str === ';') {
221+
$traitMethodOverrides[$class][substr($use, strrpos($use, '::') + 2)] = $use;
222+
$state = self::TRAIT_USE_BLOCK;
223+
$use = '';
224+
}
225+
break;
226+
227+
case self::TRAIT_USE_AS:
228+
if ($type === T_STRING) {
229+
$use_as .= $str;
230+
} elseif ($type === self::CHAR && $str === ';') {
231+
// Ignore use... as statements that only change method visibility.
232+
if ($use_as !== '') {
233+
$traitMethodOverrides[$class][$use_as] = $use;
234+
}
235+
$state = self::TRAIT_USE_BLOCK;
236+
$use = '';
237+
}
175238
break;
176239

177240
case self::MEMBER:
@@ -233,6 +296,8 @@ public function parse($source, $path)
233296
$code = "return array(\n";
234297
$code .= " '#namespace' => " . var_export($namespace, true) . ",\n";
235298
$code .= " '#uses' => " . var_export($uses, true) . ",\n";
299+
$code .= " '#traitMethodOverrides' => " . var_export($traitMethodOverrides, true) . ",\n";
300+
236301
foreach ($index as $key => $docblocks) {
237302
$array = array();
238303
foreach ($docblocks as $str) {

0 commit comments

Comments
 (0)