diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 29ef69ef..33f4467d 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -15,9 +15,14 @@ '@Symfony' => true, '@Symfony:risky' => true, 'array_syntax' => ['syntax' => 'short'], - 'braces' => [ - 'allow_single_line_closure' => true, - ], + 'single_space_around_construct' => true, + 'control_structure_braces' => true, + 'control_structure_continuation_position' => true, + 'declare_parentheses' => true, + 'no_multiple_statements_per_line' => true, + 'braces_position' => true, + 'statement_indentation' => true, + 'no_extra_blank_lines' => true, 'concat_space' => [ 'spacing' => 'one', ], diff --git a/src/Compiler/Compiler.php b/src/Compiler/Compiler.php index eeea2403..699389e9 100644 --- a/src/Compiler/Compiler.php +++ b/src/Compiler/Compiler.php @@ -5,6 +5,7 @@ namespace DI\Compiler; use function chmod; +use DI\Container; use DI\Definition\ArrayDefinition; use DI\Definition\DecoratorDefinition; use DI\Definition\Definition; @@ -22,6 +23,8 @@ use function file_put_contents; use InvalidArgumentException; use Laravel\SerializableClosure\Support\ReflectionClosure; +use ReflectionFunction; +use ReflectionFunctionAbstract; use function rename; use function sprintf; use function tempnam; @@ -276,6 +279,33 @@ private function compileDefinition(string $entryName, Definition $definition) : )); } + if ($value instanceof \Closure) { + $reflection = new ReflectionFunction($value); + $requestedEntry = new RequestedEntryHolder($entryName); + $parametersByClassName = [ + 'DI\Factory\RequestedEntry' => $requestedEntry, + ]; + // default non-typehinted parameters + $defaultParameters = [new Reference(Container::class), $requestedEntry]; + + $resolvedParameters = $this->resolveFactoryParameters( + $reflection, + $definition->getParameters(), + $parametersByClassName, + $defaultParameters + ); + + $definitionParameters = array_map(fn ($value) => $this->compileValue($value), $resolvedParameters); + + $code = sprintf( + 'return (%s)(%s);', + $this->compileValue($value), + implode(', ', $definitionParameters) + ); + break; + } + + // todo optimize other (non-closure) factories $definitionParameters = ''; if (!empty($definition->getParameters())) { $definitionParameters = ', ' . $this->compileValue($definition->getParameters()); @@ -307,6 +337,11 @@ public function compileValue(mixed $value) : string throw new InvalidDefinition($errorMessage); } + // one step ahead to skip CompiledContainer->resolveFactory + if ($value instanceof RequestedEntryHolder) { + return 'new DI\Compiler\RequestedEntryHolder(\'' . $value->getName() . '\')'; + } + if ($value instanceof Definition) { // Give it an arbitrary unique name $subEntryName = 'subEntry' . (++$this->subEntryCounter); @@ -364,6 +399,10 @@ private function isCompilable($value) : string|bool if ($value instanceof \Closure) { return true; } + // added for skipping CompiledContainer->resolveFactory - there is a special case for this in compileValue method + if ($value instanceof RequestedEntryHolder) { + return true; + } /** @psalm-suppress UndefinedClass */ if ((\PHP_VERSION_ID >= 80100) && ($value instanceof \UnitEnum)) { return true; @@ -399,4 +438,40 @@ private function compileClosure(\Closure $closure) : string return trim($code, "\t\n\r;"); } + + public function resolveFactoryParameters( + ReflectionFunctionAbstract $reflection, + array $definitionParameters = [], + array $parametersByClassName = [], + array $defaultParameters = [] + ) : array { + $resolvedParameters = []; + $parameters = $reflection->getParameters(); + + foreach ($parameters as $index => $parameter) { + $name = $parameter->getName(); + if (array_key_exists($name, $definitionParameters)) { + $resolvedParameters[$index] = $definitionParameters[$name]; + continue; + } + + $parameterType = $parameter->getType(); + if (!$parameterType) { + if (array_key_exists($index, $defaultParameters)) { + // take default parameters, when no typehint + $resolvedParameters[$index] = $defaultParameters[$index]; + } + continue; + } + + $parameterTypeName = $parameterType->getName(); + if (isset($parametersByClassName[$parameterTypeName])) { + $resolvedParameters[$index] = $parametersByClassName[$parameterTypeName]; + } else { + $resolvedParameters[$index] = new Reference($parameterTypeName); + } + } + + return $resolvedParameters; + } }