PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.2.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.2.0
4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 All 200 releases
betterdocs / includes / Dependencies / DI / Definition / Source / AnnotationBasedAutowiring.php

AnnotationBasedAutowiring.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.2.0, at includes/Dependencies/DI/Definition/Source/AnnotationBasedAutowiring.php

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