diff --git a/README.md b/README.md index f0f2cec..66d06a0 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,14 @@ $factory = new ContainerFactory(); $container = $factory( new Config([ 'dependencies' => [ - 'services' => [], - 'invokables' => [], - 'factories' => [], - 'aliases' => [], - 'delegators' => [], - 'extensions' => [], + 'services' => [], + 'invokables' => [], + 'factories' => [], + 'aliases' => [], + 'delegators' => [], + 'extensions' => [], + 'shared' => [], + 'shared_by_default' => true, ], // ... other configuration ]) @@ -62,6 +64,11 @@ The `dependencies` sub associative array can contain the following keys: for more details. - `extensions`: an associative array that maps service names to lists of extension factory names, see the [the section below](#extensions). +- `shared`: associative array that map a service name to a boolean, in order to + indicate the service manager if it should cache or not a service created + through the get method, independant of the shared_by_default setting. +- `shared_by_default`: boolean that indicates whether services created through + the `get` method should be cached. This is `true` by default. > Please note, that the whole configuration is available in the `$container` > on `config` key: diff --git a/composer.json b/composer.json index 795348b..fda160a 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,9 @@ "pimple/pimple": "^3.2.2" }, "require-dev": { - "phpunit/phpunit": "^7.0.2", - "zendframework/zend-coding-standard": "~1.0.0" + "phpunit/phpunit": "^7.1.2", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-container-config-test": "^0.2 || ^1.0" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index b0d82a6..8809122 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "8a93b313d2f2879a115b3b91c1e681a0", + "content-hash": "9f63ab73fb5fe68712e044ae4159da10", "packages": [ { "name": "pimple/pimple", @@ -525,16 +525,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "6.0.1", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba" + "reference": "774a82c0c5da4c1c7701790c262035d235ab7856" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f8ca4b604baf23dab89d87773c28cc07405189ba", - "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/774a82c0c5da4c1c7701790c262035d235ab7856", + "reference": "774a82c0c5da4c1c7701790c262035d235ab7856", "shasum": "" }, "require": { @@ -545,7 +545,7 @@ "phpunit/php-text-template": "^1.2.1", "phpunit/php-token-stream": "^3.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.0", + "sebastian/environment": "^3.1", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, @@ -584,7 +584,7 @@ "testing", "xunit" ], - "time": "2018-02-02T07:01:41+00:00" + "time": "2018-04-06T15:39:20+00:00" }, { "name": "phpunit/php-file-iterator", @@ -774,16 +774,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.0.2", + "version": "7.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9" + "reference": "6a17c170fb92845896e1b3b00fcb462cd4b3c017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9", - "reference": "e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6a17c170fb92845896e1b3b00fcb462cd4b3c017", + "reference": "6a17c170fb92845896e1b3b00fcb462cd4b3c017", "shasum": "" }, "require": { @@ -797,11 +797,11 @@ "phar-io/version": "^1.0", "php": "^7.1", "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0", + "phpunit/php-code-coverage": "^6.0.1", "phpunit/php-file-iterator": "^1.4.3", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^2.0", - "phpunit/phpunit-mock-objects": "^6.0", + "phpunit/phpunit-mock-objects": "^6.1", "sebastian/comparator": "^2.1", "sebastian/diff": "^3.0", "sebastian/environment": "^3.1", @@ -824,7 +824,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.0-dev" + "dev-master": "7.1-dev" } }, "autoload": { @@ -850,20 +850,20 @@ "testing", "xunit" ], - "time": "2018-02-26T07:03:12+00:00" + "time": "2018-04-10T11:40:22+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "6.0.1", + "version": "6.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "e3249dedc2d99259ccae6affbc2684eac37c2e53" + "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/e3249dedc2d99259ccae6affbc2684eac37c2e53", - "reference": "e3249dedc2d99259ccae6affbc2684eac37c2e53", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/70c740bde8fd9ea9ea295be1cd875dd7b267e157", + "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157", "shasum": "" }, "require": { @@ -881,7 +881,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.0.x-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -906,7 +906,7 @@ "mock", "xunit" ], - "time": "2018-02-15T05:27:38+00:00" + "time": "2018-04-11T04:50:36+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1667,6 +1667,61 @@ "zf" ], "time": "2016-11-09T21:30:43+00:00" + }, + { + "name": "zendframework/zend-container-config-test", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-container-config-test.git", + "reference": "64a73e2cfc12cb07103e8936dfb25c136ba0496f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-container-config-test/zipball/64a73e2cfc12cb07103e8936dfb25c136ba0496f", + "reference": "64a73e2cfc12cb07103e8936dfb25c136ba0496f", + "shasum": "" + }, + "require": { + "php": "^7.1", + "psr/container": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0.2", + "zendframework/zend-auradi-config": "^1.0.0", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-pimple-config": "^1.0.0", + "zendframework/zend-servicemanager": "^3.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "src/TestAsset/function-factory.php", + "src/TestAsset/function-factory-with-name.php" + ], + "psr-4": { + "Zend\\ContainerConfigTest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Expressive PSR-11 container configuration tests", + "keywords": [ + "PSR-11", + "ZendFramework", + "container", + "expressive", + "test", + "zf" + ], + "time": "2018-04-11T14:09:33+00:00" } ], "aliases": [], diff --git a/src/Config.php b/src/Config.php index eeaf1cc..56817cd 100644 --- a/src/Config.php +++ b/src/Config.php @@ -1,7 +1,7 @@ config['dependencies']; } + $dependencies['shared_by_default'] = isset($dependencies['shared_by_default']) + ? (bool) $dependencies['shared_by_default'] + : true; $this->injectServices($container, $dependencies); $this->injectFactories($container, $dependencies); $this->injectInvokables($container, $dependencies); $this->injectAliases($container, $dependencies); $this->injectExtensions($container, $dependencies); - $this->injectDelegators($container, $dependencies); } private function injectServices(Container $container, array $dependencies) : void @@ -66,15 +73,30 @@ private function injectFactories(Container $container, array $dependencies) : vo } foreach ($dependencies['factories'] as $name => $object) { - $container[$name] = function (Container $c) use ($object, $name) { - if ($c->offsetExists($object)) { - $factory = $c->offsetGet($object); - } else { - $factory = new $object(); - $c[$object] = $c->protect($factory); + $callback = function () use ($container, $object, $name) { + if (is_callable($object)) { + $factory = $object; + } elseif (! is_string($object) || ! class_exists($object) || ! is_callable($factory = new $object())) { + throw new ExpectedInvokableException(sprintf( + 'Factory provided to initialize service %s does not exist or is not callable', + $name + )); } - return $factory(new PsrContainer($c), $name); + + return $factory(new PsrContainer($container), $name); }; + + if (isset($dependencies['delegators'][$name])) { + $this->injectDelegator( + $container, + $callback, + $name, + $dependencies['delegators'][$name] + ); + continue; + } + + $this->setService($container, $dependencies, $name, $callback); } } @@ -86,16 +108,32 @@ private function injectInvokables(Container $container, array $dependencies) : v return; } - foreach ($dependencies['invokables'] as $name => $object) { - if ($name !== $object) { - $container[$name] = function (Container $c) use ($object) { - return $c->offsetGet($object); - }; - } + foreach ($dependencies['invokables'] as $alias => $object) { + $callback = function () use ($object) { + if (! class_exists($object)) { + throw new ExpectedInvokableException(sprintf( + 'Class %s does not exist', + $object + )); + } - $container[$object] = function (Container $c) use ($object) { return new $object(); }; + + if (isset($dependencies['delegators'][$object])) { + $this->injectDelegator( + $container, + $callback, + $object, + $dependencies['delegators'][$object] + ); + } else { + $this->setService($container, $dependencies, $object, $callback); + } + + if (! is_int($alias) && $alias !== $object) { + $this->setAlias($container, $dependencies, $alias, $object); + } } } @@ -108,9 +146,7 @@ private function injectAliases(Container $container, array $dependencies) : void } foreach ($dependencies['aliases'] as $alias => $target) { - $container[$alias] = function (Container $c) use ($target) { - return $c->offsetGet($target); - }; + $this->setAlias($container, $dependencies, $alias, $target); } } @@ -132,24 +168,58 @@ private function injectExtensions(Container $container, array $dependencies) : v } } - private function injectDelegators(Container $container, array $dependencies) : void + private function injectDelegator(Container $container, callable $callback, string $name, array $delegators) { - if (empty($dependencies['delegators']) - || ! is_array($dependencies['delegators']) - ) { - return; - } + $container[$name] = function (Container $c) use ($callback, $name, $delegators) { + foreach ($delegators as $delegatorClass) { + if (! class_exists($delegatorClass)) { + throw new ExpectedInvokableException(); + } - foreach ($dependencies['delegators'] as $name => $delegators) { - foreach ($delegators as $delegator) { - $container->extend($name, function ($service, Container $c) use ($delegator, $name) { - $factory = new $delegator(); - $callback = function () use ($service) { - return $service; - }; - return $factory(new PsrContainer($c), $name, $callback); - }); + $delegator = new $delegatorClass(); + + if (! is_callable($delegator)) { + throw new ExpectedInvokableException(); + } + + $instance = $delegator(new PsrContainer($c), $name, $callback); + $callback = function () use ($instance) { + return $instance; + }; } - } + + return $instance ?? $callback(); + }; + } + + private function setAlias(Container $container, array $dependencies, string $alias, string $target) + { + $this->setService( + $container, + $dependencies, + $alias, + function () use ($container, $dependencies, $alias, $target) { + $instance = $container->offsetGet($target); + + if (! $this->isShared($dependencies, $alias)) { + return clone $instance; + } + + return $instance; + } + ); + } + + private function setService(Container $container, array $dependencies, string $name, callable $callback) + { + $container[$name] = $this->isShared($dependencies, $name) + ? $callback + : $container->factory($callback); + } + + private function isShared(array $dependencies, string $name) + { + return ($dependencies['shared_by_default'] === true && ! isset($dependencies['shared'][$name])) + || (isset($dependencies['shared'][$name]) && $dependencies['shared'][$name] === true); } } diff --git a/test/ConfigTest.php b/test/ConfigTest.php index 11ed738..817dd60 100644 --- a/test/ConfigTest.php +++ b/test/ConfigTest.php @@ -1,7 +1,7 @@ container->offsetGet('config')); } - public function testInjectService() - { - $myService = new TestAsset\Service(); - - $dependencies = [ - 'services' => [ - 'foo-bar' => $myService, - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists('foo-bar')); - self::assertSame($myService, $this->container->offsetGet('foo-bar')); - } - - public function testInjectServiceFactory() - { - $factory = new TestAsset\Factory(); - - $dependencies = [ - 'services' => [ - 'factory' => $factory, - ], - 'factories' => [ - 'foo-bar' => 'factory', - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists('factory')); - self::assertTrue($this->container->offsetExists('foo-bar')); - self::assertInstanceOf(TestAsset\Service::class, $this->container->offsetGet('foo-bar')); - } - - public function testInjectInvokableFactory() - { - $dependencies = [ - 'factories' => [ - 'foo-bar' => TestAsset\Factory::class, - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists('foo-bar')); - self::assertInstanceOf(TestAsset\Service::class, $this->container->offsetGet('foo-bar')); - } - - public function testInjectInvokable() - { - $dependencies = [ - 'invokables' => [ - 'foo-bar' => TestAsset\Service::class, - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists('foo-bar')); - self::assertInstanceOf(TestAsset\Service::class, $this->container->offsetGet('foo-bar')); - } - - public function testInjectAlias() - { - $myService = new TestAsset\Service(); - - $dependencies = [ - 'services' => [ - 'foo-bar' => $myService, - ], - 'aliases' => [ - 'alias' => 'foo-bar', - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists('alias')); - self::assertSame($myService, $this->container->offsetGet('alias')); - } - public function testInjectExtensionForInvokable() { $dependencies = [ @@ -236,134 +153,4 @@ public function testInjectMultipleExtensionsAsDecorators() self::assertInstanceOf(TestAsset\Decorator1::class, $service->originService); self::assertSame($myService, $service->originService->originService); } - - public function testInjectDelegatorForInvokable() - { - $dependencies = [ - 'invokables' => [ - 'foo-bar' => TestAsset\Service::class, - ], - 'delegators' => [ - 'foo-bar' => [ - TestAsset\DelegatorFactory::class, - ], - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists('foo-bar')); - $delegator = $this->container->offsetGet('foo-bar'); - self::assertInstanceOf(TestAsset\Delegator::class, $delegator); - $callback = $delegator->callback; - self::assertInstanceOf(TestAsset\Service::class, $callback()); - } - - public function testInjectDelegatorForService() - { - $myService = new TestAsset\Service(); - $dependencies = [ - 'services' => [ - 'foo-bar' => $myService, - ], - 'delegators' => [ - 'foo-bar' => [ - TestAsset\DelegatorFactory::class, - ], - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists('foo-bar')); - $delegator = $this->container->offsetGet('foo-bar'); - self::assertInstanceOf(TestAsset\Delegator::class, $delegator); - $callback = $delegator->callback; - self::assertSame($myService, $callback()); - } - - public function testInjectDelegatorForFactory() - { - $dependencies = [ - 'factories' => [ - 'foo-bar' => TestAsset\Factory::class, - ], - 'delegators' => [ - 'foo-bar' => [ - TestAsset\DelegatorFactory::class, - ], - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists('foo-bar')); - $delegator = $this->container->offsetGet('foo-bar'); - self::assertInstanceOf(TestAsset\Delegator::class, $delegator); - $callback = $delegator->callback; - self::assertInstanceOf(TestAsset\Service::class, $callback()); - } - - public function testInjectMultipleDelegators() - { - $dependencies = [ - 'invokables' => [ - 'foo-bar' => TestAsset\Service::class, - ], - 'delegators' => [ - 'foo-bar' => [ - TestAsset\Delegator1Factory::class, - TestAsset\Delegator2Factory::class, - ], - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists('foo-bar')); - $service = $this->container->offsetGet('foo-bar'); - self::assertInstanceOf(TestAsset\Service::class, $service); - self::assertEquals( - [ - TestAsset\Delegator1Factory::class, - TestAsset\Delegator2Factory::class, - ], - $service->injected - ); - } - - public function testInvokableWithoutAlias() - { - $dependencies = [ - 'invokables' => [ - TestAsset\Service::class, - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists(TestAsset\Service::class)); - $service = $this->container->offsetGet(TestAsset\Service::class); - self::assertInstanceOf(TestAsset\Service::class, $service); - self::assertTrue($this->container->offsetExists('0')); - } - - public function testInvokableWithAlias() - { - $dependencies = [ - 'invokables' => [ - 'alias' => TestAsset\Service::class, - ], - ]; - - (new Config(['dependencies' => $dependencies]))->configureContainer($this->container); - - self::assertTrue($this->container->offsetExists('alias')); - $service = $this->container->offsetGet('alias'); - self::assertInstanceOf(TestAsset\Service::class, $service); - self::assertTrue($this->container->offsetExists(TestAsset\Service::class)); - $originService = $this->container->offsetGet(TestAsset\Service::class); - self::assertInstanceOf(TestAsset\Service::class, $originService); - self::assertSame($service, $originService); - } } diff --git a/test/ContainerTest.php b/test/ContainerTest.php new file mode 100644 index 0000000..2cc038d --- /dev/null +++ b/test/ContainerTest.php @@ -0,0 +1,28 @@ + $config])); + } +} diff --git a/test/TestAsset/Delegator.php b/test/TestAsset/Delegator.php deleted file mode 100644 index 88bf9fc..0000000 --- a/test/TestAsset/Delegator.php +++ /dev/null @@ -1,20 +0,0 @@ -callback = $callback; - } -} diff --git a/test/TestAsset/Delegator1Factory.php b/test/TestAsset/Delegator1Factory.php deleted file mode 100644 index 8309123..0000000 --- a/test/TestAsset/Delegator1Factory.php +++ /dev/null @@ -1,23 +0,0 @@ -inject(static::class); - - return $service; - } -} diff --git a/test/TestAsset/Delegator2Factory.php b/test/TestAsset/Delegator2Factory.php deleted file mode 100644 index 1b1d8d3..0000000 --- a/test/TestAsset/Delegator2Factory.php +++ /dev/null @@ -1,14 +0,0 @@ -