PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.7.2
Defender Security – Malware Scanner, Login Security & Firewall v5.7.2
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 / AttributeBasedAutowiring.php

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

264 lines 8.8 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\Attribute\Inject;
8 use WPMU_DEV\Defender\Vendor\DI\Attribute\Injectable;
9 use WPMU_DEV\Defender\Vendor\DI\Definition\Exception\InvalidAttribute;
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 InvalidArgumentException;
15 use ReflectionClass;
16 use ReflectionMethod;
17 use ReflectionNamedType;
18 use ReflectionParameter;
19 use ReflectionProperty;
20 use Throwable;
21
22 /**
23 * Provides WPMU_DEV\Defender\Vendor\DI definitions by reading PHP 8 attributes such as #[Inject] and #[Injectable].
24 *
25 * This source automatically includes the reflection source.
26 *
27 * @author Matthieu Napoli <[email protected]>
28 */
29 class AttributeBasedAutowiring implements DefinitionSource, Autowiring
30 {
31 /**
32 * @throws InvalidAttribute
33 */
34 public function autowire(string $name, ?ObjectDefinition $definition = null) : ?ObjectDefinition
35 {
36 $className = $definition ? $definition->getClassName() : $name;
37
38 if (!class_exists($className) && !interface_exists($className)) {
39 return $definition;
40 }
41
42 $definition = $definition ?: new ObjectDefinition($name);
43
44 $class = new ReflectionClass($className);
45
46 $this->readInjectableAttribute($class, $definition);
47
48 // Browse the class properties looking for annotated properties
49 $this->readProperties($class, $definition);
50
51 // Browse the object's methods looking for annotated methods
52 $this->readMethods($class, $definition);
53
54 return $definition;
55 }
56
57 /**
58 * @throws InvalidAttribute
59 * @throws InvalidArgumentException The class doesn't exist
60 */
61 public function getDefinition(string $name) : ?ObjectDefinition
62 {
63 return $this->autowire($name);
64 }
65
66 /**
67 * Autowiring cannot guess all existing definitions.
68 */
69 public function getDefinitions() : array
70 {
71 return [];
72 }
73
74 /**
75 * Browse the class properties looking for annotated properties.
76 */
77 private function readProperties(ReflectionClass $class, ObjectDefinition $definition) : void
78 {
79 foreach ($class->getProperties() as $property) {
80 $this->readProperty($property, $definition);
81 }
82
83 // Read also the *private* properties of the parent classes
84 /** @noinspection PhpAssignmentInConditionInspection */
85 while ($class = $class->getParentClass()) {
86 foreach ($class->getProperties(ReflectionProperty::IS_PRIVATE) as $property) {
87 $this->readProperty($property, $definition, $class->getName());
88 }
89 }
90 }
91
92 /**
93 * @throws InvalidAttribute
94 */
95 private function readProperty(ReflectionProperty $property, ObjectDefinition $definition, ?string $classname = null) : void
96 {
97 if ($property->isStatic() || $property->isPromoted()) {
98 return;
99 }
100
101 // Look for #[Inject] attribute
102 try {
103 $attribute = $property->getAttributes(Inject::class)[0] ?? null;
104 if (! $attribute) {
105 return;
106 }
107 /** @var Inject $inject */
108 $inject = $attribute->newInstance();
109 } catch (Throwable $e) {
110 throw new InvalidAttribute(sprintf(
111 '#[Inject] annotation on property %s::%s is malformed. %s',
112 $property->getDeclaringClass()->getName(),
113 $property->getName(),
114 $e->getMessage()
115 ), 0, $e);
116 }
117
118 // Try to #[Inject("name")] or look for the property type
119 $entryName = $inject->getName();
120
121 // Try using typed properties
122 $propertyType = $property->getType();
123 if ($entryName === null && $propertyType instanceof ReflectionNamedType) {
124 if (! class_exists($propertyType->getName()) && ! interface_exists($propertyType->getName())) {
125 throw new InvalidAttribute(sprintf(
126 '#[Inject] found on property %s::%s but unable to guess what to inject, the type of the property does not look like a valid class or interface name',
127 $property->getDeclaringClass()->getName(),
128 $property->getName()
129 ));
130 }
131 $entryName = $propertyType->getName();
132 }
133
134 if ($entryName === null) {
135 throw new InvalidAttribute(sprintf(
136 '#[Inject] found on property %s::%s but unable to guess what to inject, please add a type to the property',
137 $property->getDeclaringClass()->getName(),
138 $property->getName()
139 ));
140 }
141
142 $definition->addPropertyInjection(
143 new PropertyInjection($property->getName(), new Reference($entryName), $classname)
144 );
145 }
146
147 /**
148 * Browse the object's methods looking for annotated methods.
149 */
150 private function readMethods(ReflectionClass $class, ObjectDefinition $objectDefinition) : void
151 {
152 // This will look in all the methods, including those of the parent classes
153 foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
154 if ($method->isStatic()) {
155 continue;
156 }
157
158 $methodInjection = $this->getMethodInjection($method);
159
160 if (! $methodInjection) {
161 continue;
162 }
163
164 if ($method->isConstructor()) {
165 $objectDefinition->completeConstructorInjection($methodInjection);
166 } else {
167 $objectDefinition->completeFirstMethodInjection($methodInjection);
168 }
169 }
170 }
171
172 private function getMethodInjection(ReflectionMethod $method) : ?MethodInjection
173 {
174 // Look for #[Inject] attribute
175 $attribute = $method->getAttributes(Inject::class)[0] ?? null;
176
177 if ($attribute) {
178 /** @var Inject $inject */
179 $inject = $attribute->newInstance();
180 $annotationParameters = $inject->getParameters();
181 } elseif ($method->isConstructor()) {
182 // #[Inject] on constructor is implicit, we continue
183 $annotationParameters = [];
184 } else {
185 return null;
186 }
187
188 $parameters = [];
189 foreach ($method->getParameters() as $index => $parameter) {
190 $entryName = $this->getMethodParameter($index, $parameter, $annotationParameters);
191
192 if ($entryName !== null) {
193 $parameters[$index] = new Reference($entryName);
194 }
195 }
196
197 if ($method->isConstructor()) {
198 return MethodInjection::constructor($parameters);
199 }
200
201 return new MethodInjection($method->getName(), $parameters);
202 }
203
204 /**
205 * @return string|null Entry name or null if not found.
206 */
207 private function getMethodParameter(int $parameterIndex, ReflectionParameter $parameter, array $annotationParameters) : ?string
208 {
209 // Let's check if this parameter has an #[Inject] attribute
210 $attribute = $parameter->getAttributes(Inject::class)[0] ?? null;
211 if ($attribute) {
212 /** @var Inject $inject */
213 $inject = $attribute->newInstance();
214
215 return $inject->getName();
216 }
217
218 // #[Inject] has definition for this parameter (by index, or by name)
219 if (isset($annotationParameters[$parameterIndex])) {
220 return $annotationParameters[$parameterIndex];
221 }
222 if (isset($annotationParameters[$parameter->getName()])) {
223 return $annotationParameters[$parameter->getName()];
224 }
225
226 // Skip optional parameters if not explicitly defined
227 if ($parameter->isOptional()) {
228 return null;
229 }
230
231 // Look for the property type
232 $parameterType = $parameter->getType();
233 if ($parameterType instanceof ReflectionNamedType && !$parameterType->isBuiltin()) {
234 return $parameterType->getName();
235 }
236
237 return null;
238 }
239
240 /**
241 * @throws InvalidAttribute
242 */
243 private function readInjectableAttribute(ReflectionClass $class, ObjectDefinition $definition) : void
244 {
245 try {
246 $attribute = $class->getAttributes(Injectable::class)[0] ?? null;
247 if (! $attribute) {
248 return;
249 }
250 $attribute = $attribute->newInstance();
251 } catch (Throwable $e) {
252 throw new InvalidAttribute(sprintf(
253 'Error while reading #[Injectable] on %s: %s',
254 $class->getName(),
255 $e->getMessage()
256 ), 0, $e);
257 }
258
259 if ($attribute->isLazy() !== null) {
260 $definition->setLazy($attribute->isLazy());
261 }
262 }
263 }
264