PluginProbe
Cookiebot by Usercentrics – Automatic Cookie Banner for GDPR/CCPA & Google Consent Mode / 3.11.3
Cookiebot by Usercentrics – Automatic Cookie Banner for GDPR/CCPA & Google Consent Mode v3.11.3
4.7.4 4.7.5 4.7.3 4.7.2 4.7.1 trunk 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 3.0.0 3.0.1 3.1.0 3.10.0 3.10.1 3.11.1 3.11.2 3.11.3 3.2.0 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 All 95 releases
cookiebot / addons / inc / Dependencies / DI / Definition / Source / AnnotationReader.php

AnnotationReader.php in Cookiebot by Usercentrics – Automatic Cookie Banner for GDPR/CCPA & Google Consent Mode 3.11.3, at addons/inc/Dependencies/DI/Definition/Source/AnnotationReader.php

299 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PHP-DI
4 *
5 * @link http://php-di.org/
6 * @copyright Matthieu Napoli (http://mnapoli.fr/)
7 * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
8 */
9
10 namespace Cybot\Dependencies\DI\Definition\Source;
11
12 use Cybot\Dependencies\DI\Annotation\Inject;
13 use Cybot\Dependencies\DI\Annotation\Injectable;
14 use Cybot\Dependencies\DI\Definition\ObjectDefinition;
15 use Cybot\Dependencies\DI\Definition\EntryReference;
16 use Cybot\Dependencies\DI\Definition\Exception\AnnotationException;
17 use Cybot\Dependencies\DI\Definition\Exception\DefinitionException;
18 use Cybot\Dependencies\DI\Definition\ObjectDefinition\MethodInjection;
19 use Cybot\Dependencies\DI\Definition\ObjectDefinition\PropertyInjection;
20 use Doctrine\Common\Annotations\AnnotationRegistry;
21 use Doctrine\Common\Annotations\Reader;
22 use Doctrine\Common\Annotations\SimpleAnnotationReader;
23 use InvalidArgumentException;
24 use Cybot\Dependencies\PhpDocReader\PhpDocReader;
25 use ReflectionClass;
26 use ReflectionMethod;
27 use ReflectionParameter;
28 use ReflectionProperty;
29 use UnexpectedValueException;
30
31 /**
32 * Provides DI definitions by reading annotations such as @ Inject and @ var annotations.
33 *
34 * Uses Autowiring, Doctrine's Annotations and regex docblock parsing.
35 * This source automatically includes the reflection source.
36 *
37 * @author Matthieu Napoli <[email protected]>
38 */
39 class AnnotationReader implements DefinitionSource
40 {
41 /**
42 * @var Reader
43 */
44 private $annotationReader;
45
46 /**
47 * @var PhpDocReader
48 */
49 private $phpDocReader;
50
51 /**
52 * @var bool
53 */
54 private $ignorePhpDocErrors;
55
56 /**
57 * @param bool $ignorePhpDocErrors
58 */
59 public function __construct($ignorePhpDocErrors = false)
60 {
61 $this->ignorePhpDocErrors = (bool) $ignorePhpDocErrors;
62 }
63
64 /**
65 * {@inheritdoc}
66 * @throws AnnotationException
67 * @throws InvalidArgumentException The class doesn't exist
68 */
69 public function getDefinition($name)
70 {
71 if (!class_exists($name) && !interface_exists($name)) {
72 return null;
73 }
74
75 $class = new ReflectionClass($name);
76 $definition = new ObjectDefinition($name);
77
78 $this->readInjectableAnnotation($class, $definition);
79
80 // Browse the class properties looking for annotated properties
81 $this->readProperties($class, $definition);
82
83 // Browse the object's methods looking for annotated methods
84 $this->readMethods($class, $definition);
85
86 return $definition;
87 }
88
89 /**
90 * Browse the class properties looking for annotated properties.
91 *
92 * @param ReflectionClass $reflectionClass
93 * @param ObjectDefinition $objectDefinition
94 */
95 private function readProperties(ReflectionClass $reflectionClass, ObjectDefinition $objectDefinition)
96 {
97 // This will look in all the properties, including those of the parent classes
98 foreach ($reflectionClass->getProperties() as $property) {
99 // Ignore static properties
100 if ($property->isStatic()) {
101 continue;
102 }
103
104 $propertyInjection = $this->getPropertyInjection($property);
105
106 if ($propertyInjection) {
107 $objectDefinition->addPropertyInjection($propertyInjection);
108 }
109 }
110 }
111
112 /**
113 * @param ReflectionProperty $property
114 * @return PropertyInjection|null
115 */
116 private function getPropertyInjection(ReflectionProperty $property)
117 {
118 // Look for @Inject annotation
119 /** @var $annotation Inject */
120 $annotation = $this->getAnnotationReader()->getPropertyAnnotation($property, 'Cybot\Dependencies\DI\Annotation\Inject');
121 if ($annotation === null) {
122 return null;
123 }
124
125 // @Inject("name") or look for @var content
126 $entryName = $annotation->getName() ?: $this->getPhpDocReader()->getPropertyClass($property);
127
128 if ($entryName === null) {
129 throw new AnnotationException(sprintf(
130 '@Inject found on property %s::%s but unable to guess what to inject, use a @var annotation',
131 $property->getDeclaringClass()->getName(),
132 $property->getName()
133 ));
134 }
135
136 return new PropertyInjection($property->getName(), new EntryReference($entryName));
137 }
138
139 /**
140 * Browse the object's methods looking for annotated methods.
141 *
142 * @param ReflectionClass $class
143 * @param ObjectDefinition $objectDefinition
144 */
145 private function readMethods(ReflectionClass $class, ObjectDefinition $objectDefinition)
146 {
147 // This will look in all the methods, including those of the parent classes
148 foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
149 if ($method->isStatic()) {
150 continue;
151 }
152
153 $methodInjection = $this->getMethodInjection($method);
154
155 if (! $methodInjection) {
156 continue;
157 }
158
159 if ($method->isConstructor()) {
160 $objectDefinition->setConstructorInjection($methodInjection);
161 } else {
162 $objectDefinition->addMethodInjection($methodInjection);
163 }
164 }
165 }
166
167 /**
168 * {@inheritdoc}
169 */
170 private function getMethodInjection(ReflectionMethod $method)
171 {
172 // Look for @Inject annotation
173 /** @var $annotation Inject|null */
174 try {
175 $annotation = $this->getAnnotationReader()->getMethodAnnotation($method, 'Cybot\Dependencies\DI\Annotation\Inject');
176 } catch (AnnotationException $e) {
177 throw new AnnotationException(sprintf(
178 '@Inject annotation on %s::%s is malformed. %s',
179 $method->getDeclaringClass()->getName(),
180 $method->getName(),
181 $e->getMessage()
182 ), 0, $e);
183 }
184 $annotationParameters = $annotation ? $annotation->getParameters() : [];
185
186 // @Inject on constructor is implicit
187 if (! ($annotation || $method->isConstructor())) {
188 return null;
189 }
190
191 $parameters = [];
192 foreach ($method->getParameters() as $index => $parameter) {
193 $entryName = $this->getMethodParameter($index, $parameter, $annotationParameters);
194
195 if ($entryName !== null) {
196 $parameters[$index] = new EntryReference($entryName);
197 }
198 }
199
200 if ($method->isConstructor()) {
201 return MethodInjection::constructor($parameters);
202 } else {
203 return new MethodInjection($method->getName(), $parameters);
204 }
205 }
206
207 /**
208 * @param int $parameterIndex
209 * @param ReflectionParameter $parameter
210 * @param array $annotationParameters
211 *
212 * @return string|null Entry name or null if not found.
213 */
214 private function getMethodParameter($parameterIndex, ReflectionParameter $parameter, array $annotationParameters)
215 {
216 // @Inject has definition for this parameter (by index, or by name)
217 if (isset($annotationParameters[$parameterIndex])) {
218 return $annotationParameters[$parameterIndex];
219 }
220 if (isset($annotationParameters[$parameter->getName()])) {
221 return $annotationParameters[$parameter->getName()];
222 }
223
224 // Skip optional parameters if not explicitly defined
225 if ($parameter->isOptional()) {
226 return null;
227 }
228
229 // Try to use the type-hinting
230 $parameterClass = $parameter->getClass();
231 if ($parameterClass) {
232 return $parameterClass->getName();
233 }
234
235 // Last resort, look for @param tag
236 return $this->getPhpDocReader()->getParameterClass($parameter);
237 }
238
239 /**
240 * @return Reader The annotation reader
241 */
242 public function getAnnotationReader()
243 {
244 if ($this->annotationReader == null) {
245 AnnotationRegistry::registerAutoloadNamespace('Cybot\Dependencies\DI\Annotation', __DIR__ . '/../../../');
246 $this->annotationReader = new SimpleAnnotationReader();
247 $this->annotationReader->addNamespace('Cybot\Dependencies\DI\Annotation');
248 }
249
250 return $this->annotationReader;
251 }
252
253 /**
254 * @param Reader $annotationReader The annotation reader
255 */
256 public function setAnnotationReader(Reader $annotationReader)
257 {
258 $this->annotationReader = $annotationReader;
259 }
260
261 /**
262 * @return PhpDocReader
263 */
264 private function getPhpDocReader()
265 {
266 if ($this->phpDocReader === null) {
267 $this->phpDocReader = new PhpDocReader($this->ignorePhpDocErrors);
268 }
269
270 return $this->phpDocReader;
271 }
272
273 private function readInjectableAnnotation(ReflectionClass $class, ObjectDefinition $definition)
274 {
275 try {
276 /** @var $annotation Injectable|null */
277 $annotation = $this->getAnnotationReader()
278 ->getClassAnnotation($class, 'Cybot\Dependencies\DI\Annotation\Injectable');
279 } catch (UnexpectedValueException $e) {
280 throw new DefinitionException(sprintf(
281 'Error while reading @Injectable on %s: %s',
282 $class->getName(),
283 $e->getMessage()
284 ), 0, $e);
285 }
286
287 if (! $annotation) {
288 return;
289 }
290
291 if ($annotation->getScope()) {
292 $definition->setScope($annotation->getScope());
293 }
294 if ($annotation->isLazy() !== null) {
295 $definition->setLazy($annotation->isLazy());
296 }
297 }
298 }
299