3
3
namespace PhpDocReader ;
4
4
5
5
use PhpDocReader \PhpParser \UseStatementParser ;
6
+ use ReflectionClass ;
7
+ use ReflectionMethod ;
6
8
use ReflectionParameter ;
7
9
use ReflectionProperty ;
10
+ use Reflector ;
8
11
9
12
/**
10
13
* PhpDoc reader
@@ -95,35 +98,10 @@ public function getPropertyClass(ReflectionProperty $property)
95
98
96
99
// If the class name is not fully qualified (i.e. doesn't start with a \)
97
100
if ($ type [0 ] !== '\\' ) {
98
- $ alias = (false === $ pos = strpos ($ type , '\\' )) ? $ type : substr ($ type , 0 , $ pos );
99
- $ loweredAlias = strtolower ($ alias );
100
-
101
- // Retrieve "use" statements
102
- $ uses = $ this ->parser ->parseUseStatements ($ property ->getDeclaringClass ());
103
-
104
- $ found = false ;
105
-
106
- if (isset ($ uses [$ loweredAlias ])) {
107
- // Imported classes
108
- if (false !== $ pos ) {
109
- $ type = $ uses [$ loweredAlias ] . substr ($ type , $ pos );
110
- } else {
111
- $ type = $ uses [$ loweredAlias ];
112
- }
113
- $ found = true ;
114
- } elseif ($ this ->classExists ($ class ->getNamespaceName () . '\\' . $ type )) {
115
- $ type = $ class ->getNamespaceName () . '\\' . $ type ;
116
- $ found = true ;
117
- } elseif (isset ($ uses ['__NAMESPACE__ ' ]) && $ this ->classExists ($ uses ['__NAMESPACE__ ' ] . '\\' . $ type )) {
118
- // Class namespace
119
- $ type = $ uses ['__NAMESPACE__ ' ] . '\\' . $ type ;
120
- $ found = true ;
121
- } elseif ($ this ->classExists ($ type )) {
122
- // No namespace
123
- $ found = true ;
124
- }
101
+ // Try to resolve the FQN using the class context
102
+ $ resolvedType = $ this ->tryResolveFqn ($ type , $ class , $ property );
125
103
126
- if (!$ found && !$ this ->ignorePhpDocErrors ) {
104
+ if (!$ resolvedType && !$ this ->ignorePhpDocErrors ) {
127
105
throw new AnnotationException (sprintf (
128
106
'The @var annotation on %s::%s contains a non existent class "%s". '
129
107
. 'Did you maybe forget to add a "use" statement for this annotation? ' ,
@@ -132,6 +110,8 @@ public function getPropertyClass(ReflectionProperty $property)
132
110
$ type
133
111
));
134
112
}
113
+
114
+ $ type = $ resolvedType ;
135
115
}
136
116
137
117
if (!$ this ->classExists ($ type ) && !$ this ->ignorePhpDocErrors ) {
@@ -203,35 +183,10 @@ public function getParameterClass(ReflectionParameter $parameter)
203
183
204
184
// If the class name is not fully qualified (i.e. doesn't start with a \)
205
185
if ($ type [0 ] !== '\\' ) {
206
- $ alias = (false === $ pos = strpos ($ type , '\\' )) ? $ type : substr ($ type , 0 , $ pos );
207
- $ loweredAlias = strtolower ($ alias );
208
-
209
- // Retrieve "use" statements
210
- $ uses = $ this ->parser ->parseUseStatements ($ class );
211
-
212
- $ found = false ;
213
-
214
- if (isset ($ uses [$ loweredAlias ])) {
215
- // Imported classes
216
- if (false !== $ pos ) {
217
- $ type = $ uses [$ loweredAlias ] . substr ($ type , $ pos );
218
- } else {
219
- $ type = $ uses [$ loweredAlias ];
220
- }
221
- $ found = true ;
222
- } elseif ($ this ->classExists ($ class ->getNamespaceName () . '\\' . $ type )) {
223
- $ type = $ class ->getNamespaceName () . '\\' . $ type ;
224
- $ found = true ;
225
- } elseif (isset ($ uses ['__NAMESPACE__ ' ]) && $ this ->classExists ($ uses ['__NAMESPACE__ ' ] . '\\' . $ type )) {
226
- // Class namespace
227
- $ type = $ uses ['__NAMESPACE__ ' ] . '\\' . $ type ;
228
- $ found = true ;
229
- } elseif ($ this ->classExists ($ type )) {
230
- // No namespace
231
- $ found = true ;
232
- }
233
-
234
- if (!$ found && !$ this ->ignorePhpDocErrors ) {
186
+ // Try to resolve the FQN using the class context
187
+ $ resolvedType = $ this ->tryResolveFqn ($ type , $ class , $ parameter );
188
+
189
+ if (!$ resolvedType && !$ this ->ignorePhpDocErrors ) {
235
190
throw new AnnotationException (sprintf (
236
191
'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s". '
237
192
. 'Did you maybe forget to add a "use" statement for this annotation? ' ,
@@ -241,6 +196,8 @@ public function getParameterClass(ReflectionParameter $parameter)
241
196
$ type
242
197
));
243
198
}
199
+
200
+ $ type = $ resolvedType ;
244
201
}
245
202
246
203
if (!$ this ->classExists ($ type ) && !$ this ->ignorePhpDocErrors ) {
@@ -259,6 +216,89 @@ public function getParameterClass(ReflectionParameter $parameter)
259
216
return $ type ;
260
217
}
261
218
219
+ /**
220
+ * Attempts to resolve the FQN of the provided $type based on the $class and $member context.
221
+ *
222
+ * @param string $type
223
+ * @param ReflectionClass $class
224
+ * @param Reflector $member
225
+ *
226
+ * @return string|null Fully qualified name of the type, or null if it could not be resolved
227
+ */
228
+ private function tryResolveFqn ($ type , ReflectionClass $ class , Reflector $ member )
229
+ {
230
+ $ alias = ($ pos = strpos ($ type , '\\' )) === false ? $ type : substr ($ type , 0 , $ pos );
231
+ $ loweredAlias = strtolower ($ alias );
232
+
233
+ // Retrieve "use" statements
234
+ $ uses = $ this ->parser ->parseUseStatements ($ class );
235
+
236
+ if (isset ($ uses [$ loweredAlias ])) {
237
+ // Imported classes
238
+ if ($ pos !== false ) {
239
+ return $ uses [$ loweredAlias ] . substr ($ type , $ pos );
240
+ } else {
241
+ return $ uses [$ loweredAlias ];
242
+ }
243
+ } elseif ($ this ->classExists ($ class ->getNamespaceName () . '\\' . $ type )) {
244
+ return $ class ->getNamespaceName () . '\\' . $ type ;
245
+ } elseif (isset ($ uses ['__NAMESPACE__ ' ]) && $ this ->classExists ($ uses ['__NAMESPACE__ ' ] . '\\' . $ type )) {
246
+ // Class namespace
247
+ return $ uses ['__NAMESPACE__ ' ] . '\\' . $ type ;
248
+ } elseif ($ this ->classExists ($ type )) {
249
+ // No namespace
250
+ return $ type ;
251
+ }
252
+
253
+ if (version_compare (phpversion (), '5.4.0 ' , '< ' )) {
254
+ return null ;
255
+ } else {
256
+ // If all fail, try resolving through related traits
257
+ return $ this ->tryResolveFqnInTraits ($ type , $ class , $ member );
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Attempts to resolve the FQN of the provided $type based on the $class and $member context, specifically searching
263
+ * through the traits that are used by the provided $class.
264
+ *
265
+ * @param string $type
266
+ * @param ReflectionClass $class
267
+ * @param Reflector $member
268
+ *
269
+ * @return string|null Fully qualified name of the type, or null if it could not be resolved
270
+ */
271
+ private function tryResolveFqnInTraits ($ type , ReflectionClass $ class , Reflector $ member )
272
+ {
273
+ /** @var ReflectionClass[] $traits */
274
+ $ traits = array ();
275
+
276
+ // Get traits for the class and its parents
277
+ while ($ class ) {
278
+ $ traits = array_merge ($ traits , $ class ->getTraits ());
279
+ $ class = $ class ->getParentClass ();
280
+ }
281
+
282
+ foreach ($ traits as $ trait ) {
283
+ // Eliminate traits that don't have the property/method/parameter
284
+ if ($ member instanceof ReflectionProperty && !$ trait ->hasProperty ($ member ->name )) {
285
+ continue ;
286
+ } elseif ($ member instanceof ReflectionMethod && !$ trait ->hasMethod ($ member ->name )) {
287
+ continue ;
288
+ } elseif ($ member instanceof ReflectionParameter && !$ trait ->hasMethod ($ member ->getDeclaringFunction ()->name )) {
289
+ continue ;
290
+ }
291
+
292
+ // Run the resolver again with the ReflectionClass instance for the trait
293
+ $ resolvedType = $ this ->tryResolveFqn ($ type , $ trait , $ member );
294
+
295
+ if ($ resolvedType ) {
296
+ return $ resolvedType ;
297
+ }
298
+ }
299
+ return null ;
300
+ }
301
+
262
302
/**
263
303
* @param string $class
264
304
* @return bool
0 commit comments