Skip to content

Commit 1ca1267

Browse files
committed
implemented support for lazy services in PHP 8.4
1 parent 39edde7 commit 1ca1267

6 files changed

+131
-9
lines changed

src/DI/Definitions/ServiceDefinition.php

+28-9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use Nette;
1313
use Nette\DI\ServiceCreationException;
14+
use Nette\Utils\Strings;
1415

1516

1617
/**
@@ -24,6 +25,7 @@ final class ServiceDefinition extends Definition
2425
{
2526
use Nette\SmartObject;
2627

28+
public ?bool $lazy = null;
2729
private Statement $creator;
2830

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

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

190-
$code = '$service = ' . $code;
191-
foreach ($this->setup as $setup) {
192-
$code .= $generator->formatStatement($setup) . ";\n";
191+
if ($this->canBeLazy() && !preg_grep('#(?:func_get_arg|func_num_args)#i', $lines)) { // latteFactory workaround
192+
$class = $this->creator->getEntity();
193+
$lines[0] = (new \ReflectionClass($class))->hasMethod('__construct')
194+
? $generator->formatPhp("\$service->__construct(...?:);\n", [$this->creator->arguments])
195+
: '';
196+
$method->setBody("return new ReflectionClass($class::class)->newLazyGhost(function (\$service) {\n"
197+
. Strings::indent(implode('', $lines))
198+
. '});');
199+
200+
} elseif (count($lines) === 1) {
201+
$method->setBody('return ' . implode('', $lines));
202+
203+
} else {
204+
$method->setBody('$service = ' . implode('', $lines) . 'return $service;');
193205
}
206+
}
194207

195-
$code .= 'return $service;';
196-
$method->setBody($code);
208+
209+
private function canBeLazy(): bool
210+
{
211+
return $this->lazy
212+
&& is_string($class = $this->creator->getEntity())
213+
&& ($this->creator->arguments || $this->setup)
214+
&& ($ancestor = ($tmp = class_parents($class)) ? array_pop($tmp) : $class)
215+
&& !(new \ReflectionClass($ancestor))->isInternal();
197216
}
198217

199218

src/DI/Extensions/DIExtension.php

+15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace Nette\DI\Extensions;
1111

1212
use Nette;
13+
use Nette\DI\Definitions\ServiceDefinition;
1314
use Tracy;
1415

1516

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

5860

61+
public function beforeCompile(): void
62+
{
63+
if ($this->config->lazy && PHP_VERSION_ID >= 80400) {
64+
$builder = $this->getContainerBuilder();
65+
foreach ($builder->getDefinitions() as $def) {
66+
if ($def instanceof ServiceDefinition) {
67+
$def->lazy ??= true;
68+
}
69+
}
70+
}
71+
}
72+
73+
5974
public function afterCompile(Nette\PhpGenerator\ClassType $class): void
6075
{
6176
if ($this->config->parentClass) {

src/DI/Extensions/DefinitionSchema.php

+1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ private static function getServiceSchema(): Schema
173173
'tags' => Expect::array(),
174174
'reset' => Expect::array(),
175175
'alteration' => Expect::bool(),
176+
'lazy' => Expect::bool(),
176177
]);
177178
}
178179

src/DI/Extensions/ServicesExtension.php

+4
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ private function updateServiceDefinition(Definitions\ServiceDefinition $definiti
113113
if (isset($config->inject)) {
114114
$definition->addTag(InjectExtension::TagInject, $config->inject);
115115
}
116+
117+
if (isset($config->lazy)) {
118+
$definition->lazy = $config->lazy;
119+
}
116120
}
117121

118122

tests/DI/Compiler.loadConfig.include.phpt

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Assert::equal([
4949
'tags' => [],
5050
'reset' => [],
5151
'alteration' => null,
52+
'lazy' => null,
5253
'defType' => Nette\DI\Definitions\ServiceDefinition::class,
5354
],
5455
],

tests/DI/DIExtension.lazy.phpt

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
/**
4+
* Test: DIExtension lazy services
5+
* @phpVersion 8.4
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
use Nette\DI;
11+
use Nette\DI\Extensions\DIExtension;
12+
use Tester\Assert;
13+
14+
require __DIR__ . '/../bootstrap.php';
15+
16+
17+
class Service
18+
{
19+
private $id;
20+
21+
22+
public function __construct()
23+
{
24+
}
25+
}
26+
27+
28+
function isLazy(object $obj): bool
29+
{
30+
return new ReflectionObject($obj)->isUninitializedLazyObject($obj);
31+
}
32+
33+
34+
test('Eager is default', function () {
35+
$compiler = new DI\Compiler;
36+
$compiler->addExtension('di', new DIExtension);
37+
$container = createContainer($compiler, '
38+
services:
39+
internal: stdClass
40+
trivial: stdClass
41+
default: Service(10)
42+
eager:
43+
create: Service(10)
44+
lazy: false
45+
lazy:
46+
create: Service(10)
47+
lazy: true
48+
');
49+
50+
Assert::false(isLazy($container->getByName('internal')));
51+
Assert::false(isLazy($container->getByName('trivial')));
52+
Assert::false(isLazy($container->getByName('default')));
53+
Assert::false(isLazy($container->getByName('eager')));
54+
Assert::true(isLazy($container->getByName('lazy')));
55+
});
56+
57+
58+
test('Lazy is default', function () {
59+
$compiler = new DI\Compiler;
60+
$compiler->addExtension('di', new DIExtension);
61+
$container = createContainer($compiler, '
62+
di:
63+
lazy: true
64+
65+
services:
66+
internal: stdClass
67+
trivial: stdClass
68+
default: Service(10)
69+
eager:
70+
create: Service(10)
71+
lazy: false
72+
lazy:
73+
create: Service(10)
74+
lazy: true
75+
');
76+
77+
Assert::false(isLazy($container->getByName('internal')));
78+
Assert::false(isLazy($container->getByName('trivial')));
79+
Assert::true(isLazy($container->getByName('default')));
80+
Assert::false(isLazy($container->getByName('eager')));
81+
Assert::true(isLazy($container->getByName('lazy')));
82+
});

0 commit comments

Comments
 (0)