diff --git a/composer.json b/composer.json index 79bceaa..e20c04a 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ }, "require": { "php": "~7.4.12", + "psr/container": "~1.0.0", "ocramius/proxy-manager": "~2.10.0" }, "require-dev": { diff --git a/src/Exceptions.php b/src/Exceptions.php index c8ef7c4..2081f5c 100644 --- a/src/Exceptions.php +++ b/src/Exceptions.php @@ -21,8 +21,10 @@ namespace modethirteen\OpenContainer; use Exception; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; -class OpenContainerCannotBuildDeferredInstanceException extends Exception { +class OpenContainerCannotBuildDeferredInstanceException extends Exception implements ContainerExceptionInterface { /** * @param string $id @@ -32,7 +34,7 @@ public function __construct(string $id) { } } -class OpenContainerNotRegisteredInContainerException extends Exception { +class OpenContainerNotRegisteredInContainerException extends Exception implements NotFoundExceptionInterface { /** * @param string $id diff --git a/src/IContainer.php b/src/IContainer.php index 6b4d738..94f54b7 100644 --- a/src/IContainer.php +++ b/src/IContainer.php @@ -17,12 +17,13 @@ namespace modethirteen\OpenContainer; use Closure; +use Psr\Container\ContainerInterface; /** * Interface IContainer * @package modethirteen\OpenContainer */ -interface IContainer { +interface IContainer extends ContainerInterface { /** * @param string $id diff --git a/src/OpenContainer.php b/src/OpenContainer.php index 6b7ed27..730aa12 100644 --- a/src/OpenContainer.php +++ b/src/OpenContainer.php @@ -68,6 +68,24 @@ public function __construct() { * @throws OpenContainerCannotBuildDeferredInstanceException */ public function __get(string $id) : object { + return $this->get($id); + } + + public function flushInstance(string $id) : void { + if(isset($this->instances[$id])) { + unset($this->instances[$id]); + } + } + + /** + * @note type hint is not leveraged in this method as the psr/container interface does not include it + * @param string $id + * @return mixed|void + * @noinspection PhpMissingParamTypeInspection + * @throws OpenContainerNotRegisteredInContainerException + * @throws OpenContainerCannotBuildDeferredInstanceException + */ + public function get($id) { if($this->isDeferred && isset($this->deferredInstances[$id])) { // requesting a previously generated deferred instance @@ -107,7 +125,7 @@ public function __get(string $id) : object { // build deferred instance $instance = $this->deferredInstanceFactory ->createProxy($type, function(&$instance, LazyLoadingInterface $proxy) use ($builder, $type) : bool { - $proxy->setProxyInitializer(null); + $proxy->setProxyInitializer(); $instance = $builder !== null ? $builder($this) : new $type($this); return true; }); @@ -121,10 +139,14 @@ public function __get(string $id) : object { return $instance; } - public function flushInstance(string $id) : void { - if(isset($this->instances[$id])) { - unset($this->instances[$id]); - } + /** + * @note type hint is not leveraged in this method as the psr/container interface does not include it + * @param string $id + * @return bool + * @noinspection PhpMissingParamTypeInspection + */ + public function has($id) : bool { + return $this->isRegistered($id); } public function isDeferredContainer(): bool { diff --git a/tests/DependencyContainer.php b/tests/DependencyContainer.php index 98b1765..f7a6f2b 100644 --- a/tests/DependencyContainer.php +++ b/tests/DependencyContainer.php @@ -34,5 +34,7 @@ public function __construct() { return []; }); $this->registerInstance('Instance', new Instance()); + $this->registerType('PsrCompatibleCircularDependencyOne', PsrCompatibleCircularDependencyOne::class); + $this->registerType('PsrCompatibleCircularDependencyTwo', PsrCompatibleCircularDependencyTwo::class); } } diff --git a/tests/OpenContainerTest.php b/tests/OpenContainerTest.php index f7ec01d..d90ab67 100644 --- a/tests/OpenContainerTest.php +++ b/tests/OpenContainerTest.php @@ -29,33 +29,57 @@ class OpenContainerTest extends TestCase { /** * @return array */ - public static function containerProvider() : array { + public static function container_Provider() : array { return [ 'container' => [new DependencyContainer()], - 'deferred container' => [(new DependencyContainer())->toDeferredContainer()] + 'deferred container' => [(new DependencyContainer())->toDeferredContainer()], ]; } /** - * @dataProvider containerProvider + * @return array + */ + public static function container_psr_Provider() : array { + return [ + 'container' => [new DependencyContainer(), false], + 'deferred container' => [(new DependencyContainer())->toDeferredContainer(), false], + 'container with psr interface' => [new DependencyContainer(), true], + 'deferred container with psr interface' => [(new DependencyContainer())->toDeferredContainer(), true] + ]; + } + + /** + * @return array + */ + public static function psr_Provider() : array { + return [ + 'with psr interface' => [true], + 'without psr interface' => [false] + ]; + } + + /** + * @dataProvider container_psr_Provider * @param IDependencyContainer $container + * @param bool $psr * @test */ - public function Can_handle_instance_registration(IDependencyContainer $container) : void { + public function Can_handle_instance_registration(IDependencyContainer $container, bool $psr) : void { // act - $result = $container->Instance; + $result = $psr ? $container->get('Instance') : $container->Instance; // assert static::assertInstanceOf(Instance::class, $result); } /** - * @dataProvider containerProvider + * @dataProvider container_psr_Provider * @param IDependencyContainer $container + * @param bool $psr * @test */ - public function Can_handle_type_registration(IDependencyContainer $container) : void { + public function Can_handle_type_registration(IDependencyContainer $container, bool $psr) : void { // arrange $container->flushInstance('Instance'); @@ -63,18 +87,19 @@ public function Can_handle_type_registration(IDependencyContainer $container) : // act /** @noinspection PhpUndefinedFieldInspection */ - $result = $container->Plugh; + $result = $psr ? $container->get('Plugh') : $container->Plugh; // assert static::assertInstanceOf(Instance::class, $result); } /** - * @dataProvider containerProvider + * @dataProvider container_psr_Provider * @param IDependencyContainer $container + * @param bool $psr * @test */ - public function Can_handle_builder_registration(IDependencyContainer $container) : void { + public function Can_handle_builder_registration(IDependencyContainer $container, bool $psr) : void { // arrange $container->flushInstance('Instance'); @@ -85,60 +110,83 @@ public function Can_handle_builder_registration(IDependencyContainer $container) // act /** @noinspection PhpUndefinedFieldInspection */ - $result = $container->Xyzzy; + $result = $psr ? $container->get('Xyzzy') : $container->Xyzzy; // assert static::assertInstanceOf(Instance::class, $result); } /** - * @dataProvider containerProvider + * @dataProvider container_psr_Provider * @param IDependencyContainer $container + * @param bool $psr * @test */ - public function Can_handle_circular_dependency_resolution(IDependencyContainer $container) : void { + public function Can_handle_circular_dependency_resolution(IDependencyContainer $container, bool $psr) : void { + + // arrange + if($psr && !$container->isDeferredContainer()) { + static::markTestSkipped('Using the PSR-11 container "get" method with a non-deferred container creates an endless nested function loop'); + } // act - $foo = $container->CircularDependencyOne; - $bar = $container->CircularDependencyTwo; + $foo = $psr ? $container->get('PsrCompatibleCircularDependencyOne') : $container->CircularDependencyOne; + $bar = $psr ? $container->get('PsrCompatibleCircularDependencyTwo') : $container->CircularDependencyTwo; // assert if($container->isDeferredContainer()) { // deferred container can manage circular dependencies at construction - static::assertInstanceOf(CircularDependencyOne::class, $foo); - static::assertInstanceOf(CircularDependencyTwo::class, $bar); - static::assertInstanceOf(CircularDependencyOne::class, $bar->getDependency()); - static::assertInstanceOf(CircularDependencyTwo::class, $foo->getDependency()); + if($psr) { + static::assertInstanceOf(PsrCompatibleCircularDependencyOne::class, $foo); + static::assertInstanceOf(PsrCompatibleCircularDependencyTwo::class, $bar); + static::assertInstanceOf(PsrCompatibleCircularDependencyOne::class, $bar->getDependency()); + static::assertInstanceOf(PsrCompatibleCircularDependencyTwo::class, $foo->getDependency()); + } else { + static::assertInstanceOf(CircularDependencyOne::class, $foo); + static::assertInstanceOf(CircularDependencyTwo::class, $bar); + static::assertInstanceOf(CircularDependencyOne::class, $bar->getDependency()); + static::assertInstanceOf(CircularDependencyTwo::class, $foo->getDependency()); + } } else { // without deferred container, circular dependencies are presented as null in injectable class constructors - static::assertInstanceOf(CircularDependencyOne::class, $foo); - static::assertInstanceOf(CircularDependencyTwo::class, $bar); - static::assertNull($bar->getDependency()); - static::assertInstanceOf(CircularDependencyTwo::class, $foo->getDependency()); + if($psr) { + static::assertInstanceOf(PsrCompatibleCircularDependencyOne::class, $foo); + static::assertInstanceOf(PsrCompatibleCircularDependencyTwo::class, $bar); + static::assertNull($bar->getDependency()); + static::assertInstanceOf(PsrCompatibleCircularDependencyTwo::class, $foo->getDependency()); + } else { + static::assertInstanceOf(CircularDependencyOne::class, $foo); + static::assertInstanceOf(CircularDependencyTwo::class, $bar); + static::assertNull($bar->getDependency()); + static::assertInstanceOf(CircularDependencyTwo::class, $foo->getDependency()); + } } } /** - * @dataProvider containerProvider + * @dataProvider container_psr_Provider * @param IDependencyContainer $container + * @param bool $psr * @test */ - public function Can_handle_unregistered_dependency(IDependencyContainer $container) { + public function Can_handle_unregistered_dependency(IDependencyContainer $container, bool $psr) : void { // assert static::expectException(OpenContainerNotRegisteredInContainerException::class); // act /** @noinspection PhpUndefinedFieldInspection */ - $container->Ogre; + $psr ? $container->get('Ogre') : $container->Ogre; } /** + * @dataProvider psr_Provider + * @param bool $psr * @test */ - public function Can_handle_deferred_dependency_construction_error() { + public function Can_handle_deferred_dependency_construction_error(bool $psr) : void { // assert static::expectException(OpenContainerCannotBuildDeferredInstanceException::class); @@ -150,19 +198,20 @@ public function Can_handle_deferred_dependency_construction_error() { }); // act - $container->Puppy; + $psr ? $container->get('Puppy') : $container->Puppy; } /** - * @dataProvider containerProvider + * @dataProvider container_psr_Provider * @param IDependencyContainer $container + * @param bool $psr * @test */ - public function Can_check_if_dependency_is_registered(IDependencyContainer $container) { + public function Can_check_if_dependency_is_registered(IDependencyContainer $container, bool $psr) : void { // act - $result1 = $container->isRegistered('Instance'); - $result2 = $container->isRegistered('Fred'); + $result1 = $psr ? $container->has('Instance') : $container->isRegistered('Instance'); + $result2 = $psr ? $container->has('Fred') : $container->isRegistered('Fred'); // assert static::assertTrue($result1); @@ -170,11 +219,11 @@ public function Can_check_if_dependency_is_registered(IDependencyContainer $cont } /** - * @dataProvider containerProvider + * @dataProvider container_Provider * @param IDependencyContainer $container * @test */ - public function Can_check_if_dependency_is_resolved(IDependencyContainer $container) { + public function Can_check_if_dependency_is_resolved(IDependencyContainer $container) : void { // arrange $container->registerType('Plugh', Instance::class); diff --git a/tests/PsrCompatibleCircularDependencyOne.php b/tests/PsrCompatibleCircularDependencyOne.php new file mode 100644 index 0000000..e2cf942 --- /dev/null +++ b/tests/PsrCompatibleCircularDependencyOne.php @@ -0,0 +1,43 @@ +dependency = $container->get('PsrCompatibleCircularDependencyTwo'); + } + + /** + * @return PsrCompatibleCircularDependencyTwo|null + */ + public function getDependency() : ?PsrCompatibleCircularDependencyTwo { + return $this->dependency; + } +} diff --git a/tests/PsrCompatibleCircularDependencyTwo.php b/tests/PsrCompatibleCircularDependencyTwo.php new file mode 100644 index 0000000..57091b4 --- /dev/null +++ b/tests/PsrCompatibleCircularDependencyTwo.php @@ -0,0 +1,43 @@ +dependency = $container->get('PsrCompatibleCircularDependencyOne'); + } + + /** + * @return PsrCompatibleCircularDependencyOne|null + */ + public function getDependency() : ?PsrCompatibleCircularDependencyOne { + return $this->dependency; + } +}