|
1 | 1 | <?php |
2 | 2 |
|
| 3 | +declare( strict_types=1 ); |
| 4 | + |
3 | 5 | namespace Axpecto\Annotation; |
4 | 6 |
|
5 | 7 | use Axpecto\Collection\Klist; |
6 | 8 | use Axpecto\Container\Container; |
7 | 9 | use Axpecto\Reflection\ReflectionUtils; |
8 | | -use Exception; |
| 10 | +use ReflectionAttribute; |
9 | 11 | use ReflectionException; |
10 | | -use ReflectionMethod; |
| 12 | +use ReflectionParameter; |
11 | 13 |
|
12 | 14 | /** |
13 | | - * Class AnnotationReader |
14 | | - * |
15 | | - * This class is responsible for reading annotations on classes and methods, specifically |
16 | | - * for use in an Aspect-Oriented Programming (AOP) context. It handles fetching annotations, |
17 | | - * including build-related annotations, and supports dependency injection for these annotations. |
| 15 | + * Reads PHP8 attributes and turns them into AOP-style Annotation instances, |
| 16 | + * filtering by class vs. method targets and injecting their properties via DI. |
18 | 17 | * |
19 | | - * @template T |
| 18 | + * @template A of Annotation |
| 19 | + * @psalm-consistent-constructor |
20 | 20 | */ |
21 | 21 | class AnnotationReader { |
22 | | - |
23 | | - /** |
24 | | - * Constructor for AnnotationReader. |
25 | | - * |
26 | | - * @param Container $container The dependency injection container. |
27 | | - * @param ReflectionUtils $reflect Utility for handling reflection of classes and methods. |
28 | | - */ |
29 | 22 | public function __construct( |
30 | 23 | private readonly Container $container, |
31 | | - private readonly ReflectionUtils $reflect, |
| 24 | + private readonly ReflectionUtils $reflection |
32 | 25 | ) { |
33 | 26 | } |
34 | 27 |
|
35 | 28 | /** |
36 | | - * Fetches annotations for a specific method. |
| 29 | + * Fetch all annotations of a given type on a class. |
37 | 30 | * |
38 | | - * @param class-string<T> $class The fully qualified class name. |
39 | | - * @param string $method The method name. |
40 | | - * @param string $annotationClass The annotation class to filter. |
| 31 | + * @template T |
| 32 | + * @param class-string<T> $class |
| 33 | + * @param class-string<A> $annotationClass |
41 | 34 | * |
42 | | - * @return Klist<Annotation> A list of annotations for the method. |
43 | | - * @throws ReflectionException|Exception |
| 35 | + * @return Klist<T> |
| 36 | + * @throws ReflectionException |
44 | 37 | */ |
45 | | - public function getMethodAnnotations( string $class, string $method, string $annotationClass = Annotation::class ): Klist { |
46 | | - return $this->mapAttributesToAnnotations( |
47 | | - attributes: $this->reflect->getMethodAttributes( $class, $method ), |
48 | | - annotationClass: $annotationClass |
49 | | - ); |
| 38 | + public function getClassAnnotations( |
| 39 | + string $class, |
| 40 | + string $annotationClass, |
| 41 | + ): Klist { |
| 42 | + $raw = $this->reflection->getClassAttributes( $class ); |
| 43 | + |
| 44 | + return $this |
| 45 | + ->filterAndInject( $raw, $annotationClass ) |
| 46 | + ->map( fn( Annotation $ann ): Annotation => $ann->setAnnotatedClass( $class ) ); |
50 | 47 | } |
51 | 48 |
|
52 | 49 | /** |
53 | | - * Fetches build-related annotations for a method. |
| 50 | + * Fetch all annotations of a given type on a method. |
54 | 51 | * |
55 | | - * @param class-string<T> $class The fully qualified class name. |
56 | | - * @param string $method The method name. |
57 | | - * @param string $annotationClass The annotation class to filter. |
| 52 | + * @template T |
| 53 | + * @param class-string<T> $class |
| 54 | + * @param string $method |
| 55 | + * @param class-string<A> $annotationClass |
58 | 56 | * |
59 | | - * @return Klist<Annotation> A list of build annotations for the method. |
| 57 | + * @return Klist<T> |
60 | 58 | * @throws ReflectionException |
61 | 59 | */ |
62 | | - public function getMethodExecutionAnnotations( string $class, string $method, string $annotationClass = Annotation::class ): Klist { |
63 | | - return $this->getMethodAnnotations( $class, $method, $annotationClass ) |
64 | | - ->filter( fn( Annotation $annotation ) => $annotation->isMethodExecutionAnnotation() ) |
65 | | - ->map( fn( Annotation $annotation ) => $annotation->setAnnotatedClass( $class )->setAnnotatedMethod( $method ) ); |
| 60 | + public function getMethodAnnotations( |
| 61 | + string $class, |
| 62 | + string $method, |
| 63 | + string $annotationClass, |
| 64 | + ): Klist { |
| 65 | + $raw = $this->reflection->getMethodAttributes( $class, $method ); |
| 66 | + |
| 67 | + return $this |
| 68 | + ->filterAndInject( $raw, $annotationClass ) |
| 69 | + ->map( fn( Annotation $ann ): Annotation => $ann |
| 70 | + ->setAnnotatedClass( $class ) |
| 71 | + ->setAnnotatedMethod( $method ) |
| 72 | + ); |
66 | 73 | } |
67 | 74 |
|
68 | 75 | /** |
69 | | - * Fetches build-related annotations for a method. |
| 76 | + * Fetch both class‑level and method‑level annotations of a given type. |
70 | 77 | * |
71 | | - * @param class-string<T> $class The fully qualified class name. |
72 | | - * @param string $method The method name. |
73 | | - * @param string $annotationClass The annotation class to filter. |
| 78 | + * @template T |
| 79 | + * @param class-string<T> $class |
| 80 | + * @param class-string<A> $annotationClass |
74 | 81 | * |
75 | | - * @return Klist<Annotation> A list of build annotations for the method. |
| 82 | + * @return Klist<T> |
76 | 83 | * @throws ReflectionException |
77 | 84 | */ |
78 | | - public function getMethodBuildAnnotations( string $class, string $method, string $annotationClass = Annotation::class ): Klist { |
79 | | - return $this->getMethodAnnotations( $class, $method, $annotationClass ) |
80 | | - ->filter( fn( Annotation $annotation ) => $annotation->isBuildAnnotation() ) |
81 | | - ->map( fn( Annotation $annotation ) => $annotation->setAnnotatedClass( $class )->setAnnotatedMethod( $method ) ); |
| 85 | + public function getAllAnnotations( |
| 86 | + string $class, |
| 87 | + string $annotationClass = Annotation::class |
| 88 | + ): Klist { |
| 89 | + $classAnns = $this->getClassAnnotations( $class, $annotationClass ); |
| 90 | + $methodAnns = $this->reflection |
| 91 | + ->getAnnotatedMethods( $class, $annotationClass ) |
| 92 | + ->map( fn( \ReflectionMethod $m ) => $this->getMethodAnnotations( $class, $m->getName(), $annotationClass ) |
| 93 | + ) |
| 94 | + ->flatten(); |
| 95 | + |
| 96 | + return $classAnns->merge( $methodAnns ); |
82 | 97 | } |
83 | 98 |
|
84 | 99 | /** |
85 | | - * Fetches all build-related annotations for a class, including its methods. |
| 100 | + * Fetch all annotations of a given type on one of a method’s parameters. |
86 | 101 | * |
87 | | - * @param class-string<T> $class The fully qualified class name. |
88 | | - * @param string $annotationClass The annotation class to filter. |
| 102 | + * @template T |
| 103 | + * @param class-string<T> $class |
| 104 | + * @param string $method |
| 105 | + * @param string $parameterName |
| 106 | + * @param class-string<A> $annotationClass |
89 | 107 | * |
90 | | - * @return Klist<Annotation> A list of all build annotations for the class. |
| 108 | + * @return Klist<A> |
91 | 109 | * @throws ReflectionException |
92 | 110 | */ |
93 | | - public function getAllBuildAnnotations( string $class, string $annotationClass = Annotation::class ): Klist { |
94 | | - return $this->reflect |
95 | | - ->getAnnotatedMethods( $class ) |
96 | | - ->map( fn( ReflectionMethod $method ) => $this->getMethodBuildAnnotations( $class, $method->getName(), $annotationClass ) ) |
97 | | - ->flatten() |
98 | | - ->merge( $this->getClassBuildAnnotations( $class, $annotationClass ) ); |
99 | | - } |
| 111 | + public function getParameterAnnotations( |
| 112 | + string $class, |
| 113 | + string $method, |
| 114 | + string $parameterName, |
| 115 | + string $annotationClass, |
| 116 | + ): Klist { |
| 117 | + $parameter = listFrom( $this->reflection->getClassMethod( $class, $method )->getParameters() ) |
| 118 | + ->filter( fn( ReflectionParameter $p ) => $p->getName() === $parameterName ) |
| 119 | + ->firstOrNull(); |
100 | 120 |
|
101 | | - /** |
102 | | - * Fetches annotations for a class. |
103 | | - * |
104 | | - * @param class-string<T> $class The fully qualified class name. |
105 | | - * @param string $annotationClass The annotation class to filter. |
106 | | - * |
107 | | - * @return Klist<Annotation> A list of annotations for the class. |
108 | | - * @throws ReflectionException|Exception |
109 | | - */ |
110 | | - public function getClassAnnotations( string $class, string $annotationClass = Annotation::class ): Klist { |
111 | | - return $this->mapAttributesToAnnotations( |
112 | | - attributes: $this->reflect->getClassAttributes( $class ), |
113 | | - annotationClass: $annotationClass |
114 | | - ); |
| 121 | + if ( ! $parameter ) { |
| 122 | + return emptyList(); |
| 123 | + } |
| 124 | + |
| 125 | + return listFrom( $parameter->getAttributes() ) |
| 126 | + ->map( fn( ReflectionAttribute $p ) => $p->newInstance() ) |
| 127 | + ->maybe( fn( Klist $attributes ) => $this->filterAndInject( $attributes, $annotationClass ) ) |
| 128 | + ->foreach( fn( Annotation $ann ) => $ann->setAnnotatedClass( $class )->setAnnotatedMethod( $method ) ); |
115 | 129 | } |
116 | 130 |
|
117 | 131 | /** |
118 | | - * Fetches build-related annotations for a class. |
| 132 | + * Fetch a single annotation of a given type on a property. |
119 | 133 | * |
120 | | - * @param class-string<T> $class The fully qualified class name. |
121 | | - * @param string $annotationClass The annotation class to filter. |
| 134 | + * @template T |
| 135 | + * @param class-string<T> $class |
| 136 | + * @param string $property |
| 137 | + * @param class-string<A> $annotationClass |
122 | 138 | * |
123 | | - * @return Klist<Annotation> A list of build annotations for the class. |
124 | | - * @throws ReflectionException|Exception |
| 139 | + * @return A|null |
| 140 | + * @throws ReflectionException |
125 | 141 | */ |
126 | | - public function getClassBuildAnnotations( string $class, string $annotationClass = Annotation::class ): Klist { |
127 | | - return $this->getClassAnnotations( $class, $annotationClass ) |
128 | | - ->filter( fn( Annotation $annotation ) => $annotation->isBuildAnnotation() ) |
129 | | - ->map( fn( Annotation $annotation ) => $annotation->setAnnotatedClass( $class ) ); |
| 142 | + public function getPropertyAnnotation( |
| 143 | + string $class, |
| 144 | + string $property, |
| 145 | + string $annotationClass = Annotation::class |
| 146 | + ): mixed { |
| 147 | + $attributes = $this->reflection |
| 148 | + ->getReflectionClass( $class ) |
| 149 | + ->getProperty( $property ) |
| 150 | + ->getAttributes(); |
| 151 | + |
| 152 | + return listFrom( $attributes ) |
| 153 | + ->map( fn( ReflectionAttribute $a ) => $a->newInstance() ) |
| 154 | + ->maybe( fn( Klist $attributes ) => $this->filterAndInject( $attributes, $annotationClass ) ) |
| 155 | + ->firstOrNull() |
| 156 | + ?->setAnnotatedClass( $class ); |
130 | 157 | } |
131 | 158 |
|
132 | 159 | /** |
133 | | - * Maps attributes to annotations and applies property injection. |
134 | | - * |
135 | | - * @param Klist $attributes A list of attributes. |
136 | | - * @param string $annotationClass The annotation class to filter. |
| 160 | + * @template T of Annotation |
| 161 | + * @param Klist<Annotation> $instances |
| 162 | + * @param class-string<T> $annotationClass |
137 | 163 | * |
138 | | - * @return Klist A list of filtered and injected annotations. |
139 | | - * @throws Exception |
| 164 | + * @return Klist<T> |
140 | 165 | */ |
141 | | - private function mapAttributesToAnnotations( Klist $attributes, string $annotationClass ): Klist { |
142 | | - return $attributes |
143 | | - ->filter( fn( $annotation ) => $annotation instanceof $annotationClass ) |
144 | | - ->foreach( fn( $annotation ) => $this->container->applyPropertyInjection( $annotation ) ); |
| 166 | + private function filterAndInject( Klist $instances, string $annotationClass ): Klist { |
| 167 | + return $instances |
| 168 | + ->filter( fn( $i ) => is_a( $i, $annotationClass, true ) ) |
| 169 | + ->foreach( fn( Annotation $ann ) => $this->container->applyPropertyInjection( $ann ) ); |
145 | 170 | } |
146 | 171 | } |
0 commit comments