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,9 @@ 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 );
101
+ $ resolvedType = $ this ->tryResolveFqn ($ type , $ class , $ property );
100
102
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
- }
125
-
126
- if (!$ found && !$ this ->ignorePhpDocErrors ) {
103
+ if (!$ resolvedType && !$ this ->ignorePhpDocErrors ) {
127
104
throw new AnnotationException (sprintf (
128
105
'The @var annotation on %s::%s contains a non existent class "%s". '
129
106
. 'Did you maybe forget to add a "use" statement for this annotation? ' ,
@@ -132,6 +109,8 @@ public function getPropertyClass(ReflectionProperty $property)
132
109
$ type
133
110
));
134
111
}
112
+
113
+ $ type = $ resolvedType ;
135
114
}
136
115
137
116
if (!$ this ->classExists ($ type ) && !$ this ->ignorePhpDocErrors ) {
@@ -203,35 +182,9 @@ public function getParameterClass(ReflectionParameter $parameter)
203
182
204
183
// If the class name is not fully qualified (i.e. doesn't start with a \)
205
184
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 ) {
185
+ $ resolvedType = $ this ->tryResolveFqn ($ type , $ class , $ parameter );
186
+
187
+ if (!$ resolvedType && !$ this ->ignorePhpDocErrors ) {
235
188
throw new AnnotationException (sprintf (
236
189
'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s". '
237
190
. 'Did you maybe forget to add a "use" statement for this annotation? ' ,
@@ -241,6 +194,8 @@ public function getParameterClass(ReflectionParameter $parameter)
241
194
$ type
242
195
));
243
196
}
197
+
198
+ $ type = $ resolvedType ;
244
199
}
245
200
246
201
if (!$ this ->classExists ($ type ) && !$ this ->ignorePhpDocErrors ) {
@@ -259,6 +214,81 @@ public function getParameterClass(ReflectionParameter $parameter)
259
214
return $ type ;
260
215
}
261
216
217
+ /**
218
+ * Attempts to resolve the FQN of the provided $type based on the $class and $member context.
219
+ *
220
+ * @param string $type
221
+ * @param ReflectionClass $class
222
+ * @param Reflector $member
223
+ *
224
+ * @return string|null Fully qualified name of the type, or null if it could not be resolved
225
+ */
226
+ private function tryResolveFqn ($ type , ReflectionClass $ class , Reflector $ member )
227
+ {
228
+ $ alias = ($ pos = strpos ($ type , '\\' )) === false ? $ type : substr ($ type , 0 , $ pos );
229
+ $ loweredAlias = strtolower ($ alias );
230
+
231
+ // Retrieve "use" statements
232
+ $ uses = $ this ->parser ->parseUseStatements ($ class );
233
+
234
+ if (isset ($ uses [$ loweredAlias ])) {
235
+ // Imported classes
236
+ if ($ pos !== false ) {
237
+ return $ uses [$ loweredAlias ] . substr ($ type , $ pos );
238
+ } else {
239
+ return $ uses [$ loweredAlias ];
240
+ }
241
+ } elseif ($ this ->classExists ($ class ->getNamespaceName () . '\\' . $ type )) {
242
+ return $ class ->getNamespaceName () . '\\' . $ type ;
243
+ } elseif (isset ($ uses ['__NAMESPACE__ ' ]) && $ this ->classExists ($ uses ['__NAMESPACE__ ' ] . '\\' . $ type )) {
244
+ // Class namespace
245
+ return $ uses ['__NAMESPACE__ ' ] . '\\' . $ type ;
246
+ } elseif ($ this ->classExists ($ type )) {
247
+ // No namespace
248
+ return $ type ;
249
+ }
250
+
251
+ return $ this ->tryResolveFqnInTraits ($ type , $ class , $ member );
252
+ }
253
+
254
+ /**
255
+ * Attempts to resolve the FQN of the provided $type based on the $class and $member context, specifically searching
256
+ * through the traits that are used by the provided $class.
257
+ *
258
+ * @param string $type
259
+ * @param ReflectionClass $class
260
+ * @param Reflector $member
261
+ *
262
+ * @return string|null Fully qualified name of the type, or null if it could not be resolved
263
+ */
264
+ private function tryResolveFqnInTraits ($ type , ReflectionClass $ class , Reflector $ member )
265
+ {
266
+ /** @var ReflectionClass[] $traits */
267
+ $ traits = [];
268
+
269
+ while ($ class ) {
270
+ $ traits = array_merge ($ traits , $ class ->getTraits ());
271
+ $ class = $ class ->getParentClass ();
272
+ }
273
+
274
+ foreach ($ traits as $ trait ) {
275
+ if ($ member instanceof ReflectionProperty && !$ trait ->hasProperty ($ member ->name )) {
276
+ continue ;
277
+ } elseif ($ member instanceof ReflectionMethod && !$ trait ->hasMethod ($ member ->name )) {
278
+ continue ;
279
+ } elseif ($ member instanceof ReflectionParameter && !$ trait ->hasMethod ($ member ->getDeclaringFunction ()->name )) {
280
+ continue ;
281
+ }
282
+
283
+ $ resolvedType = $ this ->tryResolveFqn ($ type , $ trait , $ member );
284
+
285
+ if ($ resolvedType ) {
286
+ return $ resolvedType ;
287
+ }
288
+ }
289
+ return null ;
290
+ }
291
+
262
292
/**
263
293
* @param string $class
264
294
* @return bool
0 commit comments