Skip to content

Commit f35d1b1

Browse files
Improved annotations/attributes loading
- Use object class instead of previously array used. - Added loading attributes from function - Improved performance of loading attributes/annotations
1 parent dc3aa5d commit f35d1b1

File tree

2 files changed

+127
-102
lines changed

2 files changed

+127
-102
lines changed

README.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ This project requires [PHP] 7.2 or higher. The recommended way to install, is vi
1818
$ composer require biurad/annotations
1919
```
2020

21-
We all know writing annotations support for a project takes alot of time, work and sometimes end up changing the whole code to suite the current changes of [PHP]. In short, this library is meant to be a base building block that utilizes [Doctrine Annotations][doctrine] and attributes introduced in [PHP] 8 for building your project with annotations.
22-
2321
Let's say you working on a few projects and you need annotations support for each. With this library we make your work easier, all you need is a instance of `Biurad\Annotations\ListenerInterface` and an annotated class for finding annotations or attributes.
2422

2523
**To know more about how to use this library, try going through the `tests` directory and find out how to integrate this library into your project.**
@@ -32,7 +30,7 @@ use Spiral\Attributes\AnnotationReader;
3230
use Spiral\Attributes\AttributeReader;
3331
use Spiral\Attributes\Composite\MergeReader;
3432

35-
// The doctrine annotation reader
33+
// The doctrine annotation reader requires doctrine/annotations library
3634
$doctrine = new AnnotationReader();
3735

3836
// With spiral/attributes library, we can use PHP 8 attributes in PHP 7.2 +
@@ -41,13 +39,11 @@ $attribute = new AttributeReader();
4139
// Create a new annotation loader from readers ...
4240
$annotation = new AnnotationLoader(new MergeReader([$doctrine, $attribute]));
4341

44-
$annotation->attachListener(...); // Add your implemented Annotation listeners
45-
46-
$annotation->attach(...); // Add a class string, classless file, or directory
42+
$annotation->listener(...); // Add your implemented Annotation listeners
4743

48-
$annotation->build(); // Allow annotations to be compile once
44+
$annotation->resource(...); // Add a class/function string, class file, or directory
4945

50-
$listeners = \iterator_to_array($annotation->load());
46+
$listeners = $annotation->load(); // Compile once, then load cached ...
5147

5248
// To use a collector you implemented into your instance of `Biurad\Annotations\ListenerInterface`
5349
foreach ($$listeners as $collector) {

src/AnnotationLoader.php

Lines changed: 123 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@
1919

2020
use 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+
*/
2227
class 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

Comments
 (0)