diff --git a/.gitattributes b/.gitattributes new file mode 100755 index 0000000..7c27e7d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,50 @@ +.deployignore text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +.editorconfig text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +.env.* text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +.eslintignore text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +.eslintrc text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +.gitattributes text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +.gitignore text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +.htaccess text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +yarn.lock text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 + +*.css text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.csv text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.dist text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.feature text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.html text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 diff=html +*.js text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.json text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.lock text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.map text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.md text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.php text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 diff=php +*.po text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.script text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.scss text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.sh text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 diff=php +*.sql text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.svg text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.twig text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.txt text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.xml text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.xsd text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 +*.yml text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=4 + +# Define binary file attributes. +# - Do not treat them as text. +# - Include binary diff in patches instead of "binary files differ." +*.gif -text diff +*.gz -text diff +*.ico -text diff +*.jpeg -text diff +*.jpg -text diff +*.png -text diff +*.phar -text diff +*.pdf -text diff +*.exe -text diff +*.svgz -text diff +*.eot -text diff +*.ttf -text diff +*.woff -text diff +*.woff2 -text diff diff --git a/.gitignore b/.gitignore index 7854275..97e51bb 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ -.idea/ -vendor/ -composer.phar -.phpunit.result.cache -coverage.xml \ No newline at end of file +.idea/ +vendor/ +composer.phar +.phpunit.result.cache +.php_cs.cache +coverage.xml +clover.xml + diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100755 index 0000000..beea3ce --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,27 @@ +build: + nodes: + php74: + environment: + php: 7.4 + analysis: + environment: + php: 7.4 + project_setup: + override: + - 'true' + tests: + override: + - php-scrutinizer-run + - command: phpcs-run +checks: + php: true +coding_style: + php: + spaces: + general: + linefeed_character: return-newline + before_parentheses: + closure_definition: true +filter: + dependency_paths: + - "vendor/" diff --git a/.travis.yml b/.travis.yml index 2632c10..ffbf419 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,18 @@ -language: php - -php: - - 7.2 - - 7.3 - - 7.4 - -install: - - travis_retry composer install --no-interaction --no-suggest - -script: - - composer test - -after_success: - - bash <(curl -s https://codecov.io/bash) - -branches: - only: - - master - - dev +language: php + +php: + - 7.4 + +install: + - travis_retry composer install --no-interaction --no-suggest + +script: + - composer test + +after_success: + - bash <(curl -s https://codecov.io/bash) + +branches: + only: + - master + - dev diff --git a/LICENSE b/LICENSE deleted file mode 100755 index 8761915..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Alex Patterson - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 0000000..cc9a411 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2020 Alex Patterson + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index c4091d8..eae9d0a 100755 --- a/README.md +++ b/README.md @@ -1,134 +1,206 @@ -[![Build Status](https://travis-ci.com/alex-patterson-webdev/container.svg?branch=master)](https://travis-ci.com/alex-patterson-webdev/container) -[![codecov](https://codecov.io/gh/alex-patterson-webdev/container/branch/master/graph/badge.svg)](https://codecov.io/gh/alex-patterson-webdev/container) - -# Arp\Container - -## About - -A PSR-11 compatible Dependency Injection Container implementation, supporting generic service registration for popular DI containers. - -## Installation - -Installation via [Composer](https://getcomposer.org). - - require alex-patterson-webdev/container ^1 - -### Implementations - -There are a number of projects that implement the interfaces provided in this library. Please see the relevant project for -container specific documentation. - -- [alex-patterson-webdev/container-array](https://github.com/alex-patterson-webdev/container-array) -Adapter for a simple array based container that stores services and factories in PHP arrays - -- (In DEV) [alex-patterson-webdev/container-pimple](https://github.com/alex-patterson-webdev/container-pimple) -Adapter for the Symfony Pimple container. - -- (In DEV) alex-patterson-webdev/container-laminas : Adapter for the Laminas Service Manager -Adapter for the Laminas Service Manager (Formally Zend 3). - -## The Container - -The PSR-11 compatible container is `Arp\Container\Container`. Internally the `Container` class proxies its method calls -to an adapter class implementing `Arp\Container\Adapter\ContainerAdapterInterface`. - -To create the container, the library provides a default factory class, `Arp\Container\Factory\ContainerFactory`. - - use Arp\Container\Container; - use Arp\Container\Factory\ContainerFactory; - - $config = [ - 'adapter' => new \My\Container\Adapter\Foo(), - ]; - $container = (new ContainerFactory())->create($config); - - if ($container->has('Foo')) { - $fooService = $container->get('Foo'); - } - -## Container Adapters - -Container Adapters unify the different approaches many popular dependency injection containers take when implementing -service registration. - -The adapter interface exposes a number of basic methods that they all share - - namespace Arp\Container\Adpater; - - interface ContainerAdapterInterface - { - public function hasService(string $name): bool; - public function getService(string $name); - public function setService(string $name, $service): self; - public function setFactory(string $name, callable $factory): self; - } - -A concrete adapter class can implement their own logic in the `setService()` and `setFactory()` to allow the service registration -methods to differ between different DI container implementations. The basic methods include : - -#### Registering a service factory - -Service factories are any PHP `callable` type. When retrieving services from the container the callable will be invoked -and the container will be injected. - -Factories can be any PHP `callable`. - - use Psr\Container\ContainerInterface; - - $adapter->setServiceFactory('FooService', static function(ContainerInterface $container) { - return new FooService(); - }); - -#### Registering an existing service - -We can also directly set a value using `setService()`, which can be used for service that are already created or that do not -require a factory. - - $adapter->setService('FooService', new FooService()); - $adapter->setService('SpecialNumber', 12345); - - $fooService = $adapter->get('FooService'); - $specialNumber = $adapter->getService('FooService'); - -#### Other features - -Some containers support more service registration features than others, in these cases there are additional adapter interfaces. - -- `Arp\Container\Adapter\FactoryClassAwareInterface` - -Containers that allow registration of service factory classes - -- `Arp\Container\Adapter\BuildAwareInterface` - -Containers that allow new instances to be created based on passed configuration options - -## Service Providers - -A service provider is any class implementing `Arp\Container\Provider\ServiceProviderInterface`. The interface provides a single place -to interact with the container adapter directly to register services. - - use Arp\Container\Provider\ServiceProviderInterface; - use Arp\Container\Adapter\ContainerAdapterInterface; - - class MyServiceProvider implements ServiceProviderInterface - { - public function registerServices(ContainerAdapterInterface $adapter) - { - $adapter->setService('Hello', new \stdClass()); - - $adapter->setFactory('BarService', function($container) { - return new BarService(); - }); - } - } - -The registration of the service We can then pass the service provider to the container and fetch our services. - - // @var \Arp\Container\Container $container - $container->registerServices(new MyServiceProvider()); - -## Unit Tests - -Unit test using PHPUnit 8 - - php vendor/bin/phpunit +[![Build Status](https://travis-ci.com/alex-patterson-webdev/container.svg?branch=master)](https://travis-ci.com/alex-patterson-webdev/container) +[![codecov](https://codecov.io/gh/alex-patterson-webdev/container/branch/master/graph/badge.svg)](https://codecov.io/gh/alex-patterson-webdev/container) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/alex-patterson-webdev/container/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/alex-patterson-webdev/container/?branch=master) + +# Arp\Container + +## About + +A PSR-11 compatible Dependency Injection Container implementation providing agnostic service registration for popular DI containers. + +The [PSR-11: Container interface](https://www.php-fig.org/psr/psr-11/) provides interoperability +for PHP projects when _retrieving_ services from different DI container implementations. The `Arp\Container` project is +intended to complement this specification by unionising the various different approaches that containers use for _service registration_. +With unification, we can create software libraries which utilise features IoC containers and register services +in a single location, without dictating a specific container that should be used. + +## Installation + +This library provides the main `Arp\Container\Container` class and various interfaces that can be used for the container +registration; it does not however provide a specific implementation. Developers can choose to use their own 'adapters' or use +and already existing implementation. + +Installation via [Composer](https://getcomposer.org). + + require alex-patterson-webdev/container ^1 + +## Container + +The container is an instance of `Arp\Container\Container` and implements `Psr\ContainerInterface`. In addition to the normal +`get()` and `has()` methods required as part of the `Psr\ContainerInterface`, the class provides a `registerServices()` method. +The `registerServices()` method abstracts the service registration by using an implementation of `Arp\Container\ServiceProviderInterface`. + + final class Container implements ContainerInterface + { + public function __construct(ContainerAdapterInterface $adapter) + public function has($name): bool; + public function get($name); + public function registerServices(ServiceProviderInterface $serviceProvider): void; + } + +## Adapters + +Internally the `Container` class proxies its method calls to an adapter class implementing `Arp\Container\Adapter\ContainerAdapterInterface`. +In order to use the container we must provide an adapter class for the container you wish to use. + + use Arp\Container\Container; + + $container = new Container($adapter); + +There are a number of existing projects that have already been created; depending on which container you are currently using. + +#### [arp\container-array](https://github.com/alex-patterson-webdev/container-array) +- Provides a simple implementation of the required adapters that does not require an existing container. You should use this +adapter if you do not already have your own container that needs to be integrated with. + +#### [arp\contianer-pimple](https://github.com/alex-patterson-webdev/container-pimple) +- Required container integration library for the Pimple container + +#### arp\container-php-di +- Required container integration library for the PHP-DI container. This consists of an adapter that can be used to register +services with a PHP-DI container you are already using in your projects. (@todo) + +#### arp\container-laminas-service-manager +- Required container integration library for the Laminas ServiceManager (@todo) + +The container 'adapters' unify the different approaches many popular dependency injection containers take when implementing +service registration. + +The adapter interface exposes a number of basic methods that are common for popular implementations. + + namespace Arp\Container\Adpater; + + interface ContainerAdapterInterface + { + public function hasService(string $name): bool; + public function getService(string $name); + public function setService(string $name, $service); + public function setFactory(string $name, callable $factory); + } + +All adapters provide a way to check and fetch services using `has($name)` and `get($name)`. They also provide a way to +register different types of services using `setService()` and `setFactory()`. + +`setService()` is used when we want to add a value to the container which is returned as it was added. This is useful +for objects that have already been created. + + $fooService = new \stdClass(); + $adapter->setService('FooService', $fooService); + $adapter->setService('pi', 3.1415); + +`setFactory()` can be used to register a service factory that will create the service when it is requested from the container. +Any type of PHP `callable` can be used as a service factory. The container is injected as the first argument to all +service factories. + + $adapter->setFactory('BarService', static function (Psr\ContainerInterface $container) { + return new BarService( + $container->get('FooService'), + $container->get('pi') + ); + }; + +## Registering Services + +We gain access to the required Adapter methods by creating a new `Arp\Container\Provider\ServiceProviderInterface`. + + use Arp\Container\Provider\ServiceProviderInterface; + use Arp\Container\Adapter\ContainerAdapterInterface; + + final class BarServiceProvider implements ServiceProviderInterface + { + public function registerServices(ContainerAdapterInterface $adapter): void + { + $adapter->setFactory('BarService', static function (Psr\ContainerInterface $container) { + return new BarService( + $container->get('FooService'), + $container->get('pi') + ); + }; + } + } + +The Service Provider is then passed to the container to register the services. + + $container = new Container($adapter); + $container->registerServices(new BarServiceProvider()); + +## Additional Features + +There are many additional registration features that popular DI containers provide. In order to support these +differences in a generic way the library provides more specific adapter interfaces that can be implemented. + +#### Arp\Container\Adapter\AliasAwareInterface +Containers which allow services names to be substituted for an alias, or alternative name for the service. + + use Arp\Container\Provider\ServiceProviderInterface; + use Arp\Container\Adapter\ContainerAdapterInterface; + use Arp\Container\Provider\Exception\NotSupportedException; + + class FooServiceProvider implments ServiceProviderInterface + { + public function registerServices(ContainerAdapterInterface $adapter): void + { + if (!$adapter instanceof AliasAwareInterface) { + throw new NotSupportedException('The adapter does not support the use of aliases'); + } + $adapter->setService('Bar', new \stdClass()); + $adapter->setAlias('Foo', 'Bar'); + } + } + +When registering an alias, you must ensure the service being aliased has already been registered. + +#### Arp\Container\Adapter\FactoryClassAwareInterface + +Allows registration of service factories as strings. This is a useful if most of your service registration is +configuration based as you will not need to create the factories to register them, they will only be created +once you request the relevant service. + + use Arp\Container\Provider\ServiceProviderInterface; + use Arp\Container\Adapter\ContainerAdapterInterface; + use Arp\Container\Provider\Exception\NotSupportedException; + + class BarServiceFactory { + public function __invoke(ContainerInterface $container): BarService + { + return new BarService($container->get('FooService')); + } + } + + class FooServiceProvider implments ServiceProviderInterface + { + public function registerServices(ContainerAdapterInterface $adapter): void + { + if (!$adapter instanceof FactoryClassAwareInterface) { + throw new NotSupportedException('The adapter does not support the use of string factories'); + } + $adapter->setFactoryClass('Bar', BarServiceFactory::class); + } + } + +## ConfigServiceProvider + +We can reduce the need to write repeated Service Provider logic by using a configuration array passed to the +`Arp\Provider\ConfigServiceProvider`. + + $config = [ + ConfigServiceProvider::FACTORIES = [ + 'FooService' => static function (Psr\ContainerInterface $container) { + return new FooService($container->get('BarService')); + } + ], + ConfigServiceProvider::ALIASES = [ + 'FooAlias' => 'FooService', + 'AnotherAliasForBar' => 'BarService', + ], + ConfigServiceProvider::SERVICES = [ + 'BarService' => new BarService(); + ], + ]; + $container->registerService(new ConfigServiceProvider($config)); + +## Unit Tests + +Unit test using PHPUnit + + php vendor/bin/phpunit diff --git a/composer.json b/composer.json index ffc6291..0621532 100755 --- a/composer.json +++ b/composer.json @@ -1,44 +1,42 @@ -{ - "name" : "alex-patterson-webdev/container", - "description": "PSR container adapter", - "version": "2.0.0", - "minimum-stability": "stable", - "prefer-stable": true, - "authors": [ - { - "name": "Alex Patterson", - "email": "alex.patterson.webdev@gmail.com" - } - ], - "provide" : { - "psr/container-implementation" : "1.0.0" - }, - "require": { - "php" : ">=7.2", - "psr/container" : "^1", - "psr/log" : "^1", - "alex-patterson-webdev/factory": "^1" - }, - "require-dev": { - "squizlabs/php_codesniffer": "^3.5", - "phpunit/phpunit" : "^8" - }, - "autoload": { - "psr-4": { - "Arp\\Container\\" : "src/" - } - }, - "autoload-dev": { - "psr-4": { - "ArpTest\\Container\\" : "test/phpunit" - } - }, - "scripts": { - "test": "phpunit --coverage-clover=coverage.xml" - }, - "config": { - "preferred-install": "dist", - "optimize-autoloader": true, - "sort-packages": true - } -} +{ + "name" : "alex-patterson-webdev/container", + "description": "PSR container adapter", + "minimum-stability": "dev", + "prefer-stable": true, + "authors": [ + { + "name": "Alex Patterson", + "email": "alex.patterson.webdev@gmail.com" + } + ], + "provide" : { + "psr/container-implementation" : "1.0.0" + }, + "require": { + "php" : ">=7.4", + "psr/container" : "^1", + "psr/log": "^1", + "alex-patterson-webdev/factory": "^1" + }, + "require-dev": { + "phpunit/phpunit" : "^9.2" + }, + "autoload": { + "psr-4": { + "Arp\\Container\\" : "src/" + } + }, + "autoload-dev": { + "psr-4": { + "ArpTest\\Container\\" : "test/phpunit" + } + }, + "scripts": { + "test": "php vendor/bin/phpunit --coverage-clover=coverage.xml" + }, + "config": { + "preferred-install": "dist", + "optimize-autoloader": true, + "sort-packages": true + } +} diff --git a/composer.lock b/composer.lock old mode 100644 new mode 100755 index 28b2a73..ec40c92 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b9c5b465432e45ace530dc9b51e9fe39", + "content-hash": "fa493c6c2c4c6b2a8adb5f4ef7c22852", "packages": [ { "name": "alex-patterson-webdev/factory", @@ -92,16 +92,16 @@ }, { "name": "psr/log", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", "shasum": "" }, "require": { @@ -135,26 +135,26 @@ "psr", "psr-3" ], - "time": "2019-11-01T11:05:21+00:00" + "time": "2020-03-23T09:12:05+00:00" } ], "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", - "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", @@ -193,24 +193,24 @@ "constructor", "instantiate" ], - "time": "2019-10-21T16:45:58+00:00" + "time": "2020-05-29T17:27:14+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.9.5", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", - "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "replace": { "myclabs/deep-copy": "self.version" @@ -241,32 +241,85 @@ "object", "object graph" ], - "time": "2020-01-17T21:11:47+00:00" + "time": "2020-06-29T13:22:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.10.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "1b479e7592812411c20c34d9ed33db3957bde66e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1b479e7592812411c20c34d9ed33db3957bde66e", + "reference": "1b479e7592812411c20c34d9ed33db3957bde66e", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2020-09-23T18:23:49+00:00" }, { "name": "phar-io/manifest", - "version": "1.0.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^2.0", - "php": "^5.6 || ^7.0" + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -296,24 +349,24 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2018-07-08T19:23:20+00:00" + "time": "2020-06-27T14:33:11+00:00" }, { "name": "phar-io/version", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + "reference": "c6bb6825def89e0a32220f88337f8ceaf1975fa0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "url": "https://api.github.com/repos/phar-io/version/zipball/c6bb6825def89e0a32220f88337f8ceaf1975fa0", + "reference": "c6bb6825def89e0a32220f88337f8ceaf1975fa0", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -343,32 +396,29 @@ } ], "description": "Library for handling version information and constraints", - "time": "2018-07-08T19:19:57+00:00" + "time": "2020-06-27T14:39:04+00:00" }, { "name": "phpdocumentor/reflection-common", - "version": "2.0.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "~6" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { @@ -395,32 +445,31 @@ "reflection", "static analysis" ], - "time": "2018-08-07T13:53:10+00:00" + "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.1.0", + "version": "5.2.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", - "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", "shasum": "" }, "require": { - "ext-filter": "^7.1", - "php": "^7.2", - "phpdocumentor/reflection-common": "^2.0", - "phpdocumentor/type-resolver": "^1.0", - "webmozart/assert": "^1" + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" }, "require-dev": { - "doctrine/instantiator": "^1", - "mockery/mockery": "^1" + "mockery/mockery": "~1.3.2" }, "type": "library", "extra": { @@ -448,34 +497,33 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2020-02-22T12:28:44+00:00" + "time": "2020-09-03T19:13:55+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.1.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", - "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", "shasum": "" }, "require": { - "php": "^7.2", + "php": "^7.2 || ^8.0", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "^7.2", - "mockery/mockery": "~1" + "ext-tokenizer": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { @@ -494,37 +542,37 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2020-02-18T18:59:58+00:00" + "time": "2020-09-17T18:55:26+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.10.3", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "451c3cd1418cf640de218914901e51b064abb093" + "reference": "b20034be5efcdab4fb60ca3a29cba2949aead160" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", - "reference": "451c3cd1418cf640de218914901e51b064abb093", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b20034be5efcdab4fb60ca3a29cba2949aead160", + "reference": "b20034be5efcdab4fb60ca3a29cba2949aead160", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + "doctrine/instantiator": "^1.2", + "php": "^7.2", + "phpdocumentor/reflection-docblock": "^5.0", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^2.5 || ^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10.x-dev" + "dev-master": "1.11.x-dev" } }, "autoload": { @@ -557,44 +605,48 @@ "spy", "stub" ], - "time": "2020-03-05T15:02:03+00:00" + "time": "2020-07-08T12:44:21+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "7.0.10", + "version": "9.1.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf" + "reference": "c9394cb9d07ecfa9351b96f2e296bad473195f4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf", - "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c9394cb9d07ecfa9351b96f2e296bad473195f4d", + "reference": "c9394cb9d07ecfa9351b96f2e296bad473195f4d", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-xmlwriter": "*", - "php": "^7.2", - "phpunit/php-file-iterator": "^2.0.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.1.1", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.2.2", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1.3" + "nikic/php-parser": "^4.8", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^8.2.2" + "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-xdebug": "^2.7.2" + "ext-pcov": "*", + "ext-xdebug": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "7.0-dev" + "dev-master": "9.1-dev" } }, "autoload": { @@ -620,32 +672,32 @@ "testing", "xunit" ], - "time": "2019-11-20T13:55:58+00:00" + "time": "2020-09-19T05:29:17+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.2", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "050bedf145a257b1ff02746c31894800e5122946" + "reference": "25fefc5b19835ca653877fe081644a3f8c1d915e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", - "reference": "050bedf145a257b1ff02746c31894800e5122946", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/25fefc5b19835ca653877fe081644a3f8c1d915e", + "reference": "25fefc5b19835ca653877fe081644a3f8c1d915e", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -670,26 +722,38 @@ "filesystem", "iterator" ], - "time": "2018-09-13T20:33:42+00:00" + "time": "2020-07-11T05:18:21+00:00" }, { - "name": "phpunit/php-text-template", - "version": "1.2.1", + "name": "phpunit/php-invoker", + "version": "3.1.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "7a85b66acc48cacffdf87dadd3694e7123674298" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/7a85b66acc48cacffdf87dadd3694e7123674298", + "reference": "7a85b66acc48cacffdf87dadd3694e7123674298", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -706,37 +770,37 @@ "role": "lead" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "template" + "process" ], - "time": "2015-06-21T13:50:34+00:00" + "time": "2020-08-06T07:04:15+00:00" }, { - "name": "phpunit/php-timer", - "version": "2.1.2", + "name": "phpunit/php-text-template", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "6ff9c8ea4d3212b88fcf74e25e516e2c51c99324" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", - "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/6ff9c8ea4d3212b88fcf74e25e516e2c51c99324", + "reference": "6ff9c8ea4d3212b88fcf74e25e516e2c51c99324", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -755,38 +819,37 @@ "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "timer" + "template" ], - "time": "2019-06-07T04:22:29+00:00" + "time": "2020-06-26T11:55:37+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "3.1.1", + "name": "phpunit/php-timer", + "version": "5.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "cc49734779cbb302bf51a44297dab8c4bbf941e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", - "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/cc49734779cbb302bf51a44297dab8c4bbf941e7", + "reference": "cc49734779cbb302bf51a44297dab8c4bbf941e7", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.1" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -801,64 +864,68 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "tokenizer" + "timer" ], - "time": "2019-09-17T06:23:10+00:00" + "time": "2020-06-26T11:58:13+00:00" }, { "name": "phpunit/phpunit", - "version": "8.5.2", + "version": "9.3.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "018b6ac3c8ab20916db85fa91bf6465acb64d1e0" + "reference": "f7316ea106df7c9507f4fdaa88c47bc10a3b27a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/018b6ac3c8ab20916db85fa91bf6465acb64d1e0", - "reference": "018b6ac3c8ab20916db85fa91bf6465acb64d1e0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f7316ea106df7c9507f4fdaa88c47bc10a3b27a1", + "reference": "f7316ea106df7c9507f4fdaa88c47bc10a3b27a1", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.2.0", + "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.9.1", - "phar-io/manifest": "^1.0.3", - "phar-io/version": "^2.0.1", - "php": "^7.2", - "phpspec/prophecy": "^1.8.1", - "phpunit/php-code-coverage": "^7.0.7", - "phpunit/php-file-iterator": "^2.0.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1.2", - "sebastian/comparator": "^3.0.2", - "sebastian/diff": "^3.0.2", - "sebastian/environment": "^4.2.2", - "sebastian/exporter": "^3.1.1", - "sebastian/global-state": "^3.0.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0.1", - "sebastian/type": "^1.1.3", - "sebastian/version": "^2.0.1" + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.1", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.11.1", + "phpunit/php-code-coverage": "^9.1.11", + "phpunit/php-file-iterator": "^3.0.4", + "phpunit/php-invoker": "^3.1", + "phpunit/php-text-template": "^2.0.2", + "phpunit/php-timer": "^5.0.1", + "sebastian/cli-parser": "^1.0", + "sebastian/code-unit": "^1.0.5", + "sebastian/comparator": "^4.0.3", + "sebastian/diff": "^4.0.2", + "sebastian/environment": "^5.1.2", + "sebastian/exporter": "^4.0.2", + "sebastian/global-state": "^5.0", + "sebastian/object-enumerator": "^4.0.2", + "sebastian/resource-operations": "^3.0.2", + "sebastian/type": "^2.2.1", + "sebastian/version": "^3.0.1" }, "require-dev": { - "ext-pdo": "*" + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" }, "suggest": { "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0.0" + "ext-xdebug": "*" }, "bin": [ "phpunit" @@ -866,12 +933,15 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.5-dev" + "dev-master": "9.3-dev" } }, "autoload": { "classmap": [ "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -892,32 +962,124 @@ "testing", "xunit" ], - "time": "2020-01-08T08:49:49+00:00" + "time": "2020-09-24T08:08:49+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2a4a38c56e62f7295bedb8b1b7439ad523d4ea82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2a4a38c56e62f7295bedb8b1b7439ad523d4ea82", + "reference": "2a4a38c56e62f7295bedb8b1b7439ad523d4ea82", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "time": "2020-08-12T10:49:21+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "c1e2df332c905079980b119c4db103117e5e5c90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/c1e2df332c905079980b119c4db103117e5e5c90", + "reference": "c1e2df332c905079980b119c4db103117e5e5c90", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "time": "2020-06-26T12:50:45+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + "reference": "ee51f9bb0c6d8a43337055db3120829fa14da819" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ee51f9bb0c6d8a43337055db3120829fa14da819", + "reference": "ee51f9bb0c6d8a43337055db3120829fa14da819", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -937,34 +1099,34 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "time": "2020-06-26T12:04:00+00:00" }, { "name": "sebastian/comparator", - "version": "3.0.2", + "version": "4.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + "reference": "dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f", + "reference": "dcc580eadfaa4e7f9d2cf9ae1922134ea962e14f", "shasum": "" }, "require": { - "php": "^7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" + "php": "^7.3 || ^8.0", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -977,6 +1139,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -988,10 +1154,6 @@ { "name": "Bernhard Schussek", "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" } ], "description": "Provides the functionality to compare PHP values for equality", @@ -1001,33 +1163,80 @@ "compare", "equality" ], - "time": "2018-07-12T15:12:46+00:00" + "time": "2020-06-26T12:05:46+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "33fcd6a26656c6546f70871244ecba4b4dced097" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/33fcd6a26656c6546f70871244ecba4b4dced097", + "reference": "33fcd6a26656c6546f70871244ecba4b4dced097", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "time": "2020-07-25T14:01:34+00:00" }, { "name": "sebastian/diff", - "version": "3.0.2", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + "reference": "1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113", + "reference": "1e90b4cf905a7d06c420b1d2e9d11a4dc8a13113", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" + "phpunit/phpunit": "^9.0", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1040,13 +1249,13 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], "description": "Diff implementation", @@ -1057,27 +1266,27 @@ "unidiff", "unified diff" ], - "time": "2019-02-04T06:01:07+00:00" + "time": "2020-06-30T04:46:02+00:00" }, { "name": "sebastian/environment", - "version": "4.2.3", + "version": "5.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" + "reference": "0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", - "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2", + "reference": "0a757cab9d5b7ef49a619f1143e6c9c1bc0fe9d2", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^9.0" }, "suggest": { "ext-posix": "*" @@ -1085,7 +1294,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -1110,34 +1319,34 @@ "environment", "hhvm" ], - "time": "2019-11-20T08:46:58+00:00" + "time": "2020-06-26T12:07:24+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.2", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + "reference": "571d721db4aec847a0e59690b954af33ebf9f023" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", - "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/571d721db4aec847a0e59690b954af33ebf9f023", + "reference": "571d721db4aec847a0e59690b954af33ebf9f023", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" + "php": "^7.3 || ^8.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1177,30 +1386,30 @@ "export", "exporter" ], - "time": "2019-09-14T09:02:43+00:00" + "time": "2020-06-26T12:08:55+00:00" }, { "name": "sebastian/global-state", - "version": "3.0.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" + "reference": "22ae663c951bdc39da96603edc3239ed3a299097" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", - "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/22ae663c951bdc39da96603edc3239ed3a299097", + "reference": "22ae663c951bdc39da96603edc3239ed3a299097", "shasum": "" }, "require": { - "php": "^7.2", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": "^7.3 || ^8.0", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-uopz": "*" @@ -1208,7 +1417,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -1231,34 +1440,81 @@ "keywords": [ "global state" ], - "time": "2019-02-01T05:30:01+00:00" + "time": "2020-08-07T04:09:03+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e02bf626f404b5daec382a7b8a6a4456e49017e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e02bf626f404b5daec382a7b8a6a4456e49017e5", + "reference": "e02bf626f404b5daec382a7b8a6a4456e49017e5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "time": "2020-07-22T18:33:42+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.3", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + "reference": "074fed2d0a6d08e1677dd8ce9d32aecb384917b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/074fed2d0a6d08e1677dd8ce9d32aecb384917b8", + "reference": "074fed2d0a6d08e1677dd8ce9d32aecb384917b8", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": "^7.3 || ^8.0", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1278,32 +1534,32 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" + "time": "2020-06-26T12:11:32+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" + "reference": "127a46f6b057441b201253526f81d5406d6c7840" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/127a46f6b057441b201253526f81d5406d6c7840", + "reference": "127a46f6b057441b201253526f81d5406d6c7840", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1323,32 +1579,32 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" + "time": "2020-06-26T12:12:55+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.0", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + "reference": "062231bf61d2b9448c4fa5a7643b5e1829c11d63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/062231bf61d2b9448c4fa5a7643b5e1829c11d63", + "reference": "062231bf61d2b9448c4fa5a7643b5e1829c11d63", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1361,14 +1617,14 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, { "name": "Adam Harvey", "email": "aharvey@php.net" @@ -1376,29 +1632,32 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "time": "2020-06-26T12:14:17+00:00" }, { "name": "sebastian/resource-operations", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + "reference": "0653718a5a629b065e91f774595267f8dc32e213" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0653718a5a629b065e91f774595267f8dc32e213", + "reference": "0653718a5a629b065e91f774595267f8dc32e213", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1418,32 +1677,32 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2018-10-04T04:07:39+00:00" + "time": "2020-06-26T12:16:22+00:00" }, { "name": "sebastian/type", - "version": "1.1.3", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" + "reference": "86991e2b33446cd96e648c18bcdb1e95afb2c05a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", - "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/86991e2b33446cd96e648c18bcdb1e95afb2c05a", + "reference": "86991e2b33446cd96e648c18bcdb1e95afb2c05a", "shasum": "" }, "require": { - "php": "^7.2" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^8.2" + "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.2-dev" } }, "autoload": { @@ -1464,29 +1723,29 @@ ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", - "time": "2019-07-02T08:10:15+00:00" + "time": "2020-07-05T08:31:53+00:00" }, { "name": "sebastian/version", - "version": "2.0.1", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "reference": "626586115d0ed31cb71483be55beb759b5af5a3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/626586115d0ed31cb71483be55beb759b5af5a3c", + "reference": "626586115d0ed31cb71483be55beb759b5af5a3c", "shasum": "" }, "require": { - "php": ">=5.6" + "php": "^7.3 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1507,71 +1766,20 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.5.4", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "dceec07328401de6211037abbb18bda423677e26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dceec07328401de6211037abbb18bda423677e26", - "reference": "dceec07328401de6211037abbb18bda423677e26", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards" - ], - "time": "2020-01-30T22:20:29+00:00" + "time": "2020-06-26T12:18:43+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.14.0", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38" + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", - "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454", + "reference": "1c302646f6efc070cd46856e600e5e0684d6b454", "shasum": "" }, "require": { @@ -1583,7 +1791,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "1.18-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -1616,27 +1828,27 @@ "polyfill", "portable" ], - "time": "2020-01-13T11:15:53+00:00" + "time": "2020-07-14T12:35:20+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.3", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + "reference": "75a63c33a8577608444246075ea0af0d052e452a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", "shasum": "" }, "require": { "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": "^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -1656,28 +1868,29 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2019-06-13T22:48:21+00:00" + "time": "2020-07-12T23:59:07+00:00" }, { "name": "webmozart/assert", - "version": "1.7.0", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0", + "php": "^5.3.3 || ^7.0 || ^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "vimeo/psalm": "<3.6.0" + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" @@ -1704,16 +1917,16 @@ "check", "validate" ], - "time": "2020-02-14T12:15:55+00:00" + "time": "2020-07-08T17:02:28+00:00" } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "dev", "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=7.2" + "php": ">=7.4" }, "platform-dev": [] } diff --git a/phpcs.xml b/phpcs.xml new file mode 100755 index 0000000..7650561 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,6 @@ + + + ARP coding standard + + ./vendor/* + diff --git a/phpunit.xml b/phpunit.xml index c8aa637..301bbb6 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,15 +1,29 @@ - - - - - ./test - - - - - - src - - - - \ No newline at end of file + + + + + src + + + + + + + + ./test/phpunit + + + ./test/integration + + + + diff --git a/src/Adapter/AbstractPsrBridgeAdapter.php b/src/Adapter/AbstractPsrAdapter.php similarity index 50% rename from src/Adapter/AbstractPsrBridgeAdapter.php rename to src/Adapter/AbstractPsrAdapter.php index 501820d..bd71578 100755 --- a/src/Adapter/AbstractPsrBridgeAdapter.php +++ b/src/Adapter/AbstractPsrAdapter.php @@ -1,82 +1,71 @@ - - * @package Arp\Container\Adapter - */ -abstract class AbstractPsrBridgeAdapter implements ContainerAdapterInterface -{ - /** - * @var ContainerInterface - */ - protected $container; - - /** - * @param ContainerInterface $container - */ - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - /** - * Check if a service is registered with this name. - * - * @param string $name The name of the service to check. - * - * @return bool - * - * @throws AdapterException If the container raises an error - */ - public function hasService(string $name): bool - { - try { - return $this->container->has($name); - } catch (\Throwable $e) { - throw new AdapterException( - sprintf('The check for service \'%s\' failed : %s', $name, $e->getMessage()), - $e->getCode(), - $e - ); - } - } - - /** - * Return a service matching the provided name. - * - * @param string $name The name of the service to return. - * - * @return mixed - * - * @throws NotFoundException If the requested service cannot be found registered with the container - * @throws AdapterException If the requested service was found by could not be created or returned - */ - public function getService(string $name) - { - try { - return $this->container->get($name); - } catch (NotFoundExceptionInterface $e) { - throw new NotFoundException( - sprintf('The service \'%s\' could not be found : %s', $name, $e->getMessage()), - $e->getCode(), - $e - ); - } catch (ContainerExceptionInterface $e) { - throw new AdapterException( - sprintf('The service \'%s\' was found but could not be returned : %s', $name, $e->getMessage()), - $e->getCode(), - $e - ); - } - } -} + + * @package Arp\Container\Adapter + */ +abstract class AbstractPsrAdapter implements ContainerAdapterInterface +{ + /** + * @var ContainerInterface + */ + protected ContainerInterface $container; + + /** + * @param ContainerInterface $container + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * @param string $name The name of the service to check. + * + * @return bool + * + * @throws AdapterException If the container raises an error + */ + public function hasService(string $name): bool + { + try { + return $this->container->has($name); + } catch (ContainerExceptionInterface $e) { + throw new AdapterException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * @param string $name The name of the service to return + * + * @return mixed + * + * @throws NotFoundException + * @throws AdapterException + */ + public function getService(string $name) + { + try { + return $this->container->get($name); + } catch (NotFoundExceptionInterface $e) { + /** @var \Throwable|NotFoundExceptionInterface */ + throw new NotFoundException($e->getMessage(), $e->getCode(), $e); + } catch (ContainerExceptionInterface $e) { + /** @var \Throwable|ContainerExceptionInterface */ + throw new AdapterException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/src/Adapter/AliasAwareInterface.php b/src/Adapter/AliasAwareInterface.php index 5d1e25c..78e652a 100755 --- a/src/Adapter/AliasAwareInterface.php +++ b/src/Adapter/AliasAwareInterface.php @@ -1,26 +1,26 @@ - - * @package Arp\Container\Adapter - */ -interface AliasAwareInterface extends ContainerAdapterInterface -{ - /** - * Set an alias for a given service - * - * @param string $alias The name of the alias to set - * @param string $name The name of the service that - * - * @return ContainerAdapterInterface - * - * @throws AdapterException If the alias cannot be set - */ - public function setAlias(string $alias, string $name): ContainerAdapterInterface; -} + + * @package Arp\Container\Adapter + */ +interface AliasAwareInterface extends ContainerAdapterInterface +{ + /** + * Set an alias for a given service + * + * @param string $alias The name of the alias to set + * @param string $name The name of the service that + * + * @return AliasAwareInterface|$this + * + * @throws AdapterException If the alias cannot be set + */ + public function setAlias(string $alias, string $name): AliasAwareInterface; +} diff --git a/src/Adapter/BuildAwareInterface.php b/src/Adapter/BuildAwareInterface.php index 3422718..63768c6 100755 --- a/src/Adapter/BuildAwareInterface.php +++ b/src/Adapter/BuildAwareInterface.php @@ -1,26 +1,26 @@ - - * @package Arp\Container\Adapter - */ -interface BuildAwareInterface extends ContainerAdapterInterface -{ - /** - * Create a new instance of requested service - * - * @param string $name The name of the service to create - * @param array $options The service's creation options - * - * @return mixed - * - * @throws AdapterException If the service cannot be created - */ - public function build(string $name, array $options = []); -} + + * @package Arp\Container\Adapter + */ +interface BuildAwareInterface extends ContainerAdapterInterface +{ + /** + * Create a new instance of requested service + * + * @param string $name The name of the service to create + * @param array $options The service's creation options + * + * @return BuildAwareInterface|$this + * + * @throws AdapterException If the service cannot be created + */ + public function build(string $name, array $options = []): BuildAwareInterface; +} diff --git a/src/Adapter/ContainerAdapterInterface.php b/src/Adapter/ContainerAdapterInterface.php index ee3ec68..efa8882 100755 --- a/src/Adapter/ContainerAdapterInterface.php +++ b/src/Adapter/ContainerAdapterInterface.php @@ -1,60 +1,62 @@ - - * @package Arp\Container\Adapter - */ -interface ContainerAdapterInterface -{ - /** - * Check if a service is registered with this name. - * - * @param string $name The name of the service to check. - * - * @return bool - * - * @throws AdapterException - */ - public function hasService(string $name): bool; - - /** - * Return a service matching the provided name. - * - * @param string $name The name of the service to return. - * - * @throws NotFoundException - * @throws AdapterException - */ - public function getService(string $name); - - /** - * Set a new service on the container. - * - * @param string $name The name of the service to set. - * @param mixed $service The service to register. - * - * @return self - * - * @throws AdapterException - */ - public function setService(string $name, $service): self; - - /** - * Register a callable factory for the container. - * - * @param string $name The name of the service to register. - * @param callable $factory The factory callable responsible for creating the service. - * - * @return self - * - * @throws AdapterException - */ - public function setFactory(string $name, callable $factory): self; -} + + * @package Arp\Container\Adapter + */ +interface ContainerAdapterInterface +{ + /** + * Check if a service is registered with this name. + * + * @param string $name The name of the service to check. + * + * @return bool + * + * @throws AdapterException + */ + public function hasService(string $name): bool; + + /** + * Return a service matching the provided name. + * + * @param string $name The name of the service to return + * + * @return mixed + * + * @throws NotFoundException + * @throws AdapterException + */ + public function getService(string $name); + + /** + * Set a new service on the container. + * + * @param string $name The name of the service to set. + * @param mixed $service The service to register. + * + * @return ContainerAdapterInterface|$this + * + * @throws AdapterException + */ + public function setService(string $name, $service): ContainerAdapterInterface; + + /** + * Register a callable factory for the container. + * + * @param string $name The name of the service to register. + * @param callable $factory The factory callable responsible for creating the service. + * + * @return ContainerAdapterInterface|$this + * + * @throws AdapterException + */ + public function setFactory(string $name, callable $factory): ContainerAdapterInterface; +} diff --git a/src/Adapter/Exception/AdapterException.php b/src/Adapter/Exception/AdapterException.php index 17a4b8c..d294c5e 100755 --- a/src/Adapter/Exception/AdapterException.php +++ b/src/Adapter/Exception/AdapterException.php @@ -1,14 +1,14 @@ - - * @package Arp\Container\Adapter\Exception - */ -class AdapterException extends \Exception -{ - -} + + * @package Arp\Container\Adapter\Exception + */ +class AdapterException extends \Exception +{ + +} diff --git a/src/Adapter/Exception/InvalidArgumentException.php b/src/Adapter/Exception/InvalidArgumentException.php new file mode 100755 index 0000000..3ccb1cb --- /dev/null +++ b/src/Adapter/Exception/InvalidArgumentException.php @@ -0,0 +1,14 @@ + + * @package Arp\Container\Adapter\Exception + */ +class InvalidArgumentException extends AdapterException +{ + +} diff --git a/src/Adapter/Exception/NotFoundException.php b/src/Adapter/Exception/NotFoundException.php index b669912..ab80b1c 100755 --- a/src/Adapter/Exception/NotFoundException.php +++ b/src/Adapter/Exception/NotFoundException.php @@ -1,16 +1,14 @@ - - * @package Arp\Container\Adapter\Exception - */ -final class NotFoundException extends AdapterException -{ - -} \ No newline at end of file + + * @package Arp\Container\Adapter\Exception + */ +final class NotFoundException extends RuntimeException +{ + +} diff --git a/src/Adapter/Exception/RuntimeException.php b/src/Adapter/Exception/RuntimeException.php new file mode 100755 index 0000000..fb4085b --- /dev/null +++ b/src/Adapter/Exception/RuntimeException.php @@ -0,0 +1,14 @@ + + * @package Arp\Container\Adapter\Exception + */ +class RuntimeException extends AdapterException +{ + +} diff --git a/src/Adapter/FactoryClassAwareInterface.php b/src/Adapter/FactoryClassAwareInterface.php index 2b7d982..1772c89 100755 --- a/src/Adapter/FactoryClassAwareInterface.php +++ b/src/Adapter/FactoryClassAwareInterface.php @@ -1,26 +1,27 @@ - - * @package Arp\Container\Adapter - */ -interface FactoryClassAwareInterface extends ContainerAdapterInterface -{ - /** - * Set the class name of a factory that will create service $name. - * - * @param string $name The name of the service to set the factory for. - * @param string $factoryClass The fully qualified class name of the factory. - * - * @return ContainerAdapterInterface - * - * @throws AdapterException If the factory class cannot be set - */ - public function setFactoryClass(string $name, string $factoryClass): ContainerAdapterInterface; -} + + * @package Arp\Container\Adapter + */ +interface FactoryClassAwareInterface extends ContainerAdapterInterface +{ + /** + * Set the class name of a factory that will create service $name. + * + * @param string $name The name of the service to set the factory for. + * @param string $factory The fully qualified class name of the factory. + * @param string|null $method The name of the factory method to call. + * + * @return FactoryClassAwareInterface|$this + * + * @throws AdapterException If the factory class cannot be set + */ + public function setFactoryClass(string $name, string $factory, string $method = null): FactoryClassAwareInterface; +} diff --git a/src/Container.php b/src/Container.php index d1fb32e..2090e18 100755 --- a/src/Container.php +++ b/src/Container.php @@ -1,122 +1,106 @@ - - * @package Arp\Container - */ -final class Container implements ContainerInterface -{ - /** - * @var ContainerAdapterInterface - */ - private $adapter; - - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @param ContainerAdapterInterface $adapter - * @param LoggerInterface $logger - */ - public function __construct(ContainerAdapterInterface $adapter, LoggerInterface $logger) - { - $this->adapter = $adapter; - $this->logger = $logger; - } - - /** - * Returns true if the container can return an entry for the given identifier. - * Returns false otherwise. - * - * `has($name)` returning true does not mean that `get($name)` will not throw an exception. - * It does however mean that `get($name)` will not throw a `NotFoundExceptionInterface`. - * - * @param string $name Identifier of the entry to look for. - * - * @return bool - * - * @throws Exception\ContainerException If the operation cannot be completed - */ - public function has($name): bool - { - try { - return $this->adapter->hasService($name); - } catch (AdapterException $e) { - $errorMessage = sprintf('The has() failed for service \'%s\' : %s', $name, $e->getMessage()); - - $this->logger->debug($errorMessage, ['exception' => $e, 'name' => $name]); - - throw new Exception\ContainerException($errorMessage, $e->getCode(), $e); - } - } - - /** - * Finds an entry of the container by its identifier and returns it. - * - * @param string $name Identifier of the entry to look for. - * - * @return mixed - * - * @throws NotFoundExceptionInterface - * @throws ContainerExceptionInterface - */ - public function get($name) - { - try { - return $this->adapter->getService($name); - } catch (NotFoundException $e) { - $errorMessage = sprintf('The service \'%s\' could not be found', $name); - - $this->logger->error($errorMessage, ['exception' => $e, 'name' => $name]); - - throw new Exception\NotFoundException($errorMessage, $e->getCode(), $e); - } catch (AdapterException $e) { - $errorMessage = sprintf('The get() failed for service \'%s\' : %s', $name, $e->getMessage()); - - $this->logger->error($errorMessage, ['exception' => $e, 'name' => $name]); - - throw new Exception\ContainerException($errorMessage, $e->getCode(), $e); - } - } - - /** - * Register a collection of services defined in the provided service provider. - * - * @param ServiceProviderInterface $serviceProvider - * - * @return self - * - * @throws ContainerException - */ - public function registerServices(ServiceProviderInterface $serviceProvider): self - { - try { - $serviceProvider->registerServices($this->adapter); - } catch (ServiceProviderException $e) { - $errorMessage = sprintf('Failed to register service provider : %s', $e->getMessage()); - - $this->logger->error($errorMessage, ['exception' => $e, 'serviceProvider' => get_class($serviceProvider)]); - - throw new ContainerException($errorMessage, $e->getCode(), $e); - } - - return $this; - } -} + + * @package Arp\Container + */ +final class Container implements ContainerInterface +{ + /** + * @var ContainerAdapterInterface + */ + private ContainerAdapterInterface $adapter; + + /** + * @param ContainerAdapterInterface $adapter + */ + public function __construct(ContainerAdapterInterface $adapter) + { + $this->adapter = $adapter; + } + + /** + * Returns true if the container can return an entry for the given identifier. + * Returns false otherwise. + * + * `has($name)` returning true does not mean that `get($name)` will not throw an exception. + * It does however mean that `get($name)` will not throw a `NotFoundExceptionInterface`. + * + * @param string $name Identifier of the entry to look for. + * + * @return bool + * + * @throws Exception\ContainerException If the operation cannot be completed + * + * @noinspection PhpMissingParamTypeInspection Not currently part of PSR-11 specification + */ + public function has($name): bool + { + try { + return $this->adapter->hasService($name); + } catch (AdapterException $e) { + throw new Exception\ContainerException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Finds an entry of the container by its identifier and returns it. + * + * @param string $name Identifier of the entry to look for. + * + * @return mixed + * + * @throws NotFoundExceptionInterface + * @throws ContainerExceptionInterface + * + * @noinspection PhpMissingParamTypeInspection Not currently part of PSR-11 specification + */ + public function get($name) + { + try { + return $this->adapter->getService($name); + } catch (NotFoundException $e) { + throw new Exception\NotFoundException($e->getMessage(), $e->getCode(), $e); + } catch (AdapterException $e) { + throw new Exception\ContainerException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Register a collection of services defined in the provided service provider. + * + * @param ServiceProviderInterface $serviceProvider + * + * @throws ContainerException + */ + public function registerServices(ServiceProviderInterface $serviceProvider): void + { + try { + $serviceProvider->registerServices($this->adapter); + } catch (ServiceProviderException $e) { + throw new ContainerException( + sprintf( + 'Failed to register service provider \'%s\': %s', + get_class($serviceProvider), + $e->getMessage() + ), + $e->getCode(), + $e + ); + } + } +} diff --git a/src/Exception/ContainerException.php b/src/Exception/ContainerException.php index a735fb3..94a5950 100755 --- a/src/Exception/ContainerException.php +++ b/src/Exception/ContainerException.php @@ -1,16 +1,16 @@ - - * @package Arp\Container\Exception - */ -class ContainerException extends \Exception implements ContainerExceptionInterface -{ - -} + + * @package Arp\Container\Exception + */ +class ContainerException extends \Exception implements ContainerExceptionInterface +{ + +} diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php new file mode 100755 index 0000000..2362c1d --- /dev/null +++ b/src/Exception/InvalidArgumentException.php @@ -0,0 +1,14 @@ + + * @package Arp\Container\Exception + */ +final class InvalidArgumentException extends ContainerException +{ + +} diff --git a/src/Exception/NotFoundException.php b/src/Exception/NotFoundException.php index fcc7c81..a859328 100755 --- a/src/Exception/NotFoundException.php +++ b/src/Exception/NotFoundException.php @@ -1,16 +1,16 @@ - - * @package Arp\Container\Exception - */ -class NotFoundException extends ContainerException implements NotFoundExceptionInterface -{ - -} + + * @package Arp\Container\Exception + */ +final class NotFoundException extends ContainerException implements NotFoundExceptionInterface +{ + +} diff --git a/src/Factory/ContainerFactory.php b/src/Factory/ContainerFactory.php index d373954..eee6279 100755 --- a/src/Factory/ContainerFactory.php +++ b/src/Factory/ContainerFactory.php @@ -1,61 +1,80 @@ - - * @package Arp\Container\Factory - */ -class ContainerFactory implements FactoryInterface -{ - /** - * Create a new container instance - * - * @param array $config The optional factory configuration options - * - * @return Container - * - * @throws FactoryException If the container cannot be created - */ - public function create(array $config = []): Container - { - $adapter = $config['adapter'] ?? null; - $logger = $config['logger'] ?? new NullLogger(); - - if (null === $adapter) { - throw new FactoryException(sprintf( - 'The \'adapter\' configuration option is required in \'%s\'', - static::class - )); - } - - if (! $adapter instanceof ContainerAdapterInterface) { - throw new FactoryException(sprintf( - 'The \'adapter\' configuration option must be a object of type \'%s\'; \'%s\' provided in \'%s\'', - ContainerAdapterInterface::class, - (is_object($adapter) ? get_class($adapter) : gettype($adapter)), - static::class - )); - } - - if (! $logger instanceof LoggerInterface) { - throw new FactoryException(sprintf( - 'The \'logger\' configuration option must be a object of type \'%s\'; \'%s\' provided in \'%s\'', - LoggerInterface::class, - (is_object($logger) ? get_class($logger) : gettype($logger)), - static::class - )); - } - - return new Container($adapter, $logger); - } -} + + * @package Arp\Container\Factory + */ +final class ContainerFactory implements FactoryInterface +{ + /** + * @var FactoryInterface + */ + private FactoryInterface $adapterFactory; + + /** + * @param FactoryInterface $adapterFactory + */ + public function __construct(FactoryInterface $adapterFactory) + { + $this->adapterFactory = $adapterFactory; + } + + /** + * @param array $config + * + * @return Container + * + * @throws FactoryException + */ + public function create(array $config = []): Container + { + $adapter = $config['adapter'] ?? null; + + if (null === $adapter) { + throw new FactoryException( + sprintf( + 'The required \'adapter\' configuration option is missing in \'%s\'', + static::class + ) + ); + } + + return new Container($this->createAdapter($adapter)); + } + + /** + * @param ContainerAdapterInterface|array $adapter + * + * @return ContainerAdapterInterface + * + * @throws FactoryException + */ + private function createAdapter($adapter): ContainerAdapterInterface + { + if (is_array($adapter)) { + $adapter = $this->adapterFactory->create($adapter); + } + + if (!$adapter instanceof ContainerAdapterInterface) { + throw new FactoryException( + sprintf( + 'The \'adapter\' configuration option must be a object of type \'%s\'; \'%s\' provided in \'%s\'', + ContainerAdapterInterface::class, + (is_object($adapter) ? get_class($adapter) : gettype($adapter)), + static::class + ) + ); + } + + return $adapter; + } +} diff --git a/src/Provider/ConfigServiceProvider.php b/src/Provider/ConfigServiceProvider.php index ad32af6..728dd5a 100755 --- a/src/Provider/ConfigServiceProvider.php +++ b/src/Provider/ConfigServiceProvider.php @@ -1,87 +1,244 @@ - - * @package Arp\Container\Provider - */ -final class ConfigServiceProvider implements ServiceProviderInterface -{ - /** - * @var array - */ - private $config; - - /** - * @param array $config - */ - public function __construct(array $config) - { - $this->config = $config; - } - - /** - * Register services with the container adapter - * - * @param ContainerAdapterInterface $adapter The adapter that should be registered with - * - * @throws ServiceProviderException If the service registration fails - */ - public function registerServices(ContainerAdapterInterface $adapter): void - { - try { - $factories = $this->config['factories'] ?? []; - - foreach ($factories as $name => $factory) { - if (is_string($factory)) { - if (! $adapter instanceof FactoryClassAwareInterface) { - $exceptionMessage = sprintf( - 'The adapter class \'%s\' does not support factory class registration for service \'%s\'', - get_class($adapter), - $name - ); - throw new NotSupportedException($exceptionMessage); - } - $adapter->setFactoryClass($name, $factory); - continue; - } - - if (is_callable($factory)) { - $adapter->setFactory($name, $factory); - continue; - } - - $exceptionMessage = sprintf( - 'Service factories must be of type \'callable\'; \'%s\' provided for service \'%s\'', - (is_object($factory) ? get_class($factory) : gettype($factory)), - $name - ); - - throw new ServiceProviderException($exceptionMessage); - } - - $services = $this->config['services'] ?? []; - - foreach ($services as $name => $service) { - $adapter->setService($name, $service); - } - } catch (ServiceProviderException $e) { - throw $e; - } catch (AdapterException $e) { - throw new ServiceProviderException( - sprintf('Failed to register adapter services : %s', $e->getMessage()), - $e->getCode(), - $e - ); - } - } -} + + * @package Arp\Container\Provider + */ +final class ConfigServiceProvider implements ServiceProviderInterface +{ + public const ALIASES = 'aliases'; + public const FACTORIES = 'factories'; + public const SERVICES = 'services'; + + /** + * @var array + */ + private array $config; + + /** + * @param array $config + */ + public function __construct(array $config) + { + $this->config = $config; + } + + /** + * Attempt to register services from configuration with the provided $adapter. + * + * @param ContainerAdapterInterface $adapter The adapter to register services with. + * + * @throws NotSupportedException + * @throws ServiceProviderException + */ + public function registerServices(ContainerAdapterInterface $adapter): void + { + $services = $this->config[static::SERVICES] ?? []; + foreach ($services as $name => $service) { + try { + $adapter->setService($name, $service); + } catch (AdapterException $e) { + throw new ServiceProviderException( + sprintf('Failed to register service \'%s\': %s', $name, $e->getMessage()), + $e->getCode(), + $e + ); + } + } + + $factories = $this->config[static::FACTORIES] ?? []; + if (!empty($factories)) { + $this->registerFactories($adapter, $factories); + } + + $aliases = $this->config[static::ALIASES] ?? []; + if (!empty($aliases) && $adapter instanceof AliasAwareInterface) { + $this->registerAliases($adapter, $aliases); + } + } + + /** + * @param ContainerAdapterInterface $adapter + * @param array $factories + * + * @throws NotSupportedException + * @throws ServiceProviderException + */ + private function registerFactories(ContainerAdapterInterface $adapter, array $factories): void + { + foreach ($factories as $name => $factory) { + if (is_array($factory)) { + $this->registerArrayFactory($adapter, $name, $factory); + continue; + } + + if (is_string($factory)) { + $this->registerStringFactory($adapter, $name, $factory); + continue; + } + + $this->registerFactory($adapter, $name, $factory); + } + } + + /** + * @param AliasAwareInterface $adapter + * @param array $aliases + * + * @throws ServiceProviderException + */ + private function registerAliases(AliasAwareInterface $adapter, array $aliases): void + { + foreach ($aliases as $alias => $serviceName) { + try { + $adapter->setAlias($alias, $serviceName); + } catch (AdapterException $e) { + throw new ServiceProviderException( + sprintf( + 'Failed to register alias \'%s\' for service \'%s\': %s', + $alias, + $serviceName, + $e->getMessage() + ), + $e->getCode(), + $e + ); + } + } + } + + /** + * @param ContainerAdapterInterface $adapter + * @param string $serviceName + * @param object|callable $factory + * @param string|null $methodName + * + * @throws ServiceProviderException + */ + private function registerFactory( + ContainerAdapterInterface $adapter, + string $serviceName, + $factory, + string $methodName = null + ): void { + $methodName = $methodName ?? '__invoke'; + + if (!is_callable($factory) && !$factory instanceof \Closure) { + $factory = [$factory, $methodName]; + } + + if (!is_callable($factory)) { + throw new ServiceProviderException( + sprintf('Failed to register service \'%s\': The factory provided is not callable', $serviceName), + ); + } + + try { + $adapter->setFactory($serviceName, $factory); + } catch (AdapterException $e) { + throw new ServiceProviderException( + sprintf('Failed to register service \'%s\': %s', $serviceName, $e->getMessage()), + $e->getCode(), + $e + ); + } + } + + /** + * Register a factory that was provided as a configuration array. + * + * Using the array format of [$factory, $methodName] + * + * $factory can be callable|object|string + * + * @param ContainerAdapterInterface $adapter + * @param string $serviceName + * @param array $factoryConfig + * + * @throws ServiceProviderException + */ + private function registerArrayFactory( + ContainerAdapterInterface $adapter, + string $serviceName, + array $factoryConfig + ): void { + $factory = $factoryConfig[0] ?? null; + + if (null !== $factory) { + $methodName = $factoryConfig[1] ?? null; + + if (is_string($factory)) { + $this->registerStringFactory($adapter, $serviceName, $factory, $methodName); + return; + } + + if (is_object($factory) || is_callable($factory)) { + $this->registerFactory($adapter, $serviceName, $factory, $methodName); + return; + } + } + + throw new ServiceProviderException( + sprintf('Failed to register service \'%s\': The provided array configuration is invalid', $serviceName) + ); + } + + /** + * Register a factory provided as a string + * + * @param ContainerAdapterInterface $adapter + * @param string $serviceName + * @param string $factory + * @param string|null $methodName + * + * @throws NotSupportedException + * @throws ServiceProviderException + */ + private function registerStringFactory( + ContainerAdapterInterface $adapter, + string $serviceName, + string $factory, + string $methodName = null + ): void { + if (!$adapter instanceof FactoryClassAwareInterface) { + throw new NotSupportedException( + sprintf( + 'Failed to register service \'%s\': The adapter class \'%s\' does not support factory \'%s\' ' + . 'which is of type \'string\'.' + . 'The adapter must implement \'%s\' in order to support registration \'string\' factory types', + get_class($adapter), + $serviceName, + $factory, + FactoryClassAwareInterface::class + ) + ); + } + + try { + $adapter->setFactoryClass($serviceName, $factory, $methodName); + } catch (AdapterException $e) { + throw new ServiceProviderException( + sprintf( + 'Failed to register service \'%s\' with adapter \'%s\' using factory class \'%s\': %s', + $serviceName, + get_class($adapter), + $factory, + $e->getMessage() + ), + $e->getCode(), + $e + ); + } + } +} diff --git a/src/Provider/Exception/NotSupportedException.php b/src/Provider/Exception/NotSupportedException.php index 6f3ac61..888b734 100755 --- a/src/Provider/Exception/NotSupportedException.php +++ b/src/Provider/Exception/NotSupportedException.php @@ -1,16 +1,16 @@ - - * @package Arp\Container\Adapter\Exception - */ -final class NotSupportedException extends ServiceProviderException -{ - -} + + * @package Arp\Container\Adapter\Exception + */ +final class NotSupportedException extends ServiceProviderException +{ + +} diff --git a/src/Provider/Exception/ServiceProviderException.php b/src/Provider/Exception/ServiceProviderException.php index 2dd3ab3..dcaf48a 100755 --- a/src/Provider/Exception/ServiceProviderException.php +++ b/src/Provider/Exception/ServiceProviderException.php @@ -1,16 +1,16 @@ - - * @package Arp\Container\Provider\Exception - */ -class ServiceProviderException extends \Exception -{ - -} + + * @package Arp\Container\Provider\Exception + */ +class ServiceProviderException extends \Exception +{ + +} diff --git a/src/Provider/ServiceProviderInterface.php b/src/Provider/ServiceProviderInterface.php index 67e8ccc..1b34cb2 100755 --- a/src/Provider/ServiceProviderInterface.php +++ b/src/Provider/ServiceProviderInterface.php @@ -1,24 +1,24 @@ - - * @package Arp\Container\Provider - */ -interface ServiceProviderInterface -{ - /** - * Register a collection of services with the container. - * - * @param ContainerAdapterInterface $adapter - * - * @throws ServiceProviderException - */ - public function registerServices(ContainerAdapterInterface $adapter): void; -} + + * @package Arp\Container\Provider + */ +interface ServiceProviderInterface +{ + /** + * Register a collection of services with the container. + * + * @param ContainerAdapterInterface $adapter + * + * @throws ServiceProviderException + */ + public function registerServices(ContainerAdapterInterface $adapter): void; +} diff --git a/test/phpunit/Adapter/AbstractPsrBridgeAdapterTest.php b/test/phpunit/Adapter/AbstractPsrAdapterTest.php similarity index 50% rename from test/phpunit/Adapter/AbstractPsrBridgeAdapterTest.php rename to test/phpunit/Adapter/AbstractPsrAdapterTest.php index 5f1726c..ad855df 100755 --- a/test/phpunit/Adapter/AbstractPsrBridgeAdapterTest.php +++ b/test/phpunit/Adapter/AbstractPsrAdapterTest.php @@ -1,145 +1,133 @@ - - * @package ArpTest\Container\Adapter - */ -class AbstractPsrBridgeAdapterTest extends TestCase -{ - /** - * @var ContainerInterface|MockObject - */ - private $container; - - /** - * Set up the test case dependencies - */ - public function setUp(): void - { - /** @var ContainerInterface|MockObject $container */ - $this->container = $this->getMockForAbstractClass(ContainerInterface::class); - } - - /** - * Assert that the class extends ContainerAdapterInterface. - * - * @covers \Arp\Container\Adapter\AbstractPsrBridgeAdapter - */ - public function testImplementsContainerAdapterInterface(): void - { - /** @var AbstractPsrBridgeAdapter|MockObject $adapter */ - $adapter = $this->getMockForAbstractClass(AbstractPsrBridgeAdapter::class, [$this->container]); - - $this->assertInstanceOf(ContainerAdapterInterface::class, $adapter); - } - - /** - * Assert that and AdapterException will be thrown if the call to hasService() cannot be completed. - * - * @covers \Arp\Container\Adapter\AbstractPsrBridgeAdapter::hasService - * - * @throws AdapterException - */ - public function testHasServiceWillThrowAnAdapterException(): void - { - /** @var AbstractPsrBridgeAdapter|MockObject $adapter */ - $adapter = $this->getMockForAbstractClass(AbstractPsrBridgeAdapter::class, [$this->container]); - - $name = 'FooService'; - $exceptionMessage = 'This is a test exception message'; - $exception = new \Exception($exceptionMessage); - - $this->container->expects($this->once()) - ->method('has') - ->with($name) - ->willThrowException($exception); - - $this->expectException(AdapterException::class); - $this->expectExceptionMessage(sprintf( - 'The check for service \'%s\' failed : %s', - $name, - $exceptionMessage - )); - - $adapter->hasService($name); - } - - /** - * Assert that the getService() method will throw a NotFoundException. - * - * @throws AdapterException - * @throws NotFoundException - */ - public function testGetServiceWillThrowNotFoundExceptionForUnknownServiceName(): void - { - /** @var AbstractPsrBridgeAdapter|MockObject $adapter */ - $adapter = $this->getMockForAbstractClass(AbstractPsrBridgeAdapter::class, [$this->container]); - - $name = 'FooService'; - $exceptionMessage = 'This is a test exception message'; - $exception = new PsrContainerNotFoundException($exceptionMessage, 123); - - $this->container->expects($this->once()) - ->method('get') - ->with($name) - ->willThrowException($exception); - - $this->expectException(NotFoundException::class); - $this->expectExceptionCode(123); - $this->expectExceptionMessage(sprintf( - 'The service \'%s\' could not be found : %s', - $name, - $exceptionMessage - )); - - $adapter->getService($name); - } - - /** - * Assert that a AdapterException is thrown on error in method getService() - * - * @covers \Arp\Container\Adapter\AbstractPsrBridgeAdapter::getService - * - * @throws AdapterException - * @throws NotFoundException - */ - public function testGetServiceWillThrowAdapterException(): void - { - /** @var AbstractPsrBridgeAdapter|MockObject $adapter */ - $adapter = $this->getMockForAbstractClass(AbstractPsrBridgeAdapter::class, [$this->container]); - - $name = 'FooService'; - $exceptionMessage = 'This is a test exception message'; - $exception = new PsrContainerException($exceptionMessage, 123); - - $this->container->expects($this->once()) - ->method('get') - ->with($name) - ->willThrowException($exception); - - $this->expectException(AdapterException::class); - $this->expectExceptionCode(123); - $this->expectExceptionMessage(sprintf( - 'The service \'%s\' was found but could not be returned : %s', - $name, - $exceptionMessage - )); - - $adapter->getService($name); - } - -} + + * @package ArpTest\Container\Adapter + */ +final class AbstractPsrAdapterTest extends TestCase +{ + /** + * @var ContainerInterface|MockObject + */ + private $container; + + /** + * Set up the test case dependencies + */ + public function setUp(): void + { + $this->container = $this->getMockForAbstractClass(ContainerInterface::class); + } + + /** + * Assert that the class extends ContainerAdapterInterface + */ + public function testImplementsContainerAdapterInterface(): void + { + /** @var AbstractPsrAdapter|MockObject $adapter */ + $adapter = $this->getMockForAbstractClass(AbstractPsrAdapter::class, [$this->container]); + + $this->assertInstanceOf(ContainerAdapterInterface::class, $adapter); + } + + /** + * Assert that and AdapterException will be thrown if the call to hasService() cannot be completed + * + * @throws AdapterException + */ + public function testHasServiceWillThrowAnAdapterException(): void + { + /** @var AbstractPsrAdapter|MockObject $adapter */ + $adapter = $this->getMockForAbstractClass(AbstractPsrAdapter::class, [$this->container]); + + $name = 'FooService'; + + $exceptionMessage = 'This is a test exception message'; + $exceptionCode = 456; + $exception = new ContainerException($exceptionMessage, $exceptionCode); + + $this->container->expects($this->once()) + ->method('has') + ->with($name) + ->willThrowException($exception); + + $this->expectException(AdapterException::class); + $this->expectExceptionMessage($exceptionMessage); + $this->expectExceptionCode($exceptionCode); + + $adapter->hasService($name); + } + + /** + * Assert that the getService() method will throw a NotFoundException. + * + * @throws AdapterException + * @throws AdapterNotFoundException + */ + public function testGetServiceWillThrowNotFoundExceptionForUnknownServiceName(): void + { + /** @var AbstractPsrAdapter|MockObject $adapter */ + $adapter = $this->getMockForAbstractClass(AbstractPsrAdapter::class, [$this->container]); + + $name = 'FooService'; + + $exceptionMessage = 'This is a test exception message'; + $exceptionCode = 123; + $exception = new NotFoundException($exceptionMessage, $exceptionCode); + + $this->container->expects($this->once()) + ->method('get') + ->with($name) + ->willThrowException($exception); + + $this->expectException(AdapterNotFoundException::class); + $this->expectExceptionMessage($exceptionMessage); + $this->expectExceptionCode($exceptionCode); + + $adapter->getService($name); + } + + /** + * Assert that a AdapterException is thrown on error in method getService() + * + * @throws AdapterException + */ + public function testGetServiceWillThrowAdapterException(): void + { + /** @var AbstractPsrAdapter|MockObject $adapter */ + $adapter = $this->getMockForAbstractClass(AbstractPsrAdapter::class, [$this->container]); + + $name = 'FooService'; + + $exceptionMessage = 'This is a test exception message'; + $exceptionCode = 987; + $exception = new ContainerException($exceptionMessage, $exceptionCode); + + $this->container->expects($this->once()) + ->method('get') + ->with($name) + ->willThrowException($exception); + + $this->expectException(AdapterException::class); + $this->expectExceptionMessage($exceptionMessage); + $this->expectExceptionCode($exceptionCode); + + $adapter->getService($name); + } +} diff --git a/test/phpunit/ContainerTest.php b/test/phpunit/ContainerTest.php index a86dd2c..8bc5505 100755 --- a/test/phpunit/ContainerTest.php +++ b/test/phpunit/ContainerTest.php @@ -1,275 +1,233 @@ - - * @package ArpTest\Container\ContainerTest - */ -final class ContainerTest extends TestCase -{ - /** - * @var ContainerAdapterInterface|MockObject - */ - private $adapter; - - /** - * @var LoggerInterface|MockObject - */ - private $logger; - - /** - * @return void - */ - public function setUp(): void - { - $this->adapter = $this->getMockForAbstractClass(ContainerAdapterInterface::class); - - $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); - } - - /** - * Ensure that the container implements the PSR - * - * @covers \Arp\Container\Container - */ - public function testImplementsContainerInterface(): void - { - $container = new Container($this->adapter, $this->logger); - - $this->assertInstanceOf(ContainerInterface::class, $container); - } - - /** - * Assert AdapterException thrown by has() will be caught and rethrown as ContainerException. - * - * @throws ContainerException - * - * @covers \Arp\Container\Container::has - */ - public function testHasWillCatchAdapterExceptionAndReThrowAsAContainerException(): void - { - $name = 'FooService'; - - $container = new Container($this->adapter, $this->logger); - - $exceptionMessage = 'This is a test adapter exception message'; - $exception = new AdapterException($exceptionMessage); - - $this->adapter->expects($this->once()) - ->method('hasService') - ->willThrowException($exception); - - $errorMessage = sprintf('The has() failed for service \'%s\' : %s', $name, $exceptionMessage); - - $this->logger->expects($this->once()) - ->method('debug') - ->with($errorMessage, compact('exception', 'name')); - - $this->expectException(ContainerException::class); - $this->expectExceptionMessage($errorMessage); - $this->expectExceptionCode($exception->getCode()); - - $container->has($name); - } - - /** - * Ensure that registered services will return true when calling has(). - * - * @covers \Arp\Container\Container::has - * - * @throws ContainerException - */ - public function testHasReturnsTrueForRegisteredService(): void - { - $container = new Container($this->adapter, $this->logger); - - $serviceName = 'TestService'; - - $this->adapter->expects($this->once()) - ->method('hasService') - ->with($serviceName) - ->willReturn(true); - - $this->assertTrue($container->has($serviceName)); - } - - /** - * Ensure that non-registered services will return false when calling has(). - * - * @covers \Arp\Container\Container::has - * - * @throws ContainerException - */ - public function testHasReturnsFalseForNonRegisteredService(): void - { - $container = new Container($this->adapter, $this->logger); - - $serviceName = 'TestService'; - - $this->adapter->expects($this->once()) - ->method('hasService') - ->with($serviceName) - ->willReturn(false); - - $this->assertFalse($container->has($serviceName)); - } - - /** - * Assert that if the requested service to get() fails to be found a NotFoundException is thrown. - * - * @covers \Arp\Container\Container::get - */ - public function testGetWillThrowNotFoundExceptionIfRequestedServiceIsNotFound(): void - { - $container = new Container($this->adapter, $this->logger); - - $name = 'FooService'; - - $exception = new AdapterNotFoundException('This is a test exception message', 999); - - $this->adapter->expects($this->once()) - ->method('getService') - ->with($name) - ->willThrowException($exception); - - $errorMessage = sprintf('The service \'%s\' could not be found', $name); - - $this->logger->expects($this->once()) - ->method('error') - ->with($errorMessage, compact('exception', 'name')); - - $this->expectException(NotFoundException::class); - $this->expectExceptionMessage($errorMessage); - $this->expectExceptionCode(999); - - $container->get($name); - } - - /** - * Assert that NotFoundException's thrown by the adapter are caught, logged and rethrown as - * container NotFoundException's. - * - * @covers \Arp\Container\Container::get - */ - public function testGetWillThrowContainerExceptionIfGetServiceFails(): void - { - $container = new Container($this->adapter, $this->logger); - - $name = 'FooService'; - - $exceptionMessage = 'This is a test exception message'; - $exception = new AdapterException($exceptionMessage, 888); - - $this->adapter->expects($this->once()) - ->method('getService') - ->with($name) - ->willThrowException($exception); - - $errorMessage = sprintf('The get() failed for service \'%s\' : %s', $name, $exceptionMessage); - - $this->logger->expects($this->once()) - ->method('error') - ->with($errorMessage, compact('exception', 'name')); - - $this->expectException(ContainerException::class); - $this->expectExceptionMessage($errorMessage); - $this->expectExceptionCode(888); - - $container->get($name); - } - - /** - * Ensure that calls to get() will return a registered service from the adapter. - * - * @covers \Arp\Container\Container::get - */ - public function testGetWillReturnRegisteredService(): void - { - $container = new Container($this->adapter, $this->logger); - - $serviceName = 'TestService'; - $service = new \stdClass(); - - $this->adapter->expects($this->once()) - ->method('getService') - ->with($serviceName) - ->willReturn($service); - - $this->assertSame($service, $container->get($serviceName)); - } - - /** - * Assert that AdapterException's that are thrown when registering services are caught, logged and rethrow - * as ContainerException. - * - * @covers \Arp\Container\Container::registerServices - * - * @throws ContainerException - */ - public function testRegisterServiceWillCatchAndRethrowServiceProviderExceptionsAsContainerException(): void - { - $container = new Container($this->adapter, $this->logger); - - /** @var ServiceProviderInterface|MockObject $serviceProvider */ - $serviceProvider = $this->getMockForAbstractClass(ServiceProviderInterface::class); - - $exceptionMessage = 'This is a test service provider exception message'; - $exception = new ServiceProviderException($exceptionMessage, 777); - - $serviceProvider->expects($this->once()) - ->method('registerServices') - ->with($this->adapter) - ->willThrowException($exception); - - $errorMessage = sprintf('Failed to register service provider : %s', $exceptionMessage); - - $this->logger->expects($this->once()) - ->method('error') - ->with($errorMessage, ['exception' => $exception, 'serviceProvider' => get_class($serviceProvider)]); - - $this->expectException(ContainerException::class); - $this->expectExceptionMessage($errorMessage); - $this->expectExceptionCode(777); - - $container->registerServices($serviceProvider); - } - - - /** - * Ensure that the service provider will have the containers adapter passed to it - * when calling registerServices(). - * - * @covers \Arp\Container\Container::registerServices - * - * @throws ContainerException - */ - public function testRegisterServicesWillPassAdapterToProvidedServiceProvider(): void - { - $container = new Container($this->adapter, $this->logger); - - /** @var ServiceProviderInterface|MockObject $serviceProvider */ - $serviceProvider = $this->getMockForAbstractClass(ServiceProviderInterface::class); - - $serviceProvider->expects($this->once()) - ->method('registerServices') - ->with($this->adapter); - - $this->assertSame($container, $container->registerServices($serviceProvider)); - } -} + + * @package ArpTest\Container + */ +final class ContainerTest extends TestCase +{ + /** + * @var ContainerAdapterInterface|MockObject + */ + private $adapter; + + /** + * @return void + */ + public function setUp(): void + { + $this->adapter = $this->getMockForAbstractClass(ContainerAdapterInterface::class); + } + + /** + * Ensure that the container implements the PSR + */ + public function testImplementsContainerInterface(): void + { + $container = new Container($this->adapter); + + $this->assertInstanceOf(ContainerInterface::class, $container); + } + + /** + * Assert AdapterException thrown by has() will be caught and rethrown as ContainerException. + * + * @throws ContainerException + */ + public function testHasWillCatchAdapterExceptionAndReThrowAsAContainerException(): void + { + $name = 'FooService'; + + $container = new Container($this->adapter); + + $exceptionMessage = 'This is a test adapter exception message'; + $exception = new AdapterException($exceptionMessage); + + $this->adapter->expects($this->once()) + ->method('hasService') + ->willThrowException($exception); + + $this->expectException(ContainerException::class); + $this->expectExceptionMessage($exceptionMessage); + $this->expectExceptionCode($exception->getCode()); + + $container->has($name); + } + + /** + * Ensure that registered services will return true when calling has(). + * + * @throws ContainerException + */ + public function testHasReturnsTrueForRegisteredService(): void + { + $container = new Container($this->adapter); + + $serviceName = 'TestService'; + + $this->adapter->expects($this->once()) + ->method('hasService') + ->with($serviceName) + ->willReturn(true); + + $this->assertTrue($container->has($serviceName)); + } + + /** + * Ensure that non-registered services will return false when calling has(). + * + * @throws ContainerException + */ + public function testHasReturnsFalseForNonRegisteredService(): void + { + $container = new Container($this->adapter); + + $serviceName = 'TestService'; + + $this->adapter->expects($this->once()) + ->method('hasService') + ->with($serviceName) + ->willReturn(false); + + $this->assertFalse($container->has($serviceName)); + } + + /** + * Assert that if the requested service to get() fails to be found a NotFoundException is thrown. + */ + public function testGetWillThrowNotFoundExceptionIfRequestedServiceIsNotFound(): void + { + $container = new Container($this->adapter); + + $name = 'FooService'; + + $exceptionMessage = 'This is a test exception message'; + $exceptionCode = 999; + $exception = new AdapterNotFoundException($exceptionMessage, $exceptionCode); + + $this->adapter->expects($this->once()) + ->method('getService') + ->with($name) + ->willThrowException($exception); + + $this->expectException(NotFoundException::class); + $this->expectExceptionMessage($exceptionMessage); + $this->expectExceptionCode($exceptionCode); + + $container->get($name); + } + + /** + * Assert that NotFoundException's thrown by the adapter are caught, logged and rethrown as + * container NotFoundException's. + */ + public function testGetWillThrowContainerExceptionIfGetServiceFails(): void + { + $container = new Container($this->adapter); + + $name = 'FooService'; + + $exceptionMessage = 'This is a test exception message'; + $exceptionCode = 888; + $exception = new AdapterException($exceptionMessage, $exceptionCode); + + $this->adapter->expects($this->once()) + ->method('getService') + ->with($name) + ->willThrowException($exception); + + $this->expectException(ContainerException::class); + $this->expectExceptionMessage($exceptionMessage); + $this->expectExceptionCode($exceptionCode); + + $container->get($name); + } + + /** + * Ensure that calls to get() will return a registered service from the adapter. + * + * @throws ContainerExceptionInterface + */ + public function testGetWillReturnRegisteredService(): void + { + $container = new Container($this->adapter); + + $serviceName = 'TestService'; + $service = new \stdClass(); + + $this->adapter->expects($this->once()) + ->method('getService') + ->with($serviceName) + ->willReturn($service); + + $this->assertSame($service, $container->get($serviceName)); + } + + /** + * Assert that AdapterException's that are thrown when registering services are caught, logged and rethrow + * as ContainerException. + * + * @throws ContainerException + */ + public function testRegisterServiceWillCatchAndRethrowServiceProviderExceptionsAsContainerException(): void + { + $container = new Container($this->adapter); + + /** @var ServiceProviderInterface|MockObject $serviceProvider */ + $serviceProvider = $this->getMockForAbstractClass(ServiceProviderInterface::class); + + $exceptionMessage = 'This is a test service provider exception message'; + $exceptionCode = 777; + $exception = new ServiceProviderException($exceptionMessage, $exceptionCode); + + $serviceProvider->expects($this->once()) + ->method('registerServices') + ->with($this->adapter) + ->willThrowException($exception); + + $this->expectException(ContainerException::class); + $this->expectExceptionMessage($exceptionMessage); + $this->expectExceptionCode($exceptionCode); + + $container->registerServices($serviceProvider); + } + + /** + * Ensure that the service provider will have the containers adapter passed to it + * when calling registerServices(). + * + * @throws ContainerException + */ + public function testRegisterServicesWillPassAdapterToProvidedServiceProvider(): void + { + $container = new Container($this->adapter); + + /** @var ServiceProviderInterface|MockObject $serviceProvider */ + $serviceProvider = $this->getMockForAbstractClass(ServiceProviderInterface::class); + + $serviceProvider->expects($this->once()) + ->method('registerServices') + ->with($this->adapter); + + $container->registerServices($serviceProvider); + } +} diff --git a/test/phpunit/Factory/ContainerFactoryTest.php b/test/phpunit/Factory/ContainerFactoryTest.php index 3645345..af6212f 100755 --- a/test/phpunit/Factory/ContainerFactoryTest.php +++ b/test/phpunit/Factory/ContainerFactoryTest.php @@ -1,136 +1,135 @@ - - * @package ArpTest\Container\Factory - */ -final class ContainerFactoryTest extends TestCase -{ - /** - * Assert that the container factory implements FactoryInterface - * - * @covers \Arp\Container\Factory\ContainerFactory - */ - public function testImplementsFactoryInterface(): void - { - $factory = new ContainerFactory(); - - $this->assertInstanceOf(FactoryInterface::class, $factory); - } - - /** - * Assert that if the required 'adapter' configuration is not provided then FactoryException is thrown. - * - * @throws FactoryException - * - * @covers \Arp\Container\Factory\ContainerFactory::create - */ - public function testCreateWillThrowFactoryExceptionIfTheAdapterConfigurationOptionIsNotProvided(): void - { - $factory = new ContainerFactory(); - - $this->expectException(FactoryException::class); - $this->expectExceptionMessage(sprintf( - 'The \'adapter\' configuration option is required in \'%s\'', - ContainerFactory::class - )); - - $factory->create([]); - } - - /** - * Assert that a FactoryException is thrown from create if the provided adapter is not of of - * type ContainerAdapterInterface. - * - * @covers \Arp\Container\Factory\ContainerFactory::create - * - * @throws FactoryException - */ - public function testCreateWillThrowFactoryExceptionIfProvidedAdapterIsInvalid(): void - { - $factory = new ContainerFactory(); - - $config = [ - 'adapter' => 123, // invalid adapter! - ]; - - $this->expectException(FactoryException::class); - $this->expectExceptionMessage(sprintf( - 'The \'adapter\' configuration option must be a object of type \'%s\'; \'%s\' provided in \'%s\'', - ContainerAdapterInterface::class, - gettype($config['adapter']), - ContainerFactory::class - )); - - $factory->create($config); - } - - /** - * Assert that a FactoryException is thrown if providing an invalid instance of the logger to create(). - * - * @covers \Arp\Container\Factory\ContainerFactory::create - * - * @throws FactoryException - */ - public function testCreateWillThrowFactoryExceptionIfProvidedLoggerIsInvalid(): void - { - $factory = new ContainerFactory(); - - $logger = new \stdClass(); - $config = [ - 'adapter' => $this->getMockForAbstractClass(ContainerAdapterInterface::class), - 'logger' => $logger, // invalid logger! - ]; - - $this->expectException(FactoryException::class); - $this->expectExceptionMessage(sprintf( - 'The \'logger\' configuration option must be a object of type \'%s\'; \'%s\' provided in \'%s\'', - LoggerInterface::class, - get_class($logger), - ContainerFactory::class - )); - - $factory->create($config); - } - - /** - * Assert that a valid Container is created when providing configuration options to create(). - * - * @covers \Arp\Container\Factory\ContainerFactory::create - * - * @throws FactoryException - */ - public function testCreateWillReturnContainer(): void - { - $factory = new ContainerFactory(); - - /** @var ContainerAdapterInterface|MockObject $adapter */ - $adapter = $this->getMockForAbstractClass(ContainerAdapterInterface::class); - - /** @var LoggerInterface $logger */ - $logger = $this->getMockForAbstractClass(LoggerInterface::class); - - $config = [ - 'adapter' => $adapter, - 'logger' => $logger, - ]; - - $container = $factory->create($config); - - $this->assertInstanceOf(ContainerInterface::class, $container); - } -} + + * @package ArpTest\Container\Factory + */ +final class ContainerFactoryTest extends TestCase +{ + /** + * @var FactoryInterface|MockObject + */ + private $adapterFactory; + + /** + * Create test case dependencies + */ + public function setUp(): void + { + $this->adapterFactory = $this->createMock(FactoryInterface::class); + } + + /** + * Assert that the container factory implements FactoryInterface + */ + public function testImplementsFactoryInterface(): void + { + $factory = new ContainerFactory($this->adapterFactory); + + $this->assertInstanceOf(FactoryInterface::class, $factory); + } + + /** + * Assert that if the required 'adapter' configuration is not provided then FactoryException is thrown. + * + * @throws FactoryException + */ + public function testCreateWillThrowFactoryExceptionIfTheAdapterConfigurationOptionIsNotProvided(): void + { + $factory = new ContainerFactory($this->adapterFactory); + + $this->expectException(FactoryException::class); + $this->expectExceptionMessage( + sprintf( + 'The required \'adapter\' configuration option is missing in \'%s\'', + ContainerFactory::class + ) + ); + + $factory->create([]); + } + + /** + * Assert that a FactoryException is thrown from create if the provided adapter is not of of + * type ContainerAdapterInterface. + * + * @throws FactoryException + */ + public function testCreateWillThrowFactoryExceptionIfProvidedAdapterIsInvalid(): void + { + $factory = new ContainerFactory($this->adapterFactory); + + $adapter = new \stdClass(); + + $this->expectException(FactoryException::class); + $this->expectExceptionMessage( + sprintf( + 'The \'adapter\' configuration option must be a object of type \'%s\'; \'%s\' provided in \'%s\'', + ContainerAdapterInterface::class, + (is_object($adapter) ? get_class($adapter) : gettype($adapter)), + ContainerFactory::class + ) + ); + + $factory->create(compact('adapter')); + } + + /** + * Assert that a valid Container is created when providing configuration options to create() that contains + * and already created adapter instance + * + * @throws FactoryException + */ + public function testCreateWillReturnContainerWithAdapterInstance(): void + { + $factory = new ContainerFactory($this->adapterFactory); + + /** @var ContainerAdapterInterface|MockObject $adapter */ + $adapter = $this->getMockForAbstractClass(ContainerAdapterInterface::class); + + $this->assertInstanceOf(ContainerInterface::class, $factory->create(compact('adapter'))); + } + + /** + * Assert that a valid Container is created when providing configuration options to create() that contains + * adapter configuration as an array + * + * @throws FactoryException + */ + public function testCreateWillReturnContainerWithAdapterArrayConfiguration(): void + { + $factory = new ContainerFactory($this->adapterFactory); + + $config = [ + 'adapter' => [ + 'foo' => 'bar', + 'test' => new \stdClass(), + ] + ]; + + /** @var ContainerAdapterInterface|MockObject $adapter */ + $adapter = $this->getMockForAbstractClass(ContainerAdapterInterface::class); + + $this->adapterFactory->expects($this->once()) + ->method('create') + ->with($config['adapter']) + ->willReturn($adapter); + + $this->assertInstanceOf(ContainerInterface::class, $factory->create($config)); + } +} diff --git a/test/phpunit/Provider/ConfigServiceProviderTest.php b/test/phpunit/Provider/ConfigServiceProviderTest.php index 1027eb6..073e842 100755 --- a/test/phpunit/Provider/ConfigServiceProviderTest.php +++ b/test/phpunit/Provider/ConfigServiceProviderTest.php @@ -1,229 +1,444 @@ - - * @package ArpTest\Container\Provider - */ -final class ConfigServiceProviderTest extends TestCase -{ - /** - * Assert that the class implements ServiceProviderInterface. - * - * @covers \Arp\Container\Provider\ConfigServiceProvider - */ - public function testImplementsServiceProviderInterface(): void - { - $serviceProvider = new ConfigServiceProvider([]); - - $this->assertInstanceOf(ServiceProviderInterface::class, $serviceProvider); - } - - /** - * Assert that a NotSupportedException will be thrown if we tru to set a string factory class with an adapter - * that does not implement FactoryClassAwareInterface - * - * @throws ServiceProviderException - * - * @covers \Arp\Container\Provider\ConfigServiceProvider::registerServices - */ - public function testUsingStringFactoryWithNonFactoryClassAwareAdapterWillThrowNotSupportedException(): void - { - $name = 'FooService'; - $service = \stdClass::class; - - $serviceProvider = new ConfigServiceProvider([ - 'factories' => [$name => $service] - ]); - - /** @var ContainerAdapterInterface|MockObject $adapter */ - $adapter = $this->getMockForAbstractClass(ContainerAdapterInterface::class); - - $this->expectException(NotSupportedException::class); - $this->expectExceptionMessage(sprintf( - 'The adapter class \'%s\' does not support factory class registration for service \'%s\'', - get_class($adapter), - $name - )); - - $serviceProvider->registerServices($adapter); - } - - /** - * Assert that invalid factories will raise a ServiceProviderException - * - * @throws ServiceProviderException - * - * @covers \Arp\Container\Provider\ConfigServiceProvider::registerServices - */ - public function testRegisterServicesWillThrowServiceProviderExceptionIfProvidedFactoryIsInvalid(): void - { - $serviceName = 'FooService'; - $serviceFactory = false; // this is our invalid factory - - $serviceProvider = new ConfigServiceProvider([ - 'factories' => [ - $serviceName => $serviceFactory - ] - ]); - - /** @var ContainerAdapterInterface|MockObject $adapter */ - $adapter = $this->getMockForAbstractClass(ContainerAdapterInterface::class); - - $exceptionMessage = sprintf( - 'Service factories must be of type \'callable\'; \'%s\' provided for service \'%s\'', - (is_object($serviceFactory) ? get_class($serviceFactory) : gettype($serviceFactory)), - $serviceName - ); - - $this->expectException(ServiceProviderException::class); - $this->expectExceptionMessage($exceptionMessage); - - $serviceProvider->registerServices($adapter); - } - - /** - * Assert that AdapterException thrown from the adapter are caught and rethrown as ServiceProviderException. - * - * @throws ServiceProviderException - * - * @covers \Arp\Container\Provider\ConfigServiceProvider::registerServices - */ - public function testRegisterServicesWillCatchAdapterExceptionAndRethrowAsServiceProviderException(): void - { - $serviceName = 'Foo'; - $serviceFunc = static function (): \stdClass { - return new \stdClass(); - }; - - $config = [ - 'factories' => [ - $serviceName => $serviceFunc, - ], - ]; - - $serviceProvider = new ConfigServiceProvider($config); - - /** @var ContainerAdapterInterface|MockObject $adapter */ - $adapter = $this->getMockForAbstractClass(ContainerAdapterInterface::class); - - $exceptionMessage = 'This is a test exception message'; - $exception = new AdapterException($exceptionMessage, 123); - - $adapter->expects($this->once()) - ->method('setFactory') - ->with($serviceName, $serviceFunc) - ->willThrowException($exception); - - $this->expectException(ServiceProviderException::class); - $this->expectExceptionMessage(sprintf('Failed to register adapter services : %s', $exceptionMessage)); - $this->expectExceptionCode(123); - - $serviceProvider->registerServices($adapter); - } - - /** - * Assert that register services will correctly register the provided services defined in $config. - * - * @param array $config The services that should be set - * - * @dataProvider getRegisterServicesData - * @covers \Arp\Container\Provider\ConfigServiceProvider::registerServices - * - * @throws ServiceProviderException - */ - public function testRegisterServices(array $config): void - { - $serviceProvider = new ConfigServiceProvider($config); - - /** @var ContainerAdapterInterface|FactoryClassAwareInterface|MockObject $adapter */ - $adapter = $this->createMock(FactoryClassAwareInterface::class); - - $factories = $config['factories'] ?? []; - $services = $config['services'] ?? []; - - $setFactoryArgs = $setServiceArgs = $setClassArgs = []; - - foreach ($factories as $name => $factory) { - if (is_string($factory)) { - $setClassArgs[] = [$name, $factory]; - } else { - $setFactoryArgs[] = [$name, $factory]; - } - } - foreach ($services as $name => $service) { - $setServiceArgs[] = [$name, $service]; - } - - $adapter->expects($this->exactly(count($setFactoryArgs))) - ->method('setFactory') - ->withConsecutive(...$setFactoryArgs); - - $adapter->expects($this->exactly(count($setClassArgs))) - ->method('setFactoryClass') - ->withConsecutive(...$setClassArgs); - - $adapter->expects($this->exactly(count($setServiceArgs))) - ->method('setService') - ->withConsecutive(...$setServiceArgs); - - $serviceProvider->registerServices($adapter); - } - - /** - * @return array - */ - public function getRegisterServicesData(): array - { - return [ - [ - [], // empty config test - ], - - [ - [ - 'factories' => [ - 'FooService' => static function () { - return 'Hi'; - }, - ], - ] - ], - - [ - [ - 'services' => [ - 'FooService' => new \stdClass(), - 'BarService' => new \stdClass(), - 'Baz' => 123, - ], - ], - ], - - [ - [ - 'factories' => [ - 'BazStringService' => 'Hello', - 'Bar' => static function () { - return 'Test'; - }, - 'Foo' => 'FooFactory', - ], - ], - ], - ]; - } -} + + * @package ArpTest\Container\Provider + */ +final class ConfigServiceProviderTest extends TestCase +{ + /** + * @var ContainerAdapterInterface|MockObject + */ + private $adapter; + + /** + * Prepare the test case dependencies + */ + public function setUp(): void + { + $this->adapter = $this->createMock(ContainerAdapterInterface::class); + } + + /** + * Assert that the class implements ServiceProviderInterface. + */ + public function testImplementsServiceProviderInterface(): void + { + $serviceProvider = new ConfigServiceProvider([]); + + $this->assertInstanceOf(ServiceProviderInterface::class, $serviceProvider); + } + + /** + * Assert that is the adapter raises an exception when executing setService() a ServiceProviderException + * exception is thrown instead + * + * @throws NotSupportedException + * @throws ServiceProviderException + */ + public function testRegisterServicesWillThrowServiceProviderExceptionIfServiceCannotBeRegistered(): void + { + $name = 'FooService'; + $service = new \stdClass(); + $config = [ + ConfigServiceProvider::SERVICES => [ + $name => $service, + ], + ]; + + $exceptionMessage = 'This is a test exception message from the adapter'; + $exceptionCode = 123454; + $exception = new AdapterException($exceptionMessage, $exceptionCode); + + $this->adapter->expects($this->once()) + ->method('setService') + ->with($name, $service) + ->willThrowException($exception); + + $this->expectException(ServiceProviderException::class); + $this->expectExceptionCode($exceptionCode); + $this->expectExceptionMessage(sprintf('Failed to register service \'%s\': %s', $name, $exceptionMessage)); + + (new ConfigServiceProvider($config))->registerServices($this->adapter); + } + + /** + * Assert that invalid factories will raise a ServiceProviderException + * + * @throws ServiceProviderException + */ + public function testRegisterServicesWillThrowServiceProviderExceptionIfProvidedFactoryIsInvalid(): void + { + $serviceName = 'FooService'; + $serviceFactory = false; // this is our invalid factory + + $serviceProvider = new ConfigServiceProvider( + [ + 'factories' => [ + $serviceName => $serviceFactory, + ], + ] + ); + + $this->expectException(ServiceProviderException::class); + $this->expectExceptionMessage( + sprintf('Failed to register service \'%s\': The factory provided is not callable', $serviceName) + ); + + $serviceProvider->registerServices($this->adapter); + } + + /** + * Assert that AdapterException thrown from the adapter are caught and rethrown as ServiceProviderException. + * + * @throws ServiceProviderException + */ + public function testRegisterServicesWillCatchAdapterExceptionAndRethrowAsServiceProviderException(): void + { + $serviceName = 'Foo'; + $serviceFactory = static function (): \stdClass { + return new \stdClass(); + }; + + $config = [ + 'factories' => [ + $serviceName => $serviceFactory, + ], + ]; + + $exceptionMessage = 'This is a test exception message'; + $exceptionCode = 3456; + $exception = new AdapterException($exceptionMessage, $exceptionCode); + + $this->adapter->expects($this->once()) + ->method('setFactory') + ->with($serviceName, $serviceFactory) + ->willThrowException($exception); + + $this->expectException(ServiceProviderException::class); + $this->expectExceptionCode($exceptionCode); + $this->expectExceptionMessage( + sprintf('Failed to register service \'%s\': %s', $serviceName, $exceptionMessage), + ); + + (new ConfigServiceProvider($config))->registerServices($this->adapter); + } + + /** + * @throws NotSupportedException + * @throws ServiceProviderException + */ + public function testRegisterServicesWillThrowServiceProviderExceptionIfTheServiceAliasCannotBeSet(): void + { + $service = new \stdClass(); + $serviceName = 'FooService'; + $aliasName = 'FooAlias'; + + $config = [ + 'services' => [ + $serviceName => $service, + ], + 'aliases' => [ + $aliasName => $serviceName, + ], + ]; + + $exceptionMessage = 'Test exception message'; + $exceptionCode = 12345; + $exception = new AdapterException($exceptionMessage, $exceptionCode); + + /** @var AliasAwareInterface|MockObject $adapter */ + $adapter = $this->getMockForAbstractClass(AliasAwareInterface::class); + + $adapter->expects($this->once()) + ->method('setService') + ->with($serviceName, $service); + + $adapter->expects($this->once()) + ->method('setAlias') + ->with($aliasName, $serviceName) + ->willThrowException($exception); + + $this->expectException(ServiceProviderException::class); + $this->expectExceptionCode($exceptionCode); + $this->expectExceptionMessage( + sprintf( + 'Failed to register alias \'%s\' for service \'%s\': %s', + $aliasName, + $serviceName, + $exceptionMessage + ) + ); + + (new ConfigServiceProvider($config))->registerServices($adapter); + } + + /** + * @throws NotSupportedException + * @throws ServiceProviderException + */ + public function testRegisterServicesWillThrowServiceProviderExceptionIfTheArrayServiceIsInvalid(): void + { + $serviceName = 'FooService'; + + $config = [ + 'factories' => [ + $serviceName => [], + ], + ]; + + $this->expectException(ServiceProviderException::class); + $this->expectExceptionMessage( + sprintf('Failed to register service \'%s\': The provided array configuration is invalid', $serviceName) + ); + + (new ConfigServiceProvider($config))->registerServices($this->adapter); + } + + /** + * Assert that register services will correctly register the provided services and factories defined in $config. + * + * @param array $config The services that should be set + * + * @dataProvider getRegisterServicesWithFactoriesAndServicesData + * + * @throws ServiceProviderException + */ + public function testRegisterServicesWithFactoriesAndServices(array $config): void + { + $serviceProvider = new ConfigServiceProvider($config); + + $factories = $config[ConfigServiceProvider::FACTORIES] ?? []; + $services = $config[ConfigServiceProvider::SERVICES] ?? []; + + $setFactoryArgs = $setServiceArgs = $setFactoryClassArgs = []; + + /** @var FactoryClassAwareInterface|MockObject $adapter */ + $adapter = $this->getMockForAbstractClass(FactoryClassAwareInterface::class); + foreach ($factories as $name => $factory) { + $methodName = null; + + if (is_array($factory)) { + $methodName = $factory[1] ?? null; + $factory = $factory[0] ?? null; + + if (is_string($factory)) { + $setFactoryClassArgs[] = [$name, $factory, $methodName]; + continue; + } + if (!is_callable($factory) && !$factory instanceof \Closure) { + $factory = [$factory, $methodName]; + } + } + + if (is_string($factory)) { + $setFactoryClassArgs[] = [$name, $factory, $methodName]; + continue; + } + $setFactoryArgs[] = [$name, $factory]; + } + + foreach ($services as $name => $service) { + $setServiceArgs[] = [$name, $service]; + } + + $adapter->expects($this->exactly(count($setFactoryClassArgs))) + ->method('setFactoryClass') + ->withConsecutive(...$setFactoryClassArgs); + + $adapter->expects($this->exactly(count($setFactoryArgs))) + ->method('setFactory') + ->withConsecutive(...$setFactoryArgs); + + $adapter->expects($this->exactly(count($setServiceArgs))) + ->method('setService') + ->withConsecutive(...$setServiceArgs); + + $serviceProvider->registerServices($adapter); + } + + /** + * @return array + */ + public function getRegisterServicesWithFactoriesAndServicesData(): array + { + return [ + [ + [], // empty config test + ], + + [ + [ + ConfigServiceProvider::FACTORIES => [ + 'FooService' => static function () { + return 'Hi'; + }, + ], + ], + ], + + [ + [ + 'services' => [ + 'FooService' => new \stdClass(), + 'BarService' => new \stdClass(), + 'Baz' => 123, + ], + ], + ], + + // Array based registration for non callable factory object with custom method name 'create' + [ + [ + 'factories' => [ + 'FooService' => [ + new class { + public function create(): \stdClass + { + return new \stdClass(); + } + }, + 'create', + ], + ], + ], + ], + + // String and array based registration + [ + [ + 'factories' => [ + 'FooService' => 'StringFactoryName', + 'BazService' => ['BazServiceFactory'], + 'BarService' => ['StringBarServiceFactory', 'methodNameThatWillBeCalled'], + 'ZapService' => ['ZapServiceFactory', '__invoke'], + ], + ] + ] + ]; + } + + /** + * Assert that a NotSupportedException is thrown when passing a string factory configuration to a adapter that + * does not implement FactoryClassAwareInterface + * + * @throws NotSupportedException + * @throws ServiceProviderException + */ + public function testNotSupportExceptionIsThrownIsStringFactoryIsNotSupportedByTheAdapter(): void + { + $serviceName = 'Test123'; + $factoryName = \stdClass::class; + $config = [ + 'factories' => [ + $serviceName => $factoryName, + ] + ]; + + $serviceProvider = new ConfigServiceProvider($config); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage( + sprintf( + 'Failed to register service \'%s\': The adapter class \'%s\' does not support factory \'%s\' ' + . 'which is of type \'string\'.' + . 'The adapter must implement \'%s\' in order to support registration \'string\' factory types', + get_class($this->adapter), + $serviceName, + $factoryName, + FactoryClassAwareInterface::class + ) + ); + + // We provide an adapter mock that is doe NOT implement FactoryClassAwareInterface + $serviceProvider->registerServices($this->adapter); + } + + /** + * Assert that a ServiceProviderException is thrown when the provider is unable to register a factory class + * + * @throws NotSupportedException + * @throws ServiceProviderException + */ + public function testStringFactoryRegistrationFailureWillThrowServiceProviderException(): void + { + $serviceName = 'FooService'; + $factoryName = 'FooServiceFactory'; + $factoryMethodName = null; + + $config = [ + 'factories' => [ + $serviceName => $factoryName, + ] + ]; + + /** @var FactoryClassAwareInterface|MockObject $adapter */ + $adapter = $this->getMockForAbstractClass(FactoryClassAwareInterface::class); + + $exceptionMessage = 'The exception message test string'; + $exceptionCode = 12345; + $exception = new AdapterException($exceptionMessage, $exceptionCode); + + $this->expectException(ServiceProviderException::class); + $this->expectExceptionCode($exceptionCode); + $this->expectExceptionMessage( + sprintf( + 'Failed to register service \'%s\' with adapter \'%s\' using factory class \'%s\': %s', + $serviceName, + get_class($adapter), + $factoryName, + $exceptionMessage + ) + ); + + $adapter->expects($this->once()) + ->method('setFactoryClass') + ->with($serviceName, $factoryName, $factoryMethodName) + ->willThrowException($exception); + + (new ConfigServiceProvider($config))->registerServices($adapter); + } + + /** + * Assert that the ServiceProvider supports string factory registration + * + * @throws NotSupportedException + * @throws ServiceProviderException + */ + public function testRegistrationOfStringFactories(): void + { + $serviceName = 'Test123'; + $factoryName = \stdClass::class; + $config = [ + 'factories' => [ + $serviceName => $factoryName, + ] + ]; + + /** @var FactoryClassAwareInterface|MockObject $adapter */ + $adapter = $this->getMockForAbstractClass(FactoryClassAwareInterface::class); + $adapter->expects($this->once()) + ->method('setFactoryClass') + ->with($serviceName, $factoryName, null); + + // We provide an adapter mock that is doe NOT implement FactoryClassAwareInterface + (new ConfigServiceProvider($config))->registerServices($adapter); + } +}