33namespace PhpDocReader ;
44
55use PhpDocReader \PhpParser \UseStatementParser ;
6+ use ReflectionClass ;
7+ use ReflectionMethod ;
68use ReflectionParameter ;
79use ReflectionProperty ;
10+ use Reflector ;
811
912/**
1013 * PhpDoc reader
@@ -95,35 +98,9 @@ public function getPropertyClass(ReflectionProperty $property)
9598
9699 // If the class name is not fully qualified (i.e. doesn't start with a \)
97100 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 );
100102
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 ) {
127104 throw new AnnotationException (sprintf (
128105 'The @var annotation on %s::%s contains a non existent class "%s". '
129106 . 'Did you maybe forget to add a "use" statement for this annotation? ' ,
@@ -132,6 +109,8 @@ public function getPropertyClass(ReflectionProperty $property)
132109 $ type
133110 ));
134111 }
112+
113+ $ type = $ resolvedType ;
135114 }
136115
137116 if (!$ this ->classExists ($ type ) && !$ this ->ignorePhpDocErrors ) {
@@ -203,35 +182,9 @@ public function getParameterClass(ReflectionParameter $parameter)
203182
204183 // If the class name is not fully qualified (i.e. doesn't start with a \)
205184 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 ) {
235188 throw new AnnotationException (sprintf (
236189 'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s". '
237190 . 'Did you maybe forget to add a "use" statement for this annotation? ' ,
@@ -241,6 +194,8 @@ public function getParameterClass(ReflectionParameter $parameter)
241194 $ type
242195 ));
243196 }
197+
198+ $ type = $ resolvedType ;
244199 }
245200
246201 if (!$ this ->classExists ($ type ) && !$ this ->ignorePhpDocErrors ) {
@@ -259,6 +214,81 @@ public function getParameterClass(ReflectionParameter $parameter)
259214 return $ type ;
260215 }
261216
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+
262292 /**
263293 * @param string $class
264294 * @return bool
0 commit comments