1919
2020use Spiral \Attributes \ReaderInterface ;
2121
22+ /**
23+ * This class allows loading of annotations/attributes using listeners.
24+ *
25+ * @author Divine Niiquaye Ibok <divineibok@gmail.com>
26+ */
2227class AnnotationLoader implements LoaderInterface
2328{
2429 /** @var ReaderInterface */
2530 private $ reader ;
2631
27- /** @var null| mixed[] */
32+ /** @var mixed[] */
2833 private $ annotations ;
2934
3035 /** @var ListenerInterface[] */
@@ -37,8 +42,7 @@ class AnnotationLoader implements LoaderInterface
3742 private $ classLoader ;
3843
3944 /**
40- * @param ReaderInterface $reader
41- * @param callable $classLoader
45+ * @param callable $classLoader
4246 */
4347 public function __construct (ReaderInterface $ reader , callable $ classLoader = null )
4448 {
@@ -76,7 +80,7 @@ public function build(): void
7680 continue ;
7781 }
7882
79- if (!\class_exists ($ resource )) {
83+ if (!( \class_exists ($ resource) || \function_exists ( $ resource ) )) {
8084 continue ;
8185 }
8286
@@ -86,13 +90,21 @@ public function build(): void
8690 $ classes += $ this ->findClasses ($ files );
8791
8892 foreach ($ classes as $ class ) {
89- $ annotations += $ this ->findAnnotations ($ class );
90-
91- //TODO: Read annotations from functions ...
93+ $ annotations [] = $ this ->findAnnotations ($ class );
9294 }
9395
9496 foreach ($ this ->listeners as $ listener ) {
95- if (null !== $ found = $ listener ->onAnnotation ($ annotations )) {
97+ $ listenerAnnotations = [];
98+
99+ foreach ($ annotations as $ annotation ) {
100+ if (isset ($ annotation [$ listener ->getAnnotation ()])) {
101+ $ listenerAnnotations [] = $ annotation [$ listener ->getAnnotation ()];
102+ }
103+ }
104+
105+ $ found = $ listener ->onAnnotation ($ listenerAnnotations );
106+
107+ if (null !== $ found ) {
96108 $ this ->annotations [] = $ found ;
97109 }
98110 }
@@ -109,160 +121,179 @@ public function load(): iterable
109121 $ this ->build ();
110122 }
111123
112- return yield from $ this ->annotations ;
124+ return $ this ->annotations ;
113125 }
114126
115127 /**
116- * Finds annotations in the given resource
128+ * Finds annotations in the given resource.
117129 *
118- * @param class-string $resource
130+ * @param class-string|string $resource
119131 *
120- * @return array<string,array<string,mixed>>
132+ * @return Locate\Class_[]|Locate\Function_[]
121133 */
122- private function findAnnotations (string $ resource ): array
134+ private function findAnnotations (string $ resource )
123135 {
124136 $ annotations = [];
125137
126- $ classReflection = new \ ReflectionClass ( $ resource );
127- $ className = $ classReflection -> getName ();
138+ foreach ( $ this -> listeners as $ listener ) {
139+ $ annotationClass = $ listener -> getAnnotation ();
128140
129- if ($ classReflection ->isAbstract ()) {
130- throw new InvalidAnnotationException (\sprintf (
131- 'Annotations from class "%s" cannot be read as it is abstract. ' ,
132- $ classReflection ->getName ()
133- ));
134- }
141+ if (\function_exists ($ resource )) {
142+ $ funcReflection = new \ReflectionFunction ($ resource );
143+ $ function = $ this ->fetchFunctionAnnotation ($ funcReflection , $ this ->getAnnotations ($ funcReflection , $ annotationClass ), $ annotationClass );
135144
136- foreach ( $ this -> getAnnotations ( $ classReflection ) as $ annotation ) {
137- $ annotations [$ className ][ ' class ' ][] = $ annotation ;
138- }
145+ if ( null !== $ function ) {
146+ $ annotations [$ annotationClass ] = $ function ;
147+ }
139148
140- // Reflections belonging to class object.
141- $ reflections = \array_merge (
142- $ classReflection ->getMethods (),
143- $ classReflection ->getProperties (),
144- $ classReflection ->getConstants ()
145- );
149+ continue ;
150+ }
146151
147- return $ this ->fetchAnnotations ($ className , $ reflections , $ annotations );
152+ $ classReflection = new \ReflectionClass ($ resource );
153+
154+ if ($ classReflection ->isAbstract ()) {
155+ continue ;
156+ }
157+
158+ $ annotation = new Locate \Class_ ($ this ->getAnnotations ($ classReflection , $ annotationClass ), $ classReflection );
159+
160+ // Reflections belonging to class object.
161+ $ reflections = \array_merge (
162+ $ classReflection ->getMethods (),
163+ $ classReflection ->getProperties (),
164+ $ classReflection ->getConstants ()
165+ );
166+
167+ $ annotations [$ annotationClass ] = $ this ->fetchAnnotations ($ annotation , $ reflections , $ annotationClass );
168+ }
169+
170+ return $ annotations ;
148171 }
149172
150173 /**
151- * @param \Reflector $reflection
174+ * @param class-string $annotation
152175 *
153176 * @return iterable<object>
154177 */
155- private function getAnnotations (\Reflector $ reflection ): iterable
178+ private function getAnnotations (\Reflector $ reflection, string $ annotation ): iterable
156179 {
157180 $ annotations = [];
158181
159182 switch (true ) {
160183 case $ reflection instanceof \ReflectionClass:
161- $ annotations = $ this ->reader ->getClassMetadata ($ reflection );
184+ $ annotations = $ this ->reader ->getClassMetadata ($ reflection, $ annotation );
162185
163186 break ;
164187
165- case $ reflection instanceof \ReflectionMethod :
166- $ annotations = $ this ->reader ->getFunctionMetadata ($ reflection );
188+ case $ reflection instanceof \ReflectionFunctionAbstract :
189+ $ annotations = $ this ->reader ->getFunctionMetadata ($ reflection, $ annotation );
167190
168191 break ;
169192
170193 case $ reflection instanceof \ReflectionProperty:
171- $ annotations = $ this ->reader ->getPropertyMetadata ($ reflection );
194+ $ annotations = $ this ->reader ->getPropertyMetadata ($ reflection, $ annotation );
172195
173196 break ;
174197
175198 case $ reflection instanceof \ReflectionClassConstant:
176- $ annotations = $ this ->reader ->getConstantMetadata ($ reflection );
199+ $ annotations = $ this ->reader ->getConstantMetadata ($ reflection, $ annotation );
177200
178201 break ;
179202
180203 case $ reflection instanceof \ReflectionParameter:
181- $ annotations = $ this ->reader ->getParameterMetadata ($ reflection );
204+ $ annotations = $ this ->reader ->getParameterMetadata ($ reflection, $ annotation );
182205 }
183206
184- foreach ($ annotations as $ annotation ) {
185- foreach ($ this ->listeners as $ listener ) {
186- $ annotationClass = $ listener ->getAnnotation ();
187-
188- if ($ annotation instanceof $ annotationClass ) {
189- yield $ annotation ;
190- }
191- }
192- }
207+ return $ annotations instanceof \Generator ? \iterator_to_array ($ annotations ) : $ annotations ;
193208 }
194209
195210 /**
196- * @param \ReflectionParameter[] $parameters
211+ * Fetch annotations from methods, constant, property and methods parameter.
197212 *
198- * @return iterable<int,object[]>
213+ * @param \Reflector[] $reflections
199214 */
200- private function getMethodParameter ( array $ parameters ): iterable
215+ private function fetchAnnotations ( Locate \ Class_ $ classAnnotation , array $ reflections , string $ annotationClass ): ? Locate \ Class_
201216 {
202- foreach ($ this ->listeners as $ listener ) {
203- foreach ($ parameters as $ parameter ) {
204- if (\in_array ($ parameter ->getName (), $ listener ->getArguments (), true )) {
205- foreach ($ this ->getAnnotations ($ parameter ) as $ annotation ) {
206- yield [$ parameter , $ annotation ];
207- }
217+ $ classRefCount = 0 ;
218+
219+ foreach ($ reflections as $ name => $ reflection ) {
220+ if (\is_string ($ name )) {
221+ $ reflection = new \ReflectionClassConstant ((string ) $ classAnnotation , $ name );
222+ }
223+
224+ $ annotations = $ this ->getAnnotations ($ reflection , $ annotationClass );
225+
226+ if ($ reflection instanceof \ReflectionMethod) {
227+ $ method = $ this ->fetchFunctionAnnotation ($ reflection , $ annotations , $ annotationClass );
228+
229+ if ($ method instanceof Locate \Method) {
230+ $ classAnnotation ->methods [] = $ method ;
231+ ++$ classRefCount ;
208232 }
233+
234+ continue ;
209235 }
210- }
211- }
212236
213- /**
214- * Fetch annotations from methods, constant, property and methods parameter
215- *
216- * @param string $className
217- * @param \Reflector[] $reflections
218- * @param array<string,array<string,mixed>> $annotations
219- *
220- * @return array<string,array<string,mixed>>
221- */
222- private function fetchAnnotations (string $ className , array $ reflections , array $ annotations ): array
223- {
224- foreach ($ reflections as $ name => $ reflection ) {
225- if ($ reflection instanceof \ReflectionMethod && $ reflection ->isAbstract ()) {
237+ if ([] === $ annotations ) {
226238 continue ;
227239 }
240+ ++$ classRefCount ;
241+
242+ if ($ reflection instanceof \ReflectionProperty) {
243+ $ classAnnotation ->properties [] = new Locate \Property ($ annotations , $ reflection );
228244
229- if (is_string ($ name )) {
230- $ reflection = new \ReflectionClassConstant ($ className , $ name );
245+ continue ;
231246 }
232247
233- foreach ($ this ->getAnnotations ($ reflection ) as $ annotation ) {
234- if ($ reflection instanceof \ReflectionMethod) {
235- $ annotations [$ className ]['method ' ][] = [$ reflection , $ annotation ];
248+ if ($ reflection instanceof \ReflectionClassConstant) {
249+ $ classAnnotation ->constants [] = new Locate \Constant ($ annotations , $ reflection );
236250
237- foreach ( $ this -> getMethodParameter ( $ reflection -> getParameters ()) as $ parameter ) {
238- $ annotations [ $ className ][ ' method_property ' ][] = $ parameter ;
239- }
251+ continue ;
252+ }
253+ }
240254
241- continue ;
242- }
255+ if (0 === $ classRefCount && [] === $ classAnnotation ->getAnnotation ()) {
256+ return null ;
257+ }
243258
244- if ( $ reflection instanceof \ReflectionClassConstant) {
245- $ annotations [ $ className ][ ' constant ' ][] = [ $ reflection , $ annotation ];
259+ return $ classAnnotation ;
260+ }
246261
247- continue ;
248- }
262+ /**
263+ * @return Locate\Method|Locate\Function_|null
264+ */
265+ private function fetchFunctionAnnotation (\ReflectionFunctionAbstract $ reflection , iterable $ annotations , string $ annotationClass )
266+ {
267+ if ($ reflection instanceof \ReflectionMethod) {
268+ $ function = new Locate \Method ($ annotations , $ reflection );
269+ } else {
270+ $ function = new Locate \Function_ ($ annotations , $ reflection );
271+ }
249272
250- $ annotations [$ className ]['property ' ][] = [$ reflection , $ annotation ];
273+ foreach ($ reflection ->getParameters () as $ parameter ) {
274+ $ attributes = $ this ->getAnnotations ($ parameter , $ annotationClass );
275+
276+ if ([] !== $ attributes ) {
277+ $ function ->parameters [] = new Locate \Parameter ($ attributes , $ parameter );
251278 }
252279 }
253280
254- return $ annotations ;
281+ return ([] !== $ annotations || [] !== $ function -> parameters ) ? $ function : null ;
255282 }
256283
257284 /**
258- * Finds classes in the given resource directory
285+ * Finds classes in the given resource directory.
259286 *
260287 * @param string[] $files
261288 *
262289 * @return class-string[]
263290 */
264291 private function findClasses (array $ files ): array
265292 {
293+ if ([] === $ files ) {
294+ return [];
295+ }
296+
266297 if (null !== $ this ->classLoader ) {
267298 return ($ this ->classLoader )($ files );
268299 }
@@ -277,17 +308,15 @@ private function findClasses(array $files): array
277308 }
278309
279310 /**
280- * Finds files in the given resource
281- *
282- * @param string $resource
311+ * Finds files in the given resource.
283312 *
284313 * @return string[]
285314 */
286315 private function findFiles (string $ resource ): array
287316 {
288317 $ directory = new \RecursiveDirectoryIterator ($ resource , \FilesystemIterator::CURRENT_AS_PATHNAME );
289- $ iterator = new \RecursiveIteratorIterator ($ directory );
290- $ files = new \RegexIterator ($ iterator , '/\.php$/ ' );
318+ $ iterator = new \RecursiveIteratorIterator ($ directory );
319+ $ files = new \RegexIterator ($ iterator , '/\.php$/ ' );
291320
292321 return \iterator_to_array ($ files );
293322 }
0 commit comments