Skip to content

Commit

Permalink
implemented support for lazy services in PHP 8.4
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Jan 10, 2025
1 parent 39edde7 commit 1ca1267
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 9 deletions.
37 changes: 28 additions & 9 deletions src/DI/Definitions/ServiceDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use Nette;
use Nette\DI\ServiceCreationException;
use Nette\Utils\Strings;


/**
Expand All @@ -24,6 +25,7 @@ final class ServiceDefinition extends Definition
{
use Nette\SmartObject;

public ?bool $lazy = null;
private Statement $creator;

/** @var Statement[] */
Expand Down Expand Up @@ -181,19 +183,36 @@ private function prependSelf(Statement $setup): Statement

public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$code = $generator->formatStatement($this->creator) . ";\n";
if (!$this->setup) {
$method->setBody('return ' . $code);
return;
$lines = [];
foreach ([$this->creator, ...$this->setup] as $stmt) {
$lines[] = $generator->formatStatement($stmt) . ";\n";
}

$code = '$service = ' . $code;
foreach ($this->setup as $setup) {
$code .= $generator->formatStatement($setup) . ";\n";
if ($this->canBeLazy() && !preg_grep('#(?:func_get_arg|func_num_args)#i', $lines)) { // latteFactory workaround
$class = $this->creator->getEntity();
$lines[0] = (new \ReflectionClass($class))->hasMethod('__construct')
? $generator->formatPhp("\$service->__construct(...?:);\n", [$this->creator->arguments])
: '';
$method->setBody("return new ReflectionClass($class::class)->newLazyGhost(function (\$service) {\n"
. Strings::indent(implode('', $lines))
. '});');

} elseif (count($lines) === 1) {
$method->setBody('return ' . implode('', $lines));

} else {
$method->setBody('$service = ' . implode('', $lines) . 'return $service;');
}
}

$code .= 'return $service;';
$method->setBody($code);

private function canBeLazy(): bool
{
return $this->lazy
&& is_string($class = $this->creator->getEntity())
&& ($this->creator->arguments || $this->setup)
&& ($ancestor = ($tmp = class_parents($class)) ? array_pop($tmp) : $class)
&& !(new \ReflectionClass($ancestor))->isInternal();
}


Expand Down
15 changes: 15 additions & 0 deletions src/DI/Extensions/DIExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Nette\DI\Extensions;

use Nette;
use Nette\DI\Definitions\ServiceDefinition;
use Tracy;


Expand All @@ -36,6 +37,7 @@ public function __construct(bool $debugMode = false)
public array $excluded = [];
public ?string $parentClass = null;
public object $export;
public bool $lazy = false;
};
$this->config->export = new class {
public bool $parameters = true;
Expand All @@ -56,6 +58,19 @@ public function loadConfiguration(): void
}


public function beforeCompile(): void
{
if ($this->config->lazy && PHP_VERSION_ID >= 80400) {
$builder = $this->getContainerBuilder();
foreach ($builder->getDefinitions() as $def) {
if ($def instanceof ServiceDefinition) {
$def->lazy ??= true;
}
}
}
}


public function afterCompile(Nette\PhpGenerator\ClassType $class): void
{
if ($this->config->parentClass) {
Expand Down
1 change: 1 addition & 0 deletions src/DI/Extensions/DefinitionSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ private static function getServiceSchema(): Schema
'tags' => Expect::array(),
'reset' => Expect::array(),
'alteration' => Expect::bool(),
'lazy' => Expect::bool(),
]);
}

Expand Down
4 changes: 4 additions & 0 deletions src/DI/Extensions/ServicesExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ private function updateServiceDefinition(Definitions\ServiceDefinition $definiti
if (isset($config->inject)) {
$definition->addTag(InjectExtension::TagInject, $config->inject);
}

if (isset($config->lazy)) {
$definition->lazy = $config->lazy;
}
}


Expand Down
1 change: 1 addition & 0 deletions tests/DI/Compiler.loadConfig.include.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Assert::equal([
'tags' => [],
'reset' => [],
'alteration' => null,
'lazy' => null,
'defType' => Nette\DI\Definitions\ServiceDefinition::class,
],
],
Expand Down
82 changes: 82 additions & 0 deletions tests/DI/DIExtension.lazy.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

/**
* Test: DIExtension lazy services
* @phpVersion 8.4
*/

declare(strict_types=1);

use Nette\DI;
use Nette\DI\Extensions\DIExtension;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


class Service
{
private $id;


public function __construct()
{
}
}


function isLazy(object $obj): bool
{
return new ReflectionObject($obj)->isUninitializedLazyObject($obj);
}


test('Eager is default', function () {
$compiler = new DI\Compiler;
$compiler->addExtension('di', new DIExtension);
$container = createContainer($compiler, '
services:
internal: stdClass
trivial: stdClass
default: Service(10)
eager:
create: Service(10)
lazy: false
lazy:
create: Service(10)
lazy: true
');

Assert::false(isLazy($container->getByName('internal')));
Assert::false(isLazy($container->getByName('trivial')));
Assert::false(isLazy($container->getByName('default')));
Assert::false(isLazy($container->getByName('eager')));
Assert::true(isLazy($container->getByName('lazy')));
});


test('Lazy is default', function () {
$compiler = new DI\Compiler;
$compiler->addExtension('di', new DIExtension);
$container = createContainer($compiler, '
di:
lazy: true
services:
internal: stdClass
trivial: stdClass
default: Service(10)
eager:
create: Service(10)
lazy: false
lazy:
create: Service(10)
lazy: true
');

Assert::false(isLazy($container->getByName('internal')));
Assert::false(isLazy($container->getByName('trivial')));
Assert::true(isLazy($container->getByName('default')));
Assert::false(isLazy($container->getByName('eager')));
Assert::true(isLazy($container->getByName('lazy')));
});

0 comments on commit 1ca1267

Please sign in to comment.