diff --git a/.buildpath b/.buildpath
new file mode 100644
index 0000000..a4ae71b
--- /dev/null
+++ b/.buildpath
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.project b/.project
new file mode 100644
index 0000000..6945617
--- /dev/null
+++ b/.project
@@ -0,0 +1,33 @@
+
+
+ di
+
+
+
+
+
+ org.eclipse.php.composer.core.builder.buildPathManagementBuilder
+
+
+
+
+ org.eclipse.wst.common.project.facet.core.builder
+
+
+
+
+ org.eclipse.wst.validation.validationbuilder
+
+
+
+
+ org.eclipse.dltk.core.scriptbuilder
+
+
+
+
+
+ org.eclipse.php.core.PHPNature
+ org.eclipse.wst.common.project.facet.core.nature
+
+
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..f101a3d
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,40 @@
+{
+ "name" : "pluf/di",
+ "description" : "Pluf DI library",
+ "type" : "library",
+ "homepage" : "https://github.com/pluf/di",
+ "license" : "GPL-3.0-with-GCC-exception",
+ "authors" : [{
+ "name" : "Mostafa Barmshory",
+ "email" : "mostafa.barmshory@gmail.com",
+ "role" : "author",
+ "homepage" : "https://viraweb123.ir"
+ }
+ ],
+ "support" : {
+ "email" : "info@pluf.ir",
+ "issues" : "https://github.com/pluf/di/issues",
+ "source" : "https://github.com/pluf/di",
+ "wiki" : "https://github.com/pluf/di/wiki"
+ },
+ "keywords" : [
+ "pluf",
+ "di"
+ ],
+ "autoload" : {
+ "psr-4" : {
+ "Pluf\\Di\\" : "src"
+ }
+ },
+ "autoload-dev" : {
+ "psr-4" : {
+ "Pluf\\Tests\\" : "tests"
+ }
+ },
+ "require" : {
+ "psr/container" : "1.*"
+ },
+ "require-dev" : {
+ "phpunit/phpunit" : "~7.5"
+ }
+}
\ No newline at end of file
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..1389fec
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,1578 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "ae21efcfb2b595fd14a62cadd1ab432d",
+ "packages": [
+ {
+ "name": "psr/container",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+ "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "time": "2017-02-14T16:28:37+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "f350df0268e904597e3bd9c4685c53e0e333feea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea",
+ "reference": "f350df0268e904597e3bd9c4685c53e0e333feea",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpbench/phpbench": "^0.13",
+ "phpstan/phpstan-phpunit": "^0.11",
+ "phpstan/phpstan-shim": "^0.11",
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-05-29T17:27:14+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.10.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5",
+ "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "replace": {
+ "myclabs/deep-copy": "self.version"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.0",
+ "doctrine/common": "^2.6",
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ },
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-06-29T13:22:24+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
+ "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "phar-io/version": "^2.0",
+ "php": "^5.6 || ^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "time": "2018-07-08T19:23:20+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+ "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "time": "2018-07-08T19:19:57+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-2.x": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "time": "2020-06-27T09:03:43+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "5.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
+ "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.2",
+ "phpdocumentor/type-resolver": "^1.3",
+ "webmozart/assert": "^1.9.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.3.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ },
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "account@ijaap.nl"
+ }
+ ],
+ "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-09-03T19:13:55+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.0"
+ },
+ "require-dev": {
+ "ext-tokenizer": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-1.x": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "time": "2020-09-17T18:55:26+00:00"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "1.12.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/8ce87516be71aae9b956f81906aaf0338e0d8a2d",
+ "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.2",
+ "php": "^7.2 || ~8.0, <8.1",
+ "phpdocumentor/reflection-docblock": "^5.2",
+ "sebastian/comparator": "^3.0 || ^4.0",
+ "sebastian/recursion-context": "^3.0 || ^4.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^6.0",
+ "phpunit/phpunit": "^8.0 || ^9.0 <9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Prophecy\\": "src/Prophecy"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "time": "2020-09-29T09:10:42+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "6.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
+ "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.1",
+ "phpunit/php-file-iterator": "^2.0",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-token-stream": "^3.0",
+ "sebastian/code-unit-reverse-lookup": "^1.0.1",
+ "sebastian/environment": "^3.1 || ^4.0",
+ "sebastian/version": "^2.0.1",
+ "theseer/tokenizer": "^1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0"
+ },
+ "suggest": {
+ "ext-xdebug": "^2.6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.1-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 that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "time": "2018-10-31T16:06:48+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "050bedf145a257b1ff02746c31894800e5122946"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946",
+ "reference": "050bedf145a257b1ff02746c31894800e5122946",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-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": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "time": "2018-09-13T20:33:42+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "time": "2015-06-21T13:50:34+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "2.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "1038454804406b0b5f5f520358e78c1c2f71501e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e",
+ "reference": "1038454804406b0b5f5f520358e78c1c2f71501e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-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": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "time": "2019-06-07T04:22:29+00:00"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff",
+ "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "abandoned": true,
+ "time": "2019-09-17T06:23:10+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "7.5.20",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "9467db479d1b0487c99733bb1e7944d32deded2c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c",
+ "reference": "9467db479d1b0487c99733bb1e7944d32deded2c",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.1",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "myclabs/deep-copy": "^1.7",
+ "phar-io/manifest": "^1.0.2",
+ "phar-io/version": "^2.0",
+ "php": "^7.1",
+ "phpspec/prophecy": "^1.7",
+ "phpunit/php-code-coverage": "^6.0.7",
+ "phpunit/php-file-iterator": "^2.0.1",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-timer": "^2.1",
+ "sebastian/comparator": "^3.0",
+ "sebastian/diff": "^3.0",
+ "sebastian/environment": "^4.0",
+ "sebastian/exporter": "^3.1",
+ "sebastian/global-state": "^2.0",
+ "sebastian/object-enumerator": "^3.0.3",
+ "sebastian/resource-operations": "^2.0",
+ "sebastian/version": "^2.0.1"
+ },
+ "conflict": {
+ "phpunit/phpunit-mock-objects": "*"
+ },
+ "require-dev": {
+ "ext-pdo": "*"
+ },
+ "suggest": {
+ "ext-soap": "*",
+ "ext-xdebug": "*",
+ "phpunit/php-invoker": "^2.0"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "7.5-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": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "time": "2020-01-08T08:45:45+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+ "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7 || ^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "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"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
+ "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1",
+ "sebastian/diff": "^3.0",
+ "sebastian/exporter": "^3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "time": "2018-07-12T15:12:46+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
+ "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5 || ^8.0",
+ "symfony/process": "^2 || ^3.3 || ^4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "time": "2019-02-04T06:01:07+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "4.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368",
+ "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "time": "2019-11-20T08:46:58+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "3.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e",
+ "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "time": "2019-09-14T09:02:43+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "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"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "time": "2017-04-27T15:39:26+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+ "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "sebastian/object-reflector": "^1.1.1",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "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"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "773f97c67f28de00d397be301821b06708fca0be"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be",
+ "reference": "773f97c67f28de00d397be301821b06708fca0be",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "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"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
+ "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "time": "2017-03-03T06:23:57+00:00"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
+ "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "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"
+ }
+ ],
+ "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"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-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 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": "symfony/polyfill-ctype",
+ "version": "v1.18.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "1c302646f6efc070cd46856e600e5e0684d6b454"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454",
+ "reference": "1c302646f6efc070cd46856e600e5e0684d6b454",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.18-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-07-14T12:35:20+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "75a63c33a8577608444246075ea0af0d052e452a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a",
+ "reference": "75a63c33a8577608444246075ea0af0d052e452a",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2020-07-12T23:59:07+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.9.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozart/assert.git",
+ "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
+ "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0 || ^8.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "phpstan/phpstan": "<0.12.20",
+ "vimeo/psalm": "<3.9.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.36 || ^7.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "time": "2020-07-08T17:02:28+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "1.1.0"
+}
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..d2bb63e
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,41 @@
+
+
+
+ tests/
+
+
+
+
+
+
+ ./src
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CallableResolver.php b/src/CallableResolver.php
new file mode 100644
index 0000000..ea13d0d
--- /dev/null
+++ b/src/CallableResolver.php
@@ -0,0 +1,134 @@
+
+ */
+class CallableResolver
+{
+
+ /**
+ * PSR11 Container to resolbe dependencies
+ *
+ * @var ContainerInterface
+ */
+ private ContainerInterface $container;
+
+ /**
+ * Creates new instance of resolber
+ *
+ * @param ContainerInterface $container
+ */
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ /**
+ * Resolve the given callable into a real PHP callable.
+ *
+ * @param callable|string|array $callable
+ *
+ * @return callable Real PHP callable.
+ *
+ * @throws NotCallableException|\ReflectionException
+ */
+ public function resolve($callable): callable
+ {
+ if (is_string($callable) && strpos($callable, '::') !== false) {
+ $callable = explode('::', $callable, 2);
+ }
+
+ $callable = $this->resolveFromContainer($callable);
+
+ if (! is_callable($callable)) {
+ throw NotCallableException::fromInvalidCallable($callable, true);
+ }
+
+ return $callable;
+ }
+
+ /**
+ *
+ * @param callable|string|array $callable
+ * @return callable|mixed
+ * @throws NotCallableException|\ReflectionException
+ */
+ private function resolveFromContainer($callable)
+ {
+ // Shortcut for a very common use case
+ if ($callable instanceof Closure) {
+ return $callable;
+ }
+
+ // If it's already a callable there is nothing to do
+ if (is_callable($callable)) {
+ // TODO with PHP 8 that should not be necessary to check this anymore
+ if (! $this->isStaticCallToNonStaticMethod($callable)) {
+ return $callable;
+ }
+ }
+
+ // The callable is a container entry name
+ if (is_string($callable)) {
+ try {
+ return $this->container->get($callable);
+ } catch (NotFoundExceptionInterface $e) {
+ if ($this->container->has($callable)) {
+ throw $e;
+ }
+ throw NotCallableException::fromInvalidCallable($callable, true);
+ }
+ }
+
+ // The callable is an array whose first item is a container entry name
+ // e.g. ['some-container-entry', 'methodToCall']
+ if (is_array($callable) && is_string($callable[0])) {
+ try {
+ // Replace the container entry name by the actual object
+ $callable[0] = $this->container->get($callable[0]);
+ return $callable;
+ } catch (NotFoundExceptionInterface $e) {
+ if ($this->container->has($callable[0])) {
+ throw $e;
+ }
+ throw new NotCallableException(sprintf('Cannot call %s() on %s because it is not a class nor a valid container entry', $callable[1], $callable[0]));
+ }
+ }
+
+ // Unrecognized stuff, we let it fail later
+ return $callable;
+ }
+
+ /**
+ * Check if the callable represents a static call to a non-static method.
+ *
+ * @param mixed $callable
+ *
+ * @throws \ReflectionException
+ */
+ private function isStaticCallToNonStaticMethod($callable): bool
+ {
+ if (is_array($callable) && is_string($callable[0])) {
+ [
+ $class,
+ $method
+ ] = $callable;
+ $reflection = new ReflectionMethod($class, $method);
+
+ return ! $reflection->isStatic();
+ }
+
+ return false;
+ }
+}
diff --git a/src/Container.php b/src/Container.php
new file mode 100644
index 0000000..25f201a
--- /dev/null
+++ b/src/Container.php
@@ -0,0 +1,247 @@
+
+ */
+class Container implements ArrayAccess, ContainerInterface
+{
+
+ private array $factories = [];
+
+ private array $frozen = [];
+
+ /**
+ * Stores list of all keys in this container
+ *
+ * @var array
+ */
+ private array $keys = [];
+
+ /**
+ * Parent container is used to resolve services hericically.
+ *
+ * @var Container
+ */
+ private ?Container $parent = null;
+
+ /**
+ * The invoker is used to call the factories
+ *
+ * @var Invoker
+ */
+ private ?Invoker $internalInvoker = null;
+
+ /**
+ * Instantiates the container.
+ *
+ * Objects and parameters can be passed as argument to the constructor.
+ *
+ * @param array $values
+ * The parameters or objects
+ */
+ public function __construct(?Container $parent = null)
+ {
+ $this->parent = $parent;
+ // Create an invoker
+ $this->internalInvoker = new Invoker(new ResolverChain([
+ new ParameterNameContainerResolver($this),
+ new DefaultValueResolver()
+ ]));
+
+ // register the container as service
+ $this->offsetSet('container', Container::value($this));
+ }
+
+ /**
+ * Sets a factory to provide an object
+ *
+ * Objects must be defined as Closures.
+ *
+ * Allowing any PHP callable leads to difficult to debug problems
+ * as function names (strings) are callable (creating a function with
+ * the same name as an existing parameter would break your container).
+ *
+ * @param string $id
+ * The unique identifier for the parameter or object
+ * @param mixed $factory
+ * The factory to define an object
+ *
+ * @throws FrozenServiceException Prevent override of a frozen service
+ */
+ public function offsetSet($id, $factory)
+ {
+ if (isset($this->frozen[$id])) {
+ throw new FrozenServiceException($id);
+ }
+
+ // TODO: maso, 2020: check the $value
+ if (! is_callable($factory)) {
+ throw new ExpectedInvokableException('Factory is not a Closure or invokable object.');
+ }
+
+ $this->factories[$id] = $factory;
+ $this->keys[$id] = true;
+ }
+
+ /**
+ * Gets a parameter or an object.
+ *
+ * @param string $id
+ * The unique identifier for the parameter or object
+ *
+ * @return mixed The value of the parameter or an object
+ *
+ * @throws UnknownIdentifierException If the identifier is not defined
+ */
+ public function offsetGet($id)
+ {
+ if (! isset($this->keys[$id])) {
+ // TODO: maso, 2020: fetch from parent
+ throw new UnknownIdentifierException($id);
+ }
+
+ $factory = $this->factories[$id];
+ $val = $this->internalInvoker->call($factory);
+ // value is used and you are not allowd to overrid
+ $this->frozen[$id] = true;
+ return $val;
+ }
+
+ /**
+ * Checks if a parameter or an object is set.
+ *
+ * @param string $id
+ * The unique identifier for the parameter or object
+ *
+ * @return bool
+ */
+ public function offsetExists($id)
+ {
+ return isset($this->keys[$id]);
+ }
+
+ /**
+ * Unsets a parameter or an object.
+ *
+ * @param string $id
+ * The unique identifier for the parameter or object
+ */
+ public function offsetUnset($id)
+ {
+ if (isset($this->keys[$id])) {
+ unset($this->factories[$id], $this->keys[$id], $this->frozen);
+ }
+ }
+
+ /**
+ *
+ * {@inheritdoc}
+ * @see \Psr\Container\ContainerInterface::get()
+ */
+ public function get($id)
+ {
+ return $this->offsetGet($id);
+ }
+
+ /**
+ *
+ * {@inheritdoc}
+ * @see \Psr\Container\ContainerInterface::has()
+ */
+ public function has($id)
+ {
+ return $this->offsetExists($id);
+ }
+
+ /**
+ * Returns all defined value names.
+ *
+ * @return array An array of value names
+ */
+ public function keys()
+ {
+ return array_keys($this->factories);
+ }
+
+ public function raw($id): Closure
+ {
+ if (! $this->has($id)) {
+ throw new UnknownIdentifierException($id);
+ }
+ return $this->factories[$id];
+ }
+
+ /**
+ * Marks a callable as being a factory service.
+ *
+ * @param callable $callable
+ * A service definition to be used as a factory
+ *
+ * @return callable The passed callable
+ *
+ * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object
+ */
+ public static function factory(Closure $callable): Closure
+ {
+ return $callable;
+ }
+
+ /**
+ * Returns a closure that stores the result of the given service definition
+ * for uniqueness in the scope of this instance of Pimple.
+ *
+ * @param callable $callable
+ * A service definition to wrap for uniqueness
+ *
+ * @return Closure The wrapped closure
+ */
+ public static function service($callable): Closure
+ {
+ if (! is_callable($callable)) {
+ throw new InvalidArgumentException('A factory must use to create a service.');
+ }
+ return function ($container) use ($callable) {
+ static $service;
+
+ if (null === $service) {
+ // TODO: invoke the callable
+ $invoker = new Invoker(new ParameterNameContainerResolver($container));
+ $service = $invoker->call($callable);
+ }
+
+ return $service;
+ };
+ }
+
+ /**
+ * Protects a callable from being interpreted as a service.
+ *
+ * This is useful when you want to store a callable or value as a service. So the
+ * value is not a factory.
+ *
+ * @param mixed $callable
+ * A callable to protect from being evaluated
+ *
+ * @return Closure The protected closure
+ */
+ public static function value($value)
+ {
+ return function () use ($value) {
+ return $value;
+ };
+ }
+}
diff --git a/src/Exception/ExpectedInvokableException.php b/src/Exception/ExpectedInvokableException.php
new file mode 100644
index 0000000..a0ce028
--- /dev/null
+++ b/src/Exception/ExpectedInvokableException.php
@@ -0,0 +1,13 @@
+
+ */
+class ExpectedInvokableException extends \InvalidArgumentException implements ContainerExceptionInterface
+{
+}
diff --git a/src/Exception/FrozenServiceException.php b/src/Exception/FrozenServiceException.php
new file mode 100644
index 0000000..a45de49
--- /dev/null
+++ b/src/Exception/FrozenServiceException.php
@@ -0,0 +1,24 @@
+
+ */
+class FrozenServiceException extends RuntimeException implements ContainerExceptionInterface
+{
+
+ /**
+ *
+ * @param string $id
+ * Identifier of the frozen service
+ */
+ public function __construct($id)
+ {
+ parent::__construct(sprintf('Cannot override frozen service "%s".', $id));
+ }
+}
diff --git a/src/Exception/InvalidServiceIdentifierException.php b/src/Exception/InvalidServiceIdentifierException.php
new file mode 100644
index 0000000..ddb589c
--- /dev/null
+++ b/src/Exception/InvalidServiceIdentifierException.php
@@ -0,0 +1,24 @@
+
+ */
+class InvalidServiceIdentifierException extends InvalidArgumentException implements NotFoundExceptionInterface
+{
+
+ /**
+ *
+ * @param string $id
+ * The invalid identifier
+ */
+ public function __construct($id)
+ {
+ parent::__construct(sprintf('Identifier "%s" does not contain an object definition.', $id));
+ }
+}
diff --git a/src/Exception/InvocationException.php b/src/Exception/InvocationException.php
new file mode 100644
index 0000000..0b16f6d
--- /dev/null
+++ b/src/Exception/InvocationException.php
@@ -0,0 +1,11 @@
+
+ */
+class InvocationException extends \Exception
+{
+}
diff --git a/src/Exception/NotCallableException.php b/src/Exception/NotCallableException.php
new file mode 100644
index 0000000..1651164
--- /dev/null
+++ b/src/Exception/NotCallableException.php
@@ -0,0 +1,30 @@
+
+ */
+class NotCallableException extends InvocationException
+{
+ /**
+ * @param mixed $value
+ */
+ public static function fromInvalidCallable($value, bool $containerEntry = false): self
+ {
+ if (is_object($value)) {
+ $message = sprintf('Instance of %s is not a callable', get_class($value));
+ } elseif (is_array($value) && isset($value[0], $value[1])) {
+ $class = is_object($value[0]) ? get_class($value[0]) : $value[0];
+ $extra = method_exists($class, '__call') ? ' A __call() method exists but magic methods are not supported.' : '';
+ $message = sprintf('%s::%s() is not a callable.%s', $class, $value[1], $extra);
+ } elseif ($containerEntry) {
+ $message = var_export($value, true) . ' is neither a callable nor a valid container entry';
+ } else {
+ $message = var_export($value, true) . ' is not a callable';
+ }
+
+ return new self($message);
+ }
+}
diff --git a/src/Exception/NotEnoughParametersException.php b/src/Exception/NotEnoughParametersException.php
new file mode 100644
index 0000000..8244da5
--- /dev/null
+++ b/src/Exception/NotEnoughParametersException.php
@@ -0,0 +1,11 @@
+
+ */
+class NotEnoughParametersException extends InvocationException
+{
+}
diff --git a/src/Exception/UnknownIdentifierException.php b/src/Exception/UnknownIdentifierException.php
new file mode 100644
index 0000000..524bc83
--- /dev/null
+++ b/src/Exception/UnknownIdentifierException.php
@@ -0,0 +1,23 @@
+
+ */
+class UnknownIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface
+{
+
+ /**
+ *
+ * @param string $id
+ * The unknown identifier
+ */
+ public function __construct($id)
+ {
+ parent::__construct(\sprintf('Identifier "%s" is not defined.', $id));
+ }
+}
diff --git a/src/Invoker.php b/src/Invoker.php
new file mode 100644
index 0000000..4dae263
--- /dev/null
+++ b/src/Invoker.php
@@ -0,0 +1,116 @@
+
+ */
+class Invoker
+{
+
+ /**
+ *
+ * @var CallableResolver|null
+ */
+ private ?CallableResolver $callableResolver = null;
+
+ /**
+ *
+ * @var ParameterResolver
+ */
+ private ParameterResolver $parameterResolver;
+
+ /**
+ *
+ * @var ContainerInterface|null
+ */
+ private $container;
+
+ public function __construct(?ParameterResolver $parameterResolver = null, ?ContainerInterface $container = null)
+ {
+ $this->parameterResolver = $parameterResolver ?: $this->createParameterResolver();
+
+ $this->container = $container;
+ if ($container) {
+ $this->callableResolver = new CallableResolver($container);
+ }
+ }
+
+ /**
+ *
+ * {@inheritdoc}
+ */
+ public function call($callable, array $parameters = [])
+ {
+ if ($this->callableResolver) {
+ $callable = $this->callableResolver->resolve($callable);
+ }
+
+ if (! is_callable($callable)) {
+ throw new NotCallableException(sprintf('%s is not a callable', is_object($callable) ? 'Instance of ' . get_class($callable) : var_export($callable, true)));
+ }
+
+ $callableReflection = CallableReflection::create($callable);
+
+ $args = $this->parameterResolver->getParameters($callableReflection, $parameters, array());
+
+ // Sort by array key because call_user_func_array ignores numeric keys
+ ksort($args);
+
+ // Check all parameters are resolved
+ $diff = array_diff_key($callableReflection->getParameters(), $args);
+ if (! empty($diff)) {
+ /** @var \ReflectionParameter $parameter */
+ $parameter = reset($diff);
+ throw new NotEnoughParametersException(sprintf('Unable to invoke the callable because no value was given for parameter %d ($%s)', $parameter->getPosition() + 1, $parameter->name));
+ }
+
+ return call_user_func_array($callable, $args);
+ }
+
+ /**
+ * Create the default parameter resolver.
+ */
+ private function createParameterResolver(): ParameterResolver
+ {
+ return new ResolverChain(array(
+ new NumericArrayResolver(),
+ new AssociativeArrayResolver(),
+ new DefaultValueResolver()
+ ));
+ }
+
+ /**
+ *
+ * @return ParameterResolver By default it's a ResolverChain
+ */
+ public function getParameterResolver(): ParameterResolver
+ {
+ return $this->parameterResolver;
+ }
+
+ public function getContainer(): ?ContainerInterface
+ {
+ return $this->container;
+ }
+
+ /**
+ *
+ * @return CallableResolver|null Returns null if no container was given in the constructor.
+ */
+ public function getCallableResolver(): ?CallableResolver
+ {
+ return $this->callableResolver;
+ }
+}
\ No newline at end of file
diff --git a/src/ParameterResolver/AssociativeArrayResolver.php b/src/ParameterResolver/AssociativeArrayResolver.php
new file mode 100644
index 0000000..842c31e
--- /dev/null
+++ b/src/ParameterResolver/AssociativeArrayResolver.php
@@ -0,0 +1,38 @@
+call($callable, ['foo' => 'bar'])` will inject the string `'bar'`
+ * in the parameter named `$foo`.
+ *
+ * Parameters that are not indexed by a string are ignored.
+ *
+ * @author Mostafa Barmshory
+ */
+class AssociativeArrayResolver implements ParameterResolver
+{
+ public function getParameters(
+ ReflectionFunctionAbstract $reflection,
+ array $providedParameters,
+ array $resolvedParameters
+ ) {
+ $parameters = $reflection->getParameters();
+
+ // Skip parameters already resolved
+ if (! empty($resolvedParameters)) {
+ $parameters = array_diff_key($parameters, $resolvedParameters);
+ }
+
+ foreach ($parameters as $index => $parameter) {
+ if (array_key_exists($parameter->name, $providedParameters)) {
+ $resolvedParameters[$index] = $providedParameters[$parameter->name];
+ }
+ }
+
+ return $resolvedParameters;
+ }
+}
diff --git a/src/ParameterResolver/Container/ParameterNameContainerResolver.php b/src/ParameterResolver/Container/ParameterNameContainerResolver.php
new file mode 100644
index 0000000..a6d5087
--- /dev/null
+++ b/src/ParameterResolver/Container/ParameterNameContainerResolver.php
@@ -0,0 +1,50 @@
+
+ */
+class ParameterNameContainerResolver implements ParameterResolver
+{
+ /**
+ * @var ContainerInterface
+ */
+ private $container;
+
+ /**
+ * @param ContainerInterface $container The container to get entries from.
+ */
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ public function getParameters(
+ ReflectionFunctionAbstract $reflection,
+ array $providedParameters,
+ array $resolvedParameters
+ ) {
+ $parameters = $reflection->getParameters();
+
+ // Skip parameters already resolved
+ if (! empty($resolvedParameters)) {
+ $parameters = array_diff_key($parameters, $resolvedParameters);
+ }
+
+ foreach ($parameters as $index => $parameter) {
+ $name = $parameter->name;
+
+ if ($name && $this->container->has($name)) {
+ $resolvedParameters[$index] = $this->container->get($name);
+ }
+ }
+
+ return $resolvedParameters;
+ }
+}
diff --git a/src/ParameterResolver/Container/TypeHintContainerResolver.php b/src/ParameterResolver/Container/TypeHintContainerResolver.php
new file mode 100644
index 0000000..3061ab7
--- /dev/null
+++ b/src/ParameterResolver/Container/TypeHintContainerResolver.php
@@ -0,0 +1,65 @@
+
+ */
+class TypeHintContainerResolver implements ParameterResolver
+{
+ /**
+ * @var ContainerInterface
+ */
+ private $container;
+
+ /**
+ * @param ContainerInterface $container The container to get entries from.
+ */
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ public function getParameters(
+ ReflectionFunctionAbstract $reflection,
+ array $providedParameters,
+ array $resolvedParameters
+ ) {
+ $parameters = $reflection->getParameters();
+
+ // Skip parameters already resolved
+ if (! empty($resolvedParameters)) {
+ $parameters = array_diff_key($parameters, $resolvedParameters);
+ }
+
+ foreach ($parameters as $index => $parameter) {
+ $parameterType = $parameter->getType();
+ if (!$parameterType) {
+ // No type
+ continue;
+ }
+ if ($parameterType->isBuiltin()) {
+ // Primitive types are not supported
+ continue;
+ }
+ if (!$parameterType instanceof ReflectionNamedType) {
+ // Union types are not supported
+ continue;
+ }
+
+ $parameterClass = $parameterType->getName();
+
+ if ($this->container->has($parameterClass)) {
+ $resolvedParameters[$index] = $this->container->get($parameterClass);
+ }
+ }
+
+ return $resolvedParameters;
+ }
+}
diff --git a/src/ParameterResolver/DefaultValueResolver.php b/src/ParameterResolver/DefaultValueResolver.php
new file mode 100644
index 0000000..51ada64
--- /dev/null
+++ b/src/ParameterResolver/DefaultValueResolver.php
@@ -0,0 +1,39 @@
+
+ */
+class DefaultValueResolver implements ParameterResolver
+{
+ public function getParameters(
+ ReflectionFunctionAbstract $reflection,
+ array $providedParameters,
+ array $resolvedParameters
+ ) {
+ $parameters = $reflection->getParameters();
+
+ // Skip parameters already resolved
+ if (! empty($resolvedParameters)) {
+ $parameters = array_diff_key($parameters, $resolvedParameters);
+ }
+
+ foreach ($parameters as $index => $parameter) {
+ /** @var \ReflectionParameter $parameter */
+ if ($parameter->isOptional()) {
+ try {
+ $resolvedParameters[$index] = $parameter->getDefaultValue();
+ } catch (ReflectionException $e) {
+ // Can't get default values from PHP internal classes and functions
+ }
+ }
+ }
+
+ return $resolvedParameters;
+ }
+}
diff --git a/src/ParameterResolver/NumericArrayResolver.php b/src/ParameterResolver/NumericArrayResolver.php
new file mode 100644
index 0000000..2effa63
--- /dev/null
+++ b/src/ParameterResolver/NumericArrayResolver.php
@@ -0,0 +1,38 @@
+call($callable, ['foo', 'bar'])` will simply resolve the parameters
+ * to `['foo', 'bar']`.
+ *
+ * Parameters that are not indexed by a number (i.e. parameter position)
+ * will be ignored.
+ *
+ * @author Mostafa Barmshory
+ */
+class NumericArrayResolver implements ParameterResolver
+{
+ public function getParameters(
+ ReflectionFunctionAbstract $reflection,
+ array $providedParameters,
+ array $resolvedParameters
+ ) {
+ // Skip parameters already resolved
+ if (! empty($resolvedParameters)) {
+ $providedParameters = array_diff_key($providedParameters, $resolvedParameters);
+ }
+
+ foreach ($providedParameters as $key => $value) {
+ if (is_int($key)) {
+ $resolvedParameters[$key] = $value;
+ }
+ }
+
+ return $resolvedParameters;
+ }
+}
diff --git a/src/ParameterResolver/ParameterResolver.php b/src/ParameterResolver/ParameterResolver.php
new file mode 100644
index 0000000..cf0b089
--- /dev/null
+++ b/src/ParameterResolver/ParameterResolver.php
@@ -0,0 +1,32 @@
+
+ */
+interface ParameterResolver
+{
+ /**
+ * Resolves the parameters to use to call the callable.
+ *
+ * `$resolvedParameters` contains parameters that have already been resolved.
+ *
+ * Each ParameterResolver must resolve parameters that are not already
+ * in `$resolvedParameters`. That allows to chain multiple ParameterResolver.
+ *
+ * @param ReflectionFunctionAbstract $reflection Reflection object for the callable.
+ * @param array $providedParameters Parameters provided by the caller.
+ * @param array $resolvedParameters Parameters resolved (indexed by parameter position).
+ *
+ * @return array
+ */
+ public function getParameters(
+ ReflectionFunctionAbstract $reflection,
+ array $providedParameters,
+ array $resolvedParameters
+ );
+}
diff --git a/src/ParameterResolver/ResolverChain.php b/src/ParameterResolver/ResolverChain.php
new file mode 100644
index 0000000..57f3e65
--- /dev/null
+++ b/src/ParameterResolver/ResolverChain.php
@@ -0,0 +1,65 @@
+
+ */
+class ResolverChain implements ParameterResolver
+{
+ /**
+ * @var ParameterResolver[]
+ */
+ private $resolvers;
+
+ public function __construct(array $resolvers = array())
+ {
+ $this->resolvers = $resolvers;
+ }
+
+ public function getParameters(
+ ReflectionFunctionAbstract $reflection,
+ array $providedParameters,
+ array $resolvedParameters
+ ) {
+ $reflectionParameters = $reflection->getParameters();
+
+ foreach ($this->resolvers as $resolver) {
+ $resolvedParameters = $resolver->getParameters(
+ $reflection,
+ $providedParameters,
+ $resolvedParameters
+ );
+
+ $diff = array_diff_key($reflectionParameters, $resolvedParameters);
+ if (empty($diff)) {
+ // Stop traversing: all parameters are resolved
+ return $resolvedParameters;
+ }
+ }
+
+ return $resolvedParameters;
+ }
+
+ /**
+ * Push a parameter resolver after the ones already registered.
+ */
+ public function appendResolver(ParameterResolver $resolver): void
+ {
+ $this->resolvers[] = $resolver;
+ }
+
+ /**
+ * Insert a parameter resolver before the ones already registered.
+ */
+ public function prependResolver(ParameterResolver $resolver): void
+ {
+ array_unshift($this->resolvers, $resolver);
+ }
+}
diff --git a/src/ParameterResolver/TypeHintResolver.php b/src/ParameterResolver/TypeHintResolver.php
new file mode 100644
index 0000000..182f486
--- /dev/null
+++ b/src/ParameterResolver/TypeHintResolver.php
@@ -0,0 +1,52 @@
+
+ */
+class TypeHintResolver implements ParameterResolver
+{
+ public function getParameters(
+ ReflectionFunctionAbstract $reflection,
+ array $providedParameters,
+ array $resolvedParameters
+ ) {
+ $parameters = $reflection->getParameters();
+
+ // Skip parameters already resolved
+ if (! empty($resolvedParameters)) {
+ $parameters = array_diff_key($parameters, $resolvedParameters);
+ }
+
+ foreach ($parameters as $index => $parameter) {
+ $parameterType = $parameter->getType();
+ if (!$parameterType) {
+ // No type
+ continue;
+ }
+ if ($parameterType->isBuiltin()) {
+ // Primitive types are not supported
+ continue;
+ }
+ if (!$parameterType instanceof ReflectionNamedType) {
+ // Union types are not supported
+ continue;
+ }
+
+ $parameterClass = $parameterType->getName();
+
+ if (array_key_exists($parameterClass, $providedParameters)) {
+ $resolvedParameters[$index] = $providedParameters[$parameterClass];
+ }
+ }
+
+ return $resolvedParameters;
+ }
+}
diff --git a/src/Reflection/CallableReflection.php b/src/Reflection/CallableReflection.php
new file mode 100644
index 0000000..fb97998
--- /dev/null
+++ b/src/Reflection/CallableReflection.php
@@ -0,0 +1,62 @@
+
+ */
+class CallableReflection
+{
+ /**
+ * @param callable $callable
+ *
+ * @throws NotCallableException|\ReflectionException
+ *
+ * TODO Use the `callable` type-hint once support for PHP 5.4 and up.
+ */
+ public static function create($callable): ReflectionFunctionAbstract
+ {
+ // Closure
+ if ($callable instanceof \Closure) {
+ return new ReflectionFunction($callable);
+ }
+
+ // Array callable
+ if (is_array($callable)) {
+ [$class, $method] = $callable;
+
+ if (! method_exists($class, $method)) {
+ throw NotCallableException::fromInvalidCallable($callable);
+ }
+
+ return new ReflectionMethod($class, $method);
+ }
+
+ // Callable object (i.e. implementing __invoke())
+ if (is_object($callable) && method_exists($callable, '__invoke')) {
+ return new ReflectionMethod($callable, '__invoke');
+ }
+
+ // Callable class (i.e. implementing __invoke())
+ if (is_string($callable) && class_exists($callable) && method_exists($callable, '__invoke')) {
+ return new ReflectionMethod($callable, '__invoke');
+ }
+
+ // Standard function
+ if (is_string($callable) && function_exists($callable)) {
+ return new ReflectionFunction($callable);
+ }
+
+ throw new NotCallableException(sprintf(
+ '%s is not a callable',
+ is_string($callable) ? $callable : 'Instance of ' . get_class($callable)
+ ));
+ }
+}
diff --git a/src/ServiceIterator.php b/src/ServiceIterator.php
new file mode 100644
index 0000000..3ced8f6
--- /dev/null
+++ b/src/ServiceIterator.php
@@ -0,0 +1,48 @@
+
+ */
+final class ServiceIterator implements Iterator
+{
+
+ private $container;
+
+ private $ids;
+
+ public function __construct(Container $container, array $ids)
+ {
+ $this->container = $container;
+ $this->ids = $ids;
+ }
+
+ public function rewind()
+ {
+ reset($this->ids);
+ }
+
+ public function current()
+ {
+ return $this->container[current($this->ids)];
+ }
+
+ public function key()
+ {
+ return current($this->ids);
+ }
+
+ public function next()
+ {
+ next($this->ids);
+ }
+
+ public function valid()
+ {
+ return null !== key($this->ids);
+ }
+}
diff --git a/src/ServiceLocator.php b/src/ServiceLocator.php
new file mode 100644
index 0000000..10f8640
--- /dev/null
+++ b/src/ServiceLocator.php
@@ -0,0 +1,56 @@
+
+ */
+class ServiceLocator implements ContainerInterface
+{
+
+ private $container;
+
+ private $aliases = [];
+
+ /**
+ *
+ * @param Container $container
+ * The Container instance used to locate services
+ * @param array $ids
+ * Array of service ids that can be located. String keys can be used to define aliases
+ */
+ public function __construct(Container $container, array $ids)
+ {
+ $this->container = $container;
+
+ foreach ($ids as $key => $id) {
+ $this->aliases[\is_int($key) ? $id : $key] = $id;
+ }
+ }
+
+ /**
+ *
+ * {@inheritdoc}
+ */
+ public function get($id)
+ {
+ if (! isset($this->aliases[$id])) {
+ throw new UnknownIdentifierException($id);
+ }
+
+ return $this->container[$this->aliases[$id]];
+ }
+
+ /**
+ *
+ * {@inheritdoc}
+ */
+ public function has($id)
+ {
+ return isset($this->aliases[$id]) && isset($this->container[$this->aliases[$id]]);
+ }
+}
diff --git a/tests/CallabelResolverTest.php b/tests/CallabelResolverTest.php
new file mode 100644
index 0000000..18c7f3a
--- /dev/null
+++ b/tests/CallabelResolverTest.php
@@ -0,0 +1,201 @@
+container = new Container();
+ $this->resolver = new CallableResolver($this->container);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function resolves_function()
+ {
+ $result = $this->resolver->resolve('strlen');
+
+ $this->assertSame(strlen('Hello world!'), $result('Hello world!'));
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function resolves_namespaced_function()
+ {
+ $result = $this->resolver->resolve(__NAMESPACE__ . '\foo');
+
+ $this->assertEquals('bar', $result());
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function resolves_callable_from_container()
+ {
+ $callable = function () {};
+ $this->container['thing-to-call'] = function () use (&$callable) {
+ return $callable;
+ };
+
+ $this->assertSame($callable, $this->resolver->resolve('thing-to-call'));
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function resolves_invokable_class()
+ {
+ $callable = new CallableSpy();
+ $this->container[CallableSpy::class] = function () use (&$callable) {
+ return $callable;
+ };
+
+ $this->assertSame($callable, $this->resolver->resolve(CallableSpy::class));
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function resolve_array_method_call()
+ {
+ $fixture = new InvokerTestFixture();
+ $this->container[InvokerTestFixture::class] = function () use (&$fixture) {
+ return $fixture;
+ };
+
+ $result = $this->resolver->resolve(array(
+ InvokerTestFixture::class,
+ 'foo'
+ ));
+
+ $result();
+ $this->assertTrue($fixture->wasCalled);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function resolve_string_method_call()
+ {
+ $fixture = new InvokerTestFixture();
+ $this->container[InvokerTestFixture::class] = function () use (&$fixture) {
+ return $fixture;
+ };
+
+ $result = $this->resolver->resolve(InvokerTestFixture::class . '::foo');
+
+ $result();
+ $this->assertTrue($fixture->wasCalled);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function resolves_array_method_call_with_service()
+ {
+ $fixture = new InvokerTestFixture();
+ $this->container['thing-to-call'] = function () use (&$fixture) {
+ return $fixture;
+ };
+
+ $result = $this->resolver->resolve(array(
+ 'thing-to-call',
+ 'foo'
+ ));
+
+ $result();
+ $this->assertTrue($fixture->wasCalled);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function resolves_string_method_call_with_service()
+ {
+ $fixture = new InvokerTestFixture();
+ $this->container['thing-to-call'] = function () use (&$fixture) {
+ return $fixture;
+ };
+
+ $result = $this->resolver->resolve('thing-to-call::foo');
+
+ $result();
+ $this->assertTrue($fixture->wasCalled);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function throws_resolving_non_callable_from_container()
+ {
+ $this->expectExceptionMessage("'foo' is neither a callable nor a valid container entry");
+ $this->expectException(NotCallableException::class);
+ $resolver = new CallableResolver(new Container());
+ $resolver->resolve('foo');
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function handles_objects_correctly_in_exception_message()
+ {
+ $this->expectExceptionMessage("Instance of stdClass is not a callable");
+ $this->expectException(NotCallableException::class);
+ $resolver = new CallableResolver(new Container());
+ $resolver->resolve(new stdClass());
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function handles_method_calls_correctly_in_exception_message()
+ {
+ $this->expectExceptionMessage("stdClass::test() is not a callable");
+ $this->expectException(NotCallableException::class);
+ $resolver = new CallableResolver(new Container());
+ $resolver->resolve(array(
+ new stdClass(),
+ 'test'
+ ));
+ }
+}
+
+function foo()
+{
+ return 'bar';
+}
\ No newline at end of file
diff --git a/tests/ContainerTest.php b/tests/ContainerTest.php
new file mode 100644
index 0000000..3ab6c52
--- /dev/null
+++ b/tests/ContainerTest.php
@@ -0,0 +1,616 @@
+
+ */
+class ContainerTest extends TestCase
+{
+
+ public function testWithString()
+ {
+ $container = new Container();
+ $container['param'] = Container::value('value');
+
+ $this->assertEquals('value', $container['param']);
+ }
+
+ public function testWithClosure()
+ {
+ $container = new Container();
+ $container['service'] = function () {
+ return new Fixtures\Service();
+ };
+
+ $this->assertInstanceOf('Pluf\Tests\Fixtures\Service', $container['service']);
+ }
+
+ public function testServicesShouldBeDifferent()
+ {
+ $container = new Container();
+ $container['item'] = Container::factory(function () {
+ return new Fixtures\Service();
+ });
+
+ $serviceOne = $container['item'];
+ $this->assertInstanceOf('Pluf\Tests\Fixtures\Service', $serviceOne);
+
+ $serviceTwo = $container['item'];
+ $this->assertInstanceOf('Pluf\Tests\Fixtures\Service', $serviceTwo);
+
+ $this->assertNotSame($serviceOne, $serviceTwo);
+ }
+
+ public function testShouldPassContainerAsParameter()
+ {
+ $container = new Container();
+ // Default is factory
+ $container['factory'] = function () {
+ return new Fixtures\Service();
+ };
+
+ // Container is registerd by default
+ // $container['container'] = function ($container) {
+ // return $container;
+ // };
+
+ $this->assertNotSame($container, $container['factory']);
+ $this->assertSame($container, $container['container']);
+ }
+
+ public function testIsset()
+ {
+ $container = new Container();
+ $container['param'] = Container::value('value');
+ $container['service'] = Container::service(function () {
+ return new Fixtures\Service();
+ });
+
+ $container['null'] = Container::value(null);
+
+ $this->assertTrue(isset($container['param']));
+ $this->assertTrue(isset($container['service']));
+ $this->assertTrue(isset($container['null']));
+ $this->assertFalse(isset($container['non_existent']));
+ }
+
+ // public function testConstructorInjection()
+ // {
+ // $params = [
+ // 'param' => 'value'
+ // ];
+ // $container = new Container();
+
+ // $this->assertSame($params['param'], $container['param']);
+ // }
+ public function testOffsetGetValidatesKeyIsPresent()
+ {
+ $this->expectException(UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $container = new Container();
+ echo $container['foo'];
+ }
+
+ /**
+ *
+ * @group legacy
+ */
+ public function testLegacyOffsetGetValidatesKeyIsPresent()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $container = new Container();
+ echo $container['foo'];
+ }
+
+ public function testOffsetGetHonorsNullValues()
+ {
+ $container = new Container();
+ $container['foo'] = Container::value(null);
+ $this->assertNull($container['foo']);
+ }
+
+ public function testUnset()
+ {
+ $container = new Container();
+ $container['param'] = Container::value('value');
+ $container['service'] = Container::service(function () {
+ return new Fixtures\Service();
+ });
+
+ unset($container['param'], $container['service']);
+ $this->assertFalse(isset($container['param']));
+ $this->assertFalse(isset($container['service']));
+ }
+
+ /**
+ *
+ * @dataProvider serviceDefinitionProvider
+ */
+ public function testShare($service)
+ {
+ $container = new Container();
+ $container['shared_service'] = Container::service($service);
+ $container['value'] = Container::value('value');
+
+ $serviceOne = $container['shared_service'];
+ $this->assertInstanceOf('Pluf\Tests\Fixtures\Service', $serviceOne);
+
+ $serviceTwo = $container['shared_service'];
+ $this->assertInstanceOf('Pluf\Tests\Fixtures\Service', $serviceTwo);
+
+ $this->assertSame($serviceOne, $serviceTwo);
+ }
+
+ /**
+ *
+ * @dataProvider serviceDefinitionProvider
+ */
+ public function testProtect($service)
+ {
+ $container = new Container();
+ $container['protected'] = Container::value($service);
+
+ $this->assertSame($service, $container['protected']);
+ }
+
+ public function testGlobalFunctionNameAsParameterValue()
+ {
+ $container = new Container();
+ $container['global_function'] = Container::value('strlen');
+ $this->assertSame('strlen', $container['global_function']);
+ }
+
+ public function testRaw()
+ {
+ $container = new Container();
+ $container['factory'] = $definition = function () {
+ return 'foo';
+ };
+ $this->assertSame($definition, $container->raw('factory'));
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function testRawValidatesKeyIsPresent()
+ {
+ $this->expectException(UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $container = new Container();
+ $container->raw('foo');
+ }
+
+ /**
+ *
+ * @group legacy
+ */
+ public function testLegacyRawValidatesKeyIsPresent()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $container = new Container();
+ $container->raw('foo');
+ }
+
+ // /**
+ // *
+ // * @dataProvider serviceDefinitionProvider
+ // */
+ // public function testExtend($service)
+ // {
+ // $container = new Container();
+ // $container['shared_service'] = Container::service(function () {
+ // return new Fixtures\Service();
+ // });
+ // $container['factory_service'] = function () {
+ // return new Fixtures\Service();
+ // };
+
+ // $container->extend('shared_service', $service);
+ // $serviceOne = $container['shared_service'];
+ // $this->assertInstanceOf('Pluf\Tests\Fixtures\Service', $serviceOne);
+ // $serviceTwo = $container['shared_service'];
+ // $this->assertInstanceOf('Pluf\Tests\Fixtures\Service', $serviceTwo);
+ // $this->assertSame($serviceOne, $serviceTwo);
+ // $this->assertSame($serviceOne->value, $serviceTwo->value);
+
+ // $container->extend('factory_service', $service);
+ // $serviceOne = $container['factory_service'];
+ // $this->assertInstanceOf('Pluf\Tests\Fixtures\Service', $serviceOne);
+ // $serviceTwo = $container['factory_service'];
+ // $this->assertInstanceOf('Pluf\Tests\Fixtures\Service', $serviceTwo);
+ // $this->assertNotSame($serviceOne, $serviceTwo);
+ // $this->assertNotSame($serviceOne->value, $serviceTwo->value);
+ // }
+
+ // public function testExtendDoesNotLeakWithFactories()
+ // {
+ // if (\extension_loaded('pimple')) {
+ // $this->markTestSkipped('Pimple extension does not support this test');
+ // }
+ // $container = new Container();
+
+ // $container['foo'] = $container->factory(function () {
+ // return;
+ // });
+ // $container['foo'] = $container->extend('foo', function ($foo, $container) {
+ // return;
+ // });
+ // unset($container['foo']);
+
+ // $p = new \ReflectionProperty($container, 'values');
+ // $p->setAccessible(true);
+ // $this->assertEmpty($p->getValue($container));
+
+ // $p = new \ReflectionProperty($container, 'factories');
+ // $p->setAccessible(true);
+ // $this->assertCount(0, $p->getValue($container));
+ // }
+
+ // public function testExtendValidatesKeyIsPresent()
+ // {
+ // $this->expectException(UnknownIdentifierException::class);
+ // $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ // $container = new Container();
+ // $container->extend('foo', function () {});
+ // }
+
+ // /**
+ // *
+ // * @group legacy
+ // */
+ // public function testLegacyExtendValidatesKeyIsPresent()
+ // {
+ // $this->expectException(\InvalidArgumentException::class);
+ // $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ // $container = new Container();
+ // $container->extend('foo', function () {});
+ // }
+ public function testKeys()
+ {
+ $containr = new Container();
+ $containr['foo'] = Container::value(123);
+ $containr['bar'] = Container::value(123);
+
+ $this->assertEquals([
+ 'container',
+ 'foo',
+ 'bar'
+ ], $containr->keys());
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function settingAnInvokableObjectShouldTreatItAsFactory()
+ {
+ $container = new Container();
+ $container['invokable'] = new Fixtures\Invokable();
+
+ $this->assertInstanceOf('Pluf\Tests\Fixtures\Service', $container['invokable']);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function settingNonInvokableObjectShouldTreatItAsParameter()
+ {
+ $container = new Container();
+ $container['non_invokable'] = Container::value(new Fixtures\NonInvokable());
+
+ $this->assertInstanceOf('Pluf\Tests\Fixtures\NonInvokable', $container['non_invokable']);
+ }
+
+ /**
+ *
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testFactoryFailsForInvalidServiceDefinitions($service)
+ {
+ $this->expectException(ExpectedInvokableException::class);
+// $this->expectExceptionMessage('Service definition is not a Closure or invokable object.');
+
+ $container = new Container();
+ $container['key'] = $service;
+ return $container;
+ }
+
+ /**
+ *
+ * @group legacy
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testLegacyFactoryFailsForInvalidServiceDefinitions($service)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+// $this->expectExceptionMessage('Service definition is not a Closure or invokable object.');
+
+ $container = new Container();
+ $container['key'] = $service;
+ return $container;
+ }
+
+ /**
+ *
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testProtectFailsForInvalidServiceDefinitions($service)
+ {
+ $this->expectException(ExpectedInvokableException::class);
+// $this->expectExceptionMessage('Factory is not a Closure or invokable object.');
+
+ $container = new Container();
+ $container['key'] = $service;
+ return $container;
+ }
+
+ /**
+ *
+ * @group legacy
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testLegacyProtectFailsForInvalidServiceDefinitions($service)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+// $this->expectExceptionMessage('Callable is not a Closure or invokable object.');
+
+ $container = new Container();
+ $container['key'] = $service;
+ return $container;
+ }
+
+// /**
+// *
+// * @dataProvider badServiceDefinitionProvider
+// */
+// public function testExtendFailsForKeysNotContainingServiceDefinitions($service)
+// {
+// $this->expectException(InvalidServiceIdentifierException::class);
+// $this->expectExceptionMessage('Identifier "foo" does not contain an object definition.');
+
+// $container = new Container();
+// $container['foo'] = $service;
+// $container->extend('foo', function () {});
+// }
+
+// /**
+// *
+// * @group legacy
+// * @dataProvider badServiceDefinitionProvider
+// */
+// public function testLegacyExtendFailsForKeysNotContainingServiceDefinitions($service)
+// {
+// $this->expectException(\InvalidArgumentException::class);
+// $this->expectExceptionMessage('Identifier "foo" does not contain an object definition.');
+
+// $container = new Container();
+// $container['foo'] = $service;
+// $container->extend('foo', function () {});
+// }
+
+// /**
+// *
+// * @group legacy
+// * @expectedDeprecation How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "foo" should be protected?
+// */
+// public function testExtendingProtectedClosureDeprecation()
+// {
+// $container = new Container();
+// $container['foo'] = $container->protect(function () {
+// return 'bar';
+// });
+
+// $container->extend('foo', function ($value) {
+// return $value . '-baz';
+// });
+
+// $this->assertSame('bar-baz', $container['foo']);
+// }
+
+// /**
+// *
+// * @dataProvider badServiceDefinitionProvider
+// */
+// public function testExtendFailsForInvalidServiceDefinitions($service)
+// {
+// $this->expectException(ExpectedInvokableException::class);
+// $this->expectExceptionMessage('Extension service definition is not a Closure or invokable object.');
+
+// $container = new Container();
+// $container['foo'] = function () {};
+// $container->extend('foo', $service);
+// }
+
+// /**
+// *
+// * @group legacy
+// * @dataProvider badServiceDefinitionProvider
+// */
+// public function testLegacyExtendFailsForInvalidServiceDefinitions($service)
+// {
+// $this->expectException(\InvalidArgumentException::class);
+// $this->expectExceptionMessage('Extension service definition is not a Closure or invokable object.');
+
+// $container = new Container();
+// $container['foo'] = function () {};
+// $container->extend('foo', $service);
+// }
+
+// public function testExtendFailsIfFrozenServiceIsNonInvokable()
+// {
+// $this->expectException(FrozenServiceException::class);
+// $this->expectExceptionMessage('Cannot override frozen service "foo".');
+
+// $container = new Container();
+// $container['foo'] = function () {
+// return new Fixtures\NonInvokable();
+// };
+// $foo = $container['foo'];
+
+// $container->extend('foo', function () {});
+// }
+
+// public function testExtendFailsIfFrozenServiceIsInvokable()
+// {
+// $this->expectException(FrozenServiceException::class);
+// $this->expectExceptionMessage('Cannot override frozen service "foo".');
+
+// $container = new Container();
+// $container['foo'] = function () {
+// return new Fixtures\Invokable();
+// };
+// $foo = $container['foo'];
+
+// $container->extend('foo', function () {});
+// }
+
+ /**
+ * Provider for invalid service definitions.
+ */
+ public function badServiceDefinitionProvider()
+ {
+ return [
+ [
+ 123
+ ],
+ [
+ new Fixtures\NonInvokable()
+ ]
+ ];
+ }
+
+ /**
+ * Provider for service definitions.
+ */
+ public function serviceDefinitionProvider()
+ {
+ return [
+ [
+ function ($value) {
+ $service = new Fixtures\Service();
+ $service->value = $value;
+
+ return $service;
+ }
+ ],
+ [
+ new Fixtures\Invokable()
+ ]
+ ];
+ }
+
+ public function testDefiningNewServiceAfterFreeze()
+ {
+ $container = new Container();
+ $container['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $container['foo'];
+
+ $container['bar'] = function () {
+ return 'bar';
+ };
+ $this->assertSame('bar', $container['bar']);
+ }
+
+ public function testOverridingServiceAfterFreeze()
+ {
+ $this->expectException(FrozenServiceException::class);
+ $this->expectExceptionMessage('Cannot override frozen service "foo".');
+
+ $container = new Container();
+ $container['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $container['foo'];
+
+ $container['foo'] = function () {
+ return 'bar';
+ };
+ }
+
+ /**
+ *
+ * @group legacy
+ */
+ public function testLegacyOverridingServiceAfterFreeze()
+ {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Cannot override frozen service "foo".');
+
+ $container = new Container();
+ $container['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $container['foo'];
+
+ $container['foo'] = function () {
+ return 'bar';
+ };
+ }
+
+ public function testRemovingServiceAfterFreeze()
+ {
+ $container = new Container();
+ $container['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $container['foo'];
+
+ unset($container['foo']);
+ $container['foo'] = function () {
+ return 'bar';
+ };
+ $this->assertSame('bar', $container['foo']);
+ }
+
+// public function testExtendingService()
+// {
+// $container = new Container();
+// $container['foo'] = function () {
+// return 'foo';
+// };
+// $container['foo'] = $container->extend('foo', function ($foo, $app) {
+// return "$foo.bar";
+// });
+// $container['foo'] = $container->extend('foo', function ($foo, $app) {
+// return "$foo.baz";
+// });
+// $this->assertSame('foo.bar.baz', $container['foo']);
+// }
+
+// public function testExtendingServiceAfterOtherServiceFreeze()
+// {
+// $container = new Container();
+// $container['foo'] = function () {
+// return 'foo';
+// };
+// $container['bar'] = function () {
+// return 'bar';
+// };
+// $foo = $container['foo'];
+
+// $container['bar'] = $container->extend('bar', function ($bar, $app) {
+// return "$bar.baz";
+// });
+// $this->assertSame('bar.baz', $container['bar']);
+// }
+}
diff --git a/tests/Fixtures/Invokable.php b/tests/Fixtures/Invokable.php
new file mode 100644
index 0000000..927551d
--- /dev/null
+++ b/tests/Fixtures/Invokable.php
@@ -0,0 +1,19 @@
+
+ *
+ */
+class Invokable
+{
+
+ public function __invoke($value = null)
+ {
+ $service = new Service();
+ $service->value = $value;
+
+ return $service;
+ }
+}
diff --git a/tests/Fixtures/InvokerTestFixture.php b/tests/Fixtures/InvokerTestFixture.php
new file mode 100644
index 0000000..a974d24
--- /dev/null
+++ b/tests/Fixtures/InvokerTestFixture.php
@@ -0,0 +1,20 @@
+
+ *
+ */
+class InvokerTestFixture
+{
+
+ public $wasCalled = false;
+
+ public function foo()
+ {
+ // Use this to make sure we are not called from a static context
+ $this->wasCalled = true;
+ return 'bar';
+ }
+}
\ No newline at end of file
diff --git a/tests/Fixtures/InvokerTestMagicMethodFixture.php b/tests/Fixtures/InvokerTestMagicMethodFixture.php
new file mode 100644
index 0000000..0dbcc1b
--- /dev/null
+++ b/tests/Fixtures/InvokerTestMagicMethodFixture.php
@@ -0,0 +1,24 @@
+
+ *
+ */
+class InvokerTestMagicMethodFixture
+{
+
+ public $wasCalled = false;
+
+ public function __call($name, $args)
+ {
+ if ($name === 'foo') {
+ $this->wasCalled = true;
+ return 'bar';
+ }
+ throw new Exception('Unknown method');
+ }
+}
\ No newline at end of file
diff --git a/tests/Fixtures/InvokerTestStaticFixture.php b/tests/Fixtures/InvokerTestStaticFixture.php
new file mode 100644
index 0000000..3040007
--- /dev/null
+++ b/tests/Fixtures/InvokerTestStaticFixture.php
@@ -0,0 +1,16 @@
+
+ *
+ */
+class InvokerTestStaticFixture
+{
+
+ public static function foo()
+ {
+ return 'bar';
+ }
+}
\ No newline at end of file
diff --git a/tests/Fixtures/NonInvokable.php b/tests/Fixtures/NonInvokable.php
new file mode 100644
index 0000000..0d9582d
--- /dev/null
+++ b/tests/Fixtures/NonInvokable.php
@@ -0,0 +1,14 @@
+
+ *
+ */
+class NonInvokable
+{
+
+ public function __call($a, $b)
+ {}
+}
diff --git a/tests/Fixtures/Service.php b/tests/Fixtures/Service.php
new file mode 100644
index 0000000..c193895
--- /dev/null
+++ b/tests/Fixtures/Service.php
@@ -0,0 +1,12 @@
+
+ */
+class Service
+{
+
+ public $value;
+}
diff --git a/tests/InvokerTest.php b/tests/InvokerTest.php
new file mode 100644
index 0000000..b2fadd1
--- /dev/null
+++ b/tests/InvokerTest.php
@@ -0,0 +1,509 @@
+
+ *
+ */
+class InvokerTest extends TestCase
+{
+
+ /**
+ *
+ * @var Invoker
+ */
+ private $invoker;
+
+ /**
+ *
+ * @var Container
+ */
+ private $container;
+
+ public function setUp(): void
+ {
+ parent::setUp();
+ $this->container = new Container();
+ $this->invoker = new Invoker(null, $this->container);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_invoke_closure()
+ {
+ $callable = new CallableSpy();
+
+ $this->invoker->call($callable);
+
+ $this->assertWasCalled($callable);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_invoke_method()
+ {
+ $fixture = new InvokerTestFixture();
+
+ $this->invoker->call([
+ $fixture,
+ 'foo'
+ ]);
+
+ $this->assertTrue($fixture->wasCalled);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function cannot_invoke_unknown_method()
+ {
+ $this->expectExceptionMessage("Pluf\Tests\Fixtures\InvokerTestFixture::bar() is not a callable.");
+ $this->expectException(NotCallableException::class);
+ $this->invoker->call([
+ new InvokerTestFixture(),
+ 'bar'
+ ]);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function cannot_invoke_magic_method()
+ {
+ $this->expectExceptionMessage("Pluf\Tests\Fixtures\InvokerTestMagicMethodFixture::foo() is not a callable. A __call() method exists but magic methods are not supported.");
+ $this->expectException(NotCallableException::class);
+ $this->invoker->call(array(
+ new InvokerTestMagicMethodFixture(),
+ 'foo'
+ ));
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_invoke_static_method()
+ {
+ $result = $this->invoker->call([
+ InvokerTestStaticFixture::class,
+ 'foo'
+ ]);
+
+ $this->assertEquals('bar', $result);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_invoke_static_method_with_scope_resolution_syntax()
+ {
+ $result = $this->invoker->call('Pluf\Tests\Fixtures\InvokerTestStaticFixture::foo');
+
+ $this->assertEquals('bar', $result);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_return_the_callable_return_value()
+ {
+ $result = $this->invoker->call(function () {
+ return 42;
+ });
+
+ $this->assertEquals(42, $result);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_throw_if_no_value_for_parameter()
+ {
+ $this->expectExceptionMessage('Unable to invoke the callable because no value was given for parameter 2 ($bar)');
+ $this->expectException(NotEnoughParametersException::class);
+ $this->invoker->call(function ($foo, $bar, $baz) {}, [
+ 'foo' => 'foo',
+ 'baz' => 'baz'
+ ]);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_throw_if_no_value_for_parameter_even_with_trailing_optional_parameters()
+ {
+ $this->expectExceptionMessage('Unable to invoke the callable because no value was given for parameter 2 ($bar)');
+ $this->expectException(NotEnoughParametersException::class);
+ $this->invoker->call(function ($foo, $bar, $baz = null) {}, [
+ 'foo' => 'foo',
+ 'baz' => 'baz'
+ ]);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_invoke_callable_with_parameters_indexed_by_position()
+ {
+ $callable = new CallableSpy();
+
+ $this->invoker->call($callable, [
+ 'foo',
+ 'bar'
+ ]);
+
+ $this->assertWasCalledWith($callable, [
+ 'foo',
+ 'bar'
+ ]);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_invoke_callable_with_parameters_indexed_by_name()
+ {
+ $parameters = [
+ 'foo' => 'foo',
+ 'bar' => 'bar'
+ ];
+
+ $result = $this->invoker->call(function ($foo, $bar) {
+ return $foo . $bar;
+ }, $parameters);
+
+ $this->assertEquals('foobar', $result);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_invoke_callable_with_default_value_for_undefined_parameters()
+ {
+ $parameters = [
+ 'foo', // Positioned parameter
+ 'baz' => 'baz' // Named parameter
+ ];
+
+ $result = $this->invoker->call(function ($foo, $bar = 'bar', $baz = null) {
+ return $foo . $bar . $baz;
+ }, $parameters);
+
+ $this->assertEquals('foobarbaz', $result);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_do_dependency_injection_with_typehint_container_resolver()
+ {
+ $resolver = new TypeHintContainerResolver($this->container);
+ $this->invoker->getParameterResolver()->prependResolver($resolver);
+
+ $expected = new stdClass();
+ $this->container['stdClass'] = function () use (&$expected) {
+ return $expected;
+ };
+
+ $result = $this->invoker->call(function (stdClass $foo) {
+ return $foo;
+ });
+
+ $this->assertSame($expected, $result);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_do_dependency_injection_with_parameter_name_container_resolver()
+ {
+ $resolver = new ParameterNameContainerResolver($this->container);
+ $this->invoker->getParameterResolver()->prependResolver($resolver);
+
+ $expected = new stdClass();
+ $this->container['foo'] = function () use (&$expected) {
+ return $expected;
+ };
+
+ $result = $this->invoker->call(function ($foo) {
+ return $foo;
+ });
+
+ $this->assertSame($expected, $result);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_resolve_callable_from_container()
+ {
+ $callable = new CallableSpy();
+ $this->container['thing-to-call'] = function () use (&$callable) {
+ return $callable;
+ };
+
+ $this->invoker->call('thing-to-call');
+
+ $this->assertWasCalled($callable);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_resolve_array_callable_from_container()
+ {
+ $fixture = new InvokerTestFixture();
+ $this->container['thing-to-call'] = function () use (&$fixture) {
+ return $fixture;
+ };
+
+ $result = $this->invoker->call([
+ 'thing-to-call',
+ 'foo'
+ ]);
+
+ $this->assertEquals('bar', $result);
+ $this->assertTrue($fixture->wasCalled);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_resolve_callable_from_container_with_scope_resolution_syntax()
+ {
+ $fixture = new InvokerTestFixture();
+ $this->container['thing-to-call'] = function () use (&$fixture) {
+ return $fixture;
+ };
+
+ $result = $this->invoker->call('thing-to-call::foo');
+
+ $this->assertEquals('bar', $result);
+ $this->assertTrue($fixture->wasCalled);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_resolve_array_callable_from_container_with_class_name()
+ {
+ $fixture = new InvokerTestFixture();
+ $this->container[InvokerTestFixture::class] = function () use (&$fixture) {
+ return $fixture;
+ };
+
+ $result = $this->invoker->call([
+ InvokerTestFixture::class,
+ 'foo'
+ ]);
+
+ $this->assertEquals('bar', $result);
+ $this->assertTrue($fixture->wasCalled);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_resolve_callable_from_container_with_class_name_in_scope_resolution_syntax()
+ {
+ $fixture = new InvokerTestFixture();
+ $this->container[InvokerTestFixture::class] = function () use (&$fixture) {
+ return $fixture;
+ };
+
+ $result = $this->invoker->call('Pluf\Tests\Fixtures\InvokerTestFixture::foo');
+
+ $this->assertEquals('bar', $result);
+ $this->assertTrue($fixture->wasCalled);
+ }
+
+ /**
+ * Mixing named parameters with positioned parameters is a really bad idea.
+ * When that happens, the positioned parameters have the highest priority and will
+ * override named parameters in case of conflicts.
+ *
+ * Note that numeric array indexes ignore string indexes. In our example, the
+ * 'bar' value has the position `0`, which overrides the 'foo' value.
+ *
+ * @test
+ */
+ public function positioned_parameters_have_the_highest_priority()
+ {
+ $factory = function ($foo, $bar = 300) {
+ return [
+ $foo,
+ $bar
+ ];
+ };
+ $result = $this->invoker->call($factory, [
+ 'foo' => 'foo',
+ 'bar'
+ ]);
+
+ $this->assertEquals([
+ 'bar',
+ 300
+ ], $result);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_not_invoke_statically_a_non_static_method()
+ {
+ $this->expectExceptionMessage("Cannot call foo() on Pluf\Tests\Fixtures\InvokerTestFixture because it is not a class nor a valid container entry");
+ $this->expectException(NotCallableException::class);
+ $this->invoker->call([
+ InvokerTestFixture::class,
+ 'foo'
+ ]);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_throw_if_calling_non_callable_without_container()
+ {
+ $this->expectExceptionMessage("'foo' is not a callable");
+ $this->expectException(NotCallableException::class);
+ $invoker = new Invoker();
+ $invoker->call('foo');
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_throw_if_calling_non_callable_without_container_2()
+ {
+ $this->expectExceptionMessage("NULL is not a callable");
+ $this->expectException(NotCallableException::class);
+ $invoker = new Invoker();
+ $invoker->call(null);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_throw_if_calling_non_callable_with_container()
+ {
+ $this->expectExceptionMessage("'foo' is neither a callable nor a valid container entry");
+ $this->expectException(NotCallableException::class);
+ $invoker = new Invoker(null, new Container());
+ $invoker->call('foo');
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_throw_if_calling_non_callable_object()
+ {
+ $this->expectExceptionMessage('Instance of stdClass is not a callable');
+ $this->expectException(NotCallableException::class);
+ $invoker = new Invoker();
+ $invoker->call(new stdClass());
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_invoke_static_method_rather_than_resolving_entry_from_container()
+ {
+ // Register a non-callable so that test fails if we try to invoke that
+ $this->container[InvokerTestStaticFixture::class] = function () {
+ return 'foobar';
+ };
+
+ // Call the static method: shouldn't get from the container even though the
+ // entry exist (because we are calling a static method)
+ $result = $this->invoker->call([
+ InvokerTestStaticFixture::class,
+ 'foo'
+ ]);
+ $this->assertEquals('bar', $result);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_throw_if_no_value_for_optional_parameter_1()
+ {
+ $this->expectExceptionMessage('Unable to invoke the callable because no value was given for parameter 2 ($bar)');
+ $this->expectException(NotEnoughParametersException::class);
+ // Create without the DefaultValueResolver
+ $this->invoker = new Invoker(new AssociativeArrayResolver(), $this->container);
+ $this->invoker->call(function ($foo, $bar = null) {}, [
+ 'foo' => 'foo'
+ ]);
+ }
+
+ /**
+ *
+ * @test
+ */
+ public function should_throw_if_no_value_for_optional_parameter_2()
+ {
+ $this->expectExceptionMessage('Unable to invoke the callable because no value was given for parameter 2 ($bar)');
+ $this->expectException(NotEnoughParametersException::class);
+ // Create without the DefaultValueResolver
+ $this->invoker = new Invoker(new AssociativeArrayResolver(), $this->container);
+ $this->invoker->call(function ($foo, $bar = null, $baz = null) {}, [
+ 'foo' => 'foo',
+ 'baz' => 'baz'
+ ]);
+ }
+
+ private function assertWasCalled(CallableSpy $callableSpy)
+ {
+ $this->assertEquals(1, $callableSpy->getCallCount(), 'The callable should be called once');
+ }
+
+ private function assertWasCalledWith(CallableSpy $callableSpy, array $parameters)
+ {
+ $this->assertWasCalled($callableSpy);
+ $this->assertEquals($parameters, $callableSpy->getLastCallParameters());
+ }
+}
+
+
diff --git a/tests/Mock/CallableSpy.php b/tests/Mock/CallableSpy.php
new file mode 100644
index 0000000..9990735
--- /dev/null
+++ b/tests/Mock/CallableSpy.php
@@ -0,0 +1,69 @@
+
+ */
+class CallableSpy
+{
+
+ /**
+ *
+ * @var callable|null
+ */
+ private $callable;
+
+ /**
+ *
+ * @var int
+ */
+ private $callCount = 0;
+
+ /**
+ *
+ * @var array
+ */
+ private $parameters = [];
+
+ public static function mock($callable)
+ {
+ return new self($callable);
+ }
+
+ public function __construct($callable = null)
+ {
+ $this->callable = $callable;
+ }
+
+ public function __invoke()
+ {
+ $this->callCount ++;
+ $this->parameters = func_get_args();
+
+ if ($this->callable === null) {
+ return null;
+ }
+
+ return call_user_func_array($this->callable, func_get_args());
+ }
+
+ /**
+ *
+ * @return int
+ */
+ public function getCallCount()
+ {
+ return $this->callCount;
+ }
+
+ /**
+ *
+ * @return array
+ */
+ public function getLastCallParameters()
+ {
+ return $this->parameters;
+ }
+}
\ No newline at end of file
diff --git a/tests/Mock/Notfound.php b/tests/Mock/Notfound.php
new file mode 100644
index 0000000..658e416
--- /dev/null
+++ b/tests/Mock/Notfound.php
@@ -0,0 +1,12 @@
+
+ */
+class NotFound extends \Exception implements NotFoundExceptionInterface
+{
+}
\ No newline at end of file
diff --git a/tests/Mock/StaticCallableSpy.php b/tests/Mock/StaticCallableSpy.php
new file mode 100644
index 0000000..6f0bdd6
--- /dev/null
+++ b/tests/Mock/StaticCallableSpy.php
@@ -0,0 +1,22 @@
+
+ */
+class StaticCallableSpy
+{
+
+ /**
+ *
+ * @var int
+ */
+ public static int $callCount = 0;
+
+ public function __invoke()
+ {
+ StaticCallableSpy::$callCount ++;
+ }
+}
\ No newline at end of file
diff --git a/tests/Psr11/ContainerTest.php b/tests/Psr11/ContainerTest.php
new file mode 100644
index 0000000..95527f3
--- /dev/null
+++ b/tests/Psr11/ContainerTest.php
@@ -0,0 +1,51 @@
+
+ *
+ */
+class ContainerTest extends TestCase
+{
+
+ public function testGetReturnsExistingService()
+ {
+ $psr = new Container();
+ $psr['service'] = Container::service(function () {
+ return new Service();
+ });
+
+ $this->assertSame($psr['service'], $psr->get('service'));
+ }
+
+ public function testGetThrowsExceptionIfServiceIsNotFound()
+ {
+ $this->expectException(\Psr\Container\NotFoundExceptionInterface::class);
+ $this->expectExceptionMessage('Identifier "service" is not defined.');
+
+ $psr = new Container();
+
+ $psr->get('service');
+ }
+
+ public function testHasReturnsTrueIfServiceExists()
+ {
+ $psr = new Container();
+ $psr['service'] = function () {
+ return new Service();
+ };
+
+ $this->assertTrue($psr->has('service'));
+ }
+
+ public function testHasReturnsFalseIfServiceDoesNotExist()
+ {
+ $psr = new Container();
+ $this->assertFalse($psr->has('service'));
+ }
+}
diff --git a/tests/Psr11/ServiceLocatorTest.php b/tests/Psr11/ServiceLocatorTest.php
new file mode 100644
index 0000000..3112e2e
--- /dev/null
+++ b/tests/Psr11/ServiceLocatorTest.php
@@ -0,0 +1,123 @@
+
+ */
+class ServiceLocatorTest extends TestCase
+{
+
+ public function testCanAccessServices()
+ {
+ $container = new Container();
+ $container['service'] = Container::service(function () {
+ return new Fixtures\Service();
+ });
+ $locator = new ServiceLocator($container, [
+ 'service'
+ ]);
+
+ $this->assertSame($container['service'], $locator->get('service'));
+ }
+
+ public function testCanAccessAliasedServices()
+ {
+ $container = new Container();
+ $container['service'] = Container::service(function () {
+ return new Fixtures\Service();
+ });
+ $locator = new ServiceLocator($container, [
+ 'alias' => 'service'
+ ]);
+
+ $this->assertSame($container['service'], $locator->get('alias'));
+ }
+
+ public function testCannotAccessAliasedServiceUsingRealIdentifier()
+ {
+ $this->expectException(UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "service" is not defined.');
+
+ $container = new Container();
+ $container['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($container, [
+ 'alias' => 'service'
+ ]);
+
+ $service = $locator->get('service');
+ }
+
+ public function testGetValidatesServiceCanBeLocated()
+ {
+ $this->expectException(UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $container = new Container();
+ $container['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($container, [
+ 'alias' => 'service'
+ ]);
+
+ $service = $locator->get('foo');
+ }
+
+ public function testGetValidatesTargetServiceExists()
+ {
+ $this->expectException(UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "invalid" is not defined.');
+
+ $container = new Container();
+ $container['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($container, [
+ 'alias' => 'invalid'
+ ]);
+
+ $service = $locator->get('alias');
+ }
+
+ public function testHasValidatesServiceCanBeLocated()
+ {
+ $container = new Container();
+ $container['service1'] = function () {
+ return new Fixtures\Service();
+ };
+ $container['service2'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($container, [
+ 'service1'
+ ]);
+
+ $this->assertTrue($locator->has('service1'));
+ $this->assertFalse($locator->has('service2'));
+ }
+
+ public function testHasChecksIfTargetServiceExists()
+ {
+ $container = new Container();
+ $container['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($container, [
+ 'foo' => 'service',
+ 'bar' => 'invalid'
+ ]);
+
+ $this->assertTrue($locator->has('foo'));
+ $this->assertFalse($locator->has('bar'));
+ }
+}
diff --git a/tests/ServiceIteratorTest.php b/tests/ServiceIteratorTest.php
new file mode 100644
index 0000000..08537b4
--- /dev/null
+++ b/tests/ServiceIteratorTest.php
@@ -0,0 +1,39 @@
+
+ *
+ */
+class ServiceIteratorTest extends TestCase
+{
+
+ public function testIsIterable()
+ {
+ $container = new Container();
+ $container['service1'] = Container::service(function () {
+ return new Service();
+ });
+ $container['service2'] = Container::service(function () {
+ return new Service();
+ });
+ $container['service3'] = Container::service(function () {
+ return new Service();
+ });
+ $iterator = new ServiceIterator($container, [
+ 'service1',
+ 'service2'
+ ]);
+
+ $this->assertSame([
+ 'service1' => $container['service1'],
+ 'service2' => $container['service2']
+ ], iterator_to_array($iterator));
+ }
+}