diff --git a/README.md b/README.md index f644469..6ed5522 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ $container->register(new TasksServiceProvider()); Registering a service extension on the container. ```php +$container->bind(GitHubClient::class); $container->extend(GitHubClient::class, function (Container $container, object $client) { $client->setEnterpriseUrl($client->get(GitHubClient::GITHUB_HOST)); }); @@ -110,6 +111,7 @@ class GitHubExtension implements ExtensionInterface } } +$container->bind(GitHubClient::class); $container->add(GitHubClient::class, $container->get(GitHubExtention::class)); ``` diff --git a/composer.json b/composer.json index f335f44..4c15934 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,6 @@ "phpunit/phpunit": "^9.5", "psalm/plugin-phpunit": "^0.16", "rector/rector": "^0.12", - "roave/infection-static-analysis-plugin": "^1.18", "roave/security-advisories": "dev-latest", "symplify/easy-coding-standard": "^10.2", "vimeo/psalm": "^4.23" @@ -53,7 +52,10 @@ "autoload-dev": { "psr-4": { "Ghostwriter\\Container\\Tests\\": "tests/" - } + }, + "files": [ + "tests/Fixture/functions.php" + ] }, "config": { "allow-plugins": { @@ -77,7 +79,6 @@ "@test", "@psalm", "@rector:dry-run", - "@infection", "@psalm:security" ], "cs-check": "vendor/bin/ecs check --clear-cache || true", @@ -88,10 +89,6 @@ "@psalm", "@infection" ], - "infection": [ - "@xdebug", - "roave-infection-static-analysis-plugin" - ], "missing-returntypes": "psalm --alter --issues=MissingReturnType", "normalizer": "@composer normalize --no-check-lock", "phpunit": "phpunit --colors=always --testdox --stop-on-failure", @@ -100,7 +97,7 @@ "psalm:dry-run": "psalm --alter --issues=all --dry-run", "psalm:missing": "psalm --alter --issues=MissingReturnType", "psalm:security": "psalm --taint-analysis", - "psalm:shepherd": "psalm --shepherd --stats", + "psalm:shepherd": "psalm --shepherd --stats --no-diff --no-cache", "rector": "vendor/bin/rector process", "rector:dry-run": "vendor/bin/rector process --dry-run || true", "test": [ diff --git a/composer.lock b/composer.lock index ae40232..f808f7e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c6de38a9b91e4b4f4c0f332e00c6ba54", + "content-hash": "be0ced85ba374e94ddbfc06f16efd881", "packages": [ { "name": "psr/container", @@ -227,6 +227,79 @@ ], "time": "2021-03-30T17:13:30+00:00" }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.5", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-17T14:14:24+00:00" + }, { "name": "composer/pcre", "version": "3.0.0", @@ -653,379 +726,6 @@ }, "time": "2022-03-02T22:36:06+00:00" }, - { - "name": "infection/abstract-testframework-adapter", - "version": "0.5.0", - "source": { - "type": "git", - "url": "https://github.com/infection/abstract-testframework-adapter.git", - "reference": "18925e20d15d1a5995bb85c9dc09e8751e1e069b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/infection/abstract-testframework-adapter/zipball/18925e20d15d1a5995bb85c9dc09e8751e1e069b", - "reference": "18925e20d15d1a5995bb85c9dc09e8751e1e069b", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.8", - "friendsofphp/php-cs-fixer": "^2.17", - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Infection\\AbstractTestFramework\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Maks Rafalko", - "email": "maks.rafalko@gmail.com" - } - ], - "description": "Abstract Test Framework Adapter for Infection", - "support": { - "issues": "https://github.com/infection/abstract-testframework-adapter/issues", - "source": "https://github.com/infection/abstract-testframework-adapter/tree/0.5.0" - }, - "funding": [ - { - "url": "https://github.com/infection", - "type": "github" - }, - { - "url": "https://opencollective.com/infection", - "type": "open_collective" - } - ], - "time": "2021-08-17T18:49:12+00:00" - }, - { - "name": "infection/extension-installer", - "version": "0.1.2", - "source": { - "type": "git", - "url": "https://github.com/infection/extension-installer.git", - "reference": "9b351d2910b9a23ab4815542e93d541e0ca0cdcf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/infection/extension-installer/zipball/9b351d2910b9a23ab4815542e93d541e0ca0cdcf", - "reference": "9b351d2910b9a23ab4815542e93d541e0ca0cdcf", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2.0" - }, - "require-dev": { - "composer/composer": "^1.9 || ^2.0", - "friendsofphp/php-cs-fixer": "^2.18, <2.19", - "infection/infection": "^0.15.2", - "php-coveralls/php-coveralls": "^2.4", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.10", - "phpstan/phpstan-phpunit": "^0.12.6", - "phpstan/phpstan-strict-rules": "^0.12.2", - "phpstan/phpstan-webmozart-assert": "^0.12.2", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.8" - }, - "type": "composer-plugin", - "extra": { - "class": "Infection\\ExtensionInstaller\\Plugin" - }, - "autoload": { - "psr-4": { - "Infection\\ExtensionInstaller\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Maks Rafalko", - "email": "maks.rafalko@gmail.com" - } - ], - "description": "Infection Extension Installer", - "support": { - "issues": "https://github.com/infection/extension-installer/issues", - "source": "https://github.com/infection/extension-installer/tree/0.1.2" - }, - "funding": [ - { - "url": "https://github.com/infection", - "type": "github" - }, - { - "url": "https://opencollective.com/infection", - "type": "open_collective" - } - ], - "time": "2021-10-20T22:08:34+00:00" - }, - { - "name": "infection/include-interceptor", - "version": "0.2.5", - "source": { - "type": "git", - "url": "https://github.com/infection/include-interceptor.git", - "reference": "0cc76d95a79d9832d74e74492b0a30139904bdf7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/infection/include-interceptor/zipball/0cc76d95a79d9832d74e74492b0a30139904bdf7", - "reference": "0cc76d95a79d9832d74e74492b0a30139904bdf7", - "shasum": "" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", - "infection/infection": "^0.15.0", - "phan/phan": "^2.4 || ^3", - "php-coveralls/php-coveralls": "^2.2", - "phpstan/phpstan": "^0.12.8", - "phpunit/phpunit": "^8.5", - "vimeo/psalm": "^3.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Infection\\StreamWrapper\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Maks Rafalko", - "email": "maks.rafalko@gmail.com" - } - ], - "description": "Stream Wrapper: Include Interceptor. Allows to replace included (autoloaded) file with another one.", - "support": { - "issues": "https://github.com/infection/include-interceptor/issues", - "source": "https://github.com/infection/include-interceptor/tree/0.2.5" - }, - "funding": [ - { - "url": "https://github.com/infection", - "type": "github" - }, - { - "url": "https://opencollective.com/infection", - "type": "open_collective" - } - ], - "time": "2021-08-09T10:03:57+00:00" - }, - { - "name": "infection/infection", - "version": "0.26.6", - "source": { - "type": "git", - "url": "https://github.com/infection/infection.git", - "reference": "de9b6b92f00ff1cb39decddf95797a4ebec3a1ee" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/infection/infection/zipball/de9b6b92f00ff1cb39decddf95797a4ebec3a1ee", - "reference": "de9b6b92f00ff1cb39decddf95797a4ebec3a1ee", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.0", - "composer/xdebug-handler": "^2.0 || ^3.0", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "infection/abstract-testframework-adapter": "^0.5.0", - "infection/extension-installer": "^0.1.0", - "infection/include-interceptor": "^0.2.5", - "justinrainbow/json-schema": "^5.2.10", - "nikic/php-parser": "^4.13.2", - "ondram/ci-detector": "^4.1.0", - "php": "^7.4.7 || ^8.0", - "sanmai/later": "^0.1.1", - "sanmai/pipeline": "^5.1 || ^6", - "sebastian/diff": "^3.0.2 || ^4.0", - "seld/jsonlint": "^1.7", - "symfony/console": "^3.4.29 || ^4.1.19 || ^5.0 || ^6.0", - "symfony/filesystem": "^3.4.29 || ^4.1.19 || ^5.0 || ^6.0", - "symfony/finder": "^3.4.29 || ^4.1.19 || ^5.0 || ^6.0", - "symfony/process": "^3.4.29 || ^4.1.19 || ^5.0 || ^6.0", - "thecodingmachine/safe": "^1.1.3", - "webmozart/assert": "^1.3", - "webmozart/path-util": "^2.3" - }, - "conflict": { - "dg/bypass-finals": "*", - "phpunit/php-code-coverage": ">9 <9.1.4" - }, - "require-dev": { - "brianium/paratest": "^6.3", - "ext-simplexml": "*", - "helmich/phpunit-json-assert": "^3.0", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.1.0", - "phpstan/phpstan": "^1.2.0", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpstan/phpstan-strict-rules": "^1.1.0", - "phpstan/phpstan-webmozart-assert": "^1.0.2", - "phpunit/phpunit": "^9.3.11", - "symfony/phpunit-bridge": "^4.4.18 || ^5.1.10", - "symfony/yaml": "^5.0", - "thecodingmachine/phpstan-safe-rule": "^1.1.0" - }, - "bin": [ - "bin/infection" - ], - "type": "library", - "autoload": { - "psr-4": { - "Infection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Maks Rafalko", - "email": "maks.rafalko@gmail.com", - "homepage": "https://twitter.com/maks_rafalko" - }, - { - "name": "Oleg Zhulnev", - "homepage": "https://github.com/sidz" - }, - { - "name": "Gert de Pagter", - "homepage": "https://github.com/BackEndTea" - }, - { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com", - "homepage": "https://twitter.com/tfidry" - }, - { - "name": "Alexey Kopytko", - "email": "alexey@kopytko.com", - "homepage": "https://www.alexeykopytko.com" - }, - { - "name": "Andreas Möller", - "email": "am@localheinz.com", - "homepage": "https://localheinz.com" - } - ], - "description": "Infection is a Mutation Testing framework for PHP. The mutation adequacy score can be used to measure the effectiveness of a test set in terms of its ability to detect faults.", - "keywords": [ - "coverage", - "mutant", - "mutation framework", - "mutation testing", - "testing", - "unit testing" - ], - "support": { - "issues": "https://github.com/infection/infection/issues", - "source": "https://github.com/infection/infection/tree/0.26.6" - }, - "funding": [ - { - "url": "https://github.com/infection", - "type": "github" - }, - { - "url": "https://opencollective.com/infection", - "type": "open_collective" - } - ], - "time": "2022-03-07T11:40:30+00:00" - }, - { - "name": "justinrainbow/json-schema", - "version": "5.2.12", - "source": { - "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", - "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", - "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "JsonSchema\\": "src/JsonSchema/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" - } - ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "support": { - "issues": "https://github.com/justinrainbow/json-schema/issues", - "source": "https://github.com/justinrainbow/json-schema/tree/5.2.12" - }, - "time": "2022-04-13T08:02:27+00:00" - }, { "name": "myclabs/deep-copy", "version": "1.11.0", @@ -1192,146 +892,6 @@ }, "time": "2021-11-30T19:35:32+00:00" }, - { - "name": "ocramius/package-versions", - "version": "2.5.1", - "source": { - "type": "git", - "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "6707caff2e84a2368f29fdf2cb28a6b5a9e3ab53" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/6707caff2e84a2368f29fdf2cb28a6b5a9e3ab53", - "reference": "6707caff2e84a2368f29fdf2cb28a6b5a9e3ab53", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.2.0", - "php": "~8.0.0 || ~8.1.0" - }, - "replace": { - "composer/package-versions-deprecated": "*" - }, - "require-dev": { - "composer/composer": "^2.2.0", - "doctrine/coding-standard": "^9.0.0", - "ext-zip": "^1.15.0", - "phpunit/phpunit": "^9.5.9", - "roave/infection-static-analysis-plugin": "^1.10.0", - "vimeo/psalm": "^4.10.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Provides efficient querying for installed package versions (no runtime IO)", - "support": { - "issues": "https://github.com/Ocramius/PackageVersions/issues", - "source": "https://github.com/Ocramius/PackageVersions/tree/2.5.1" - }, - "funding": [ - { - "url": "https://github.com/Ocramius", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ocramius/package-versions", - "type": "tidelift" - } - ], - "time": "2022-03-05T18:04:57+00:00" - }, - { - "name": "ondram/ci-detector", - "version": "4.1.0", - "source": { - "type": "git", - "url": "https://github.com/OndraM/ci-detector.git", - "reference": "8a4b664e916df82ff26a44709942dfd593fa6f30" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/OndraM/ci-detector/zipball/8a4b664e916df82ff26a44709942dfd593fa6f30", - "reference": "8a4b664e916df82ff26a44709942dfd593fa6f30", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.2", - "lmc/coding-standard": "^1.3 || ^2.1", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0.5", - "phpstan/phpstan": "^0.12.58", - "phpstan/phpstan-phpunit": "^0.12.16", - "phpunit/phpunit": "^7.1 || ^8.0 || ^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "OndraM\\CiDetector\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ondřej Machulda", - "email": "ondrej.machulda@gmail.com" - } - ], - "description": "Detect continuous integration environment and provide unified access to properties of current build", - "keywords": [ - "CircleCI", - "Codeship", - "Wercker", - "adapter", - "appveyor", - "aws", - "aws codebuild", - "azure", - "azure devops", - "azure pipelines", - "bamboo", - "bitbucket", - "buddy", - "ci-info", - "codebuild", - "continuous integration", - "continuousphp", - "devops", - "drone", - "github", - "gitlab", - "interface", - "jenkins", - "pipelines", - "sourcehut", - "teamcity", - "travis" - ], - "support": { - "issues": "https://github.com/OndraM/ci-detector/issues", - "source": "https://github.com/OndraM/ci-detector/tree/4.1.0" - }, - "time": "2021-04-14T09:16:52+00:00" - }, { "name": "openlss/lib-array2xml", "version": "1.0.0", @@ -1725,16 +1285,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.6.3", + "version": "1.6.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "6128620b98292e0b69ea6d799871d77163681c8e" + "reference": "e4cd0c2dd97809fcfdcd1f367ccf0be86c235168" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6128620b98292e0b69ea6d799871d77163681c8e", - "reference": "6128620b98292e0b69ea6d799871d77163681c8e", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e4cd0c2dd97809fcfdcd1f367ccf0be86c235168", + "reference": "e4cd0c2dd97809fcfdcd1f367ccf0be86c235168", "shasum": "" }, "require": { @@ -1760,7 +1320,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.6.3" + "source": "https://github.com/phpstan/phpstan/tree/1.6.4" }, "funding": [ { @@ -1780,7 +1340,7 @@ "type": "tidelift" } ], - "time": "2022-04-28T11:27:53+00:00" + "time": "2022-05-02T16:02:21+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2373,57 +1933,6 @@ ], "time": "2022-05-01T15:50:16+00:00" }, - { - "name": "roave/infection-static-analysis-plugin", - "version": "1.18.0", - "source": { - "type": "git", - "url": "https://github.com/Roave/infection-static-analysis-plugin.git", - "reference": "f7e2194b1390fc9fa900e0ec123edea7112d0c18" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Roave/infection-static-analysis-plugin/zipball/f7e2194b1390fc9fa900e0ec123edea7112d0c18", - "reference": "f7e2194b1390fc9fa900e0ec123edea7112d0c18", - "shasum": "" - }, - "require": { - "infection/infection": "0.26.6", - "ocramius/package-versions": "^1.9.0 || ^2.0.0", - "php": "~7.4.7|~8.0.0|~8.1.0", - "sanmai/later": "^0.1.2", - "vimeo/psalm": "^4.22.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0.0", - "phpunit/phpunit": "^9.5.18" - }, - "bin": [ - "bin/roave-infection-static-analysis-plugin" - ], - "type": "library", - "autoload": { - "psr-4": { - "Roave\\InfectionStaticAnalysis\\": "src/Roave/InfectionStaticAnalysis" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Static analysis on top of mutation testing - prevents escaped mutants from being invalid according to static analysis", - "support": { - "issues": "https://github.com/Roave/infection-static-analysis-plugin/issues", - "source": "https://github.com/Roave/infection-static-analysis-plugin/tree/1.18.0" - }, - "time": "2022-03-08T08:35:20+00:00" - }, { "name": "roave/security-advisories", "version": "dev-latest", @@ -2868,171 +2377,48 @@ "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", "zendframework/zendframework": "<=3", "zendframework/zendframework1": "<1.12.20", - "zendframework/zendopenid": ">=2,<2.0.2", - "zendframework/zendxml": ">=1,<1.0.1", - "zetacomponents/mail": "<1.8.2", - "zf-commons/zfc-user": "<1.2.2", - "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", - "zfr/zfr-oauth2-server-module": "<0.1.2", - "zoujingli/thinkadmin": "<6.0.22" - }, - "default-branch": true, - "type": "metapackage", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "role": "maintainer" - }, - { - "name": "Ilya Tribusean", - "email": "slash3b@gmail.com", - "role": "maintainer" - } - ], - "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "support": { - "issues": "https://github.com/Roave/SecurityAdvisories/issues", - "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" - }, - "funding": [ - { - "url": "https://github.com/Ocramius", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", - "type": "tidelift" - } - ], - "time": "2022-04-29T21:04:00+00:00" - }, - { - "name": "sanmai/later", - "version": "0.1.2", - "source": { - "type": "git", - "url": "https://github.com/sanmai/later.git", - "reference": "9b659fecef2030193fd02402955bc39629d5606f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sanmai/later/zipball/9b659fecef2030193fd02402955bc39629d5606f", - "reference": "9b659fecef2030193fd02402955bc39629d5606f", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.13", - "infection/infection": ">=0.10.5", - "phan/phan": ">=2", - "php-coveralls/php-coveralls": "^2.0", - "phpstan/phpstan": ">=0.10", - "phpunit/phpunit": ">=7.4", - "vimeo/psalm": ">=2" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Later\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Alexey Kopytko", - "email": "alexey@kopytko.com" - } - ], - "description": "Later: deferred wrapper object", - "support": { - "issues": "https://github.com/sanmai/later/issues", - "source": "https://github.com/sanmai/later/tree/0.1.2" - }, - "funding": [ - { - "url": "https://github.com/sanmai", - "type": "github" - } - ], - "time": "2021-01-02T10:26:44+00:00" - }, - { - "name": "sanmai/pipeline", - "version": "v6.1", - "source": { - "type": "git", - "url": "https://github.com/sanmai/pipeline.git", - "reference": "3a88f2617237e18d5cd2aa38ca3d4b22770306c2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sanmai/pipeline/zipball/3a88f2617237e18d5cd2aa38ca3d4b22770306c2", - "reference": "3a88f2617237e18d5cd2aa38ca3d4b22770306c2", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.8", - "friendsofphp/php-cs-fixer": "^3", - "infection/infection": ">=0.10.5", - "league/pipeline": "^1.0 || ^0.3", - "phan/phan": ">=1.1", - "php-coveralls/php-coveralls": "^2.4.1", - "phpstan/phpstan": ">=0.10", - "phpunit/phpunit": "^7.4 || ^8.1 || ^9.4", - "vimeo/psalm": ">=2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "v6.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Pipeline\\": "src/" - } + "zendframework/zendopenid": ">=2,<2.0.2", + "zendframework/zendxml": ">=1,<1.0.1", + "zetacomponents/mail": "<1.8.2", + "zf-commons/zfc-user": "<1.2.2", + "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", + "zfr/zfr-oauth2-server-module": "<0.1.2", + "zoujingli/thinkadmin": "<6.0.22" }, + "default-branch": true, + "type": "metapackage", "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Alexey Kopytko", - "email": "alexey@kopytko.com" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "role": "maintainer" + }, + { + "name": "Ilya Tribusean", + "email": "slash3b@gmail.com", + "role": "maintainer" } ], - "description": "General-purpose collections pipeline", + "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", "support": { - "issues": "https://github.com/sanmai/pipeline/issues", - "source": "https://github.com/sanmai/pipeline/tree/v6.1" + "issues": "https://github.com/Roave/SecurityAdvisories/issues", + "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" }, "funding": [ { - "url": "https://github.com/sanmai", + "url": "https://github.com/Ocramius", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", + "type": "tidelift" } ], - "time": "2022-01-30T08:15:59+00:00" + "time": "2022-04-29T21:04:00+00:00" }, { "name": "sebastian/cli-parser", @@ -3998,70 +3384,6 @@ ], "time": "2020-09-28T06:39:44+00:00" }, - { - "name": "seld/jsonlint", - "version": "1.9.0", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "4211420d25eba80712bff236a98960ef68b866b7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/4211420d25eba80712bff236a98960ef68b866b7", - "reference": "4211420d25eba80712bff236a98960ef68b866b7", - "shasum": "" - }, - "require": { - "php": "^5.3 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.5", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" - }, - "bin": [ - "bin/jsonlint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "JSON Linter", - "keywords": [ - "json", - "linter", - "parser", - "validator" - ], - "support": { - "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.9.0" - }, - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" - } - ], - "time": "2022-04-01T13:37:23+00:00" - }, { "name": "symfony/console", "version": "v6.0.8", @@ -4157,130 +3479,6 @@ ], "time": "2022-04-20T15:01:42+00:00" }, - { - "name": "symfony/filesystem", - "version": "v6.0.7", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "6c9e4c41f2c51dfde3db298594ed9cba55dbf5ff" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/6c9e4c41f2c51dfde3db298594ed9cba55dbf5ff", - "reference": "6c9e4c41f2c51dfde3db298594ed9cba55dbf5ff", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.0.7" - }, - "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": "2022-04-01T12:54:51+00:00" - }, - { - "name": "symfony/finder", - "version": "v6.0.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "af7edab28d17caecd1f40a9219fc646ae751c21f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/af7edab28d17caecd1f40a9219fc646ae751c21f", - "reference": "af7edab28d17caecd1f40a9219fc646ae751c21f", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v6.0.8" - }, - "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": "2022-04-15T08:07:58+00:00" - }, { "name": "symfony/polyfill-ctype", "version": "v1.25.0", @@ -4694,67 +3892,6 @@ ], "time": "2022-03-04T08:16:47+00:00" }, - { - "name": "symfony/process", - "version": "v6.0.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "d074154ea8b1443a96391f6e39f9e547b2dd01b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d074154ea8b1443a96391f6e39f9e547b2dd01b9", - "reference": "d074154ea8b1443a96391f6e39f9e547b2dd01b9", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v6.0.8" - }, - "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": "2022-04-12T16:11:42+00:00" - }, { "name": "symfony/service-contracts", "version": "v3.0.1", @@ -4977,145 +4114,6 @@ ], "time": "2022-04-17T10:41:19+00:00" }, - { - "name": "thecodingmachine/safe", - "version": "v1.3.3", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/safe.git", - "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc", - "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "require-dev": { - "phpstan/phpstan": "^0.12", - "squizlabs/php_codesniffer": "^3.2", - "thecodingmachine/phpstan-strict-rules": "^0.12" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.1-dev" - } - }, - "autoload": { - "files": [ - "deprecated/apc.php", - "deprecated/libevent.php", - "deprecated/mssql.php", - "deprecated/stats.php", - "lib/special_cases.php", - "generated/apache.php", - "generated/apcu.php", - "generated/array.php", - "generated/bzip2.php", - "generated/calendar.php", - "generated/classobj.php", - "generated/com.php", - "generated/cubrid.php", - "generated/curl.php", - "generated/datetime.php", - "generated/dir.php", - "generated/eio.php", - "generated/errorfunc.php", - "generated/exec.php", - "generated/fileinfo.php", - "generated/filesystem.php", - "generated/filter.php", - "generated/fpm.php", - "generated/ftp.php", - "generated/funchand.php", - "generated/gmp.php", - "generated/gnupg.php", - "generated/hash.php", - "generated/ibase.php", - "generated/ibmDb2.php", - "generated/iconv.php", - "generated/image.php", - "generated/imap.php", - "generated/info.php", - "generated/ingres-ii.php", - "generated/inotify.php", - "generated/json.php", - "generated/ldap.php", - "generated/libxml.php", - "generated/lzf.php", - "generated/mailparse.php", - "generated/mbstring.php", - "generated/misc.php", - "generated/msql.php", - "generated/mysql.php", - "generated/mysqli.php", - "generated/mysqlndMs.php", - "generated/mysqlndQc.php", - "generated/network.php", - "generated/oci8.php", - "generated/opcache.php", - "generated/openssl.php", - "generated/outcontrol.php", - "generated/password.php", - "generated/pcntl.php", - "generated/pcre.php", - "generated/pdf.php", - "generated/pgsql.php", - "generated/posix.php", - "generated/ps.php", - "generated/pspell.php", - "generated/readline.php", - "generated/rpminfo.php", - "generated/rrd.php", - "generated/sem.php", - "generated/session.php", - "generated/shmop.php", - "generated/simplexml.php", - "generated/sockets.php", - "generated/sodium.php", - "generated/solr.php", - "generated/spl.php", - "generated/sqlsrv.php", - "generated/ssdeep.php", - "generated/ssh2.php", - "generated/stream.php", - "generated/strings.php", - "generated/swoole.php", - "generated/uodbc.php", - "generated/uopz.php", - "generated/url.php", - "generated/var.php", - "generated/xdiff.php", - "generated/xml.php", - "generated/xmlrpc.php", - "generated/yaml.php", - "generated/yaz.php", - "generated/zip.php", - "generated/zlib.php" - ], - "psr-4": { - "Safe\\": [ - "lib/", - "deprecated/", - "generated/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHP core functions that throw exceptions instead of returning FALSE on error", - "support": { - "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v1.3.3" - }, - "time": "2020-10-28T17:51:34+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.1", diff --git a/infection.json.dist b/infection.json.dist deleted file mode 100644 index 9896edf..0000000 --- a/infection.json.dist +++ /dev/null @@ -1,20 +0,0 @@ -{ - "timeout": 10, - "source": { - "directories": [ - "src" - ] - }, - "logs": { - "stryker": { - "report": "/^.*$/" - }, - "text": "php:\/\/stderr", - "github": true - }, - "mutators": { - "@default": true - }, - "minMsi": 97, - "minCoveredMsi": 100 -} diff --git a/psalm.xml.dist b/psalm.xml.dist index df33335..a5a627d 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -1,18 +1,21 @@ diff --git a/src/Container.php b/src/Container.php index 3927c39..1977dc2 100644 --- a/src/Container.php +++ b/src/Container.php @@ -4,6 +4,7 @@ namespace Ghostwriter\Container; +use Closure; use Ghostwriter\Container\Contract\ContainerInterface; use Ghostwriter\Container\Contract\ExtensionInterface; use Ghostwriter\Container\Contract\ServiceProviderInterface; @@ -15,10 +16,12 @@ use Ghostwriter\Container\Exception\NotInstantiableException; use ReflectionClass; use ReflectionException; +use ReflectionFunction; use ReflectionMethod; use ReflectionNamedType; use function array_key_exists; use function class_exists; +use function is_array; use function is_callable; use function trim; @@ -36,7 +39,7 @@ final class Container implements ContainerInterface * extensions: array, * factories: array, * providers: array, - * services: array, + * services: array, * tags: array>, * } $services */ @@ -57,16 +60,36 @@ public function __clone() throw BadMethodCallException::dontClone(self::class); } + public function __get(string $name) + { + return $this->get($name); + } + + public function __isset(string $name): bool + { + return $this->has($name); + } + public function __serialize(): array { throw BadMethodCallException::dontSerialize(self::class); } + public function __set(string $name, mixed $value): void + { + $this->set($name, $value); + } + public function __unserialize(array $data): void { throw BadMethodCallException::dontUnserialize(self::class); } + public function __unset(string $name): void + { + $this->remove($name); + } + public function add(string $id, ExtensionInterface $extension): void { if (array_key_exists($extension::class, $this->services[self::EXTENSIONS][self::class])) { @@ -171,7 +194,8 @@ public function build(string $class, array $arguments = []): object throw NotInstantiableException::unresolvableParameter( $parameterName, $reflectionMethod->getDeclaringClass() - ->getName() + ->getName(), + $reflectionMethod->getName() ); } @@ -198,43 +222,40 @@ public function extend(string $class, callable $extension): void throw InvalidArgumentException::emptyServiceId(); } - $extensions = $this->services[self::EXTENSIONS]; - if (array_key_exists($class, $extensions)) { - $this->services[self::EXTENSIONS][$class] = - static fn (ContainerInterface $container, object $service): object => $extension($container, $extensions[$class]($container, $service)); - - return; + if (! array_key_exists($class, $this->services[self::FACTORIES])) { + throw NotFoundException::notRegistered($class); } - $this->services[self::EXTENSIONS][$class] = - static fn (ContainerInterface $container, object $service): object => $extension($container, $service); + $extensions = $this->services[self::EXTENSIONS]; + + $this->services[self::EXTENSIONS][$class] = array_key_exists($class, $extensions) ? + static fn (ContainerInterface $container, object $service): object => + $extension($container, $extensions[$class]($container, $service)) : + static fn (ContainerInterface $container, object $service): object => + $extension($container, $service); } public function get(string $id): mixed { $id = $this->resolve($id); - if (array_key_exists($id, $this->services[self::SERVICES])) { - if (self::class === $id) { - return self::$instance; - } + if (self::class === $id) { + return $this; + } + if (array_key_exists($id, $this->services[self::SERVICES])) { return $this->services[self::SERVICES][$id]; } - if (array_key_exists($id, $this->services[self::FACTORIES])) { - $service = $this->services[self::FACTORIES][$id](self::$instance); + $factories = $this->services[self::FACTORIES]; + if (array_key_exists($id, $factories) || class_exists($id)) { + $service = $factories[$id] ?? static fn (Container $container): object => $container->build($id); $extensions = $this->services[self::EXTENSIONS]; - if (array_key_exists($id, $extensions)) { - return $this->services[self::SERVICES][$id] = $extensions[$id](self::$instance, $service); - } - - return $this->services[self::SERVICES][$id] = $service; - } - if (class_exists($id, true)) { - return $this->services[self::SERVICES][$id] = $this->build($id); + return $this->services[self::SERVICES][$id] = array_key_exists($id, $extensions) ? + $extensions[$id]($this, $service($this)) : + $service($this); } throw NotFoundException::notRegistered($id); @@ -250,7 +271,45 @@ public function has(string $id): bool $id = $this->resolve($id); return array_key_exists($id, $this->services[self::SERVICES]) || - array_key_exists($id, $this->services[self::FACTORIES]); + array_key_exists($id, $this->services[self::FACTORIES]) || + array_key_exists($id, $this->services[self::ALIASES]); + } + + public function invoke(callable $callback, array $arguments = []): mixed + { + $parameters = is_array($callback) ? + (new ReflectionClass($callback[0]))->getMethod($callback[1])->getParameters() : + (new ReflectionFunction(Closure::fromCallable($callback)))->getParameters(); + + foreach ($parameters as $parameter) { + $parameterName = $parameter->getName(); + if (array_key_exists($parameterName, $arguments)) { + continue; + } + + if ($parameter->isOptional()) { + continue; + } + + $parameterType = $parameter->getType(); + if ( + ! ($parameterType) instanceof ReflectionNamedType || + $parameterType->isBuiltin() + ) { + $reflectionClass = $parameter->getDeclaringClass(); + throw NotInstantiableException::unresolvableParameter( + $parameterName, + $reflectionClass instanceof ReflectionClass ? + $reflectionClass->getName() : '', + $parameter->getDeclaringFunction() + ->getName() + ); + } + + $arguments[$parameterName] = $this->get($parameterType->getName()); + } + + return $callback(...$arguments); } public function offsetExists(mixed $offset): bool @@ -279,7 +338,7 @@ public function register(ServiceProviderInterface $serviceProvider): void throw LogicException::serviceProviderAlreadyRegistered($serviceProvider::class); } - $serviceProvider(self::$instance); + $serviceProvider($this); $this->services[self::PROVIDERS][$serviceProvider::class] = true; } @@ -322,15 +381,12 @@ public function set(string $id, mixed $value, iterable $tags = []): void if ( array_key_exists($id, $this->services[self::SERVICES]) || - array_key_exists($id, $this->services[self::FACTORIES]) + array_key_exists($id, $this->services[self::FACTORIES]) || + array_key_exists($id, $this->services[self::ALIASES]) ) { throw LogicException::serviceAlreadyRegistered($id); } - if (array_key_exists($id, $this->services[self::ALIASES])) { - unset($this->services[self::ALIASES][$id]); - } - $this->services[is_callable($value, false) ? self::FACTORIES : self::SERVICES][$id] = $value; diff --git a/src/Contract/ContainerInterface.php b/src/Contract/ContainerInterface.php index 24b9b7c..3c98da4 100644 --- a/src/Contract/ContainerInterface.php +++ b/src/Contract/ContainerInterface.php @@ -16,6 +16,7 @@ use Psr\Container\ContainerExceptionInterface as PsrContainerExceptionInterface; use Psr\Container\ContainerInterface as PsrContainerInterface; use Psr\Container\NotFoundExceptionInterface as PsrNotFoundExceptionInterface; +use ReflectionException; /** * An extendable, closure based dependency injection container. @@ -27,6 +28,17 @@ interface ContainerInterface extends ArrayAccess, PsrContainerInterface */ public const ALIASES = 'aliases'; + /** + * @var array{ + * aliases: array, + * dependencies: array, + * extensions: array, + * factories: array, + * providers: array, + * services: array, + * tags: array>, + * } + */ public const DEFAULT_SERVICES = [ self::ALIASES => [ self::class => Container::class, @@ -86,6 +98,14 @@ public function __destruct(); */ public function __clone(); + /** + * @throws PsrNotFoundExceptionInterface + * @throws PsrContainerExceptionInterface + */ + public function __get(string $name); + + public function __isset(string $name): bool; + /** * @throws BadMethodCallException if "__serialize()" method is called * @@ -93,6 +113,12 @@ public function __clone(); */ public function __serialize(): array; + /** + * @throws PsrNotFoundExceptionInterface + * @throws PsrContainerExceptionInterface + */ + public function __set(string $name, mixed $value): void; + /** * @throws BadMethodCallException if "__unserialize()" method is called * @@ -100,6 +126,12 @@ public function __serialize(): array; */ public function __unserialize(array $data): void; + /** + * @throws PsrNotFoundExceptionInterface + * @throws PsrContainerExceptionInterface + */ + public function __unset(string $name): void; + /** * Add a service extension. * @@ -132,8 +164,10 @@ public function bind(string $abstract, ?string $concrete = null): void; * @param class-string|string $class the class name * @param array $arguments optional constructor arguments passed to build the new class instance * - * @throws CircularDependencyException if a circular dependency is detected - * @throws NotInstantiableException if $class is not instantiable; (is an interface or an abstract class) + * @throws PsrNotFoundExceptionInterface if no entry was found for **this** identifier + * @throws PsrContainerExceptionInterface if there is an error while retrieving the entry + * @throws CircularDependencyException if a circular dependency is detected + * @throws NotInstantiableException if $class is not instantiable; (is an interface or an abstract class) * * @return T */ @@ -179,6 +213,15 @@ public static function getInstance(): self; */ public function has(string $id): bool; + /** + * Create an object using the given Container to resolve dependencies. + * + * @param array $arguments optional arguments passed to $callback + * + * @throws ReflectionException + */ + public function invoke(callable $callback, array $arguments = []): mixed; + /** @param string $offset */ public function offsetExists(mixed $offset): bool; diff --git a/src/Exception/NotInstantiableException.php b/src/Exception/NotInstantiableException.php index 75ecdef..4c3e351 100644 --- a/src/Exception/NotInstantiableException.php +++ b/src/Exception/NotInstantiableException.php @@ -20,12 +20,16 @@ public static function classDoseNotExist(string $class): self return new self(sprintf('Class "%s" dose not exist.', $class)); } - public static function unresolvableParameter(string $parameter, string $class): self + public static function unresolvableParameter(string $parameter, string $class, string $method): self { + $isFunction = '' === $class; + return new self(sprintf( - 'Parameter "$%s" in %s::__construct is unresolvable; does not have a default value.', + 'Unresolvable %s parameter "$%s" in "%s%s"; does not have a default value.', + $isFunction ? 'function' : 'class', $parameter, - $class + $isFunction ? $method : $class, + $isFunction ? '()' : '::' . $method )); } } diff --git a/tests/Fixture/TestEvent.php b/tests/Fixture/TestEvent.php new file mode 100644 index 0000000..7d7b1ff --- /dev/null +++ b/tests/Fixture/TestEvent.php @@ -0,0 +1,20 @@ + */ + private array $events = []; + + public function collect(string $event): void + { + $this->events[] = $event; + } + + /** @return array */ + public function all(): array + { + return $this->events; + } +} diff --git a/tests/Fixture/TestEventListener.php b/tests/Fixture/TestEventListener.php new file mode 100644 index 0000000..ac6eab5 --- /dev/null +++ b/tests/Fixture/TestEventListener.php @@ -0,0 +1,29 @@ +collect(__METHOD__); + } + + public function onTest(TestEvent $event): void + { + $event->collect(__METHOD__); + } + + public static function onStatic(TestEvent $event, string $nullableWithDefault = null): void + { + $event->collect(__METHOD__); + } + + public static function onStaticCallableArray(TestEvent $event, ?string $nullable): void + { + $event->collect(__METHOD__); + } +} diff --git a/tests/Fixture/functions.php b/tests/Fixture/functions.php new file mode 100644 index 0000000..c28c7f8 --- /dev/null +++ b/tests/Fixture/functions.php @@ -0,0 +1,15 @@ +collect(__METHOD__ ); +} + +function typelessFunction($event): void +{ + $event->collect(__METHOD__ ); +} diff --git a/tests/Unit/ContainerTest.php b/tests/Unit/ContainerTest.php index 962949d..e641c43 100644 --- a/tests/Unit/ContainerTest.php +++ b/tests/Unit/ContainerTest.php @@ -5,6 +5,7 @@ namespace Ghostwriter\Container\Tests\Unit; use ArrayAccess; +use Generator; use Ghostwriter\Container\Container; use Ghostwriter\Container\Contract\ContainerExceptionInterface; use Ghostwriter\Container\Contract\ContainerInterface; @@ -41,6 +42,8 @@ use Ghostwriter\Container\Tests\Fixture\Foo; use Ghostwriter\Container\Tests\Fixture\ServiceProvider\FoobarServiceProvider; use Ghostwriter\Container\Tests\Fixture\ServiceProvider\FoobarWithDependencyServiceProvider; +use Ghostwriter\Container\Tests\Fixture\TestEvent; +use Ghostwriter\Container\Tests\Fixture\TestEventListener; use Ghostwriter\Container\Tests\Fixture\UnionTypehintWithDefaultValue; use Ghostwriter\Container\Tests\Fixture\UnionTypehintWithoutDefaultValue; use Ghostwriter\Container\Tests\Fixture\UnresolvableParameter; @@ -52,6 +55,7 @@ use Throwable; use function array_key_exists; use function is_subclass_of; +use function random_int; use function serialize; use function sprintf; use function strlen; @@ -79,9 +83,53 @@ protected function tearDown(): void } /** - * @return iterable + * @psalm-return Generator + * + * @return Generator[]|TestEventListener[][]|array[]|array{nullable: null}[]|Closure():void[]|TestEventListener[]> + */ + public function dataProviderContainerCallables(): Generator + { + yield 'TypelessAnonymousFunctionCall' => [ + static function ($event): void { + $event->collect($event::class); + }, + [ + 'event'=>new TestEvent(), + ], + ]; + + yield 'AnonymousFunctionCall' => [static function (TestEvent $testEvent): void { + $testEvent->collect($testEvent::class); + }]; + + yield 'FunctionCall@typedFunction' => ['Ghostwriter\Container\Tests\Fixture\typedFunction']; + yield 'FunctionCall@typelessFunction' => [ + 'Ghostwriter\Container\Tests\Fixture\typelessFunction', [ + 'event'=>new TestEvent(), + + ], ]; + + yield 'StaticMethodCall' => [TestEventListener::class . '::onStatic']; + + yield 'CallableArrayStaticMethodCall' => [static function (TestEvent $testEvent, ?string $nullable): void { + TestEventListener::onStaticCallableArray($testEvent, $nullable); + }, [ + 'nullable' =>null, + ]]; + + yield 'CallableArrayInstanceMethodCall' => [static function (TestEvent $testEvent): void { + (new TestEventListener())->onTest($testEvent); + }]; + + yield 'Invokable' => [new TestEventListener()]; + } + + /** + * @psalm-return Generator + * + * @return Generator */ - public function dataProviderContainerExceptions(): iterable + public function dataProviderContainerExceptions(): Generator { yield 'CircularDependencyException::detected' => [ CircularDependencyException::class, @@ -114,8 +162,10 @@ static function (Container $container): void { InvalidArgumentException::class, InvalidArgumentException::emptyServiceId()->getMessage(), static function (Container $container): void { - $container->extend('', static function (Container $container): void { - }); + $container->extend( + '', + static fn (Container $container): Container => $container + ); }, ]; @@ -222,10 +272,11 @@ static function (Container $container): void { LogicException::class, LogicException::serviceExtensionAlreadyRegistered(FoobarExtension::class)->getMessage(), static function (Container $container): void { + $container->bind(stdClass::class); + $extension = $container->get(FoobarExtension::class); - $container->add('foo', $extension); - $container->add('foo', $extension); - $container->get('foo'); + $container->add(stdClass::class, $extension); + $container->add(stdClass::class, $extension); }, ]; @@ -240,7 +291,6 @@ static function (Container $container): void { */ // $serviceProvider = $container->get(FoobarServiceProvider::class); $serviceProvider = $container->build(FoobarServiceProvider::class); - // $container->register($serviceProvider); }, ]; @@ -261,6 +311,14 @@ static function (Container $container): void { }, ]; + yield 'NotFoundException::missingServiceId@extend' => [ + NotFoundException::class, + NotFoundException::notRegistered(stdClass::class)->getMessage(), + static function (Container $container): void { + $container->extend(stdClass::class, static fn (Container $container) => null); + }, + ]; + yield 'NotFoundException::missingServiceId@remove' => [ NotFoundException::class, NotFoundException::notRegistered('dose-not-exist')->getMessage(), @@ -287,11 +345,47 @@ static function (Container $container): void { yield 'NotInstantiableException::unresolvableParameter' => [ NotInstantiableException::class, - NotInstantiableException::unresolvableParameter('number', UnresolvableParameter::class)->getMessage(), + NotInstantiableException::unresolvableParameter( + 'number', + UnresolvableParameter::class, + '__construct' + )->getMessage(), static function (Container $container): void { $container->build(UnresolvableParameter::class); }, ]; + + yield 'NotInstantiableException::unresolvableParameter@call-function' => [ + NotInstantiableException::class, + NotInstantiableException::unresolvableParameter( + 'event', + '', + 'Ghostwriter\Container\Tests\Fixture\typelessFunction', + )->getMessage(), + static function (Container $container): void { + $container->invoke('Ghostwriter\Container\Tests\Fixture\typelessFunction'); + }, + ]; + + yield 'NotInstantiableException::unresolvableParameter@invoke-class' => [ + NotInstantiableException::class, + NotInstantiableException::unresolvableParameter( + 'nullable', + TestEventListener::class, + 'onStaticCallableArray' + )->getMessage(), + static function (Container $container): void { + $container->invoke([TestEventListener::class, 'onStaticCallableArray']); + }, + ]; + } + + /** @return iterable */ + public function dataProviderPropertyAccessorMagicMethods(): iterable + { + foreach (['__get', '__isset', '__set', '__unset'] as $method) { + yield $method => [$method]; + } } /** @return iterable */ @@ -304,9 +398,9 @@ public function dataProviderServiceClasses(): iterable 'value' => true, ]]; yield CallableConstructor::class => [CallableConstructor::class, [ - 'value' => static fn () => null, + 'value' => static fn (Container $container) => null, ]]; - yield EmptyConstructor::class => [EmptyConstructor::class, []]; + yield EmptyConstructor::class => [EmptyConstructor::class]; yield FloatConstructor::class => [FloatConstructor::class, [ 'value' => 13.37, ]]; @@ -322,7 +416,7 @@ public function dataProviderServiceClasses(): iterable yield ObjectConstructor::class => [ObjectConstructor::class, [ 'value' => new stdClass(), ]]; - yield OptionalConstructor::class => [OptionalConstructor::class, []]; + yield OptionalConstructor::class => [OptionalConstructor::class]; yield StringConstructor::class => [StringConstructor::class, [ 'value' => 'string', ]]; @@ -332,22 +426,22 @@ public function dataProviderServiceClasses(): iterable yield UnionTypehintWithoutDefaultValue::class => [UnionTypehintWithoutDefaultValue::class, [ 'number' => 42, ]]; - yield UnionTypehintWithDefaultValue::class => [UnionTypehintWithDefaultValue::class, []]; - yield Foo::class => [Foo::class, []]; - yield Bar::class => [Bar::class, []]; - yield Baz::class => [Baz::class, []]; - yield Container::class => [Container::class, []]; - yield FoobarWithDependencyServiceProvider::class => [FoobarWithDependencyServiceProvider::class, []]; - yield FoobarServiceProvider::class => [FoobarServiceProvider::class, []]; - yield FoobarExtension::class => [FoobarExtension::class, []]; - yield self::class => [self::class, []]; + yield UnionTypehintWithDefaultValue::class => [UnionTypehintWithDefaultValue::class]; + yield Foo::class => [Foo::class]; + yield Bar::class => [Bar::class]; + yield Baz::class => [Baz::class]; + yield Container::class => [Container::class]; + yield FoobarWithDependencyServiceProvider::class => [FoobarWithDependencyServiceProvider::class]; + yield FoobarServiceProvider::class => [FoobarServiceProvider::class]; + yield FoobarExtension::class => [FoobarExtension::class]; + yield self::class => [self::class]; } /** @return iterable */ public function dataProviderServices(): iterable { $object = new stdClass(); - $closure = static fn (): string => 'closure-called'; + $closure = static fn (Container $container): string => 'closure-called'; yield 'object' => ['object', $object, $object]; yield 'null' => ['null', null, null]; yield 'int' => ['int', 42, 42]; @@ -388,20 +482,29 @@ public function testContainerAdd(): void * @covers \Ghostwriter\Container\Container::getInstance * @covers \Ghostwriter\Container\Container::resolve * @covers \Ghostwriter\Container\Container::alias + * @covers \Ghostwriter\Container\Container::bind + * @covers \Ghostwriter\Container\Container::build * @covers \Ghostwriter\Container\Container::has * @covers \Ghostwriter\Container\Container::get + * @covers \Ghostwriter\Container\Container::set * * @throws Throwable */ public function testContainerAlias(): void { - self::assertFalse($this->container->has('container')); + self::assertFalse($this->container->has(stdClass::class)); + + $this->container->bind(stdClass::class); - $this->container->alias('container', Container::class); + self::assertTrue($this->container->has(stdClass::class)); - self::assertTrue($this->container->has('container')); + self::assertFalse($this->container->has('class')); - self::assertInstanceOf(ContainerInterface::class, $this->container->get('container')); + $this->container->alias('class', stdClass::class); + + self::assertTrue($this->container->has('class')); + + self::assertInstanceOf(stdClass::class, $this->container->get('class')); } /** @@ -445,7 +548,7 @@ public function testContainerBind(): void * * @throws Throwable */ - public function testContainerBuild(string $class, array $arguments): void + public function testContainerBuild(string $class, array $arguments = []): void { $buildService = $this->container->build($class, $arguments); @@ -458,6 +561,37 @@ public function testContainerBuild(string $class, array $arguments): void } } + /** + * @covers \Ghostwriter\Container\Container::__construct + * @covers \Ghostwriter\Container\Container::__destruct + * @covers \Ghostwriter\Container\Container::invoke + * @covers \Ghostwriter\Container\Container::get + * @covers \Ghostwriter\Container\Container::getInstance + * @covers \Ghostwriter\Container\Container::resolve + * @covers \Ghostwriter\Container\Container::set + * + * @dataProvider dataProviderContainerCallables + * + * @param callable():void|string|TestEventListener|class-string[]|TestEventListener[]|string[] $callback + * @param array $arguments + * + * @throws Throwable + */ + public function testContainerCall(callable $callback, array $arguments = []): void + { + $this->container->set(TestEvent::class, $arguments['event'] ?? new TestEvent()); + $actual = random_int(10, 50); + + $expectedCount = $actual; + + while ($actual) { + $this->container->invoke($callback, $arguments); + --$actual; + } + + self::assertCount($expectedCount, $this->container->get(TestEvent::class)->all()); + } + /** * @covers \Ghostwriter\Container\Container::__construct * @covers \Ghostwriter\Container\Container::__destruct @@ -481,19 +615,55 @@ public function testContainerConstruct(): void */ public function testContainerDestruct(): void { - $container = $this->container; - - self::assertSame($container, Container::getInstance()); - $this->container->set('test', true); self::assertTrue($this->container->has('test')); - $container->__destruct(); + $this->container->__destruct(); self::assertFalse($this->container->has('test')); } + /** + * @covers \Ghostwriter\Container\Container::__construct + * @covers \Ghostwriter\Container\Container::__destruct + * @covers \Ghostwriter\Container\Container::bind + * @covers \Ghostwriter\Container\Container::build + * @covers \Ghostwriter\Container\Container::extend + * @covers \Ghostwriter\Container\Container::get + * @covers \Ghostwriter\Container\Container::getInstance + * @covers \Ghostwriter\Container\Container::has + * @covers \Ghostwriter\Container\Container::resolve + * @covers \Ghostwriter\Container\Container::set + * + * @throws Throwable + */ + public function testContainerExtend(): void + { + $this->container->set('extend', true); + + $this->container->bind(stdClass::class); + + $this->container->extend( + stdClass::class, + static function (Container $container, object $stdClass): stdClass { + $stdClass->one = $container->get('extend'); + return $stdClass; + } + ); + + $this->container->extend( + stdClass::class, + static function (Container $container, object $stdClass): stdClass { + $stdClass->two = $container->get('extend'); + return $stdClass; + } + ); + + self::assertTrue($this->container->get(stdClass::class)->one); + self::assertTrue($this->container->get(stdClass::class)->two); + } + /** * @covers \Ghostwriter\Container\Container::__construct * @covers \Ghostwriter\Container\Container::__destruct @@ -527,6 +697,38 @@ public function testContainerImplementsArrayAccessInterface(): void self::assertArrayNotHasKey(__METHOD__, $this->container); } + /** + * @covers \Ghostwriter\Container\Container::__construct + * @covers \Ghostwriter\Container\Container::__destruct + * @covers \Ghostwriter\Container\Container::__get + * @covers \Ghostwriter\Container\Container::__isset + * @covers \Ghostwriter\Container\Container::__set + * @covers \Ghostwriter\Container\Container::__unset + * @covers \Ghostwriter\Container\Container::get + * @covers \Ghostwriter\Container\Container::getInstance + * @covers \Ghostwriter\Container\Container::has + * @covers \Ghostwriter\Container\Container::remove + * @covers \Ghostwriter\Container\Container::resolve + * @covers \Ghostwriter\Container\Container::set + * @dataProvider dataProviderPropertyAccessorMagicMethods + * + * @throws Throwable + */ + public function testContainerImplementsPropertyAccessorMagicMethods(string $method): void + { + self::assertTrue(method_exists($this->container, $method)); + + $this->container->{$method} = true; + + self::assertTrue(isset($this->container->{$method})); + + self::assertTrue($this->container->{$method}); + + unset($this->container->{$method}); + + self::assertFalse(isset($this->container->{$method})); + } + /** * @covers \Ghostwriter\Container\Container::__construct * @covers \Ghostwriter\Container\Container::__destruct @@ -609,14 +811,11 @@ public function testContainerRemove(): void * @covers \Ghostwriter\Container\Container::has * @covers \Ghostwriter\Container\Container::resolve * @covers \Ghostwriter\Container\Container::set - * - * @template T - * - * @param T $expected - * @param T $value - * * @dataProvider dataProviderServices * + * @param bool|Closure():null|float|int|stdClass|string|string|string[] $value + * @param bool|float|int|stdClass|string|string[] $expected + * * @throws PsrNotFoundExceptionInterface * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface @@ -646,12 +845,12 @@ public function testContainerSet(string $key, mixed $value, mixed $expected): vo */ public function testContainerTag(): void { - $this->container->set('stdclass1', static fn (): string => 'first-tag', ['tag-1']); + $this->container->set('stdclass1', static fn (Container $container): string => 'first-tag', ['tag-1']); - $this->container->set('stdclass2', static fn (): string => 'first-tag', ['tag-1']); + $this->container->set('stdclass2', static fn (Container $container): string => 'first-tag', ['tag-1']); - $this->container->set('stdclass3', static fn (): stdClass => new stdClass(), ['tag-2']); - $this->container->set('stdclass4', static fn (): stdClass => new stdClass()); + $this->container->set('stdclass3', static fn (Container $container): stdClass => new stdClass(), ['tag-2']); + $this->container->set('stdclass4', static fn (Container $container): stdClass => new stdClass()); $this->container->tag('stdclass4', ['tag-2']); self::assertNotNull($this->container->tagged('tag-1')); @@ -681,6 +880,7 @@ public function testContainerTag(): void * @covers \Ghostwriter\Container\Container::get * @covers \Ghostwriter\Container\Container::getInstance * @covers \Ghostwriter\Container\Container::has + * @covers \Ghostwriter\Container\Container::invoke * @covers \Ghostwriter\Container\Container::register * @covers \Ghostwriter\Container\Container::remove * @covers \Ghostwriter\Container\Container::resolve @@ -721,7 +921,11 @@ public function testExceptionsImplementPsrContainerExceptionInterface( try { $test($this->container); } catch (Throwable $throwable) { - self::assertSame($throwable::class, $exception); + if ($exception !== $throwable::class) { + self::assertSame($exception, $throwable->getMessage()); + } + + self::assertSame($exception, $throwable::class); self::assertInstanceOf(PsrContainerExceptionInterface::class, $throwable); self::assertInstanceOf(ContainerExceptionInterface::class, $throwable); @@ -742,7 +946,7 @@ public function testExceptionsImplementPsrContainerExceptionInterface( public function testNotFoundExceptionImplementsPsrContainerNotFoundExceptionInterface(): void { try { - $this->container->get(__METHOD__); + $this->container->get('not-found'); } catch (Throwable $throwable) { self::assertInstanceOf(PsrContainerExceptionInterface::class, $throwable); self::assertInstanceOf(ContainerExceptionInterface::class, $throwable);