PluginProbe
Elementor Website Builder – more than just a page builder / 3.29.0-beta4
Elementor Website Builder – more than just a page builder v3.29.0-beta4
4.3.2 4.3.1 4.3.0 4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 All 455 releases
elementor / vendor_prefixed / dependency-injection / php-di / php-di / src / Compiler / Compiler.php

Compiler.php in Elementor Website Builder – more than just a page builder 3.29.0-beta4, at vendor_prefixed/dependency-injection/php-di/php-di/src/Compiler/Compiler.php

345 lines 14.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 namespace ElementorDeps\DI\Compiler;
5
6 use function chmod;
7 use ElementorDeps\DI\Definition\ArrayDefinition;
8 use ElementorDeps\DI\Definition\DecoratorDefinition;
9 use ElementorDeps\DI\Definition\Definition;
10 use ElementorDeps\DI\Definition\EnvironmentVariableDefinition;
11 use ElementorDeps\DI\Definition\Exception\InvalidDefinition;
12 use ElementorDeps\DI\Definition\FactoryDefinition;
13 use ElementorDeps\DI\Definition\ObjectDefinition;
14 use ElementorDeps\DI\Definition\Reference;
15 use ElementorDeps\DI\Definition\Source\DefinitionSource;
16 use ElementorDeps\DI\Definition\StringDefinition;
17 use ElementorDeps\DI\Definition\ValueDefinition;
18 use ElementorDeps\DI\DependencyException;
19 use ElementorDeps\DI\Proxy\ProxyFactory;
20 use function dirname;
21 use function file_put_contents;
22 use InvalidArgumentException;
23 use ElementorDeps\Laravel\SerializableClosure\Support\ReflectionClosure;
24 use function rename;
25 use function sprintf;
26 use function tempnam;
27 use function unlink;
28 /**
29 * Compiles the container into PHP code much more optimized for performances.
30 *
31 * @author Matthieu Napoli <[email protected]>
32 */
33 class Compiler
34 {
35 /**
36 * @var string
37 */
38 private $containerClass;
39 /**
40 * @var string
41 */
42 private $containerParentClass;
43 /**
44 * Definitions indexed by the entry name. The value can be null if the definition needs to be fetched.
45 *
46 * Keys are strings, values are `Definition` objects or null.
47 *
48 * @var \ArrayIterator
49 */
50 private $entriesToCompile;
51 /**
52 * Progressive counter for definitions.
53 *
54 * Each key in $entriesToCompile is defined as 'SubEntry' + counter
55 * and each definition has always the same key in the CompiledContainer
56 * if PHP-DI configuration does not change.
57 *
58 * @var int
59 */
60 private $subEntryCounter;
61 /**
62 * Progressive counter for CompiledContainer get methods.
63 *
64 * Each CompiledContainer method name is defined as 'get' + counter
65 * and remains the same after each recompilation
66 * if PHP-DI configuration does not change.
67 *
68 * @var int
69 */
70 private $methodMappingCounter;
71 /**
72 * Map of entry names to method names.
73 *
74 * @var string[]
75 */
76 private $entryToMethodMapping = [];
77 /**
78 * @var string[]
79 */
80 private $methods = [];
81 /**
82 * @var bool
83 */
84 private $autowiringEnabled;
85 /**
86 * @var ProxyFactory
87 */
88 private $proxyFactory;
89 public function __construct(ProxyFactory $proxyFactory)
90 {
91 $this->proxyFactory = $proxyFactory;
92 }
93 public function getProxyFactory() : ProxyFactory
94 {
95 return $this->proxyFactory;
96 }
97 /**
98 * Compile the container.
99 *
100 * @return string The compiled container file name.
101 */
102 public function compile(DefinitionSource $definitionSource, string $directory, string $className, string $parentClassName, bool $autowiringEnabled) : string
103 {
104 $fileName = \rtrim($directory, '/') . '/' . $className . '.php';
105 if (\file_exists($fileName)) {
106 // The container is already compiled
107 return $fileName;
108 }
109 $this->autowiringEnabled = $autowiringEnabled;
110 // Validate that a valid class name was provided
111 $validClassName = \preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $className);
112 if (!$validClassName) {
113 throw new InvalidArgumentException("The container cannot be compiled: `{$className}` is not a valid PHP class name");
114 }
115 $this->entriesToCompile = new \ArrayIterator($definitionSource->getDefinitions());
116 // We use an ArrayIterator so that we can keep adding new items to the list while we compile entries
117 foreach ($this->entriesToCompile as $entryName => $definition) {
118 $silenceErrors = \false;
119 // This is an entry found by reference during autowiring
120 if (!$definition) {
121 $definition = $definitionSource->getDefinition($entryName);
122 // We silence errors for those entries because type-hints may reference interfaces/abstract classes
123 // which could later be defined, or even not used (we don't want to block the compilation for those)
124 $silenceErrors = \true;
125 }
126 if (!$definition) {
127 // We do not throw a `NotFound` exception here because the dependency
128 // could be defined at runtime
129 continue;
130 }
131 // Check that the definition can be compiled
132 $errorMessage = $this->isCompilable($definition);
133 if ($errorMessage !== \true) {
134 continue;
135 }
136 try {
137 $this->compileDefinition($entryName, $definition);
138 } catch (InvalidDefinition $e) {
139 if ($silenceErrors) {
140 // forget the entry
141 unset($this->entryToMethodMapping[$entryName]);
142 } else {
143 throw $e;
144 }
145 }
146 }
147 $this->containerClass = $className;
148 $this->containerParentClass = $parentClassName;
149 \ob_start();
150 require __DIR__ . '/Template.php';
151 $fileContent = \ob_get_clean();
152 $fileContent = "<?php\n" . $fileContent;
153 $this->createCompilationDirectory(dirname($fileName));
154 $this->writeFileAtomic($fileName, $fileContent);
155 return $fileName;
156 }
157 private function writeFileAtomic(string $fileName, string $content) : int
158 {
159 $tmpFile = @tempnam(dirname($fileName), 'swap-compile');
160 if ($tmpFile === \false) {
161 throw new InvalidArgumentException(sprintf('Error while creating temporary file in %s', dirname($fileName)));
162 }
163 @chmod($tmpFile, 0666);
164 $written = file_put_contents($tmpFile, $content);
165 if ($written === \false) {
166 @unlink($tmpFile);
167 throw new InvalidArgumentException(sprintf('Error while writing to %s', $tmpFile));
168 }
169 @chmod($tmpFile, 0666);
170 $renamed = @rename($tmpFile, $fileName);
171 if (!$renamed) {
172 @unlink($tmpFile);
173 throw new InvalidArgumentException(sprintf('Error while renaming %s to %s', $tmpFile, $fileName));
174 }
175 return $written;
176 }
177 /**
178 * @throws DependencyException
179 * @throws InvalidDefinition
180 * @return string The method name
181 */
182 private function compileDefinition(string $entryName, Definition $definition) : string
183 {
184 // Generate a unique method name
185 $methodName = 'get' . ++$this->methodMappingCounter;
186 $this->entryToMethodMapping[$entryName] = $methodName;
187 switch (\true) {
188 case $definition instanceof ValueDefinition:
189 $value = $definition->getValue();
190 $code = 'return ' . $this->compileValue($value) . ';';
191 break;
192 case $definition instanceof Reference:
193 $targetEntryName = $definition->getTargetEntryName();
194 $code = 'return $this->delegateContainer->get(' . $this->compileValue($targetEntryName) . ');';
195 // If this method is not yet compiled we store it for compilation
196 if (!isset($this->entriesToCompile[$targetEntryName])) {
197 $this->entriesToCompile[$targetEntryName] = null;
198 }
199 break;
200 case $definition instanceof StringDefinition:
201 $entryName = $this->compileValue($definition->getName());
202 $expression = $this->compileValue($definition->getExpression());
203 $code = 'return \\DI\\Definition\\StringDefinition::resolveExpression(' . $entryName . ', ' . $expression . ', $this->delegateContainer);';
204 break;
205 case $definition instanceof EnvironmentVariableDefinition:
206 $variableName = $this->compileValue($definition->getVariableName());
207 $isOptional = $this->compileValue($definition->isOptional());
208 $defaultValue = $this->compileValue($definition->getDefaultValue());
209 $code = <<<PHP
210 \$value = \$_ENV[{$variableName}] ?? \$_SERVER[{$variableName}] ?? getenv({$variableName});
211 if (false !== \$value) return \$value;
212 if (!{$isOptional}) {
213 throw new \\DI\\Definition\\Exception\\InvalidDefinition("The environment variable '{$definition->getVariableName()}' has not been defined");
214 }
215 return {$defaultValue};
216 PHP;
217 break;
218 case $definition instanceof ArrayDefinition:
219 try {
220 $code = 'return ' . $this->compileValue($definition->getValues()) . ';';
221 } catch (\Exception $e) {
222 throw new DependencyException(sprintf('Error while compiling %s. %s', $definition->getName(), $e->getMessage()), 0, $e);
223 }
224 break;
225 case $definition instanceof ObjectDefinition:
226 $compiler = new ObjectCreationCompiler($this);
227 $code = $compiler->compile($definition);
228 $code .= "\n return \$object;";
229 break;
230 case $definition instanceof DecoratorDefinition:
231 $decoratedDefinition = $definition->getDecoratedDefinition();
232 if (!$decoratedDefinition instanceof Definition) {
233 if (!$definition->getName()) {
234 throw new InvalidDefinition('Decorators cannot be nested in another definition');
235 }
236 throw new InvalidDefinition(sprintf('Entry "%s" decorates nothing: no previous definition with the same name was found', $definition->getName()));
237 }
238 $code = sprintf('return call_user_func(%s, %s, $this->delegateContainer);', $this->compileValue($definition->getCallable()), $this->compileValue($decoratedDefinition));
239 break;
240 case $definition instanceof FactoryDefinition:
241 $value = $definition->getCallable();
242 // Custom error message to help debugging
243 $isInvokableClass = \is_string($value) && \class_exists($value) && \method_exists($value, '__invoke');
244 if ($isInvokableClass && !$this->autowiringEnabled) {
245 throw new InvalidDefinition(sprintf('Entry "%s" cannot be compiled. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $entryName));
246 }
247 $definitionParameters = '';
248 if (!empty($definition->getParameters())) {
249 $definitionParameters = ', ' . $this->compileValue($definition->getParameters());
250 }
251 $code = sprintf('return $this->resolveFactory(%s, %s%s);', $this->compileValue($value), \var_export($entryName, \true), $definitionParameters);
252 break;
253 default:
254 // This case should not happen (so it cannot be tested)
255 throw new \Exception('Cannot compile definition of type ' . \get_class($definition));
256 }
257 $this->methods[$methodName] = $code;
258 return $methodName;
259 }
260 public function compileValue($value) : string
261 {
262 // Check that the value can be compiled
263 $errorMessage = $this->isCompilable($value);
264 if ($errorMessage !== \true) {
265 throw new InvalidDefinition($errorMessage);
266 }
267 if ($value instanceof Definition) {
268 // Give it an arbitrary unique name
269 $subEntryName = 'subEntry' . ++$this->subEntryCounter;
270 // Compile the sub-definition in another method
271 $methodName = $this->compileDefinition($subEntryName, $value);
272 // The value is now a method call to that method (which returns the value)
273 return "\$this->{$methodName}()";
274 }
275 if (\is_array($value)) {
276 $value = \array_map(function ($value, $key) {
277 $compiledValue = $this->compileValue($value);
278 $key = \var_export($key, \true);
279 return " {$key} => {$compiledValue},\n";
280 }, $value, \array_keys($value));
281 $value = \implode('', $value);
282 return "[\n{$value} ]";
283 }
284 if ($value instanceof \Closure) {
285 return $this->compileClosure($value);
286 }
287 return \var_export($value, \true);
288 }
289 private function createCompilationDirectory(string $directory)
290 {
291 if (!\is_dir($directory) && !@\mkdir($directory, 0777, \true) && !\is_dir($directory)) {
292 throw new InvalidArgumentException(sprintf('Compilation directory does not exist and cannot be created: %s.', $directory));
293 }
294 if (!\is_writable($directory)) {
295 throw new InvalidArgumentException(sprintf('Compilation directory is not writable: %s.', $directory));
296 }
297 }
298 /**
299 * @return string|true If true is returned that means that the value is compilable.
300 */
301 private function isCompilable($value)
302 {
303 if ($value instanceof ValueDefinition) {
304 return $this->isCompilable($value->getValue());
305 }
306 if ($value instanceof DecoratorDefinition) {
307 if (empty($value->getName())) {
308 return 'Decorators cannot be nested in another definition';
309 }
310 }
311 // All other definitions are compilable
312 if ($value instanceof Definition) {
313 return \true;
314 }
315 if ($value instanceof \Closure) {
316 return \true;
317 }
318 if (\is_object($value)) {
319 return 'An object was found but objects cannot be compiled';
320 }
321 if (\is_resource($value)) {
322 return 'A resource was found but resources cannot be compiled';
323 }
324 return \true;
325 }
326 /**
327 * @throws \DI\Definition\Exception\InvalidDefinition
328 */
329 private function compileClosure(\Closure $closure) : string
330 {
331 $reflector = new ReflectionClosure($closure);
332 if ($reflector->getUseVariables()) {
333 throw new InvalidDefinition('Cannot compile closures which import variables using the `use` keyword');
334 }
335 if ($reflector->isBindingRequired() || $reflector->isScopeRequired()) {
336 throw new InvalidDefinition('Cannot compile closures which use $this or self/static/parent references');
337 }
338 // Force all closures to be static (add the `static` keyword), i.e. they can't use
339 // $this, which makes sense since their code is copied into another class.
340 $code = ($reflector->isStatic() ? '' : 'static ') . $reflector->getCode();
341 $code = \trim($code, "\t\n\r;");
342 return $code;
343 }
344 }
345