From 74bb613a5e664623ff54e32bb6ae95e107e541f8 Mon Sep 17 00:00:00 2001 From: Luca Tumedei Date: Tue, 20 Jun 2023 08:45:56 +0200 Subject: [PATCH] feat(Container) handle cloning correctly --- CHANGELOG.md | 9 ++ config/containers/php/Dockerfile | 1 + src/Container.php | 28 ++++- tests/unit/CloneTest.php | 101 ++++++++++++++++++ tests/unit/ContextualBindingContainerTest.php | 3 +- .../PHP7ContextualBindingContainerTest.php | 2 +- 6 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 tests/unit/CloneTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1711764..3012a88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ to [Semantic Versioning](http://semver.org/). ## [unreleased] Unreleased +### Added + +- Implement the `Container::__clone` method to clone the container accessory classes correctly upon cloning. + +### Changed + +- The `Container::__construct` method will bind itself to the `Psr\Container\ContainerInterface` interface as a singleton. +- If the `Container` class is extended, it will bind itself to the extended class in the `__construct` and `__clone` methods. + ## [3.3.3] 2023-04-28; ### Fixed diff --git a/config/containers/php/Dockerfile b/config/containers/php/Dockerfile index 507d4d0..6ccdb49 100644 --- a/config/containers/php/Dockerfile +++ b/config/containers/php/Dockerfile @@ -6,6 +6,7 @@ FROM php:${PHP_VERSION}-cli-alpine COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/ RUN install-php-extensions xdebug && apk add gettext && rm -rf /var/cache/apk/* +RUN rm /usr/bin/install-php-extensions ARG XDEBUG_REMOTE_HOST='host.docker.internal' ARG XDEBUG_REMOTE_PORT='9009' diff --git a/src/Container.php b/src/Container.php index 5423856..40fe58d 100644 --- a/src/Container.php +++ b/src/Container.php @@ -101,7 +101,7 @@ public function __construct($resolveUnboundAsSingletons = false) { $this->resolver = new Builders\Resolver($resolveUnboundAsSingletons); $this->builders = new Builders\Factory($this, $this->resolver); - $this->singleton(Container::class, $this); + $this->bindThis(); } /** @@ -869,4 +869,30 @@ public function setExceptionMask($maskThrowables) { $this->maskThrowables = (int)$maskThrowables; } + + /** + * Binds the container to the base class name, the current class name and the container interface. + * + * @return void + */ + private function bindThis() + { + $this->singleton(ContainerInterface::class, $this); + $this->singleton(Container::class, $this); + if (get_class($this) !== Container::class) { + $this->singleton(get_class($this), $this); + } + } + + /** + * Upon cloning, clones the resolver and builders instances. + * + * @return void + */ + public function __clone() + { + $this->resolver = clone $this->resolver; + $this->builders = clone $this->builders; + $this->bindThis(); + } } diff --git a/tests/unit/CloneTest.php b/tests/unit/CloneTest.php new file mode 100644 index 0000000..1f484f3 --- /dev/null +++ b/tests/unit/CloneTest.php @@ -0,0 +1,101 @@ +bind('bound', $object1); + $container->singleton('singleton', $object2); + + $clone = clone $container; + + $this->assertNotSame($clone, $container); + $this->assertTrue($clone->has('bound')); + $this->assertSame($clone->get('bound'), $container->get('bound')); + $this->assertTrue($clone->has('singleton')); + $this->assertSame($clone->get('singleton'), $container->get('singleton')); + $this->assertFalse($clone->has('not-bound')); + } + + /** + * It should clone the container resolver + * + * @test + */ + public function should_clone_the_container_resolver() + { + $container = new Container(); + $container->singleton('object', function () { + return new \stdClass(); + }); + + $clone = clone $container; + + $container->setVar('test', 23); + $clone->setVar('test', 89); + + $this->assertEquals(23, $container->getVar('test')); + $this->assertEquals(89, $clone->getVar('test')); + + $containerObject = $container->get('object'); + $cloneObject = $clone->get('object'); + + $this->assertSame($container->get('object'), $container->get('object')); + $this->assertSame($clone->get('object'), $clone->get('object')); + $this->assertNotSame($containerObject, $cloneObject); + } + + /** + * It should bind clone as singleton container on clone + * + * @test + */ + public function should_bind_clone_as_singleton_container_on_clone() + { + $container = new Container(); + $clone = clone $container; + + $this->assertNotSame($container, $clone); + $this->assertSame($container, $container->get(Container::class)); + $this->assertSame($container, $container->get(ContainerInterface::class)); + $this->assertSame($clone, $clone->get(Container::class)); + $this->assertSame($clone, $clone->get(ContainerInterface::class)); + } + + /** + * It should bind the clone as singleton when Container class extended + * + * @test + */ + public function should_bind_the_clone_as_singleton_when_container_class_extended() + { + $container = new ContainerExtension(); + $clone = clone $container; + + $this->assertNotSame($container, $clone); + $this->assertSame($container, $container->get(Container::class)); + $this->assertSame($container, $container->get(ContainerExtension::class)); + $this->assertSame($container, $container->get(ContainerInterface::class)); + $this->assertSame($clone, $clone->get(Container::class)); + $this->assertSame($clone, $clone->get(ContainerExtension::class)); + $this->assertSame($clone, $clone->get(ContainerInterface::class)); + } +} diff --git a/tests/unit/ContextualBindingContainerTest.php b/tests/unit/ContextualBindingContainerTest.php index 4ffbae1..eb315b1 100644 --- a/tests/unit/ContextualBindingContainerTest.php +++ b/tests/unit/ContextualBindingContainerTest.php @@ -51,8 +51,7 @@ public function it_should_resolve_primitive_contextual_bindings_in_a_PHP53_class /** * @test */ - public function it_should_resolve_primitive_contextual_bindings_in_a_php7_class_when_its_bound_interface_is_resolved( - ) + public function it_should_resolve_primitive_contextual_bindings_in_a_php7_class_when_its_bound_interface_is_resolved() { $container = new Container(); diff --git a/tests/unit/PHP7ContextualBindingContainerTest.php b/tests/unit/PHP7ContextualBindingContainerTest.php index 3f6b2f2..86ce252 100644 --- a/tests/unit/PHP7ContextualBindingContainerTest.php +++ b/tests/unit/PHP7ContextualBindingContainerTest.php @@ -69,7 +69,7 @@ public function it_should_resolve_primitive_contextual_bindings_in_a_php7_class_ { $container = new Container(); - $container->bind( Test7Interface::class, Primitive7ConstructorClass::class ); + $container->bind(Test7Interface::class, Primitive7ConstructorClass::class); $container->when(Primitive7ConstructorClass::class) ->needs('$num')