From a0fdad89b7914ace53bec2b061a059203c23a873 Mon Sep 17 00:00:00 2001 From: Dmitry Derepko Date: Sun, 1 Aug 2021 12:51:13 +0300 Subject: [PATCH 01/19] Decorate definition when it contains lazy --- composer.json | 4 +++- src/Container.php | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 3b6176ec..7ff726bf 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ }, "require-dev": { "league/container": "^3.3", + "ocramius/proxy-manager": "^2.13", "phpbench/phpbench": "^1.0.0", "phpunit/phpunit": "^9.4", "roave/infection-static-analysis-plugin": "^1.6", @@ -38,7 +39,8 @@ }, "suggest": { "yiisoft/injector": "^1.0@dev", - "phpbench/phpbench": "To run benchmarks." + "phpbench/phpbench": "To run benchmarks.", + "ocramius/proxy-manager": "Install this package if you want to use lazy loading." }, "provide": { "psr/container-implementation": "1.0.0" diff --git a/src/Container.php b/src/Container.php index 2051f7b4..c0c0d165 100644 --- a/src/Container.php +++ b/src/Container.php @@ -5,10 +5,13 @@ namespace Yiisoft\Di; use Closure; +use ProxyManager\Factory\LazyLoadingValueHolderFactory; use Psr\Container\ContainerInterface; use Yiisoft\Di\Contracts\DeferredServiceProviderInterface; use Yiisoft\Di\Contracts\ServiceProviderInterface; use Yiisoft\Factory\Definition\ArrayDefinition; +use Yiisoft\Factory\Definition\Decorator\LazyDefinitionDecorator; +use Yiisoft\Factory\Definition\DefinitionInterface; use Yiisoft\Factory\Definition\DefinitionValidator; use Yiisoft\Factory\DependencyResolverInterface; use Yiisoft\Factory\Exception\CircularReferenceException; @@ -16,6 +19,16 @@ use Yiisoft\Factory\Exception\NotFoundException; use Yiisoft\Factory\Exception\NotInstantiableException; use Yiisoft\Injector\Injector; +use function array_key_exists; +use function array_keys; +use function assert; +use function class_exists; +use function get_class; +use function implode; +use function in_array; +use function is_array; +use function is_object; +use function is_string; use function array_key_exists; use function array_keys; @@ -35,7 +48,8 @@ final class Container extends AbstractContainerConfigurator implements Container { private const META_TAGS = 'tags'; private const META_RESET = 'reset'; - private const ALLOWED_META = [self::META_TAGS, self::META_RESET]; + private const META_LAZY = 'lazy'; + private const ALLOWED_META = [self::META_TAGS, self::META_RESET, self::META_LAZY]; /** * @var array object definitions indexed by their types @@ -63,6 +77,7 @@ final class Container extends AbstractContainerConfigurator implements Container private ?CompositeContainer $rootContainer = null; private DependencyResolverInterface $dependencyResolver; + private LazyLoadingValueHolderFactory $lazyFactory; /** * Container constructor. @@ -195,6 +210,9 @@ protected function set(string $id, $definition): void if (isset($meta[self::META_RESET])) { $this->setResetter($id, $meta[self::META_RESET]); } + if (isset($meta[self::META_LAZY]) && $meta[self::META_LAZY] === true) { + $definition = $this->decorateLazy($id, $definition); + } unset($this->instances[$id]); $this->definitions[$id] = $definition; @@ -461,4 +479,19 @@ private function getVariableType($variable): string return gettype($variable); } + + private function decorateLazy(string $id, $definition): DefinitionInterface + { + $factory = $this->getLazyLoadingValueHolderFactory(); + + return new LazyDefinitionDecorator($factory, $definition, $id); + } + + private function getLazyLoadingValueHolderFactory(): LazyLoadingValueHolderFactory + { + if (!class_exists(LazyLoadingValueHolderFactory::class)) { + throw new \RuntimeException('You should install `ocramius/proxy-manager` if you want to use lazy services.'); + } + return $this->lazyFactory ??= new LazyLoadingValueHolderFactory(); + } } From 61f2bff2c87b628393f8f3190d2d726cc008934f Mon Sep 17 00:00:00 2001 From: Dmitry Derepko Date: Sun, 1 Aug 2021 12:51:20 +0300 Subject: [PATCH 02/19] Add tests --- tests/Unit/ContainerTest.php | 18 ++++++++++++++++++ tests/Unit/DefinitionParserTest.php | 18 ++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/tests/Unit/ContainerTest.php b/tests/Unit/ContainerTest.php index 8aef744e..3f36a18a 100644 --- a/tests/Unit/ContainerTest.php +++ b/tests/Unit/ContainerTest.php @@ -6,6 +6,7 @@ use ArrayIterator; use PHPUnit\Framework\TestCase; +use ProxyManager\Proxy\LazyLoadingInterface; use Psr\Container\ContainerInterface; use TypeError; use Yiisoft\Di\AbstractContainerConfigurator; @@ -1355,6 +1356,23 @@ public function testNestedResetter(): void $this->assertSame($color, $car->getColor()); } + public function testLazy(): void + { + $number = 42; + $container = new Container([ + EngineMarkOne::class => [ + 'class' => EngineMarkOne::class, + 'setNumber()' => [$number], + 'lazy' => true, + ], + ]); + + $engine = $container->get(EngineMarkOne::class); + + self::assertInstanceOf(LazyLoadingInterface::class, $engine); + self::assertSame($number, $engine->getNumber()); + } + public function testResetterInCompositeContainer(): void { $composite = new CompositeContainer(); diff --git a/tests/Unit/DefinitionParserTest.php b/tests/Unit/DefinitionParserTest.php index 6be7b498..0978e89c 100644 --- a/tests/Unit/DefinitionParserTest.php +++ b/tests/Unit/DefinitionParserTest.php @@ -17,10 +17,12 @@ public function testParseCallableDefinition(): void $definition = [ 'definition' => $fn, 'tags' => ['one', 'two'], + 'lazy' => true, ]; - [$definition, $meta] = (new DefinitionParser(['tags']))->parse($definition); + [$definition, $meta] = DefinitionParser::parse($definition); + $this->assertSame($fn, $definition); - $this->assertSame(['tags' => ['one', 'two']], $meta); + $this->assertSame(['tags' => ['one', 'two'], 'lazy' => true], $meta); } public function testParseArrayCallableDefinition(): void @@ -28,10 +30,12 @@ public function testParseArrayCallableDefinition(): void $definition = [ 'definition' => [StaticFactory::class, 'create'], 'tags' => ['one', 'two'], + 'lazy' => true, ]; - [$definition, $meta] = (new DefinitionParser(['tags']))->parse($definition); + [$definition, $meta] = DefinitionParser::parse($definition); + $this->assertSame([StaticFactory::class, 'create'], $definition); - $this->assertSame(['tags' => ['one', 'two']], $meta); + $this->assertSame(['tags' => ['one', 'two'], 'lazy' => true], $meta); } public function testParseArrayDefinition(): void @@ -40,14 +44,16 @@ public function testParseArrayDefinition(): void 'class' => EngineMarkOne::class, '__construct()' => [42], 'tags' => ['one', 'two'], + 'lazy' => true, ]; - [$definition, $meta] = (new DefinitionParser(['tags']))->parse($definition); + [$definition, $meta] = DefinitionParser::parse($definition); + $this->assertSame([ EngineMarkOne::class, [42], [], DefinitionParser::IS_PREPARED_ARRAY_DEFINITION_DATA => true, ], $definition); - $this->assertSame(['tags' => ['one', 'two']], $meta); + $this->assertSame(['tags' => ['one', 'two'], 'lazy' => true], $meta); } } From 1b4cfae7fa7cdce900433b515b257bc87bb12c80 Mon Sep 17 00:00:00 2001 From: Dmitry Derepko Date: Sun, 1 Aug 2021 12:52:54 +0300 Subject: [PATCH 03/19] Normalize definition out of the building process --- src/Container.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Container.php b/src/Container.php index c0c0d165..343cfa37 100644 --- a/src/Container.php +++ b/src/Container.php @@ -30,17 +30,6 @@ use function is_object; use function is_string; -use function array_key_exists; -use function array_keys; -use function assert; -use function class_exists; -use function get_class; -use function implode; -use function in_array; -use function is_array; -use function is_object; -use function is_string; - /** * Container implements a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) container. */ @@ -200,6 +189,7 @@ protected function set(string $id, $definition): void $this->validateDefinition($definition, $id); $this->validateMeta($meta); } + $definition = DefinitionNormalizer::normalize($definition, $id); if (isset($meta[self::META_TAGS])) { if ($this->validate) { @@ -386,9 +376,8 @@ private function buildInternal(string $id) return $this->buildPrimitive($id); } $this->processDefinition($this->definitions[$id]); - $definition = DefinitionNormalizer::normalize($this->definitions[$id], $id); - return $definition->resolve($this->dependencyResolver); + return $this->definitions[$id]->resolve($this->dependencyResolver); } /** From abb2e83c62a8ecdd413a10964dee2f42641fed95 Mon Sep 17 00:00:00 2001 From: Dmitry Derepko Date: Sun, 1 Aug 2021 13:30:02 +0300 Subject: [PATCH 04/19] Rework test cases --- tests/Unit/ContainerTest.php | 18 ------ tests/Unit/LazyServiceContainerTest.php | 86 +++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 tests/Unit/LazyServiceContainerTest.php diff --git a/tests/Unit/ContainerTest.php b/tests/Unit/ContainerTest.php index 3f36a18a..8aef744e 100644 --- a/tests/Unit/ContainerTest.php +++ b/tests/Unit/ContainerTest.php @@ -6,7 +6,6 @@ use ArrayIterator; use PHPUnit\Framework\TestCase; -use ProxyManager\Proxy\LazyLoadingInterface; use Psr\Container\ContainerInterface; use TypeError; use Yiisoft\Di\AbstractContainerConfigurator; @@ -1356,23 +1355,6 @@ public function testNestedResetter(): void $this->assertSame($color, $car->getColor()); } - public function testLazy(): void - { - $number = 42; - $container = new Container([ - EngineMarkOne::class => [ - 'class' => EngineMarkOne::class, - 'setNumber()' => [$number], - 'lazy' => true, - ], - ]); - - $engine = $container->get(EngineMarkOne::class); - - self::assertInstanceOf(LazyLoadingInterface::class, $engine); - self::assertSame($number, $engine->getNumber()); - } - public function testResetterInCompositeContainer(): void { $composite = new CompositeContainer(); diff --git a/tests/Unit/LazyServiceContainerTest.php b/tests/Unit/LazyServiceContainerTest.php new file mode 100644 index 00000000..0277ff74 --- /dev/null +++ b/tests/Unit/LazyServiceContainerTest.php @@ -0,0 +1,86 @@ + [ + 'class' => $class, + 'setNumber()' => [$number], + 'lazy' => true, + ], + ]); + + /* @var \Yiisoft\Di\Tests\Support\EngineMarkOne $object */ + $object = $container->get($class); + + self::assertInstanceOf(LazyLoadingInterface::class, $object); + self::assertFalse($object->isProxyInitialized()); + self::assertEquals($number, $object->getNumber()); + self::assertTrue($object->isProxyInitialized()); + + /* @var \Yiisoft\Di\Tests\Support\EngineMarkOne $object */ + $object = $container->get($class); + + self::assertInstanceOf(LazyLoadingInterface::class, $object); + self::assertTrue($object->isProxyInitialized()); + } + + /** + * @dataProvider lazyDefinitionDataProvider + */ + public function testLazy(array $definitions, string $id): void + { + $container = new Container($definitions); + + $object = $container->get($id); + + self::assertInstanceOf(LazyLoadingInterface::class, $object); + } + + public function lazyDefinitionDataProvider(): array + { + return [ + 'class as key' => [ + [EngineMarkOne::class => [ + 'class' => EngineMarkOne::class, + 'lazy' => true, + ]], + EngineMarkOne::class, + ], + 'alias as key' => [ + ['mark_one' => [ + 'class' => EngineMarkOne::class, + 'lazy' => true, + ]], + 'mark_one', + ], + 'dedicated array definition' => [ + [EngineMarkOne::class => [ + 'definition' => ['class' => EngineMarkOne::class], + 'lazy' => true, + ]], + EngineMarkOne::class, + ], + 'dedicated callback definition' => [ + [EngineMarkOne::class => [ + 'definition' => fn() => new EngineMarkOne(), + 'lazy' => true, + ]], + EngineMarkOne::class, + ], + ]; + } +} From 15f8a661cddfad1f7552f6a191e24c96430348fd Mon Sep 17 00:00:00 2001 From: Dmitry Derepko Date: Sun, 1 Aug 2021 13:30:15 +0300 Subject: [PATCH 05/19] Add class resolver --- src/Container.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Container.php b/src/Container.php index 343cfa37..1a16c4a2 100644 --- a/src/Container.php +++ b/src/Container.php @@ -469,11 +469,18 @@ private function getVariableType($variable): string return gettype($variable); } - private function decorateLazy(string $id, $definition): DefinitionInterface + private function decorateLazy(string $id, DefinitionInterface $definition): DefinitionInterface { $factory = $this->getLazyLoadingValueHolderFactory(); + if (class_exists($id) || interface_exists($id)) { + $class = $id; + } elseif ($definition instanceof ArrayDefinition) { + $class = $definition->getClass(); + } else { + throw new \RuntimeException("Could not determinate object class"); + } - return new LazyDefinitionDecorator($factory, $definition, $id); + return new LazyDefinitionDecorator($factory, $definition, $class); } private function getLazyLoadingValueHolderFactory(): LazyLoadingValueHolderFactory From dbed5b37b9dd1c8de906dda0b37f627866461c9e Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 1 Aug 2021 10:30:41 +0000 Subject: [PATCH 06/19] Apply fixes from StyleCI --- src/Container.php | 2 +- tests/Unit/LazyServiceContainerTest.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Container.php b/src/Container.php index 1a16c4a2..45e7b8df 100644 --- a/src/Container.php +++ b/src/Container.php @@ -477,7 +477,7 @@ private function decorateLazy(string $id, DefinitionInterface $definition): Defi } elseif ($definition instanceof ArrayDefinition) { $class = $definition->getClass(); } else { - throw new \RuntimeException("Could not determinate object class"); + throw new \RuntimeException('Could not determinate object class'); } return new LazyDefinitionDecorator($factory, $definition, $class); diff --git a/tests/Unit/LazyServiceContainerTest.php b/tests/Unit/LazyServiceContainerTest.php index 0277ff74..a0101d64 100644 --- a/tests/Unit/LazyServiceContainerTest.php +++ b/tests/Unit/LazyServiceContainerTest.php @@ -1,4 +1,5 @@ [ [EngineMarkOne::class => [ - 'definition' => fn() => new EngineMarkOne(), + 'definition' => fn () => new EngineMarkOne(), 'lazy' => true, ]], EngineMarkOne::class, From 592c23900b0c8165559339e7e6890ac620f8a077 Mon Sep 17 00:00:00 2001 From: Dmitry Derepko Date: Sat, 7 Aug 2021 19:11:07 +0300 Subject: [PATCH 07/19] Fix tests --- src/Container.php | 6 ++---- src/ExtensibleService.php | 10 ++++------ tests/Unit/LazyServiceContainerTest.php | 9 ++++++++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Container.php b/src/Container.php index 77eceb23..fa45c344 100644 --- a/src/Container.php +++ b/src/Container.php @@ -16,7 +16,6 @@ use Yiisoft\Factory\Exception\InvalidConfigException; use Yiisoft\Factory\Exception\NotFoundException; use Yiisoft\Factory\Exception\NotInstantiableException; - use function array_key_exists; use function array_keys; use function class_exists; @@ -339,7 +338,6 @@ private function buildInternal(string $id) if (!isset($this->definitions[$id])) { return $this->buildPrimitive($id); } - $definition = DefinitionNormalizer::normalize($this->definitions[$id], $id); /** @psalm-suppress RedundantPropertyInitializationCheck */ $this->dependencyResolver ??= new DependencyResolver($this->get(ContainerInterface::class)); @@ -357,11 +355,11 @@ private function buildInternal(string $id) private function buildPrimitive(string $class) { if (class_exists($class)) { - $definition = ArrayDefinition::fromPreparedData($class); + $this->definitions[$class] = ArrayDefinition::fromPreparedData($class); /** @psalm-suppress RedundantPropertyInitializationCheck */ $this->dependencyResolver ??= new DependencyResolver($this->get(ContainerInterface::class)); - return $definition->resolve($this->dependencyResolver); + return $this->definitions[$class]->resolve($this->dependencyResolver); } throw new NotFoundException($class); diff --git a/src/ExtensibleService.php b/src/ExtensibleService.php index 76920dab..d716da4b 100644 --- a/src/ExtensibleService.php +++ b/src/ExtensibleService.php @@ -6,7 +6,6 @@ use Psr\Container\ContainerInterface; use Yiisoft\Factory\Definition\DefinitionInterface; -use Yiisoft\Factory\Definition\Normalizer; use Yiisoft\Factory\DependencyResolverInterface; /** @@ -21,14 +20,13 @@ */ final class ExtensibleService implements DefinitionInterface { - /** @psalm-var array */ - private $definition; + private DefinitionInterface $definition; private array $extensions = []; /** - * @param mixed $definition Definition to allow registering extensions for. + * @param DefinitionInterface $definition Definition to allow registering extensions for. */ - public function __construct($definition) + public function __construct(DefinitionInterface $definition) { $this->definition = $definition; } @@ -53,7 +51,7 @@ public function addExtension(callable $closure): void public function resolve(DependencyResolverInterface $container) { - $service = (Normalizer::normalize($this->definition))->resolve($container); + $service = $this->definition->resolve($container); $containerInterface = $container->get(ContainerInterface::class); foreach ($this->extensions as $extension) { $service = $extension($containerInterface, $service); diff --git a/tests/Unit/LazyServiceContainerTest.php b/tests/Unit/LazyServiceContainerTest.php index a0101d64..e258f886 100644 --- a/tests/Unit/LazyServiceContainerTest.php +++ b/tests/Unit/LazyServiceContainerTest.php @@ -11,6 +11,13 @@ class LazyServiceContainerTest extends TestCase { + protected function setUp(): void + { + if (!class_exists(\ProxyManager\Factory\LazyLoadingValueHolderFactory::class)) { + $this->markTestSkipped('You should install `ocramius/proxy-manager` if you want to use lazy services.'); + } + } + public function testIsTheSameObject(): void { $class = EngineMarkOne::class; @@ -77,7 +84,7 @@ public function lazyDefinitionDataProvider(): array ], 'dedicated callback definition' => [ [EngineMarkOne::class => [ - 'definition' => fn () => new EngineMarkOne(), + 'definition' => fn() => new EngineMarkOne(), 'lazy' => true, ]], EngineMarkOne::class, From e4bd46f20b0e1b70c66b5ae76aa3bcae4fe62293 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 7 Aug 2021 16:11:21 +0000 Subject: [PATCH 08/19] Apply fixes from StyleCI --- tests/Unit/LazyServiceContainerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/LazyServiceContainerTest.php b/tests/Unit/LazyServiceContainerTest.php index e258f886..9bfbf61d 100644 --- a/tests/Unit/LazyServiceContainerTest.php +++ b/tests/Unit/LazyServiceContainerTest.php @@ -84,7 +84,7 @@ public function lazyDefinitionDataProvider(): array ], 'dedicated callback definition' => [ [EngineMarkOne::class => [ - 'definition' => fn() => new EngineMarkOne(), + 'definition' => fn () => new EngineMarkOne(), 'lazy' => true, ]], EngineMarkOne::class, From 5136d8d3b5120d634fed24e1af765c2020be4d1f Mon Sep 17 00:00:00 2001 From: rector-bot Date: Sun, 20 Nov 2022 08:39:38 +0000 Subject: [PATCH 09/19] [ci-review] Apply changes from Rector action. --- src/Container.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Container.php b/src/Container.php index b9ff2d75..848f9fb3 100644 --- a/src/Container.php +++ b/src/Container.php @@ -614,13 +614,10 @@ private function buildProvider(mixed $provider): ServiceProviderInterface return $providerInstance; } - /** - * @param mixed $variable - */ - private function getVariableType($variable): string + private function getVariableType(mixed $variable): string { if (is_object($variable)) { - return get_class($variable); + return $variable::class; } return gettype($variable); From 47dc08893c8f2544f5695401c19c9d5f288a4743 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 20 Nov 2022 11:50:15 +0300 Subject: [PATCH 10/19] Fix resolving lazy service --- composer.json | 4 ++-- src/Container.php | 31 +++++++++++-------------- tests/Unit/LazyServiceContainerTest.php | 28 +++++++++++++--------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/composer.json b/composer.json index 8c37a3e7..6112d09b 100644 --- a/composer.json +++ b/composer.json @@ -25,12 +25,12 @@ "php": "^8.0", "ext-mbstring": "*", "psr/container": "^1.1|^2.0", - "yiisoft/definitions": "^3.0" + "yiisoft/definitions": "dev-lazy-definition" }, "require-dev": { "league/container": "^4.2", "maglnet/composer-require-checker": "^4.2", - "ocramius/proxy-manager": "^2.13", + "ocramius/proxy-manager": "^2.14.1", "phpbench/phpbench": "^1.2.0", "phpunit/phpunit": "^9.5", "rector/rector": "^0.14.3", diff --git a/src/Container.php b/src/Container.php index b9ff2d75..5a89a82b 100644 --- a/src/Container.php +++ b/src/Container.php @@ -7,12 +7,15 @@ use Closure; use ProxyManager\Factory\LazyLoadingValueHolderFactory; use Psr\Container\ContainerInterface; +use RuntimeException; use Yiisoft\Definitions\ArrayDefinition; +use Yiisoft\Definitions\Contract\DefinitionInterface; use Yiisoft\Definitions\Exception\CircularReferenceException; use Yiisoft\Definitions\Exception\InvalidConfigException; use Yiisoft\Definitions\Exception\NotInstantiableException; use Yiisoft\Definitions\Helpers\DefinitionValidator; use Yiisoft\Definitions\DefinitionStorage; +use Yiisoft\Definitions\LazyDefinitionDecorator; use Yiisoft\Di\Helpers\DefinitionNormalizer; use Yiisoft\Di\Helpers\DefinitionParser; use Yiisoft\Di\Helpers\TagHelper; @@ -614,27 +617,21 @@ private function buildProvider(mixed $provider): ServiceProviderInterface return $providerInstance; } - /** - * @param mixed $variable - */ - private function getVariableType($variable): string - { - if (is_object($variable)) { - return get_class($variable); - } - - return gettype($variable); - } - - private function decorateLazy(string $id, DefinitionInterface $definition): DefinitionInterface + private function decorateLazy(string $id, $definition): DefinitionInterface { $factory = $this->getLazyLoadingValueHolderFactory(); if (class_exists($id) || interface_exists($id)) { $class = $id; - } elseif ($definition instanceof ArrayDefinition) { - $class = $definition->getClass(); + } elseif (is_array($definition)) { + $class = $definition['class']; } else { - throw new \RuntimeException('Could not determinate object class'); + throw new RuntimeException( + sprintf( + 'Could not handle definition type "%s" with definition class "%s"', + get_debug_type($definition), + $id, + ) + ); } return new LazyDefinitionDecorator($factory, $definition, $class); @@ -643,7 +640,7 @@ private function decorateLazy(string $id, DefinitionInterface $definition): Defi private function getLazyLoadingValueHolderFactory(): LazyLoadingValueHolderFactory { if (!class_exists(LazyLoadingValueHolderFactory::class)) { - throw new \RuntimeException('You should install `ocramius/proxy-manager` if you want to use lazy services.'); + throw new RuntimeException('You should install `ocramius/proxy-manager` if you want to use lazy services.'); } return $this->lazyFactory ??= new LazyLoadingValueHolderFactory(); } diff --git a/tests/Unit/LazyServiceContainerTest.php b/tests/Unit/LazyServiceContainerTest.php index 9bfbf61d..d918b6bf 100644 --- a/tests/Unit/LazyServiceContainerTest.php +++ b/tests/Unit/LazyServiceContainerTest.php @@ -5,15 +5,17 @@ namespace Yiisoft\Di\Tests\Unit; use PHPUnit\Framework\TestCase; +use ProxyManager\Factory\LazyLoadingValueHolderFactory; use ProxyManager\Proxy\LazyLoadingInterface; use Yiisoft\Di\Container; +use Yiisoft\Di\ContainerConfig; use Yiisoft\Di\Tests\Support\EngineMarkOne; class LazyServiceContainerTest extends TestCase { protected function setUp(): void { - if (!class_exists(\ProxyManager\Factory\LazyLoadingValueHolderFactory::class)) { + if (!class_exists(LazyLoadingValueHolderFactory::class)) { $this->markTestSkipped('You should install `ocramius/proxy-manager` if you want to use lazy services.'); } } @@ -23,15 +25,17 @@ public function testIsTheSameObject(): void $class = EngineMarkOne::class; $number = 55; - $container = new Container([ - EngineMarkOne::class => [ - 'class' => $class, - 'setNumber()' => [$number], - 'lazy' => true, - ], - ]); + $config = ContainerConfig::create() + ->withDefinitions([ + EngineMarkOne::class => [ + 'class' => $class, + 'setNumber()' => [$number], + 'lazy' => true, + ], + ]); + $container = new Container($config); - /* @var \Yiisoft\Di\Tests\Support\EngineMarkOne $object */ + /* @var EngineMarkOne $object */ $object = $container->get($class); self::assertInstanceOf(LazyLoadingInterface::class, $object); @@ -39,7 +43,7 @@ public function testIsTheSameObject(): void self::assertEquals($number, $object->getNumber()); self::assertTrue($object->isProxyInitialized()); - /* @var \Yiisoft\Di\Tests\Support\EngineMarkOne $object */ + /* @var EngineMarkOne $object */ $object = $container->get($class); self::assertInstanceOf(LazyLoadingInterface::class, $object); @@ -51,7 +55,9 @@ public function testIsTheSameObject(): void */ public function testLazy(array $definitions, string $id): void { - $container = new Container($definitions); + $config = ContainerConfig::create() + ->withDefinitions($definitions); + $container = new Container($config); $object = $container->get($id); From 1d59fbd5a286790d553ec89a87ed694f540f62a1 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 3 Dec 2022 18:00:58 +0300 Subject: [PATCH 11/19] Use `friendsofphp/proxy-manager-lts` package --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 6112d09b..09e0ade4 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "require-dev": { "league/container": "^4.2", "maglnet/composer-require-checker": "^4.2", - "ocramius/proxy-manager": "^2.14.1", + "friendsofphp/proxy-manager-lts": "^1.0", "phpbench/phpbench": "^1.2.0", "phpunit/phpunit": "^9.5", "rector/rector": "^0.14.3", @@ -43,7 +43,7 @@ "suggest": { "yiisoft/injector": "^1.0", "phpbench/phpbench": "To run benchmarks.", - "ocramius/proxy-manager": "Install this package if you want to use lazy loading." + "friendsofphp/proxy-manager-lts": "Install this package if you want to use lazy loading." }, "provide": { "psr/container-implementation": "1.0.0" From 95cdd9ea5a8b7da8d69c1a444feb64e22ef47222 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 3 Dec 2022 18:35:13 +0300 Subject: [PATCH 12/19] Fix psalm --- src/Container.php | 22 ++++++++++++++-------- tests/Unit/LazyServiceContainerTest.php | 4 +++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Container.php b/src/Container.php index ab189169..d3682e57 100644 --- a/src/Container.php +++ b/src/Container.php @@ -73,7 +73,7 @@ final class Container implements ContainerInterface */ private array $resetters = []; private bool $useResettersFromMeta = true; - private LazyLoadingValueHolderFactory $lazyFactory; + private ?LazyLoadingValueHolderFactory $lazyFactory = null; /** * @param ContainerConfigInterface $config Container configuration. @@ -633,17 +633,17 @@ private function buildProvider(mixed $provider): ServiceProviderInterface return $providerInstance; } - private function decorateLazy(string $id, $definition): DefinitionInterface + private function decorateLazy(string $id, mixed $definition): DefinitionInterface { $factory = $this->getLazyLoadingValueHolderFactory(); if (class_exists($id) || interface_exists($id)) { $class = $id; - } elseif (is_array($definition)) { - $class = $definition['class']; + } elseif (is_array($definition) && array_key_exists(ArrayDefinition::CLASS_NAME, $definition)) { + $class = (string) $definition[ArrayDefinition::CLASS_NAME]; } else { - throw new RuntimeException( + throw new InvalidConfigException( sprintf( - 'Could not handle definition type "%s" with definition class "%s"', + 'Invalid definition: lazy services are only available with array definitions or references. Got type "%s" for definition ID: "%s"', get_debug_type($definition), $id, ) @@ -656,8 +656,14 @@ private function decorateLazy(string $id, $definition): DefinitionInterface private function getLazyLoadingValueHolderFactory(): LazyLoadingValueHolderFactory { if (!class_exists(LazyLoadingValueHolderFactory::class)) { - throw new RuntimeException('You should install `ocramius/proxy-manager` if you want to use lazy services.'); + throw new RuntimeException( + 'You should install `friendsofphp/proxy-manager-lts` if you want to use lazy services.' + ); + } + if ($this->lazyFactory === null) { + $this->lazyFactory = new LazyLoadingValueHolderFactory(); } - return $this->lazyFactory ??= new LazyLoadingValueHolderFactory(); + + return $this->lazyFactory; } } diff --git a/tests/Unit/LazyServiceContainerTest.php b/tests/Unit/LazyServiceContainerTest.php index d918b6bf..d9bcaedc 100644 --- a/tests/Unit/LazyServiceContainerTest.php +++ b/tests/Unit/LazyServiceContainerTest.php @@ -16,7 +16,7 @@ class LazyServiceContainerTest extends TestCase protected function setUp(): void { if (!class_exists(LazyLoadingValueHolderFactory::class)) { - $this->markTestSkipped('You should install `ocramius/proxy-manager` if you want to use lazy services.'); + $this->markTestSkipped('You should install `friendsofphp/proxy-manager-lts` if you want to use lazy services.'); } } @@ -39,6 +39,7 @@ public function testIsTheSameObject(): void $object = $container->get($class); self::assertInstanceOf(LazyLoadingInterface::class, $object); + self::assertInstanceOf(EngineMarkOne::class, $object); self::assertFalse($object->isProxyInitialized()); self::assertEquals($number, $object->getNumber()); self::assertTrue($object->isProxyInitialized()); @@ -47,6 +48,7 @@ public function testIsTheSameObject(): void $object = $container->get($class); self::assertInstanceOf(LazyLoadingInterface::class, $object); + self::assertInstanceOf(EngineMarkOne::class, $object); self::assertTrue($object->isProxyInitialized()); } From 0b0b7845e4d5d5be2eb8ef92e0eafd494a5cee36 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 3 Dec 2022 19:33:52 +0300 Subject: [PATCH 13/19] Rework lazy definition --- src/Container.php | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/Container.php b/src/Container.php index d3682e57..52a43519 100644 --- a/src/Container.php +++ b/src/Container.php @@ -6,11 +6,9 @@ use Closure; use Psr\Container\ContainerExceptionInterface; -use ProxyManager\Factory\LazyLoadingValueHolderFactory; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; use Throwable; -use RuntimeException; use Yiisoft\Definitions\ArrayDefinition; use Yiisoft\Definitions\DefinitionStorage; use Yiisoft\Definitions\Contract\DefinitionInterface; @@ -18,7 +16,7 @@ use Yiisoft\Definitions\Exception\InvalidConfigException; use Yiisoft\Definitions\Exception\NotInstantiableException; use Yiisoft\Definitions\Helpers\DefinitionValidator; -use Yiisoft\Definitions\LazyDefinitionDecorator; +use Yiisoft\Definitions\LazyDefinition; use Yiisoft\Di\Helpers\DefinitionNormalizer; use Yiisoft\Di\Helpers\DefinitionParser; use Yiisoft\Di\Helpers\TagHelper; @@ -73,7 +71,6 @@ final class Container implements ContainerInterface */ private array $resetters = []; private bool $useResettersFromMeta = true; - private ?LazyLoadingValueHolderFactory $lazyFactory = null; /** * @param ContainerConfigInterface $config Container configuration. @@ -635,7 +632,6 @@ private function buildProvider(mixed $provider): ServiceProviderInterface private function decorateLazy(string $id, mixed $definition): DefinitionInterface { - $factory = $this->getLazyLoadingValueHolderFactory(); if (class_exists($id) || interface_exists($id)) { $class = $id; } elseif (is_array($definition) && array_key_exists(ArrayDefinition::CLASS_NAME, $definition)) { @@ -650,20 +646,6 @@ private function decorateLazy(string $id, mixed $definition): DefinitionInterfac ); } - return new LazyDefinitionDecorator($factory, $definition, $class); - } - - private function getLazyLoadingValueHolderFactory(): LazyLoadingValueHolderFactory - { - if (!class_exists(LazyLoadingValueHolderFactory::class)) { - throw new RuntimeException( - 'You should install `friendsofphp/proxy-manager-lts` if you want to use lazy services.' - ); - } - if ($this->lazyFactory === null) { - $this->lazyFactory = new LazyLoadingValueHolderFactory(); - } - - return $this->lazyFactory; + return new LazyDefinition($definition, $class); } } From d1b0c55c050e3a040808955426c502a63327c8e7 Mon Sep 17 00:00:00 2001 From: xepozz Date: Sat, 29 Jul 2023 14:18:07 +0000 Subject: [PATCH 14/19] Apply Rector changes (CI) --- src/Container.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Container.php b/src/Container.php index 52a43519..eb2809ef 100644 --- a/src/Container.php +++ b/src/Container.php @@ -257,7 +257,6 @@ private function addDefinitions(array $config): void * Each delegate must is a callable in format "function (ContainerInterface $container): ContainerInterface". * The container instance returned is used in case a service can not be found in primary container. * - * @param array $delegates * * @throws InvalidConfigException */ From feff3b7dd55e7c9f132ea1eab15bf3340f16afab Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 29 Jul 2023 14:18:14 +0000 Subject: [PATCH 15/19] Apply fixes from StyleCI --- src/Container.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Container.php b/src/Container.php index eb2809ef..4e42b26f 100644 --- a/src/Container.php +++ b/src/Container.php @@ -257,7 +257,6 @@ private function addDefinitions(array $config): void * Each delegate must is a callable in format "function (ContainerInterface $container): ContainerInterface". * The container instance returned is used in case a service can not be found in primary container. * - * * @throws InvalidConfigException */ private function setDelegates(array $delegates): void From 5bf1e81948eb5162ab5ecd33f9ab773f35b461bc Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Tue, 1 Aug 2023 23:25:15 +0300 Subject: [PATCH 16/19] Fix annotation --- src/Container.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Container.php b/src/Container.php index 4e42b26f..18f82235 100644 --- a/src/Container.php +++ b/src/Container.php @@ -209,7 +209,7 @@ private function addDefinition(string $id, mixed $definition): void $this->validateMeta($meta); } /** - * @psalm-var array{reset?:Closure,tags?:string[]} $meta + * @psalm-var array{reset?:Closure,lazy?:bool,tags?:string[]} $meta */ if (isset($meta[self::META_TAGS])) { From 1d04fbc3d2aa59032e4fa030285ac7d4873e265f Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Tue, 1 Aug 2023 23:41:54 +0300 Subject: [PATCH 17/19] Add new test case --- tests/Unit/LazyServiceContainerTest.php | 52 +++++++++++++++++-------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/tests/Unit/LazyServiceContainerTest.php b/tests/Unit/LazyServiceContainerTest.php index d9bcaedc..ab092e6e 100644 --- a/tests/Unit/LazyServiceContainerTest.php +++ b/tests/Unit/LazyServiceContainerTest.php @@ -16,7 +16,9 @@ class LazyServiceContainerTest extends TestCase protected function setUp(): void { if (!class_exists(LazyLoadingValueHolderFactory::class)) { - $this->markTestSkipped('You should install `friendsofphp/proxy-manager-lts` if you want to use lazy services.'); + $this->markTestSkipped( + 'You should install `friendsofphp/proxy-manager-lts` if you want to use lazy services.' + ); } } @@ -69,32 +71,48 @@ public function testLazy(array $definitions, string $id): void public function lazyDefinitionDataProvider(): array { return [ + 'class as definition name' => [ + [ + EngineMarkOne::class => [ + 'lazy' => true, + ], + ], + EngineMarkOne::class, + ], 'class as key' => [ - [EngineMarkOne::class => [ - 'class' => EngineMarkOne::class, - 'lazy' => true, - ]], + [ + EngineMarkOne::class => [ + 'class' => EngineMarkOne::class, + 'lazy' => true, + ], + ], EngineMarkOne::class, ], 'alias as key' => [ - ['mark_one' => [ - 'class' => EngineMarkOne::class, - 'lazy' => true, - ]], + [ + 'mark_one' => [ + 'class' => EngineMarkOne::class, + 'lazy' => true, + ], + ], 'mark_one', ], 'dedicated array definition' => [ - [EngineMarkOne::class => [ - 'definition' => ['class' => EngineMarkOne::class], - 'lazy' => true, - ]], + [ + EngineMarkOne::class => [ + 'definition' => ['class' => EngineMarkOne::class], + 'lazy' => true, + ], + ], EngineMarkOne::class, ], 'dedicated callback definition' => [ - [EngineMarkOne::class => [ - 'definition' => fn () => new EngineMarkOne(), - 'lazy' => true, - ]], + [ + EngineMarkOne::class => [ + 'definition' => fn () => new EngineMarkOne(), + 'lazy' => true, + ], + ], EngineMarkOne::class, ], ]; From bc7f70e9f5a68fae0712ad15846a16255fc99aff Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Tue, 1 Aug 2023 23:50:28 +0300 Subject: [PATCH 18/19] Fix psalm --- src/Container.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Container.php b/src/Container.php index 18f82235..ebaa18c2 100644 --- a/src/Container.php +++ b/src/Container.php @@ -10,8 +10,8 @@ use Psr\Container\NotFoundExceptionInterface; use Throwable; use Yiisoft\Definitions\ArrayDefinition; -use Yiisoft\Definitions\DefinitionStorage; use Yiisoft\Definitions\Contract\DefinitionInterface; +use Yiisoft\Definitions\DefinitionStorage; use Yiisoft\Definitions\Exception\CircularReferenceException; use Yiisoft\Definitions\Exception\InvalidConfigException; use Yiisoft\Definitions\Exception\NotInstantiableException; @@ -644,6 +644,10 @@ private function decorateLazy(string $id, mixed $definition): DefinitionInterfac ); } + /** + * @var class-string $class + */ + return new LazyDefinition($definition, $class); } } From 51fefa2e55ea261041d187f9adef5c29bd0f64c1 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 16 Apr 2024 18:57:42 +0300 Subject: [PATCH 19/19] Suggestion for lazy service (#355) * improve * Fix psalm --- psalm.xml | 1 + src/Container.php | 48 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/psalm.xml b/psalm.xml index 5ce79f0e..7d4b7337 100644 --- a/psalm.xml +++ b/psalm.xml @@ -14,6 +14,7 @@ + diff --git a/src/Container.php b/src/Container.php index 9edaf8f6..e30f2544 100644 --- a/src/Container.php +++ b/src/Container.php @@ -32,6 +32,8 @@ /** * Container implements a [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) container. + * + * @psalm-import-type MethodOrPropertyItem from ArrayDefinition */ final class Container implements ContainerInterface { @@ -202,7 +204,6 @@ public function get(string $id) */ private function addDefinition(string $id, mixed $definition): void { - /** @var mixed $definition */ [$definition, $meta] = DefinitionParser::parse($definition); if ($this->validate) { $this->validateDefinition($definition, $id); @@ -630,11 +631,42 @@ private function buildProvider(mixed $provider): ServiceProviderInterface private function decorateLazy(string $id, mixed $definition): DefinitionInterface { - if (class_exists($id) || interface_exists($id)) { - $class = $id; - } elseif (is_array($definition) && array_key_exists(ArrayDefinition::CLASS_NAME, $definition)) { - $class = (string) $definition[ArrayDefinition::CLASS_NAME]; + $class = class_exists($id) || interface_exists($id) ? $id : null; + + if (is_array($definition) && isset($definition[DefinitionParser::IS_PREPARED_ARRAY_DEFINITION_DATA])) { + /** + * @psalm-var array{ + * class: class-string|null, + * '__construct()': array, + * methodsAndProperties: array + * } $definition + */ + if (empty($class)) { + $class = $definition[ArrayDefinition::CLASS_NAME]; + } + $this->checkClassOnNullForLazyService($class, $id, $definition); + $preparedDefinition = ArrayDefinition::fromPreparedData( + $definition[ArrayDefinition::CLASS_NAME] ?? $class, + $definition[ArrayDefinition::CONSTRUCTOR], + $definition['methodsAndProperties'], + ); } else { + $this->checkClassOnNullForLazyService($class, $id, $definition); + $preparedDefinition = $definition; + } + + + + return new LazyDefinition($preparedDefinition, $class); + } + + /** + * @psalm-param class-string|null $class + * @psalm-assert class-string $class + */ + private function checkClassOnNullForLazyService(?string $class, string $id, mixed $definition): void + { + if (empty($class)) { throw new InvalidConfigException( sprintf( 'Invalid definition: lazy services are only available with array definitions or references. Got type "%s" for definition ID: "%s"', @@ -643,11 +675,5 @@ private function decorateLazy(string $id, mixed $definition): DefinitionInterfac ) ); } - - /** - * @var class-string $class - */ - - return new LazyDefinition($definition, $class); } }