PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.8.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.8.0
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 3.5.2 All 199 releases
betterdocs / includes / Dependencies / DI / Compiler / ObjectCreationCompiler.php

ObjectCreationCompiler.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.8.0, at includes/Dependencies/DI/Compiler/ObjectCreationCompiler.php

206 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:ignoreFile -- Bundled third-party (Mozart) dependency; exempt from plugin coding standards.
3
4 declare(strict_types=1);
5
6 namespace WPDeveloper\BetterDocs\Dependencies\DI\Compiler;
7
8 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\Exception\InvalidDefinition;
9 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\ObjectDefinition;
10 use WPDeveloper\BetterDocs\Dependencies\DI\Definition\ObjectDefinition\MethodInjection;
11 use ReflectionClass;
12 use ReflectionMethod;
13 use ReflectionParameter;
14 use ReflectionProperty;
15
16 /**
17 * Compiles an object definition into native PHP code that, when executed, creates the object.
18 *
19 * @author Matthieu Napoli <matthieu@mnapoli.fr>
20 */
21 class ObjectCreationCompiler
22 {
23 /**
24 * @var Compiler
25 */
26 private $compiler;
27
28 public function __construct(Compiler $compiler)
29 {
30 $this->compiler = $compiler;
31 }
32
33 public function compile(ObjectDefinition $definition) : string
34 {
35 $this->assertClassIsNotAnonymous($definition);
36 $this->assertClassIsInstantiable($definition);
37
38 // Lazy?
39 if ($definition->isLazy()) {
40 return $this->compileLazyDefinition($definition);
41 }
42
43 try {
44 $classReflection = new ReflectionClass($definition->getClassName());
45 $constructorArguments = $this->resolveParameters($definition->getConstructorInjection(), $classReflection->getConstructor());
46 $dumpedConstructorArguments = array_map(function ($value) {
47 return $this->compiler->compileValue($value);
48 }, $constructorArguments);
49
50 $code = [];
51 $code[] = sprintf(
52 '$object = new %s(%s);',
53 $definition->getClassName(),
54 implode(', ', $dumpedConstructorArguments)
55 );
56
57 // Property injections
58 foreach ($definition->getPropertyInjections() as $propertyInjection) {
59 $value = $propertyInjection->getValue();
60 $value = $this->compiler->compileValue($value);
61
62 $className = $propertyInjection->getClassName() ?: $definition->getClassName();
63 $property = new ReflectionProperty($className, $propertyInjection->getPropertyName());
64 if ($property->isPublic()) {
65 $code[] = sprintf('$object->%s = %s;', $propertyInjection->getPropertyName(), $value);
66 } else {
67 // Private/protected property
68 $code[] = sprintf(
69 '\WPDeveloper\BetterDocs\Dependencies\DI\Definition\Resolver\ObjectCreator::setPrivatePropertyValue(%s, $object, \'%s\', %s);',
70 var_export($propertyInjection->getClassName(), true),
71 $propertyInjection->getPropertyName(),
72 $value
73 );
74 }
75 }
76
77 // Method injections
78 foreach ($definition->getMethodInjections() as $methodInjection) {
79 $methodReflection = new \ReflectionMethod($definition->getClassName(), $methodInjection->getMethodName());
80 $parameters = $this->resolveParameters($methodInjection, $methodReflection);
81
82 $dumpedParameters = array_map(function ($value) {
83 return $this->compiler->compileValue($value);
84 }, $parameters);
85
86 $code[] = sprintf(
87 '$object->%s(%s);',
88 $methodInjection->getMethodName(),
89 implode(', ', $dumpedParameters)
90 );
91 }
92 } catch (InvalidDefinition $e) {
93 throw InvalidDefinition::create($definition, sprintf(
94 'Entry "%s" cannot be compiled: %s',
95 $definition->getName(),
96 $e->getMessage()
97 ));
98 }
99
100 return implode("\n ", $code);
101 }
102
103 public function resolveParameters(?MethodInjection $definition = null, ?ReflectionMethod $method = null) : array
104 {
105 $args = [];
106
107 if (! $method) {
108 return $args;
109 }
110
111 $definitionParameters = $definition ? $definition->getParameters() : [];
112
113 foreach ($method->getParameters() as $index => $parameter) {
114 if (array_key_exists($index, $definitionParameters)) {
115 // Look in the definition
116 $value = &$definitionParameters[$index];
117 } elseif ($parameter->isOptional()) {
118 // If the parameter is optional and wasn't specified, we take its default value
119 $args[] = $this->getParameterDefaultValue($parameter, $method);
120 continue;
121 } else {
122 throw new InvalidDefinition(sprintf(
123 'Parameter $%s of %s has no value defined or guessable',
124 $parameter->getName(),
125 $this->getFunctionName($method)
126 ));
127 }
128
129 $args[] = &$value;
130 }
131
132 return $args;
133 }
134
135 private function compileLazyDefinition(ObjectDefinition $definition) : string
136 {
137 $subDefinition = clone $definition;
138 $subDefinition->setLazy(false);
139 $subDefinition = $this->compiler->compileValue($subDefinition);
140
141 return <<<PHP
142 \$object = \$this->proxyFactory->createProxy(
143 '{$definition->getClassName()}',
144 function (&\$wrappedObject, \$proxy, \$method, \$params, &\$initializer) {
145 \$wrappedObject = $subDefinition;
146 \$initializer = null; // turning off further lazy initialization
147 return true;
148 }
149 );
150 PHP;
151 }
152
153 /**
154 * Returns the default value of a function parameter.
155 *
156 * @throws InvalidDefinition Can't get default values from PHP internal classes and functions
157 * @return mixed
158 */
159 private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function)
160 {
161 try {
162 return $parameter->getDefaultValue();
163 } catch (\ReflectionException $e) {
164 throw new InvalidDefinition(sprintf(
165 'The parameter "%s" of %s has no type defined or guessable. It has a default value, '
166 . 'but the default value can\'t be read through Reflection because it is a PHP internal class.',
167 $parameter->getName(),
168 $this->getFunctionName($function)
169 ));
170 }
171 }
172
173 private function getFunctionName(ReflectionMethod $method) : string
174 {
175 return $method->getName() . '()';
176 }
177
178 private function assertClassIsNotAnonymous(ObjectDefinition $definition)
179 {
180 if (strpos($definition->getClassName(), '@') !== false) {
181 throw InvalidDefinition::create($definition, sprintf(
182 'Entry "%s" cannot be compiled: anonymous classes cannot be compiled',
183 $definition->getName()
184 ));
185 }
186 }
187
188 private function assertClassIsInstantiable(ObjectDefinition $definition)
189 {
190 if (! $definition->isInstantiable()) {
191 // Check that the class exists
192 if (! $definition->classExists()) {
193 throw InvalidDefinition::create($definition, sprintf(
194 'Entry "%s" cannot be compiled: the class doesn\'t exist',
195 $definition->getName()
196 ));
197 }
198
199 throw InvalidDefinition::create($definition, sprintf(
200 'Entry "%s" cannot be compiled: the class is not instantiable',
201 $definition->getName()
202 ));
203 }
204 }
205 }
206