| 1 |
<?php |
| 2 |
|
| 3 |
declare (strict_types=1); |
| 4 |
namespace ElementorDeps\DI\Compiler; |
| 5 |
|
| 6 |
use ElementorDeps\DI\Definition\Exception\InvalidDefinition; |
| 7 |
use ElementorDeps\DI\Definition\ObjectDefinition; |
| 8 |
use ElementorDeps\DI\Definition\ObjectDefinition\MethodInjection; |
| 9 |
use ReflectionClass; |
| 10 |
use ReflectionMethod; |
| 11 |
use ReflectionParameter; |
| 12 |
use ReflectionProperty; |
| 13 |
/** |
| 14 |
* Compiles an object definition into native PHP code that, when executed, creates the object. |
| 15 |
* |
| 16 |
* @author Matthieu Napoli <matthieu@mnapoli.fr> |
| 17 |
*/ |
| 18 |
class ObjectCreationCompiler |
| 19 |
{ |
| 20 |
/** |
| 21 |
* @var Compiler |
| 22 |
*/ |
| 23 |
private $compiler; |
| 24 |
public function __construct(Compiler $compiler) |
| 25 |
{ |
| 26 |
$this->compiler = $compiler; |
| 27 |
} |
| 28 |
public function compile(ObjectDefinition $definition) : string |
| 29 |
{ |
| 30 |
$this->assertClassIsNotAnonymous($definition); |
| 31 |
$this->assertClassIsInstantiable($definition); |
| 32 |
// Lazy? |
| 33 |
if ($definition->isLazy()) { |
| 34 |
return $this->compileLazyDefinition($definition); |
| 35 |
} |
| 36 |
try { |
| 37 |
$classReflection = new ReflectionClass($definition->getClassName()); |
| 38 |
$constructorArguments = $this->resolveParameters($definition->getConstructorInjection(), $classReflection->getConstructor()); |
| 39 |
$dumpedConstructorArguments = \array_map(function ($value) { |
| 40 |
return $this->compiler->compileValue($value); |
| 41 |
}, $constructorArguments); |
| 42 |
$code = []; |
| 43 |
$code[] = \sprintf('$object = new %s(%s);', $definition->getClassName(), \implode(', ', $dumpedConstructorArguments)); |
| 44 |
// Property injections |
| 45 |
foreach ($definition->getPropertyInjections() as $propertyInjection) { |
| 46 |
$value = $propertyInjection->getValue(); |
| 47 |
$value = $this->compiler->compileValue($value); |
| 48 |
$className = $propertyInjection->getClassName() ?: $definition->getClassName(); |
| 49 |
$property = new ReflectionProperty($className, $propertyInjection->getPropertyName()); |
| 50 |
if ($property->isPublic()) { |
| 51 |
$code[] = \sprintf('$object->%s = %s;', $propertyInjection->getPropertyName(), $value); |
| 52 |
} else { |
| 53 |
// Private/protected property |
| 54 |
$code[] = \sprintf('\\DI\\Definition\\Resolver\\ObjectCreator::setPrivatePropertyValue(%s, $object, \'%s\', %s);', \var_export($propertyInjection->getClassName(), \true), $propertyInjection->getPropertyName(), $value); |
| 55 |
} |
| 56 |
} |
| 57 |
// Method injections |
| 58 |
foreach ($definition->getMethodInjections() as $methodInjection) { |
| 59 |
$methodReflection = new \ReflectionMethod($definition->getClassName(), $methodInjection->getMethodName()); |
| 60 |
$parameters = $this->resolveParameters($methodInjection, $methodReflection); |
| 61 |
$dumpedParameters = \array_map(function ($value) { |
| 62 |
return $this->compiler->compileValue($value); |
| 63 |
}, $parameters); |
| 64 |
$code[] = \sprintf('$object->%s(%s);', $methodInjection->getMethodName(), \implode(', ', $dumpedParameters)); |
| 65 |
} |
| 66 |
} catch (InvalidDefinition $e) { |
| 67 |
throw InvalidDefinition::create($definition, \sprintf('Entry "%s" cannot be compiled: %s', $definition->getName(), $e->getMessage())); |
| 68 |
} |
| 69 |
return \implode("\n ", $code); |
| 70 |
} |
| 71 |
public function resolveParameters(MethodInjection $definition = null, ReflectionMethod $method = null) : array |
| 72 |
{ |
| 73 |
$args = []; |
| 74 |
if (!$method) { |
| 75 |
return $args; |
| 76 |
} |
| 77 |
$definitionParameters = $definition ? $definition->getParameters() : []; |
| 78 |
foreach ($method->getParameters() as $index => $parameter) { |
| 79 |
if (\array_key_exists($index, $definitionParameters)) { |
| 80 |
// Look in the definition |
| 81 |
$value =& $definitionParameters[$index]; |
| 82 |
} elseif ($parameter->isOptional()) { |
| 83 |
// If the parameter is optional and wasn't specified, we take its default value |
| 84 |
$args[] = $this->getParameterDefaultValue($parameter, $method); |
| 85 |
continue; |
| 86 |
} else { |
| 87 |
throw new InvalidDefinition(\sprintf('Parameter $%s of %s has no value defined or guessable', $parameter->getName(), $this->getFunctionName($method))); |
| 88 |
} |
| 89 |
$args[] =& $value; |
| 90 |
} |
| 91 |
return $args; |
| 92 |
} |
| 93 |
private function compileLazyDefinition(ObjectDefinition $definition) : string |
| 94 |
{ |
| 95 |
$subDefinition = clone $definition; |
| 96 |
$subDefinition->setLazy(\false); |
| 97 |
$subDefinition = $this->compiler->compileValue($subDefinition); |
| 98 |
$this->compiler->getProxyFactory()->generateProxyClass($definition->getClassName()); |
| 99 |
return <<<PHP |
| 100 |
\$object = \$this->proxyFactory->createProxy( |
| 101 |
'{$definition->getClassName()}', |
| 102 |
function (&\$wrappedObject, \$proxy, \$method, \$params, &\$initializer) { |
| 103 |
\$wrappedObject = {$subDefinition}; |
| 104 |
\$initializer = null; // turning off further lazy initialization |
| 105 |
return true; |
| 106 |
} |
| 107 |
); |
| 108 |
PHP; |
| 109 |
} |
| 110 |
/** |
| 111 |
* Returns the default value of a function parameter. |
| 112 |
* |
| 113 |
* @throws InvalidDefinition Can't get default values from PHP internal classes and functions |
| 114 |
* @return mixed |
| 115 |
*/ |
| 116 |
private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function) |
| 117 |
{ |
| 118 |
try { |
| 119 |
return $parameter->getDefaultValue(); |
| 120 |
} catch (\ReflectionException $e) { |
| 121 |
throw new InvalidDefinition(\sprintf('The parameter "%s" of %s has no type defined or guessable. It has a default value, ' . 'but the default value can\'t be read through Reflection because it is a PHP internal class.', $parameter->getName(), $this->getFunctionName($function))); |
| 122 |
} |
| 123 |
} |
| 124 |
private function getFunctionName(ReflectionMethod $method) : string |
| 125 |
{ |
| 126 |
return $method->getName() . '()'; |
| 127 |
} |
| 128 |
private function assertClassIsNotAnonymous(ObjectDefinition $definition) |
| 129 |
{ |
| 130 |
if (\strpos($definition->getClassName(), '@') !== \false) { |
| 131 |
throw InvalidDefinition::create($definition, \sprintf('Entry "%s" cannot be compiled: anonymous classes cannot be compiled', $definition->getName())); |
| 132 |
} |
| 133 |
} |
| 134 |
private function assertClassIsInstantiable(ObjectDefinition $definition) |
| 135 |
{ |
| 136 |
if ($definition->isInstantiable()) { |
| 137 |
return; |
| 138 |
} |
| 139 |
$message = !$definition->classExists() ? 'Entry "%s" cannot be compiled: the class doesn\'t exist' : 'Entry "%s" cannot be compiled: the class is not instantiable'; |
| 140 |
throw InvalidDefinition::create($definition, \sprintf($message, $definition->getName())); |
| 141 |
} |
| 142 |
} |
| 143 |
|