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
| 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 |