Skip to content

[php-di v7] Optimize compiled factory definitions #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: mod7
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .php-cs-fixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
Expand Down
75 changes: 75 additions & 0 deletions src/Compiler/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}