PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.3.1
Defender Security – Malware Scanner, Login Security & Firewall v5.3.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / lib / packages / DI / Definition / Source / AnnotationBasedAutowiring.php

AnnotationBasedAutowiring.php in Defender Security – Malware Scanner, Login Security & Firewall 5.3.1, at lib/packages/DI/Definition/Source/AnnotationBasedAutowiring.php

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