diff --git a/.docheader b/.docheader index 7fada4e..c4c8668 100755 --- a/.docheader +++ b/.docheader @@ -4,8 +4,10 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container * @copyright Copyright (c) 2025-%year% Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ diff --git a/.gitattributes b/.gitattributes index b85bc56..ef7f9c4 100755 --- a/.gitattributes +++ b/.gitattributes @@ -2,12 +2,6 @@ /.github/ export-ignore /tests/ export-ignore /.docheader export-ignore -/.dockerignore export-ignore /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.php-cs-fixer.php export-ignore -/docker-compose export-ignore -/Dockerfile export-ignore -/infection.json5 export-ignore -/phpunit.xml export-ignore diff --git a/docs/wiki b/.github/wiki similarity index 100% rename from docs/wiki rename to .github/wiki diff --git a/.github/workflows/reports.yml b/.github/workflows/reports.yml new file mode 100644 index 0000000..993a891 --- /dev/null +++ b/.github/workflows/reports.yml @@ -0,0 +1,17 @@ +name: "Fast Forward Reports" + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + pages: write + id-token: write + +jobs: + reports: + uses: php-fast-forward/dev-tools/.github/workflows/reports.yml@main + secrets: inherit diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f0d1a6f..83ea015 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,95 +1,15 @@ -name: Run PHPUnit Tests +name: "Fast Forward Test Suite" on: - workflow_dispatch: - pull_request: - push: - branches: [ "main" ] + push: + workflow_dispatch: permissions: - contents: write - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false + contents: write + pages: write + id-token: write jobs: - tests: - name: Run Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - ref: ${{ github.head_ref || github.ref_name }} - - - name: Get Composer Cache Directory - id: composer-cache - run: | - echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache Composer dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-composer- - - - name: Install dependencies - uses: php-actions/composer@v6 - env: - COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ github.token }}"} }' - with: - php_version: '8.4' - - - name: Run PHPUnit tests - uses: php-actions/phpunit@v3 - env: - XDEBUG_MODE: coverage - with: - php_version: '8.4' - php_extensions: pcov - - - name: Ensure minimum code coverage - env: - MINIMUM_COVERAGE: 80 - run: | - COVERAGE=$(php -r ' - $xml = new SimpleXMLElement(file_get_contents("public/coverage/clover.xml")); - $m = $xml->project->metrics; - $pct = (int) round(((int) $m["coveredstatements"]) * 100 / (int) $m["statements"]); - echo $pct; - ') - echo "Coverage: ${COVERAGE}%" - if [ "${COVERAGE}" -lt ${{ env.MINIMUM_COVERAGE }} ]; then - echo "Code coverage below ${{ env.MINIMUM_COVERAGE }}% threshold." - exit 1 - fi - - - name: Generate phpDocumentor API Docs - uses: php-actions/composer@v6 - with: - php_version: '8.4' - command: 'docs' - - - name: Upload artifact - if: github.ref == 'refs/heads/main' - uses: actions/upload-pages-artifact@v3 - with: - path: public/ - - deploy: - name: Deploy to GitHub Pages - if: github.ref == 'refs/heads/main' - needs: tests - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + tests: + uses: php-fast-forward/dev-tools/.github/workflows/tests.yml@main + secrets: inherit diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index 6b78597..e76daa4 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -1,117 +1,17 @@ -name: Update Wiki +name: "Fast Forward Wiki Update" + on: - workflow_dispatch: - pull_request: - push: - branches: [ "main" ] + push: + branches: + - main + workflow_dispatch: permissions: - contents: write - id-token: write + contents: write + pages: write + id-token: write jobs: - wiki: - name: Update Wiki - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - token: '${{ github.token }}' - submodules: recursive - - - name: Get Composer Cache Directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache Composer dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-composer- - - - name: Install dependencies - uses: php-actions/composer@v6 - env: - COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ github.token }}"} }' - with: - php_version: '8.4' - - - name: Create Docs Markdown - uses: php-actions/composer@v6 - with: - php_version: '8.4' - command: 'docs' - - - name: Commit & push changes in wiki submodule - id: wiki_commit - working-directory: docs/wiki - run: | - echo "Status before commit (submodule docs/wiki):" - git status --porcelain || true - - changed="false" - - if [ -z "$(git status --porcelain)" ]; then - echo "wiki_changed=$changed" >> "$GITHUB_OUTPUT" - exit 0 - fi - - default_branch="$(git remote show origin | sed -n '/HEAD branch/s/.*: //p')" - - if [ -z "$default_branch" ]; then - default_branch="main" - fi - - git config user.email "github-actions[bot]@users.noreply.github.com" - git config user.name "github-actions[bot]" - - git add . - - if git diff --cached --quiet; then - echo "wiki_changed=$changed" >> "$GITHUB_OUTPUT" - exit 0 - fi - - git commit -m "Update wiki docs at $(date "+%Y-%m-%d %H:%M:%S")" - git push origin "$default_branch" - - changed="true" - echo "wiki_changed=$changed" >> "$GITHUB_OUTPUT" - - - name: Update submodules - id: update - run: git submodule update --remote --recursive - - - name: Check if parent repo has submodule pointer changes - id: changes - if: steps.wiki_commit.outputs.wiki_changed == 'true' - run: | - git status --porcelain - echo "wiki_changed='${{ steps.wiki_commit.outputs.wiki_changed }}'" - - git add docs/wiki - - changed="false" - - if ! git diff --cached --quiet; then - changed="true" - fi - - echo "changed=$changed" >> "$GITHUB_OUTPUT" - - - name: Add and commit parent repo - if: steps.changes.outputs.changed == 'true' - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git commit -m "Update wiki submodule pointer at $(date "+%Y-%m-%d %H:%M:%S")" - - - name: Push changes (parent repo) - if: steps.changes.outputs.changed == 'true' - uses: ad-m/github-push-action@master - with: - github_token: ${{ github.token }} - branch: ${{ github.ref_name }} + wiki: + uses: php-fast-forward/dev-tools/.github/workflows/wiki.yml@main + secrets: inherit diff --git a/.gitignore b/.gitignore index d3cd8b3..e2856dd 100755 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,7 @@ -# Project-specific files .idea/ -.phpdoc/ +.vscode/ public/ +tmp/ vendor/ composer.lock *.cache - -# GitHub Actions -DOCKER_ENV -Dockerfile-php-build -docker_tag -output.log diff --git a/.gitmodules b/.gitmodules index a58341b..b8843a1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "docs/wiki"] - path = docs/wiki +[submodule ".github/wiki"] + path = .github/wiki url = https://github.com/php-fast-forward/container.wiki.git diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php deleted file mode 100755 index 5275739..0000000 --- a/.php-cs-fixer.php +++ /dev/null @@ -1,26 +0,0 @@ - - * @license https://opensource.org/licenses/MIT MIT License - * @see https://datatracker.ietf.org/doc/html/rfc2119 - */ - -use CoiSA\PhpCsFixer\PhpCsFixer; - -$paths = [ - __FILE__, - __DIR__, -]; - -$header = file_get_contents(__DIR__ . '/.docheader'); - -return PhpCsFixer::create($paths, $header); diff --git a/composer.json b/composer.json index 3f66d73..797240c 100644 --- a/composer.json +++ b/composer.json @@ -15,27 +15,23 @@ "source": "https://github.com/php-fast-forward/container" }, "require": { - "php": "^8.1", + "php": "^8.3", "container-interop/service-provider": "^0.4.1", "fast-forward/config": "^1.1", "php-di/php-di": "^7.0", "psr/container": "^1.0 || ^2.0" }, "require-dev": { - "coisa/php-cs-fixer": "^2.1", - "phpdocumentor/shim": "^3.8", - "phpspec/prophecy-phpunit": "^2.3", - "phpunit/phpunit": "^9.6 || ^10.5 || ^11.5", - "saggre/phpdocumentor-markdown": "^0.1.4" + "fast-forward/dev-tools": "dev-main" }, "minimum-stability": "stable", "autoload": { - "files": [ - "src/functions.php" - ], "psr-4": { "FastForward\\Container\\": "src/" - } + }, + "files": [ + "src/functions.php" + ] }, "autoload-dev": { "psr-4": { @@ -43,30 +39,27 @@ } }, "config": { - "sort-packages": true, "allow-plugins": { - "phpdocumentor/shim": true - } + "ergebnis/composer-normalize": true, + "fast-forward/dev-tools": true, + "phpdocumentor/shim": true, + "phpro/grumphp": true + }, + "platform": { + "php": "8.3.0" + }, + "sort-packages": true }, "extra": { "branch-alias": { "dev-main": "1.x-dev" + }, + "grumphp": { + "config-default-path": "vendor/fast-forward/dev-tools/grumphp.yml" } }, "scripts": { - "cs-check": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix --dry-run --diff", - "cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix", - "docs": [ - "phpdoc -q --defaultpackagename='FastForward\\Container' -d ./src/ -t public/ --title 'Fast Forward Container API' --visibility='public,protected'", - "phpdoc -q --defaultpackagename='FastForward\\Container' -d ./src/ -t docs/wiki --title 'Fast Forward Container API' --template='vendor/saggre/phpdocumentor-markdown/themes/markdown' --visibility='public,protected'" - ], - "mutation-testing": "infection --threads=4", - "pre-commit": [ - "@cs-check", - "@static-analysis", - "@tests" - ], - "static-analysis": "phpstan analyse --level 5 src", - "tests": "phpunit --testdox" + "dev-tools": "dev-tools", + "dev-tools:fix": "@dev-tools --fix" } } diff --git a/docs/advanced/autowire.rst b/docs/advanced/autowire.rst new file mode 100644 index 0000000..3b44f4a --- /dev/null +++ b/docs/advanced/autowire.rst @@ -0,0 +1,25 @@ +Autowire +======== + +FastForward Container supports autowiring via integration with PHP-DI. The ``AutowireContainer`` wraps an aggregate container and appends a PHP-DI container for automatic dependency resolution. + +How It Works +------------ +- Services registered in the aggregate container are resolved first. +- If a service is not found, the PHP-DI container attempts to autowire it. +- This allows seamless use of both explicit and autowired services. + +Usage Example +------------- + +.. code-block:: php + + use FastForward\Container\AutowireContainer; + use FastForward\Container\AggregateContainer; + + $aggregate = new AggregateContainer($providerContainer); + $container = new AutowireContainer($aggregate); + + $service = $container->get(MyService::class); + +You can also use the ``container()`` helper to automatically build an autowire-enabled container from providers, configs, or other containers. \ No newline at end of file diff --git a/docs/advanced/built-in-containers.rst b/docs/advanced/built-in-containers.rst new file mode 100644 index 0000000..0327788 --- /dev/null +++ b/docs/advanced/built-in-containers.rst @@ -0,0 +1,61 @@ +Built-in Containers +================== + +FastForward Container provides several built-in container classes to cover common use cases and advanced scenarios. Each built-in container has a specific role and can be composed with others for maximum flexibility. + +Overview +-------- + +- **AggregateContainer**: Aggregates multiple containers into one, resolving services in order. +- **AutowireContainer**: Adds autowiring capabilities on top of any PSR-11 container. +- **ServiceProviderContainer**: Wraps a ServiceProviderInterface as a PSR-11 container. +- **ConfigContainer**: Wraps a ConfigInterface as a PSR-11 container. + +Details +------- + +AggregateContainer +------------------ +Aggregates multiple containers. When you call ``get($id)``, it queries each container in order until one returns a value. Useful for composing several independent containers or layering features. + +.. code-block:: php + + use FastForward\Container\AggregateContainer; + $container = new AggregateContainer($containerA, $containerB); + +AutowireContainer +----------------- +Adds autowiring support to any PSR-11 container. It will resolve and instantiate classes automatically if they are not found in the underlying container, using constructor injection. + +.. code-block:: php + + use FastForward\Container\AutowireContainer; + $container = new AutowireContainer($baseContainer); + +ServiceProviderContainer +------------------------ +Wraps a ServiceProviderInterface and exposes its factories and extensions as a PSR-11 container. This is the bridge between providers and the container system. + +.. code-block:: php + + use FastForward\Container\ServiceProviderContainer; + $container = new ServiceProviderContainer($provider); + +ConfigContainer +--------------- +Wraps a ConfigInterface and exposes its configuration as services in a PSR-11 container. Useful for integrating configuration-driven service definitions. + +.. code-block:: php + + use FastForward\Container\ConfigContainer; + $container = new ConfigContainer($config); + +Composing Containers +-------------------- +You can freely compose these containers. For example, you can wrap an AggregateContainer with an AutowireContainer, or use ServiceProviderContainer inside an AggregateContainer. The ``container()`` helper does this automatically for you. + +See Also +-------- +- :doc:`container-helper` +- :doc:`../providers/using-providers` +- :doc:`../providers/custom-provider` diff --git a/docs/advanced/container-helper.rst b/docs/advanced/container-helper.rst new file mode 100644 index 0000000..394e274 --- /dev/null +++ b/docs/advanced/container-helper.rst @@ -0,0 +1,104 @@ +Container Helper Function +======================== + +The ``container()`` helper is the main entry point for building a fully-featured, autowire-enabled container in FastForward Container. It is designed to be flexible and convenient, supporting multiple initialization patterns and advanced composition. + +Overview +-------- + +.. code-block:: php + + use FastForward\Container\container; + $container = container($initializer1, $initializer2, ...); + +You can pass any combination of the following as arguments: + +- **ConfigInterface**: A configuration object (for advanced setups) +- **Psr\Container\ContainerInterface**: Any PSR-11 compatible container +- **ServiceProviderInterface**: Any service provider +- **string**: The fully qualified class name of a ConfigInterface, ServiceProviderInterface, or PSR-11 container (it will be instantiated automatically) + +All arguments are optional and variadic. You can mix and match them as needed. + +How It Works +------------ + +1. Each initializer is resolved to a container instance: + - If it's already a container, it's used as-is. + - If it's a service provider, it's wrapped in a ServiceProviderContainer. + - If it's a config, it's wrapped in a ConfigContainer. + - If it's a string, the class is instantiated and then resolved as above. + - Any unsupported type throws an InvalidArgumentException. +2. All resolved containers are appended to an AggregateContainer. +3. If a ConfigContainer is present, it may provide nested initializers (via a special config key), which are also resolved and appended. +4. The final result is always an AutowireContainer wrapping the aggregate. + +Supported Initializers +---------------------- + +- ``ConfigInterface`` +- ``Psr\Container\ContainerInterface`` +- ``ServiceProviderInterface`` +- ``string`` (class name of any of the above) + +If you pass a string, the function will instantiate the class and treat it as if you had passed the object directly. + +Examples +-------- + +Basic usage with a service provider: + +.. code-block:: php + + use FastForward\Container\ServiceProvider\ArrayServiceProvider; + use FastForward\Container\container; + + $provider = new ArrayServiceProvider([ + 'foo' => fn() => new FooService(), + ]); + $container = container($provider); + $foo = $container->get('foo'); + +Using a provider class name: + +.. code-block:: php + + $container = container(MyServiceProvider::class); + +Composing multiple providers and containers: + +.. code-block:: php + + $containerA = container(ProviderA::class); + $containerB = container(ProviderB::class); + $main = container($containerA, $containerB); + +Using a config object: + +.. code-block:: php + + $config = new MyConfig(); + $container = container($config); + +Error Handling +-------------- + +If you pass an unsupported type, or a string that does not correspond to a valid class, the function will throw an InvalidArgumentException. + +Advanced: Nested Config Initializers +------------------------------------ + +If you use a ConfigContainer, it may provide a special config key (``ConfigContainer::ALIAS.ContainerInterface::class``) containing additional initializers. These will be resolved and appended automatically. This allows for advanced, modular configuration setups. + +Return Value +------------ + +The function always returns an ``AutowireContainer`` that wraps an ``AggregateContainer`` composed of all resolved sources. This means you get autowiring and aggregation out of the box, regardless of how you initialize the container. + +See Also +-------- + +- :doc:`../providers/using-providers` +- :doc:`../providers/custom-provider` +- :doc:`../providers/factories` +- :doc:`../advanced/autowire` diff --git a/docs/advanced/error-reporting.rst b/docs/advanced/error-reporting.rst new file mode 100644 index 0000000..70a14d6 --- /dev/null +++ b/docs/advanced/error-reporting.rst @@ -0,0 +1,21 @@ +Error Reporting +=============== + +FastForward Container uses custom exception classes for error handling, all under the ``FastForward\Container\Exception`` namespace: + +- ``ContainerException``: For general container errors (implements PSR-11 ``ContainerExceptionInterface``) +- ``NotFoundException``: Thrown when a service identifier is not found (implements PSR-11 ``NotFoundExceptionInterface``) +- ``InvalidArgumentException``: For invalid or unsupported arguments +- ``RuntimeException``: For runtime errors, such as non-callable extensions or non-public methods + +Example: + +.. code-block:: php + + use FastForward\Container\Exception\NotFoundException; + + try { + $service = $container->get('unknown'); + } catch (NotFoundException $e) { + // Handle missing service + } diff --git a/docs/advanced/index.rst b/docs/advanced/index.rst new file mode 100644 index 0000000..acb3216 --- /dev/null +++ b/docs/advanced/index.rst @@ -0,0 +1,10 @@ +Advanced Topics +=============== + +.. toctree:: + :maxdepth: 2 + + autowire + error-reporting + container-helper + built-in-containers diff --git a/docs/advanced/integrations.rst b/docs/advanced/integrations.rst new file mode 100644 index 0000000..66591ce --- /dev/null +++ b/docs/advanced/integrations.rst @@ -0,0 +1,11 @@ +Integrations +============ + +FastForward Container is designed for interoperability with PSR standards and common PHP libraries. + +- **PSR-11**: ContainerInterface compliance +- **PSR-3**: Logger integration +- **PSR-14**: Event Dispatcher integration +- **PSR-15**: HTTP Middleware integration + +See the ``psr/`` documentation for detailed usage examples for each integration. \ No newline at end of file diff --git a/docs/examples/basic.rst b/docs/examples/basic.rst new file mode 100644 index 0000000..eb3b88e --- /dev/null +++ b/docs/examples/basic.rst @@ -0,0 +1,17 @@ +Basic Example +============= + +.. code-block:: php + + use FastForward\Container\container; + use FastForward\Config\ArrayConfig; + + $config = new ArrayConfig([ + FastForward\Container\ContainerInterface::class => [ + SomeServiceProvider::class, + new OtherServiceProvider(), + ], + ]); + + $container = container($config); + $service = $container->get(SomeService::class); diff --git a/docs/examples/factories.rst b/docs/examples/factories.rst new file mode 100644 index 0000000..34ed7f1 --- /dev/null +++ b/docs/examples/factories.rst @@ -0,0 +1,18 @@ +Factories Example +================= + +.. code-block:: php + + use FastForward\Container\Factory\CallableFactory; + use FastForward\Container\Factory\AliasFactory; + use FastForward\Container\Factory\InvokableFactory; + use FastForward\Container\Factory\MethodFactory; + use FastForward\Container\Factory\ServiceFactory; + + $callableFactory = new CallableFactory(fn($container) => new MyService($container->get(Dep::class))); + $aliasFactory = new AliasFactory('other_service'); + $invokableFactory = new InvokableFactory(MyService::class, 'arg1'); + $methodFactory = new MethodFactory(MyService::class, 'staticMethod', 'arg1'); + $serviceFactory = new ServiceFactory(new MyService()); + + $service = $callableFactory($container); diff --git a/docs/examples/index.rst b/docs/examples/index.rst new file mode 100644 index 0000000..232900e --- /dev/null +++ b/docs/examples/index.rst @@ -0,0 +1,9 @@ +Examples +======== + +.. toctree:: + :maxdepth: 1 + + basic + providers + factories diff --git a/docs/examples/providers.rst b/docs/examples/providers.rst new file mode 100644 index 0000000..2a3d579 --- /dev/null +++ b/docs/examples/providers.rst @@ -0,0 +1,20 @@ +Providers Example +================= + +.. code-block:: php + + use FastForward\Container\ServiceProvider\ArrayServiceProvider; + use FastForward\Container\ServiceProvider\AggregateServiceProvider; + use FastForward\Container\ServiceProviderContainer; + + $provider1 = new ArrayServiceProvider([ + 'service' => fn() => new MyService(), + ]); + $provider2 = new ArrayServiceProvider([ + 'other' => fn() => new OtherService(), + ]); + + $aggregate = new AggregateServiceProvider($provider1, $provider2); + $container = new ServiceProviderContainer($aggregate); + + $service = $container->get('service'); diff --git a/docs/getting-started/basic-usage.rst b/docs/getting-started/basic-usage.rst new file mode 100644 index 0000000..2b77fcc --- /dev/null +++ b/docs/getting-started/basic-usage.rst @@ -0,0 +1,75 @@ +Basic Usage +=========== + +This section shows how to initialize and use the FastForward Container step by step, explaining what each part does so you can adapt to your own project with confidence. + +Step 1: Import the Required Classes +----------------------------------- +You need to import the main container helper and any configuration or service provider classes you want to use: + +.. code-block:: php + + use FastForward\Container\container; // The main helper function + use FastForward\Config\ArrayConfig; // Example config provider + +Step 2: Define Your Service Providers or Containers +--------------------------------------------------- +You can use configuration objects, service provider classes, or even other PSR-11 containers. Here, we use an ArrayConfig to define a list of providers and containers: + +.. code-block:: php + + $config = new ArrayConfig([ + FastForward\Container\ContainerInterface::class => [ + SomeServiceProvider::class, // A class name (will be instantiated) + SomePsr11Container::class, // Another container (class name) + new OtherServiceProvider('arg'), // An already constructed provider + new ServiceManager($dependencies), // An existing PSR-11 container + ], + ]); + +Step 3: Initialize the Container +-------------------------------- +Use the ``container()`` helper to build your container from the config or directly from a list of providers/containers: + +.. code-block:: php + + $container = container($config); + +What happens here? +------------------ +- The ``container()`` function inspects each argument: + - If it is an object implementing PSR-11, it is added directly. + - If it is a service provider, it is converted into a container. + - If it is a string (class name), it is instantiated. + - If it is a configuration, it may add more containers. +- The result is a container that aggregates all the given providers and containers, with autowire support. + +Step 4: Retrieve Services +------------------------- +Now you can fetch any registered or autowired service: + +.. code-block:: php + + $service = $container->get(SomeService::class); + +Alternative: Direct Initialization +---------------------------------- +You can pass providers/containers directly to the helper, without using a configuration object: + +.. code-block:: php + + $container = container( + SomeServiceProvider::class, // Provider class + SomePsr11Container::class, // Another container + new ApplicationConfig(), // Configuration object + new OtherServiceProvider('argument'), // Already instantiated provider + new ServiceManager($dependencies), // Already instantiated container + ); + +This approach is flexible and allows you to compose your container as you prefer. + +Summary +------- +- Always use the ``container()`` helper to initialize your container. +- You can mix providers, containers, configs, and class names. +- Once initialized, use ``$container->get(Service::class)`` to fetch any service. diff --git a/docs/getting-started/index.rst b/docs/getting-started/index.rst new file mode 100644 index 0000000..924c7af --- /dev/null +++ b/docs/getting-started/index.rst @@ -0,0 +1,12 @@ +Getting Started +=============== + +FastForward Container is a PSR-11 compliant aggregate container for PHP. It unifies and resolves services across multiple container implementations, supporting configuration objects, service providers, and custom container stacks. + +This documentation will guide you through installation, usage, advanced features, integrations, and error handling. + +.. toctree:: + :maxdepth: 2 + + installation + basic-usage \ No newline at end of file diff --git a/docs/getting-started/installation.rst b/docs/getting-started/installation.rst new file mode 100644 index 0000000..cb674b0 --- /dev/null +++ b/docs/getting-started/installation.rst @@ -0,0 +1,14 @@ +Installation +============ + +To install FastForward Container, use Composer: + +.. code-block:: bash + + composer require fast-forward/container + +Requirements: +- PHP 8.3 or higher +- Composer + +For more details, see the `Packagist page `_. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..a32510c --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,11 @@ +Documentation +============= + +.. toctree:: + :maxdepth: 2 + + getting-started/index + advanced/index + providers/index + examples/index + links/index diff --git a/docs/links/index.rst b/docs/links/index.rst new file mode 100644 index 0000000..ae243dc --- /dev/null +++ b/docs/links/index.rst @@ -0,0 +1,10 @@ +Links +===== + +For the latest status of the ``main`` branch, you can access the live reports deployed via GitHub Actions: + +- **Repository**: https://github.com/php-fast-forward/container +- **Packagist**: https://packagist.org/packages/php-fast-forward/container +- **API Documentation**: https://php-fast-forward.github.io/container/docs/ +- **Code Coverage**: https://php-fast-forward.github.io/container/coverage/ +- **Testdox Report**: https://php-fast-forward.github.io/container/coverage/testdox.html diff --git a/docs/providers/custom-provider.rst b/docs/providers/custom-provider.rst new file mode 100644 index 0000000..0e4abc4 --- /dev/null +++ b/docs/providers/custom-provider.rst @@ -0,0 +1,74 @@ +Writing Your Own Provider +========================= + +You can create your own service provider class by implementing the ``Interop\Container\ServiceProviderInterface``. This gives you full control over how services and extensions are registered. + +Example: Custom Provider Using Factories + +.. code-block:: php + + use Interop\Container\ServiceProviderInterface; + use FastForward\Container\Factory\InvokableFactory; + use FastForward\Container\Factory\AliasFactory; + use Psr\Container\ContainerInterface; + use Psr\Log\LoggerInterface; + use Psr\Log\LoggerAwareInterface; + + class MyServiceProvider implements ServiceProviderInterface + { + public function getFactories(): array + { + return [ + 'foo' => new InvokableFactory(FooService::class), + 'bar' => new AliasFactory('foo'), + ]; + } + + public function getExtensions(): array + { + return [ + 'foo' => function (ContainerInterface $container, FooService $service) { + // Decorate or modify $service as needed + if ($service instanceof LoggerAwareInterface) { + $logger = $container->get(LoggerInterface::class); + $service->setLogger($logger); + } + + return $service; + }, + ]; + } + } + +You can then use your provider with ``ServiceProviderContainer`` or the ``container()`` helper: + +.. code-block:: php + + $provider = new MyServiceProvider(); + $container = container($provider); // or container(MyServiceProvider::class); + $foo = $container->get('foo'); + +Using AggregateServiceProvider +----------------------------- + +If you want to combine multiple providers into a single one, use the ``AggregateServiceProvider``. This is useful for modular applications or when you want to compose several independent providers: + +.. code-block:: php + + use FastForward\Container\ServiceProvider\AggregateServiceProvider; + use FastForward\Container\ServiceProvider\ArrayServiceProvider; + use FastForward\Container\ServiceProviderContainer; + + $providerA = new ArrayServiceProvider([ + 'foo' => fn() => new FooService(), + ]); + + $providerB = new ArrayServiceProvider([ + 'bar' => fn() => new BarService(), + ]); + + $aggregate = new AggregateServiceProvider($providerA, $providerB); + $container = new ServiceProviderContainer($aggregate); + + $foo = $container->get('foo'); // from providerA + $bar = $container->get('bar'); // from providerB diff --git a/docs/providers/factories.rst b/docs/providers/factories.rst new file mode 100644 index 0000000..964761b --- /dev/null +++ b/docs/providers/factories.rst @@ -0,0 +1,114 @@ +Built-in Factories +================== + +What is a Factory? +------------------ +A factory is a special object or function that knows how to create a service (object, value, etc.) when the container is asked for it. In FastForward Container, factories are always callables that receive the container as their first argument, so they can fetch dependencies as needed. + +Why Use Factories? +------------------ +Factories allow you to: + +- Control exactly how a service is built (constructor, static method, closure, etc.) +- Inject dependencies from the container +- Reuse logic for creating similar services +- Alias or decorate existing services + +Types of Factories in FastForward Container +------------------------------------------- + +1. AliasFactory +^^^^^^^^^^^^^^^ +**Purpose:** Make one service ID behave as an alias for another. When you ask for the alias, you get the original service. + +**How it works:** + +.. code-block:: php + + use FastForward\Container\Factory\AliasFactory; + $factory = new AliasFactory('real_service_id'); + $service = $factory($container); // Same as $container->get('real_service_id') + +**When to use:** When you want two or more names to refer to the same service instance. + +2. CallableFactory +^^^^^^^^^^^^^^^^^^ +**Purpose:** Wrap any PHP callable (closure, function, invokable object) as a factory. The callable receives the container and can resolve dependencies dynamically. + +**How it works:** + +.. code-block:: php + + use FastForward\Container\Factory\CallableFactory; + $factory = new CallableFactory(function ($container) { + return new MyService($container->get(Dependency::class)); + }); + $service = $factory($container); + +**When to use:** When you need full control over how a service is built, or want to use closures for dynamic logic. + +3. InvokableFactory +^^^^^^^^^^^^^^^^^^^ +**Purpose:** Instantiate a class using its constructor, optionally passing arguments. If an argument is a string and matches a service ID, it is resolved from the container. + +**How it works:** + +.. code-block:: php + + use FastForward\Container\Factory\InvokableFactory; + $factory = new InvokableFactory(MyService::class, 'my.dependency', 'literal'); + $service = $factory($container); // 'my.dependency' is resolved from the container if available + +**When to use:** For simple services where dependencies are known and can be passed as constructor arguments. + +4. MethodFactory +^^^^^^^^^^^^^^^^ +**Purpose:** Call a specific method (static or instance) on a class, optionally passing arguments. Arguments that are strings and match service IDs are resolved from the container. + +**How it works:** + +.. code-block:: php + + use FastForward\Container\Factory\MethodFactory; + $factory = new MethodFactory(MyService::class, 'staticMethod', 'my.dependency'); + $result = $factory($container); + +**When to use:** When you want to use a factory method or static initializer, or need to call a method after construction. + +5. ServiceFactory +^^^^^^^^^^^^^^^^^ +**Purpose:** Always returns the same fixed instance, regardless of the container. + +**How it works:** + +.. code-block:: php + + use FastForward\Container\Factory\ServiceFactory; + $instance = new MyService(); + $factory = new ServiceFactory($instance); + $service = $factory($container); // Always returns $instance + +**When to use:** For registering pre-built or singleton objects. + +Summary Table +------------- + +================= =============================== ================================ +Factory What it does When to use +================= =============================== ================================ +AliasFactory Alias to another service Multiple names for one service +CallableFactory User-defined callable Full control, dynamic logic +InvokableFactory Class constructor Simple instantiation, DI by name +MethodFactory Class/static method Factory/static methods, post-init +ServiceFactory Fixed instance Pre-built or singleton objects +================= =============================== ================================ + +Tips for Beginners +------------------ +- If you just want to register a class, use InvokableFactory. +- If you want to alias a service, use AliasFactory. +- If you need to run custom logic, use CallableFactory. +- If you want to call a static or instance method, use MethodFactory. +- If you already have an object, use ServiceFactory. + +All factories are fully tested (see the tests/Factory directory) and can be combined with providers for advanced scenarios. \ No newline at end of file diff --git a/docs/providers/index.rst b/docs/providers/index.rst new file mode 100644 index 0000000..3d210ea --- /dev/null +++ b/docs/providers/index.rst @@ -0,0 +1,19 @@ +Service Providers +================ + +Service providers are a powerful way to register and organize services and their extensions in the container. They allow you to group related factories and extensions, making your dependency configuration modular and maintainable. + +What is a Service Provider? +-------------------------- +A service provider is any object that implements the ``Interop\Container\ServiceProviderInterface``. It must provide two methods: + +- ``getFactories()``: returns an associative array of service IDs to factory callables. +- ``getExtensions()``: returns an associative array of service IDs to extension callables (optional, for decorating services after creation). + +.. toctree:: + :maxdepth: 2 + + using-providers + custom-provider + factories + using-factories \ No newline at end of file diff --git a/docs/providers/using-factories.rst b/docs/providers/using-factories.rst new file mode 100644 index 0000000..75f70cc --- /dev/null +++ b/docs/providers/using-factories.rst @@ -0,0 +1,42 @@ +Using Factories in Providers +=========================== + +Factories are the recommended way to define how your services are created inside a service provider. They allow you to: + +- Use constructor injection (InvokableFactory) +- Alias services (AliasFactory) +- Use custom logic (CallableFactory) +- Call static or instance methods (MethodFactory) +- Register pre-built objects (ServiceFactory) + +Example: Registering with Different Factories +--------------------------------------------- + +.. code-block:: php + + use FastForward\Container\Factory\InvokableFactory; + use FastForward\Container\Factory\AliasFactory; + use FastForward\Container\Factory\CallableFactory; + use FastForward\Container\Factory\ServiceFactory; + + class MyServiceProvider implements ServiceProviderInterface + { + public function getFactories(): array + { + return [ + 'foo' => new InvokableFactory(FooService::class), + 'bar' => new AliasFactory('foo'), + 'baz' => new CallableFactory(function ($container) { + return new BazService($container->get('foo')); + }), + 'singleton' => new ServiceFactory(new SingletonService()), + ]; + } + + public function getExtensions(): array + { + return []; + } + } + +See the Factories section for a detailed explanation of each factory type and when to use them. \ No newline at end of file diff --git a/docs/providers/using-providers.rst b/docs/providers/using-providers.rst new file mode 100644 index 0000000..107d16d --- /dev/null +++ b/docs/providers/using-providers.rst @@ -0,0 +1,33 @@ +Built-in Providers +================== + +FastForward Container provides ready-to-use provider classes to help you register services quickly: + +- **ArrayServiceProvider**: Register factories and extensions using plain arrays. +- **AggregateServiceProvider**: Combine multiple providers into one. +- **ServiceProviderContainer**: Wraps a provider as a PSR-11 container. + +Basic Example +------------- + +.. code-block:: php + + use FastForward\Container\ServiceProvider\ArrayServiceProvider; + use FastForward\Container\ServiceProviderContainer; + + $provider = new ArrayServiceProvider([ + 'foo' => fn() => new FooService(), + 'bar' => fn() => new BarService(), + ]); + + $container = new ServiceProviderContainer($provider); + $foo = $container->get('foo'); + +You can also use the ``container()`` helper function: + +.. code-block:: php + + use FastForward\Container\container; + + $container = container($provider); + $foo = $container->get('foo'); diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index b79c1cd..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - tests - - - - - src - - - - - - - - - - - - - diff --git a/src/AggregateContainer.php b/src/AggregateContainer.php index f23adb7..90ad2d6 100644 --- a/src/AggregateContainer.php +++ b/src/AggregateContainer.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -28,8 +30,6 @@ * NotFoundException when a requested service cannot be found in any delegated container. * * It caches resolved entries to prevent redundant calls to delegated containers. - * - * @package FastForward\Container */ class AggregateContainer implements ContainerInterface { @@ -76,13 +76,15 @@ public function __construct(PsrContainerInterface ...$containers) * This method MAY be used to dynamically expand the resolution pool. * * @param PsrContainerInterface $container the container to append + * + * @return void */ public function append(PsrContainerInterface $container): void { $this->containers[] = $container; - if (!isset($this->resolved[\get_class($container)])) { - $this->resolved[\get_class($container)] = $container; + if (! isset($this->resolved[$container::class])) { + $this->resolved[$container::class] = $container; } } @@ -92,10 +94,12 @@ public function append(PsrContainerInterface $container): void * This method MAY be used to prioritize a container during resolution. * * @param PsrContainerInterface $container the container to prepend + * + * @return void */ public function prepend(PsrContainerInterface $container): void { - $this->resolved[\get_class($container)] = $container; + $this->resolved[$container::class] = $container; array_unshift($this->containers, $container); } @@ -135,7 +139,7 @@ public function has(string $id): bool * * @return mixed the resolved entry * - * @throws NotFoundException if the identifier cannot be found in any aggregated container + * @throws NotFoundException if the identifier cannot be found in any aggregated container * @throws ContainerExceptionInterface if the container cannot resolve the entry */ public function get(string $id): mixed @@ -147,7 +151,7 @@ public function get(string $id): mixed $exception = NotFoundException::forServiceID($id); foreach ($this->containers as $container) { - if (!$container->has($id)) { + if (! $container->has($id)) { continue; } @@ -155,9 +159,9 @@ public function get(string $id): mixed $this->resolved[$id] = $container->get($id); return $this->resolved[$id]; - } catch (NotFoundExceptionInterface $exception) { + } catch (NotFoundExceptionInterface) { // Ignore NotFoundExceptionInterface - } catch (ContainerExceptionInterface $exception) { + } catch (ContainerExceptionInterface) { // Future enhancement: Replace with a domain-specific exception if desired } } diff --git a/src/AutowireContainer.php b/src/AutowireContainer.php index 547684a..c5a6867 100644 --- a/src/AutowireContainer.php +++ b/src/AutowireContainer.php @@ -8,14 +8,17 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container; +use Throwable; use DI\Container; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface as PsrContainerInterface; @@ -30,10 +33,8 @@ * * This container MUST be used in scenarios where automatic dependency resolution * via autowiring is required alongside explicitly registered services. - * - * @package FastForward\Container */ -final class AutowireContainer implements ContainerInterface +final readonly class AutowireContainer implements ContainerInterface { /** * @var PsrContainerInterface the internal composite container with autowiring support @@ -66,7 +67,7 @@ public function __construct(PsrContainerInterface $delegateContainer) * * @return mixed the resolved entry * - * @throws NotFoundExceptionInterface if the identifier is not found + * @throws NotFoundExceptionInterface if the identifier is not found * @throws ContainerExceptionInterface if the entry cannot be resolved */ public function get(string $id): mixed @@ -74,16 +75,21 @@ public function get(string $id): mixed return $this->container->get($id); } + /** + * @param string $id + * + * @return bool + */ public function has(string $id): bool { - if (!$this->container->has($id)) { + if (! $this->container->has($id)) { return false; } try { // Attempt to resolve the service to check if it is valid $this->get($id); - } catch (\Throwable) { + } catch (Throwable) { return false; } diff --git a/src/ContainerInterface.php b/src/ContainerInterface.php index e1ef733..9e99837 100644 --- a/src/ContainerInterface.php +++ b/src/ContainerInterface.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -32,7 +34,5 @@ * * This abstraction MAY be extended in the future to incorporate additional container-related functionality * specific to the FastForward framework, without violating PSR-11 compatibility. - * - * @package FastForward\Container */ interface ContainerInterface extends PsrContainerInterface {} diff --git a/src/Exception/ContainerException.php b/src/Exception/ContainerException.php index 6853fa0..462fb2a 100644 --- a/src/Exception/ContainerException.php +++ b/src/Exception/ContainerException.php @@ -8,14 +8,18 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Exception; +use Exception; +use Throwable; use Psr\Container\ContainerExceptionInterface; /** @@ -23,10 +27,8 @@ * * This class MUST be used to signal problems occurring during service resolution * from a container that complies with PSR-11. - * - * @package FastForward\Container\Exception */ -final class ContainerException extends \Exception implements ContainerExceptionInterface +final class ContainerException extends Exception implements ContainerExceptionInterface { /** * Creates an exception for an invalid or unresolvable service identifier. @@ -34,12 +36,12 @@ final class ContainerException extends \Exception implements ContainerExceptionI * This factory method MUST be invoked when a service ID fails to resolve * or the resolved service is invalid in the current context. * - * @param string $id the identifier of the service that caused the failure - * @param \Throwable $previous the previous exception thrown during resolution + * @param string $id the identifier of the service that caused the failure + * @param Throwable $previous the previous exception thrown during resolution * * @return self an instance of ContainerException describing the issue */ - public static function forInvalidService(string $id, \Throwable $previous): self + public static function forInvalidService(string $id, Throwable $previous): self { return new self(\sprintf('Invalid service "%s".', $id), $previous->getCode(), $previous); } diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index e6fd973..b866339 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -21,8 +23,6 @@ * * This exception helps identify and handle errors related to invalid or unrecognized arguments, * especially when an unsupported initializer type is provided to the container builder. - * - * @package FastForward\Container\Exception */ final class InvalidArgumentException extends \InvalidArgumentException { @@ -38,9 +38,6 @@ final class InvalidArgumentException extends \InvalidArgumentException */ public static function forUnsupportedInitializer(mixed $value): self { - return new self(\sprintf( - 'Unsupported initializer type: %s', - get_debug_type($value) - )); + return new self(\sprintf('Unsupported initializer type: %s', get_debug_type($value))); } } diff --git a/src/Exception/NotFoundException.php b/src/Exception/NotFoundException.php index 5aaf6dc..3218a81 100644 --- a/src/Exception/NotFoundException.php +++ b/src/Exception/NotFoundException.php @@ -8,14 +8,17 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Exception; +use Exception; use Psr\Container\NotFoundExceptionInterface; /** @@ -24,10 +27,8 @@ * This class MUST be used in PSR-11 container implementations to represent an error * condition where a service ID does not exist in the container. It implements the * Psr\Container\NotFoundExceptionInterface to guarantee interoperability with PSR-11 consumers. - * - * @package FastForward\Container\Exception */ -final class NotFoundException extends \Exception implements NotFoundExceptionInterface +final class NotFoundException extends Exception implements NotFoundExceptionInterface { /** * Creates a new NotFoundException for a missing service identifier. diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index 2edf589..d7fbf09 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -21,8 +23,6 @@ * * This class MUST be thrown when an error occurs due to invalid runtime behavior * such as misconfigured extensions or illegal method accessibility. - * - * @package FastForward\Container\Exception */ final class RuntimeException extends \RuntimeException { @@ -33,17 +33,13 @@ final class RuntimeException extends \RuntimeException * is not callable, violating the expected contract of container extensions. * * @param string $service the identifier of the service with the invalid extension - * @param string $given the type or class name of the invalid value + * @param string $given the type or class name of the invalid value * * @return self a RuntimeException instance with a descriptive message */ public static function forNonCallableExtension(string $service, string $given): self { - return new self(\sprintf( - 'Service "%s" extension MUST be callable, "%s" given.', - $service, - $given - )); + return new self(\sprintf('Service "%s" extension MUST be callable, "%s" given.', $service, $given)); } /** @@ -52,18 +48,14 @@ public static function forNonCallableExtension(string $service, string $given): * This method SHOULD be used when trying to invoke a method that is not declared public, * thereby violating service visibility requirements. * - * @param string $class the fully qualified class name + * @param string $class the fully qualified class name * @param string $method the name of the method that is not publicly accessible * * @return self a RuntimeException indicating the method MUST be public */ public static function forNonPublicMethod(string $class, string $method): self { - return new self(\sprintf( - 'Method "%s::%s" MUST be public to be invoked as a service.', - $class, - $method - )); + return new self(\sprintf('Method "%s::%s" MUST be public to be invoked as a service.', $class, $method)); } /** diff --git a/src/Factory/AliasFactory.php b/src/Factory/AliasFactory.php index 2d45d02..f8f7945 100644 --- a/src/Factory/AliasFactory.php +++ b/src/Factory/AliasFactory.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -25,17 +27,9 @@ * service already registered in the container. * * When invoked, it SHALL delegate resolution to the aliased service identifier. - * - * @package FastForward\Container\Factory */ final class AliasFactory implements FactoryInterface { - /** - * @var string The identifier of the aliased service. - * This MUST correspond to a valid entry in the container. - */ - private readonly string $alias; - /** * @var array Registry of AliasFactory instances indexed by alias name. * This MAY be used to cache and reuse factory instances. @@ -47,10 +41,9 @@ final class AliasFactory implements FactoryInterface * * @param string $alias the identifier of the service to which this factory points */ - public function __construct(string $alias) - { - $this->alias = $alias; - } + public function __construct( + private readonly string $alias + ) {} /** * Resolves the aliased service from the container. diff --git a/src/Factory/CallableFactory.php b/src/Factory/CallableFactory.php index 003f891..f2b8286 100644 --- a/src/Factory/CallableFactory.php +++ b/src/Factory/CallableFactory.php @@ -8,14 +8,19 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Factory; +use Closure; +use ReflectionFunction; +use ReflectionType; use FastForward\Container\Exception\RuntimeException; use Psr\Container\ContainerInterface; @@ -26,16 +31,14 @@ * This factory SHALL be used when the construction logic must be fully delegated to a closure. * * This class allows dynamic resolution of services using the container context. - * - * @package FastForward\Container\Factory */ -final class CallableFactory implements FactoryInterface +final readonly class CallableFactory implements FactoryInterface { /** - * @var \Closure The user-defined factory callable. - * This callable MUST accept a ContainerInterface and return a service instance. + * @var Closure The user-defined factory callable. + * This callable MUST accept a ContainerInterface and return a service instance. */ - private \Closure $callable; + private Closure $callable; /** * Constructs a CallableFactory instance. @@ -56,7 +59,7 @@ public function __construct(callable $callable) */ public function __invoke(ContainerInterface $container): mixed { - $arguments = $this->getArguments($container, new \ReflectionFunction($this->callable)); + $arguments = $this->getArguments($container, new ReflectionFunction($this->callable)); return \call_user_func_array($this->callable, $arguments); } @@ -64,21 +67,22 @@ public function __invoke(ContainerInterface $container): mixed /** * Retrieves the arguments for the callable from the container. * - * @param ContainerInterface $container the PSR-11 container for dependency resolution - * @param \ReflectionFunction $function the reflection function of the callable + * @param ContainerInterface $container the PSR-11 container for dependency resolution + * @param ReflectionFunction $function the reflection function of the callable * * @return array the resolved arguments for the callable */ - private function getArguments(ContainerInterface $container, \ReflectionFunction $function): array + private function getArguments(ContainerInterface $container, ReflectionFunction $function): array { $arguments = []; foreach ($function->getParameters() as $parameter) { - if (!$parameter->getType() || $parameter->getType()->isBuiltin()) { + if (! $parameter->getType() instanceof ReflectionType || $parameter->getType()->isBuiltin()) { throw RuntimeException::forInvalidParameterType($parameter->getName()); } - $className = $parameter->getType()->getName(); + $className = $parameter->getType() + ->getName(); $arguments[] = $container->get($className); } diff --git a/src/Factory/FactoryInterface.php b/src/Factory/FactoryInterface.php index 248a788..d22fb8e 100644 --- a/src/Factory/FactoryInterface.php +++ b/src/Factory/FactoryInterface.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -25,8 +27,6 @@ * the fully constructed service instance. * * This interface is commonly used in container-based systems to register factories dynamically. - * - * @package FastForward\Container\Factory */ interface FactoryInterface { diff --git a/src/Factory/InvokableFactory.php b/src/Factory/InvokableFactory.php index 30d8176..c57f942 100644 --- a/src/Factory/InvokableFactory.php +++ b/src/Factory/InvokableFactory.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -28,22 +30,14 @@ * * It SHALL invoke the constructor directly using the spread operator and provided arguments. * This factory is suitable for services that do not require container-based dependency injection. - * - * @package FastForward\Container\Factory */ -final class InvokableFactory implements FactoryInterface +final readonly class InvokableFactory implements FactoryInterface { - /** - * @var string The fully qualified class name to instantiate. - * This MUST be a valid, instantiable class. - */ - private readonly string $class; - /** * @var array The list of arguments to pass to the class constructor. * This MAY be empty if the constructor takes no arguments. */ - private readonly array $arguments; + private array $arguments; /** * Constructs the InvokableFactory with a target class and optional constructor arguments. @@ -52,12 +46,13 @@ final class InvokableFactory implements FactoryInterface * provided SHALL be passed to the class constructor during instantiation. If an argument * is a string and matches a service ID in the container, it SHALL be resolved from the container. * - * @param string $class the fully qualified class name to be instantiated - * @param mixed ...$arguments A variadic list of constructor arguments. + * @param string $class the fully qualified class name to be instantiated + * @param mixed ...$arguments A variadic list of constructor arguments. */ - public function __construct(string $class, mixed ...$arguments) - { - $this->class = $class; + public function __construct( + private string $class, + mixed ...$arguments + ) { $this->arguments = $arguments; } @@ -75,12 +70,12 @@ public function __construct(string $class, mixed ...$arguments) * @return mixed The created service instance * * @throws ContainerExceptionInterface thrown if an error occurs during service instantiation - * @throws NotFoundExceptionInterface thrown if a required service ID is not found in the container + * @throws NotFoundExceptionInterface thrown if a required service ID is not found in the container */ public function __invoke(ContainerInterface $container): mixed { $arguments = array_map( - static fn ($argument) => \is_string($argument) && $container->has($argument) + static fn($argument) => \is_string($argument) && $container->has($argument) ? $container->get($argument) : $argument, $this->arguments diff --git a/src/Factory/MethodFactory.php b/src/Factory/MethodFactory.php index c0eec99..daf2447 100644 --- a/src/Factory/MethodFactory.php +++ b/src/Factory/MethodFactory.php @@ -8,14 +8,19 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Factory; +use ReflectionMethod; +use Throwable; +use ReflectionException; use FastForward\Container\Exception\RuntimeException; use Psr\Container\ContainerInterface; @@ -28,10 +33,8 @@ * If the method is not public, a RuntimeException SHALL be thrown. * * Arguments MAY be resolved from the container if passed as service identifiers. - * - * @package FastForward\Container\Factory */ -final class MethodFactory implements FactoryInterface +final readonly class MethodFactory implements FactoryInterface { /** * @var array arguments to be passed to the method during invocation @@ -41,9 +44,9 @@ final class MethodFactory implements FactoryInterface /** * Constructs the MethodFactory. * - * @param string $class the class name or container service ID on which the method is called - * @param string $method the name of the method to invoke - * @param mixed ...$arguments Optional arguments to pass to the method. + * @param string $class the class name or container service ID on which the method is called + * @param string $method the name of the method to invoke + * @param mixed ...$arguments Optional arguments to pass to the method. */ public function __construct( private string $class, @@ -64,21 +67,21 @@ public function __construct( * * @return mixed The result of invoking the method * - * @throws \ReflectionException If the method does not exist - * @throws RuntimeException If the method is not public + * @throws ReflectionException If the method does not exist + * @throws RuntimeException If the method is not public */ public function __invoke(ContainerInterface $container): mixed { $arguments = array_map( - static fn ($argument) => \is_string($argument) && $container->has($argument) + static fn($argument) => \is_string($argument) && $container->has($argument) ? $container->get($argument) : $argument, $this->arguments ); - $reflectionMethod = new \ReflectionMethod($this->class, $this->method); + $reflectionMethod = new ReflectionMethod($this->class, $this->method); - if (!$reflectionMethod->isPublic()) { + if (! $reflectionMethod->isPublic()) { throw RuntimeException::forNonPublicMethod($this->class, $this->method); } @@ -88,7 +91,7 @@ public function __invoke(ContainerInterface $container): mixed try { $object = $container->get($this->class); - } catch (\Throwable) { + } catch (Throwable) { $object = new ($this->class)(); } diff --git a/src/Factory/ServiceFactory.php b/src/Factory/ServiceFactory.php index 0302642..cc8b64b 100644 --- a/src/Factory/ServiceFactory.php +++ b/src/Factory/ServiceFactory.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -27,26 +29,17 @@ * The returned value MUST be the exact same instance provided at construction. * * This ensures immutability and predictable resolution. - * - * @package FastForward\Container\Factory */ -final class ServiceFactory implements FactoryInterface +final readonly class ServiceFactory implements FactoryInterface { - /** - * @var mixed The fixed service instance to return when invoked. - * This value MUST NOT change after instantiation. - */ - private readonly mixed $service; - /** * Constructs the factory with a fixed service instance. * * @param mixed $service the service instance to be returned by the factory */ - public function __construct(mixed $service) - { - $this->service = $service; - } + public function __construct( + private mixed $service + ) {} /** * Returns the fixed service instance. diff --git a/src/ServiceProvider/AggregateServiceProvider.php b/src/ServiceProvider/AggregateServiceProvider.php index d65bd34..1e80964 100644 --- a/src/ServiceProvider/AggregateServiceProvider.php +++ b/src/ServiceProvider/AggregateServiceProvider.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -30,8 +32,6 @@ * from several ServiceProviderInterface implementations. * * Factories and extensions returned by this class are merged in registration order. - * - * @package FastForward\Container\ServiceProvider */ class AggregateServiceProvider implements ServiceProviderInterface { @@ -62,15 +62,17 @@ public function getFactories(): array { $serviceProviders = array_reduce( $this->serviceProviders, - static fn (array $carry, ServiceProviderInterface $provider) => $carry + [ + static fn(array $carry, ServiceProviderInterface $provider): array => $carry + [ $provider::class => new ServiceFactory($provider), ], - [static::class => new ServiceFactory($this)], + [ + static::class => new ServiceFactory($this), + ], ); return array_reduce( $this->serviceProviders, - static fn ($factories, $serviceProvider) => array_merge($factories, $serviceProvider->getFactories()), + static fn($factories, $serviceProvider): array => array_merge($factories, $serviceProvider->getFactories()), $serviceProviders, ); } @@ -87,21 +89,25 @@ public function getFactories(): array */ public function getExtensions(): array { - return array_reduce($this->serviceProviders, static function ($extensions, $serviceProvider) { - foreach ($serviceProvider->getExtensions() as $id => $extension) { - if (!\is_callable($extension)) { - throw RuntimeException::forNonCallableExtension($id, get_debug_type($extension)); - } + return array_reduce( + $this->serviceProviders, + static function (array $extensions, ServiceProviderInterface $serviceProvider): array { + foreach ($serviceProvider->getExtensions() as $id => $extension) { + if (! \is_callable($extension)) { + throw RuntimeException::forNonCallableExtension($id, get_debug_type($extension)); + } - $extensions[$id] = !\array_key_exists($id, $extensions) - ? $extension - : static fn (ContainerInterface $container, $previous) => $extension( - $container, - $extensions[$id]($container, $previous) - ); - } + $extensions[$id] = \array_key_exists($id, $extensions) + ? static fn(ContainerInterface $container, $previous) => $extension( + $container, + $extensions[$id]($container, $previous) + ) + : $extension; + } - return $extensions; - }, []); + return $extensions; + }, + [] + ); } } diff --git a/src/ServiceProvider/ArrayServiceProvider.php b/src/ServiceProvider/ArrayServiceProvider.php index 1c20de2..5df7b3a 100644 --- a/src/ServiceProvider/ArrayServiceProvider.php +++ b/src/ServiceProvider/ArrayServiceProvider.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -27,38 +29,33 @@ * * Factories MUST be defined as an associative array where keys are service IDs and * values are callables. Extensions MUST also follow the same format. - * - * @package FastForward\Container\ServiceProvider */ -final class ArrayServiceProvider implements ServiceProviderInterface +final readonly class ArrayServiceProvider implements ServiceProviderInterface { - /** - * @var array an associative array of service factories - */ - private array $factories; - - /** - * @var array an associative array of service extensions - */ - private array $extensions; - /** * Constructs an ArrayServiceProvider with pre-defined factories and extensions. * - * @param array $factories the list of service factories + * @param array $factories the list of service factories * @param array $extensions the list of service extensions + * @param array $factories + * @param array $extensions */ - public function __construct(array $factories = [], array $extensions = []) - { - $this->factories = $factories; - $this->extensions = $extensions; - } + public function __construct( + private array $factories = [], + private array $extensions = [] + ) {} + /** + * @return array + */ public function getFactories(): array { return $this->factories; } + /** + * @return array + */ public function getExtensions(): array { return $this->extensions; diff --git a/src/ServiceProviderContainer.php b/src/ServiceProviderContainer.php index d5fbd17..f38eb17 100644 --- a/src/ServiceProviderContainer.php +++ b/src/ServiceProviderContainer.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -36,22 +38,15 @@ * * If an optional wrapper container is provided, it SHALL be passed to service factories and extensions, * allowing for delegation or decoration of service resolution. If omitted, the container defaults to itself. - * - * @package FastForward\Container */ final class ServiceProviderContainer implements ContainerInterface { - /** - * The service provider supplying factories and extensions for service construction. - */ - private ServiceProviderInterface $serviceProvider; - /** * The container instance used for service resolution and extension application. * * This property MAY reference another container for delegation, or default to this container instance. */ - private PsrContainerInterface $wrapperContainer; + private readonly PsrContainerInterface $wrapperContainer; /** * Cache of resolved services keyed by their identifier or class name. @@ -68,14 +63,13 @@ final class ServiceProviderContainer implements ContainerInterface * This constructor SHALL initialize the container with a service provider and an optional delegating container. * If no wrapper container is provided, the container SHALL delegate to itself. * - * @param ServiceProviderInterface $serviceProvider the service provider supplying factories and extensions - * @param null|PsrContainerInterface $wrapperContainer An optional container for delegation. Defaults to self. + * @param ServiceProviderInterface $serviceProvider the service provider supplying factories and extensions + * @param PsrContainerInterface|null $wrapperContainer An optional container for delegation. Defaults to self. */ public function __construct( - ServiceProviderInterface $serviceProvider, + private readonly ServiceProviderInterface $serviceProvider, ?PsrContainerInterface $wrapperContainer = null, ) { - $this->serviceProvider = $serviceProvider; $this->wrapperContainer = $wrapperContainer ?? $this; } @@ -105,7 +99,7 @@ public function has(string $id): bool * * @return mixed the service instance associated with the identifier * - * @throws NotFoundException if no factory exists for the given identifier + * @throws NotFoundException if no factory exists for the given identifier * @throws ContainerException if service construction fails due to container errors */ public function get(string $id): mixed @@ -116,13 +110,13 @@ public function get(string $id): mixed $factory = $this->serviceProvider->getFactories(); - if (!\array_key_exists($id, $factory) || !\is_callable($factory[$id])) { + if (! \array_key_exists($id, $factory) || ! \is_callable($factory[$id])) { throw NotFoundException::forServiceID($id); } try { $service = \call_user_func($factory[$id], $this->wrapperContainer); - $class = \get_class($service); + $class = $service::class; $this->applyServiceExtensions($id, $class, $service); } catch (ContainerExceptionInterface $containerException) { throw ContainerException::forInvalidService($id, $containerException); @@ -130,7 +124,7 @@ public function get(string $id): mixed $this->cache[$id] = $service; - if ($id !== $class && !isset($this->cache[$class])) { + if ($id !== $class && ! isset($this->cache[$class])) { $this->cache[$class] = $service; } @@ -148,9 +142,11 @@ public function get(string $id): mixed * Extensions MAY be used to modify or enhance services after creation. Invalid extensions * (non-callables) SHALL be ignored silently. * - * @param string $id the identifier of the resolved service - * @param string $class the fully qualified class name of the service - * @param mixed $service the service instance to apply extensions to + * @param string $id the identifier of the resolved service + * @param string $class the fully qualified class name of the service + * @param mixed $service the service instance to apply extensions to + * + * @return void * * @throws ContainerException if an extension callable fails during execution */ @@ -163,7 +159,7 @@ private function applyServiceExtensions(string $id, string $class, mixed $servic } if ($id !== $class - && !isset($this->cache[$class]) + && ! isset($this->cache[$class]) && \array_key_exists($class, $extensions) && \is_callable($extensions[$class]) ) { diff --git a/src/functions.php b/src/functions.php index 4a67805..c853be9 100644 --- a/src/functions.php +++ b/src/functions.php @@ -8,14 +8,17 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container; +use Throwable; use FastForward\Config\ConfigInterface; use FastForward\Config\Container\ConfigContainer; use FastForward\Container\Exception\InvalidArgumentException; @@ -45,22 +48,23 @@ * @return ContainerInterface the composed and autowire-enabled container * * @throws InvalidArgumentException if an unsupported initializer type is encountered - * - * @package FastForward\Container */ function container( ConfigInterface|PsrContainerInterface|ServiceProviderInterface|string ...$initializers, ): ContainerInterface { $aggregateContainer = new AggregateContainer(); - $getContainer = static fn ($initializer) => match (true) { + $getContainer = static fn($initializer): ?PsrContainerInterface => match (true) { $initializer instanceof PsrContainerInterface => $initializer, - $initializer instanceof ServiceProviderInterface => new ServiceProviderContainer($initializer, $aggregateContainer), + $initializer instanceof ServiceProviderInterface => new ServiceProviderContainer( + $initializer, + $aggregateContainer + ), $initializer instanceof ConfigInterface => new ConfigContainer($initializer), default => null, }; - $resolve = static fn ($initializer) => match (true) { + $resolve = static fn($initializer): ?PsrContainerInterface => match (true) { \is_object($initializer) => $getContainer($initializer), class_exists($initializer) => $getContainer(new ($initializer)()), default => throw InvalidArgumentException::forUnsupportedInitializer($initializer), @@ -77,7 +81,7 @@ class_exists($initializer) => $getContainer(new ($initializer)()), foreach ($container->get($configKey) as $nested) { $aggregateContainer->append($resolve($nested)); } - } catch (\Throwable) { + } catch (Throwable) { // Ignored } } diff --git a/tests/AggregateContainerTest.php b/tests/AggregateContainerTest.php index 312b80c..b7fb4b6 100644 --- a/tests/AggregateContainerTest.php +++ b/tests/AggregateContainerTest.php @@ -8,14 +8,18 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests; +use stdClass; +use Exception; use FastForward\Container\AggregateContainer; use FastForward\Container\Exception\NotFoundException; use PHPUnit\Framework\Attributes\CoversClass; @@ -36,8 +40,11 @@ final class AggregateContainerTest extends TestCase { use ProphecyTrait; + /** + * @return void + */ #[Test] - public function testHasReturnsTrueForAliasAndClassBindings(): void + public function hasReturnsTrueForAliasAndClassBindings(): void { $container = new AggregateContainer(); @@ -46,16 +53,22 @@ public function testHasReturnsTrueForAliasAndClassBindings(): void self::assertTrue($container->has(ContainerInterface::class)); } + /** + * @return void + */ #[Test] - public function testHasReturnsFalseForUnknownKey(): void + public function hasReturnsFalseForUnknownKey(): void { $container = new AggregateContainer(); self::assertFalse($container->has(uniqid('unknown_', true))); } + /** + * @return void + */ #[Test] - public function testGetReturnsSelfForAliasAndClassBindings(): void + public function getReturnsSelfForAliasAndClassBindings(): void { $container = new AggregateContainer(); @@ -64,28 +77,37 @@ public function testGetReturnsSelfForAliasAndClassBindings(): void self::assertSame($container, $container->get(ContainerInterface::class)); } + /** + * @return void + */ #[Test] - public function testHasReturnsTrueIfSubContainerHasEntry(): void + public function hasReturnsTrueIfSubContainerHasEntry(): void { $key = uniqid('svc_', true); $sub = $this->prophesize(ContainerInterface::class); - $sub->has($key)->willReturn(true); + $sub->has($key) + ->willReturn(true); $container = new AggregateContainer($sub->reveal()); self::assertTrue($container->has($key)); } + /** + * @return void + */ #[Test] - public function testGetReturnsResolvedEntryFromSubContainer(): void + public function getReturnsResolvedEntryFromSubContainer(): void { $key = uniqid('dep_', true); $value = uniqid('value_', true); $sub = $this->prophesize(ContainerInterface::class); - $sub->has($key)->willReturn(true); - $sub->get($key)->willReturn($value); + $sub->has($key) + ->willReturn(true); + $sub->get($key) + ->willReturn($value); $container = new AggregateContainer($sub->reveal()); @@ -93,85 +115,108 @@ public function testGetReturnsResolvedEntryFromSubContainer(): void self::assertSame($value, $container->get($key)); // ensures internal caching } + /** + * @return void + */ #[Test] - public function testGetSkipsContainersThrowingNotFoundException(): void + public function getSkipsContainersThrowingNotFoundException(): void { $key = uniqid('missing_', true); $value = uniqid('fallback_', true); $first = $this->prophesize(ContainerInterface::class); - $first->has($key)->willReturn(true); - $first->get($key)->willThrow(NotFoundException::class); + $first->has($key) + ->willReturn(true); + $first->get($key) + ->willThrow(NotFoundException::class); $second = $this->prophesize(ContainerInterface::class); - $second->has($key)->willReturn(true); - $second->get($key)->willReturn($value); + $second->has($key) + ->willReturn(true); + $second->get($key) + ->willReturn($value); $container = new AggregateContainer($first->reveal(), $second->reveal()); self::assertSame($value, $container->get($key)); } + /** + * @return void + */ #[Test] - public function testGetSkipsContainersThrowingStandardInterfaces(): void + public function getSkipsContainersThrowingStandardInterfaces(): void { $key = uniqid('service_', true); - $value = new \stdClass(); + $value = new stdClass(); - $nf = new class extends \Exception implements NotFoundExceptionInterface {}; - $ce = new class extends \Exception implements ContainerExceptionInterface {}; + $nf = new class extends Exception implements NotFoundExceptionInterface {}; + $ce = new class extends Exception implements ContainerExceptionInterface {}; $first = $this->prophesize(ContainerInterface::class); - $first->has($key)->willReturn(true); - $first->get($key)->willThrow($nf); + $first->has($key) + ->willReturn(true); + $first->get($key) + ->willThrow($nf); $second = $this->prophesize(ContainerInterface::class); - $second->has($key)->willReturn(true); - $second->get($key)->willThrow($ce); + $second->has($key) + ->willReturn(true); + $second->get($key) + ->willThrow($ce); $third = $this->prophesize(ContainerInterface::class); - $third->has($key)->willReturn(true); - $third->get($key)->willReturn($value); + $third->has($key) + ->willReturn(true); + $third->get($key) + ->willReturn($value); - $container = new AggregateContainer( - $first->reveal(), - $second->reveal(), - $third->reveal(), - ); + $container = new AggregateContainer($first->reveal(), $second->reveal(), $third->reveal()); self::assertSame($value, $container->get($key)); } + /** + * @return void + */ #[Test] - public function testGetThrowsWhenNoContainerCanResolve(): void + public function getThrowsWhenNoContainerCanResolve(): void { $this->expectException(NotFoundException::class); $key = uniqid('unavailable_', true); $c1 = $this->prophesize(ContainerInterface::class); - $c1->has($key)->willReturn(false); + $c1->has($key) + ->willReturn(false); $c2 = $this->prophesize(ContainerInterface::class); - $c2->has($key)->willReturn(false); + $c2->has($key) + ->willReturn(false); $container = new AggregateContainer($c1->reveal(), $c2->reveal()); $container->get($key); } + /** + * @return void + */ #[Test] - public function testAppendAddsContainerAtTheEnd(): void + public function appendAddsContainerAtTheEnd(): void { $key = uniqid('svc_', true); $value = uniqid('val_', true); $first = $this->prophesize(ContainerInterface::class); - $first->has($key)->willReturn(false); + $first->has($key) + ->willReturn(false); $second = $this->prophesize(ContainerInterface::class); - $second->has($key)->willReturn(true); - $second->get($key)->willReturn($value); + $second->has($key) + ->willReturn(true); + $second->get($key) + ->willReturn($value); $container = new AggregateContainer($first->reveal()); $container->append($second->reveal()); @@ -180,19 +225,26 @@ public function testAppendAddsContainerAtTheEnd(): void self::assertSame($value, $container->get($key)); } + /** + * @return void + */ #[Test] - public function testPrependAddsContainerAtTheBeginning(): void + public function prependAddsContainerAtTheBeginning(): void { $key = uniqid('service_', true); $value = uniqid('val_', true); $first = $this->prophesize(ContainerInterface::class); - $first->has($key)->willReturn(true); - $first->get($key)->willReturn($value); + $first->has($key) + ->willReturn(true); + $first->get($key) + ->willReturn($value); $second = $this->prophesize(ContainerInterface::class); - $second->has($key)->willReturn(true); - $second->get($key)->willReturn('incorrect'); + $second->has($key) + ->willReturn(true); + $second->get($key) + ->willReturn('incorrect'); $container = new AggregateContainer($second->reveal()); $container->prepend($first->reveal()); @@ -201,15 +253,21 @@ public function testPrependAddsContainerAtTheBeginning(): void self::assertSame($value, $container->get($key)); } + /** + * @return void + */ #[Test] - public function testGetUsesInternalCacheAfterFirstResolution(): void + public function getUsesInternalCacheAfterFirstResolution(): void { $key = 'shared.service'; $value = uniqid('resolved_', true); $sub = $this->prophesize(ContainerInterface::class); - $sub->has($key)->willReturn(true); - $sub->get($key)->willReturn($value)->shouldBeCalledTimes(1); + $sub->has($key) + ->willReturn(true); + $sub->get($key) + ->willReturn($value) + ->shouldBeCalledTimes(1); $container = new AggregateContainer($sub->reveal()); diff --git a/tests/AutowireContainerTest.php b/tests/AutowireContainerTest.php index a79d6e5..e1fb4e5 100644 --- a/tests/AutowireContainerTest.php +++ b/tests/AutowireContainerTest.php @@ -8,19 +8,25 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests; +use stdClass; +use RuntimeException; +use ArrayObject; use DI\Container; use FastForward\Container\AggregateContainer; use FastForward\Container\AutowireContainer; use FastForward\Container\Exception\NotFoundException; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -37,58 +43,87 @@ final class AutowireContainerTest extends TestCase { use ProphecyTrait; - public function testGetDelegatesToInternalContainer(): void + /** + * @return void + */ + #[Test] + public function getDelegatesToInternalContainer(): void { - $expected = new \stdClass(); + $expected = new stdClass(); $delegate = $this->prophesize(ContainerInterface::class); - $delegate->has('my.service')->willReturn(true); - $delegate->get('my.service')->willReturn($expected); + $delegate->has('my.service') + ->willReturn(true); + $delegate->get('my.service') + ->willReturn($expected); $container = new AutowireContainer($delegate->reveal()); self::assertSame($expected, $container->get('my.service')); } - public function testHasReturnsFalseWhenServiceNotPresent(): void + /** + * @return void + */ + #[Test] + public function hasReturnsFalseWhenServiceNotPresent(): void { $delegate = $this->prophesize(ContainerInterface::class); - $delegate->has('missing')->willReturn(false); + $delegate->has('missing') + ->willReturn(false); $container = new AutowireContainer($delegate->reveal()); self::assertFalse($container->has('missing')); } - public function testHasReturnsFalseWhenResolutionFails(): void + /** + * @return void + */ + #[Test] + public function hasReturnsFalseWhenResolutionFails(): void { $delegate = $this->prophesize(ContainerInterface::class); - $delegate->has('unstable')->willReturn(true); - $delegate->get('unstable')->willThrow(new \RuntimeException()); + $delegate->has('unstable') + ->willReturn(true); + $delegate->get('unstable') + ->willThrow(new RuntimeException()); $container = new AutowireContainer($delegate->reveal()); self::assertFalse($container->has('unstable')); } - public function testHasReturnsTrueWhenServiceIsResolvable(): void + /** + * @return void + */ + #[Test] + public function hasReturnsTrueWhenServiceIsResolvable(): void { - $service = new \ArrayObject(); + $service = new ArrayObject(); $delegate = $this->prophesize(ContainerInterface::class); - $delegate->has('resolvable')->willReturn(true); - $delegate->get('resolvable')->willReturn($service); + $delegate->has('resolvable') + ->willReturn(true); + $delegate->get('resolvable') + ->willReturn($service); $container = new AutowireContainer($delegate->reveal()); self::assertTrue($container->has('resolvable')); } - public function testAutowireContainerAcceptsAggregateContainerDirectly(): void + /** + * @return void + */ + #[Test] + public function autowireContainerAcceptsAggregateContainerDirectly(): void { $aggregate = $this->prophesize(AggregateContainer::class); - $aggregate->has('resolvable')->willReturn(true); - $aggregate->get('resolvable')->willReturn($aggregate->reveal()); + $aggregate->has('resolvable') + ->willReturn(true); + $aggregate->get('resolvable') + ->willReturn($aggregate->reveal()); $aggregate->append(Argument::type(Container::class))->shouldBeCalledOnce(); $container = new AutowireContainer($aggregate->reveal()); diff --git a/tests/ContainerFunctionTest.php b/tests/ContainerFunctionTest.php index 09cefbb..9fc5b3b 100644 --- a/tests/ContainerFunctionTest.php +++ b/tests/ContainerFunctionTest.php @@ -8,14 +8,17 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests; +use RuntimeException; use FastForward\Config\ConfigInterface; use FastForward\Config\Container\ConfigContainer; use FastForward\Container\AggregateContainer; @@ -45,6 +48,9 @@ final class ContainerFunctionTest extends TestCase { use ProphecyTrait; + /** + * @return void + */ public function testReturnsAutowireContainerWrappingAggregate(): void { $result = container(); @@ -52,11 +58,16 @@ public function testReturnsAutowireContainerWrappingAggregate(): void self::assertInstanceOf(AutowireContainer::class, $result); } + /** + * @return void + */ public function testAcceptsPsrContainerAsInitializer(): void { $psr = $this->prophesize(ContainerInterface::class); - $psr->has('service')->willReturn(true); - $psr->get('service')->willReturn($psr->reveal()); + $psr->has('service') + ->willReturn(true); + $psr->get('service') + ->willReturn($psr->reveal()); $container = container($psr->reveal()); @@ -64,11 +75,16 @@ public function testAcceptsPsrContainerAsInitializer(): void self::assertSame($psr->reveal(), $container->get('service')); } + /** + * @return void + */ public function testAcceptsServiceProviderAsInitializer(): void { $provider = $this->prophesize(ServiceProviderInterface::class); - $provider->getFactories()->willReturn([]); - $provider->getExtensions()->willReturn([]); + $provider->getFactories() + ->willReturn([]); + $provider->getExtensions() + ->willReturn([]); $container = container($provider->reveal()); @@ -76,6 +92,9 @@ public function testAcceptsServiceProviderAsInitializer(): void self::assertInstanceOf(ServiceProviderContainer::class, $container->get(ServiceProviderContainer::class)); } + /** + * @return void + */ public function testAcceptsConfigInterfaceAsInitializer(): void { $config = $this->prophesize(ConfigInterface::class); @@ -88,6 +107,9 @@ public function testAcceptsConfigInterfaceAsInitializer(): void self::assertInstanceOf(ConfigContainer::class, $container->get(ConfigContainer::class)); } + /** + * @return void + */ public function testAcceptsInstantiableString(): void { $container = container(DummyContainer::class); @@ -96,6 +118,9 @@ public function testAcceptsInstantiableString(): void self::assertInstanceOf(DummyContainer::class, $container->get(DummyContainer::class)); } + /** + * @return void + */ public function testThrowsForUnsupportedInitializer(): void { $this->expectException(InvalidArgumentException::class); @@ -103,6 +128,9 @@ public function testThrowsForUnsupportedInitializer(): void container(uniqid()); } + /** + * @return void + */ public function testConfigContainerWithNestedInitializers(): void { $nested = new DummyContainer(); @@ -116,11 +144,14 @@ public function testConfigContainerWithNestedInitializers(): void self::assertInstanceOf(DummyContainer::class, $container->get(DummyContainer::class)); } + /** + * @return void + */ public function testContainerSkipsThrowableThrownByConfigContainer(): void { $config = $this->prophesize(ConfigInterface::class); $config->has(ContainerInterface::class)->willReturn(true); - $config->get(ContainerInterface::class)->willThrow(new \RuntimeException('unexpected')); + $config->get(ContainerInterface::class)->willThrow(new RuntimeException('unexpected')); $container = container($config->reveal()); @@ -130,11 +161,21 @@ public function testContainerSkipsThrowableThrownByConfigContainer(): void final class DummyContainer implements ContainerInterface { + /** + * @param string $id + * + * @return mixed + */ public function get(string $id): mixed { return $this; } + /** + * @param string $id + * + * @return bool + */ public function has(string $id): bool { return true; diff --git a/tests/Exception/ContainerExceptionTest.php b/tests/Exception/ContainerExceptionTest.php index 46809e9..466b222 100644 --- a/tests/Exception/ContainerExceptionTest.php +++ b/tests/Exception/ContainerExceptionTest.php @@ -8,16 +8,20 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests\Exception; +use RuntimeException; use FastForward\Container\Exception\ContainerException; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerExceptionInterface; @@ -27,10 +31,14 @@ #[CoversClass(ContainerException::class)] final class ContainerExceptionTest extends TestCase { - public function testForInvalidServiceWillReturnProperException(): void + /** + * @return void + */ + #[Test] + public function forInvalidServiceWillReturnProperException(): void { $id = uniqid('service.id'); - $previous = new \RuntimeException('Underlying issue', 500); + $previous = new RuntimeException('Underlying issue', 500); $exception = ContainerException::forInvalidService($id, $previous); diff --git a/tests/Exception/InvalidArgumentExceptionTest.php b/tests/Exception/InvalidArgumentExceptionTest.php index 582e7f9..491eb35 100644 --- a/tests/Exception/InvalidArgumentExceptionTest.php +++ b/tests/Exception/InvalidArgumentExceptionTest.php @@ -8,16 +8,20 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests\Exception; +use stdClass; use FastForward\Container\Exception\InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; /** @@ -26,24 +30,38 @@ #[CoversClass(InvalidArgumentException::class)] final class InvalidArgumentExceptionTest extends TestCase { - public function testForUnsupportedInitializerReturnsExceptionWithCorrectMessage(): void + /** + * @return void + */ + #[Test] + public function forUnsupportedInitializerReturnsExceptionWithCorrectMessage(): void { - $input = ['not' => 'valid']; + $input = [ + 'not' => 'valid', + ]; $exception = InvalidArgumentException::forUnsupportedInitializer($input); self::assertInstanceOf(InvalidArgumentException::class, $exception); self::assertSame('Unsupported initializer type: array', $exception->getMessage()); } - public function testForUnsupportedInitializerWithObject(): void + /** + * @return void + */ + #[Test] + public function forUnsupportedInitializerWithObject(): void { - $input = new \stdClass(); + $input = new stdClass(); $exception = InvalidArgumentException::forUnsupportedInitializer($input); self::assertSame('Unsupported initializer type: stdClass', $exception->getMessage()); } - public function testForUnsupportedInitializerWithScalar(): void + /** + * @return void + */ + #[Test] + public function forUnsupportedInitializerWithScalar(): void { $exception = InvalidArgumentException::forUnsupportedInitializer(42); self::assertSame('Unsupported initializer type: int', $exception->getMessage()); diff --git a/tests/Exception/NotFoundExceptionTest.php b/tests/Exception/NotFoundExceptionTest.php index 9a601c6..bab9fb1 100644 --- a/tests/Exception/NotFoundExceptionTest.php +++ b/tests/Exception/NotFoundExceptionTest.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -28,6 +30,9 @@ #[CoversClass(NotFoundException::class)] final class NotFoundExceptionTest extends TestCase { + /** + * @return void + */ #[Test] public function testForServiceIDReturnsExpectedMessage(): void { diff --git a/tests/Exception/RuntimeExceptionTest.php b/tests/Exception/RuntimeExceptionTest.php index ae05b9a..212b46f 100644 --- a/tests/Exception/RuntimeExceptionTest.php +++ b/tests/Exception/RuntimeExceptionTest.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -18,6 +20,7 @@ use FastForward\Container\Exception\RuntimeException; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; /** @@ -26,7 +29,11 @@ #[CoversClass(RuntimeException::class)] final class RuntimeExceptionTest extends TestCase { - public function testForNonCallableExtensionReturnsProperException(): void + /** + * @return void + */ + #[Test] + public function forNonCallableExtensionReturnsProperException(): void { $exception = RuntimeException::forNonCallableExtension('db.connection', 'array'); @@ -37,7 +44,11 @@ public function testForNonCallableExtensionReturnsProperException(): void ); } - public function testForNonPublicMethodReturnsProperException(): void + /** + * @return void + */ + #[Test] + public function forNonPublicMethodReturnsProperException(): void { $exception = RuntimeException::forNonPublicMethod('My\Service', 'configure'); @@ -48,7 +59,11 @@ public function testForNonPublicMethodReturnsProperException(): void ); } - public function testForInvalidParameterTypeReturnsProperException(): void + /** + * @return void + */ + #[Test] + public function forInvalidParameterTypeReturnsProperException(): void { $exception = RuntimeException::forInvalidParameterType('logger'); diff --git a/tests/Factory/AliasFactoryTest.php b/tests/Factory/AliasFactoryTest.php index e99caf9..e6e8689 100644 --- a/tests/Factory/AliasFactoryTest.php +++ b/tests/Factory/AliasFactoryTest.php @@ -8,16 +8,20 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests\Factory; +use stdClass; use FastForward\Container\Factory\AliasFactory; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Psr\Container\ContainerInterface; @@ -30,13 +34,19 @@ final class AliasFactoryTest extends TestCase { use ProphecyTrait; - public function testInvokeResolvesAliasedServiceFromContainer(): void + /** + * @return void + */ + #[Test] + public function invokeResolvesAliasedServiceFromContainer(): void { - $service = new \stdClass(); + $service = new stdClass(); $alias = 'my.service'; $container = $this->prophesize(ContainerInterface::class); - $container->get($alias)->willReturn($service)->shouldBeCalled(); + $container->get($alias) + ->willReturn($service) + ->shouldBeCalled(); $factory = new AliasFactory($alias); $result = $factory($container->reveal()); @@ -44,7 +54,11 @@ public function testInvokeResolvesAliasedServiceFromContainer(): void self::assertSame($service, $result); } - public function testGetReturnsSameInstanceForSameAlias(): void + /** + * @return void + */ + #[Test] + public function getReturnsSameInstanceForSameAlias(): void { $a1 = AliasFactory::get('foo'); $a2 = AliasFactory::get('foo'); @@ -53,7 +67,11 @@ public function testGetReturnsSameInstanceForSameAlias(): void self::assertSame($a1, $a2); } - public function testGetReturnsNewInstanceForDifferentAliases(): void + /** + * @return void + */ + #[Test] + public function getReturnsNewInstanceForDifferentAliases(): void { $a1 = AliasFactory::get('foo'); $a2 = AliasFactory::get('bar'); diff --git a/tests/Factory/CallableFactoryTest.php b/tests/Factory/CallableFactoryTest.php index 6a6bd37..0890b9b 100644 --- a/tests/Factory/CallableFactoryTest.php +++ b/tests/Factory/CallableFactoryTest.php @@ -8,9 +8,11 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ @@ -21,6 +23,7 @@ use FastForward\Container\Factory\FactoryInterface; use Interop\Container\ServiceProviderInterface; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -35,11 +38,17 @@ final class CallableFactoryTest extends TestCase { use ProphecyTrait; - public function testInvokeWillReturnProvidedCallableReturns(): void + /** + * @return void + */ + #[Test] + public function invokeWillReturnProvidedCallableReturns(): void { $container = $this->prophesize(ContainerInterface::class)->reveal(); - $factory = new CallableFactory(static fn () => (object) ['resolved' => true]); + $factory = new CallableFactory(static fn() => (object) [ + 'resolved' => true, + ]); $result = $factory($container); @@ -47,7 +56,11 @@ public function testInvokeWillReturnProvidedCallableReturns(): void self::assertTrue($result->resolved); } - public function testClosureReceivesContainerDependenciesAsArgument(): void + /** + * @return void + */ + #[Test] + public function closureReceivesContainerDependenciesAsArgument(): void { $container = $this->prophesize(ContainerInterface::class); $factoryInterface = $this->prophesize(FactoryInterface::class)->reveal(); @@ -56,21 +69,31 @@ public function testClosureReceivesContainerDependenciesAsArgument(): void $container->get(ServiceProviderInterface::class)->willReturn($serviceProvider); $container->get(FactoryInterface::class)->willReturn($factoryInterface); - $factory = new CallableFactory(static fn ( + $factory = new CallableFactory(static fn( ServiceProviderInterface $serviceProvider, FactoryInterface $factoryInterface - ) => compact('serviceProvider', 'factoryInterface')); + ): array => [ + 'serviceProvider' => $serviceProvider, + 'factoryInterface' => $factoryInterface, + ]); $actual = $factory($container->reveal()); - self::assertSame(compact('serviceProvider', 'factoryInterface'), $actual); + self::assertSame([ + 'serviceProvider' => $serviceProvider, + 'factoryInterface' => $factoryInterface, + ], $actual); } - public function testInvokeWillThrowRuntimeExceptionIfParameterIsNotAClass(): void + /** + * @return void + */ + #[Test] + public function invokeWillThrowRuntimeExceptionIfParameterIsNotAClass(): void { $container = $this->prophesize(ContainerInterface::class)->reveal(); - $factory = new CallableFactory(static fn (string $notAClass) => $notAClass); + $factory = new CallableFactory(static fn(string $notAClass): string => $notAClass); $this->expectException(RuntimeException::class); diff --git a/tests/Factory/InvokableFactoryTest.php b/tests/Factory/InvokableFactoryTest.php index 1e148cf..2cd39ef 100644 --- a/tests/Factory/InvokableFactoryTest.php +++ b/tests/Factory/InvokableFactoryTest.php @@ -8,16 +8,21 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests\Factory; +use stdClass; +use DateTimeImmutable; use FastForward\Container\Factory\InvokableFactory; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -31,36 +36,51 @@ final class InvokableFactoryTest extends TestCase { use ProphecyTrait; - public function testInvokeInstantiatesClassWithoutArguments(): void + /** + * @return void + */ + #[Test] + public function invokeInstantiatesClassWithoutArguments(): void { - $factory = new InvokableFactory(\stdClass::class); + $factory = new InvokableFactory(stdClass::class); $container = $this->prophesize(ContainerInterface::class)->reveal(); $result = $factory($container); - self::assertInstanceOf(\stdClass::class, $result); + self::assertInstanceOf(stdClass::class, $result); } - public function testInvokeInstantiatesClassWithArguments(): void + /** + * @return void + */ + #[Test] + public function invokeInstantiatesClassWithArguments(): void { - $factory = new InvokableFactory(\DateTimeImmutable::class, '2024-01-01'); + $factory = new InvokableFactory(DateTimeImmutable::class, '2024-01-01'); $container = $this->prophesize(ContainerInterface::class); $container->has(Argument::type('string'))->willReturn(false); $result = $factory($container->reveal()); - self::assertInstanceOf(\DateTimeImmutable::class, $result); + self::assertInstanceOf(DateTimeImmutable::class, $result); self::assertSame('2024-01-01', $result->format('Y-m-d')); } - public function testInvokeResolvesStringArgumentsFromContainer(): void + /** + * @return void + */ + #[Test] + public function invokeResolvesStringArgumentsFromContainer(): void { - $dependency = new \stdClass(); + $dependency = new stdClass(); $container = $this->prophesize(ContainerInterface::class); - $container->has('my.dependency')->willReturn(true); - $container->has('literal')->willReturn(false); - $container->get('my.dependency')->willReturn($dependency); + $container->has('my.dependency') + ->willReturn(true); + $container->has('literal') + ->willReturn(false); + $container->get('my.dependency') + ->willReturn($dependency); $factory = new InvokableFactory(DummyService::class, 'my.dependency', 'literal'); $service = $factory($container->reveal()); @@ -73,6 +93,10 @@ public function testInvokeResolvesStringArgumentsFromContainer(): void class DummyService { + /** + * @param object $dependency + * @param string $name + */ public function __construct( public readonly object $dependency, public readonly string $name diff --git a/tests/Factory/MethodFactoryTest.php b/tests/Factory/MethodFactoryTest.php index 8115b9a..d7c61b5 100644 --- a/tests/Factory/MethodFactoryTest.php +++ b/tests/Factory/MethodFactoryTest.php @@ -8,17 +8,22 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests\Factory; +use stdClass; +use Exception; use FastForward\Container\Exception\RuntimeException; use FastForward\Container\Factory\MethodFactory; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -33,12 +38,17 @@ final class MethodFactoryTest extends TestCase { use ProphecyTrait; - public function testInvokeInstanceMethod(): void + /** + * @return void + */ + #[Test] + public function invokeInstanceMethod(): void { $service = new MethodFactoryTestTargetStub(); $container = $this->prophesize(ContainerInterface::class); - $container->has('prefix')->willReturn(false); + $container->has('prefix') + ->willReturn(false); $container->has(MethodFactoryTestTargetStub::class)->willReturn(true); $container->get(MethodFactoryTestTargetStub::class)->willReturn($service); @@ -49,10 +59,15 @@ public function testInvokeInstanceMethod(): void self::assertSame('prefix-instance', $result); } - public function testInvokeStaticMethod(): void + /** + * @return void + */ + #[Test] + public function invokeStaticMethod(): void { $container = $this->prophesize(ContainerInterface::class); - $container->has('value')->willReturn(false); + $container->has('value') + ->willReturn(false); $factory = new MethodFactory(MethodFactoryTestTargetStub::class, 'staticMethod', 'value'); @@ -61,16 +76,22 @@ public function testInvokeStaticMethod(): void self::assertSame('static-value', $result); } - public function testInvokeResolvesArgumentsFromContainer(): void + /** + * @return void + */ + #[Test] + public function invokeResolvesArgumentsFromContainer(): void { $service = new MethodFactoryTestTargetStub(); - $argObj = new \stdClass(); + $argObj = new stdClass(); $container = $this->prophesize(ContainerInterface::class); $container->has(MethodFactoryTestTargetStub::class)->willReturn(true); $container->get(MethodFactoryTestTargetStub::class)->willReturn($service); - $container->has('dependency')->willReturn(true); - $container->get('dependency')->willReturn($argObj); + $container->has('dependency') + ->willReturn(true); + $container->get('dependency') + ->willReturn($argObj); $factory = new MethodFactory(MethodFactoryTestTargetStub::class, 'acceptsObject', 'dependency'); @@ -79,7 +100,11 @@ public function testInvokeResolvesArgumentsFromContainer(): void self::assertSame($argObj, $result); } - public function testInvokeThrowsForNonPublicMethod(): void + /** + * @return void + */ + #[Test] + public function invokeThrowsForNonPublicMethod(): void { $container = $this->prophesize(ContainerInterface::class); $container->has(MethodFactoryTestTargetStub::class)->willReturn(false); @@ -91,11 +116,16 @@ public function testInvokeThrowsForNonPublicMethod(): void $factory($container->reveal()); } - public function testInvokeWillConstructTargetIfContainerDoesNotProvide(): void + /** + * @return void + */ + #[Test] + public function invokeWillConstructTargetIfContainerDoesNotProvide(): void { $container = $this->prophesize(ContainerInterface::class); - $container->has('prefix')->willReturn(false); - $container->get(MethodFactoryTestTargetStub::class)->willThrow(new \Exception()); + $container->has('prefix') + ->willReturn(false); + $container->get(MethodFactoryTestTargetStub::class)->willThrow(new Exception()); $factory = new MethodFactory(MethodFactoryTestTargetStub::class, 'instanceMethod', 'prefix'); @@ -104,14 +134,21 @@ public function testInvokeWillConstructTargetIfContainerDoesNotProvide(): void self::assertSame('prefix-instance', $result); } - public function testConstructorDependencyIsResolvedFromContainer(): void + /** + * @return void + */ + #[Test] + public function constructorDependencyIsResolvedFromContainer(): void { $dependency = new MethodFactoryTestDependencyStub(); $container = $this->prophesize(ContainerInterface::class); $container->has(MethodFactoryTestTargetStub::class)->willReturn(true); - $container->get(MethodFactoryTestTargetStub::class)->willReturn(new MethodFactoryTestTargetStub($dependency))->shouldBeCalledOnce(); - $container->has('suffix')->willReturn(false); + $container->get(MethodFactoryTestTargetStub::class)->willReturn( + new MethodFactoryTestTargetStub($dependency) + )->shouldBeCalledOnce(); + $container->has('suffix') + ->willReturn(false); $factory = new MethodFactory(MethodFactoryTestTargetStub::class, 'usesConstructorArgument', 'suffix'); @@ -123,28 +160,58 @@ public function testConstructorDependencyIsResolvedFromContainer(): void class MethodFactoryTestTargetStub { - public function __construct(private ?MethodFactoryTestDependencyStub $dependency = null) {} + /** + * @param MethodFactoryTestDependencyStub|null $dependency + */ + public function __construct( + private readonly ?MethodFactoryTestDependencyStub $dependency = null + ) { + $this->privateMethod(); + } + /** + * @param string $prefix + * + * @return string + */ public function instanceMethod(string $prefix): string { return $prefix . '-instance'; } + /** + * @param string $value + * + * @return string + */ public static function staticMethod(string $value): string { return 'static-' . $value; } + /** + * @param object $obj + * + * @return object + */ public function acceptsObject(object $obj): object { return $obj; } + /** + * @param string $suffix + * + * @return string + */ public function usesConstructorArgument(string $suffix): string { return $this->dependency::class . '-' . $suffix; } + /** + * @return void + */ private function privateMethod(): void {} } diff --git a/tests/Factory/ServiceFactoryTest.php b/tests/Factory/ServiceFactoryTest.php index a318a52..fd3c8a6 100644 --- a/tests/Factory/ServiceFactoryTest.php +++ b/tests/Factory/ServiceFactoryTest.php @@ -8,16 +8,20 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests\Factory; +use stdClass; use FastForward\Container\Factory\ServiceFactory; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Psr\Container\ContainerInterface; @@ -30,9 +34,13 @@ final class ServiceFactoryTest extends TestCase { use ProphecyTrait; - public function testInvokeReturnsSameInstance(): void + /** + * @return void + */ + #[Test] + public function invokeReturnsSameInstance(): void { - $service = new \stdClass(); + $service = new stdClass(); $factory = new ServiceFactory($service); $container = $this->prophesize(ContainerInterface::class)->reveal(); diff --git a/tests/ServiceProvider/AggregateServiceProviderTest.php b/tests/ServiceProvider/AggregateServiceProviderTest.php index 3649ef9..2f804b3 100644 --- a/tests/ServiceProvider/AggregateServiceProviderTest.php +++ b/tests/ServiceProvider/AggregateServiceProviderTest.php @@ -8,19 +8,23 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests\ServiceProvider; +use stdClass; use FastForward\Container\Exception\RuntimeException; use FastForward\Container\Factory\ServiceFactory; use FastForward\Container\ServiceProvider\AggregateServiceProvider; use Interop\Container\ServiceProviderInterface; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -36,22 +40,29 @@ final class AggregateServiceProviderTest extends TestCase { use ProphecyTrait; - public function testGetFactoriesMergesAllProvidersAndIncludesSelf(): void + /** + * @return void + */ + #[Test] + public function getFactoriesMergesAllProvidersAndIncludesSelf(): void { - $factory1 = static fn () => 'foo'; - $factory2 = static fn () => 'bar'; + $factory1 = static fn(): string => 'foo'; + $factory2 = static fn(): string => 'bar'; $provider1 = $this->prophesize(ServiceProviderInterface::class); - $provider1->getFactories()->willReturn(['service.a' => $factory1]); + $provider1->getFactories() + ->willReturn([ + 'service.a' => $factory1, + ]); - $provider2 = $this->prophesize(\stdClass::class); + $provider2 = $this->prophesize(stdClass::class); $provider2->willImplement(ServiceProviderInterface::class); - $provider2->getFactories()->willReturn(['service.b' => $factory2]); + $provider2->getFactories() + ->willReturn([ + 'service.b' => $factory2, + ]); - $aggregate = new AggregateServiceProvider( - $provider1->reveal(), - $provider2->reveal() - ); + $aggregate = new AggregateServiceProvider($provider1->reveal(), $provider2->reveal()); $factories = $aggregate->getFactories(); $container = $this->prophesize(ContainerInterface::class)->reveal(); @@ -73,23 +84,26 @@ public function testGetFactoriesMergesAllProvidersAndIncludesSelf(): void self::assertSame($serviceProvider2, $factories[$serviceProvider2::class]($container)); } - public function testGetExtensionsMergesAllProvidersAndComposesSameKey(): void + /** + * @return void + */ + #[Test] + public function getExtensionsMergesAllProvidersAndComposesSameKey(): void { $provider1 = $this->prophesize(ServiceProviderInterface::class); $provider2 = $this->prophesize(ServiceProviderInterface::class); - $provider1->getExtensions()->willReturn([ - 'shared.service' => static fn (ContainerInterface $c, $prev) => $prev . '.ext1', - ]); + $provider1->getExtensions() + ->willReturn([ + 'shared.service' => static fn(ContainerInterface $c, $prev): string => $prev . '.ext1', + ]); - $provider2->getExtensions()->willReturn([ - 'shared.service' => static fn (ContainerInterface $c, $prev) => $prev . '.ext2', - ]); + $provider2->getExtensions() + ->willReturn([ + 'shared.service' => static fn(ContainerInterface $c, $prev): string => $prev . '.ext2', + ]); - $aggregate = new AggregateServiceProvider( - $provider1->reveal(), - $provider2->reveal() - ); + $aggregate = new AggregateServiceProvider($provider1->reveal(), $provider2->reveal()); $extensions = $aggregate->getExtensions(); $result = $extensions['shared.service']( @@ -100,12 +114,17 @@ public function testGetExtensionsMergesAllProvidersAndComposesSameKey(): void self::assertSame('base.ext1.ext2', $result); } - public function testGetExtensionsThrowsIfExtensionIsNotCallable(): void + /** + * @return void + */ + #[Test] + public function getExtensionsThrowsIfExtensionIsNotCallable(): void { $provider = $this->prophesize(ServiceProviderInterface::class); - $provider->getExtensions()->willReturn([ - 'invalid' => 'not_a_callable', - ]); + $provider->getExtensions() + ->willReturn([ + 'invalid' => 'not_a_callable', + ]); $aggregate = new AggregateServiceProvider($provider->reveal()); diff --git a/tests/ServiceProvider/ArrayServiceProviderTest.php b/tests/ServiceProvider/ArrayServiceProviderTest.php index 3458698..904cedf 100644 --- a/tests/ServiceProvider/ArrayServiceProviderTest.php +++ b/tests/ServiceProvider/ArrayServiceProviderTest.php @@ -8,17 +8,21 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests\ServiceProvider; +use stdClass; use FastForward\Container\ServiceProvider\ArrayServiceProvider; use Interop\Container\ServiceProviderInterface; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; /** @@ -27,16 +31,26 @@ #[CoversClass(ArrayServiceProvider::class)] final class ArrayServiceProviderTest extends TestCase { - public function testImplementsServiceProviderInterface(): void + /** + * @return void + */ + #[Test] + public function implementsServiceProviderInterface(): void { $provider = new ArrayServiceProvider(); self::assertInstanceOf(ServiceProviderInterface::class, $provider); } - public function testReturnsGivenFactories(): void + /** + * @return void + */ + #[Test] + public function returnsGivenFactories(): void { - $factory = static fn () => new \stdClass(); - $provider = new ArrayServiceProvider(['id' => $factory]); + $factory = static fn(): stdClass => new stdClass(); + $provider = new ArrayServiceProvider([ + 'id' => $factory, + ]); $factories = $provider->getFactories(); @@ -44,10 +58,16 @@ public function testReturnsGivenFactories(): void self::assertSame($factory, $factories['id']); } - public function testReturnsGivenExtensions(): void + /** + * @return void + */ + #[Test] + public function returnsGivenExtensions(): void { - $extension = static fn ($c, $p) => $p; - $provider = new ArrayServiceProvider([], ['id' => $extension]); + $extension = static fn($c, $p) => $p; + $provider = new ArrayServiceProvider([], [ + 'id' => $extension, + ]); $extensions = $provider->getExtensions(); @@ -55,7 +75,11 @@ public function testReturnsGivenExtensions(): void self::assertSame($extension, $extensions['id']); } - public function testReturnsEmptyArraysByDefault(): void + /** + * @return void + */ + #[Test] + public function returnsEmptyArraysByDefault(): void { $provider = new ArrayServiceProvider(); diff --git a/tests/ServiceProviderContainerTest.php b/tests/ServiceProviderContainerTest.php index 8d52525..1827875 100644 --- a/tests/ServiceProviderContainerTest.php +++ b/tests/ServiceProviderContainerTest.php @@ -8,19 +8,24 @@ * This source file is subject to the license bundled * with this source code in the file LICENSE. * - * @link https://github.com/php-fast-forward/container - * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu + * @copyright Copyright (c) 2025-2026 Felipe Sayão Lobato Abreu * @license https://opensource.org/licenses/MIT MIT License + * + * @see https://github.com/php-fast-forward/container + * @see https://github.com/php-fast-forward * @see https://datatracker.ietf.org/doc/html/rfc2119 */ namespace FastForward\Container\Tests; +use stdClass; +use RuntimeException; use FastForward\Container\Exception\ContainerException; use FastForward\Container\Exception\NotFoundException; use FastForward\Container\ServiceProviderContainer; use Interop\Container\ServiceProviderInterface; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -42,62 +47,104 @@ final class ServiceProviderContainerTest extends TestCase private ServiceProviderContainer $container; + /** + * @return void + */ protected function setUp(): void { $this->provider = $this->prophesize(ServiceProviderInterface::class); - $this->provider->getFactories()->willReturn([]); - $this->provider->getExtensions()->willReturn([]); + $this->provider->getFactories() + ->willReturn([]); + $this->provider->getExtensions() + ->willReturn([]); $this->container = new ServiceProviderContainer($this->provider->reveal()); } - public function testHasReturnsTrueForCachedService(): void + /** + * @return void + */ + #[Test] + public function hasReturnsTrueForCachedService(): void { - $service = new \stdClass(); + $service = new stdClass(); - $this->provider->getFactories()->willReturn(['foo' => static fn () => $service]); + $this->provider->getFactories() + ->willReturn([ + 'foo' => static fn(): stdClass => $service, + ]); $this->container->get('foo'); self::assertTrue($this->container->has('foo')); } - public function testHasReturnsTrueForExistingFactory(): void + /** + * @return void + */ + #[Test] + public function hasReturnsTrueForExistingFactory(): void { - $this->provider->getFactories()->willReturn(['bar' => static fn () => new \stdClass()]); + $this->provider->getFactories() + ->willReturn([ + 'bar' => static fn(): stdClass => new stdClass(), + ]); self::assertTrue($this->container->has('bar')); } - public function testHasReturnsFalseForUnknownId(): void + /** + * @return void + */ + #[Test] + public function hasReturnsFalseForUnknownId(): void { - $this->provider->getFactories()->willReturn([]); + $this->provider->getFactories() + ->willReturn([]); self::assertFalse($this->container->has('unknown')); } - public function testGetReturnsServiceFromFactory(): void + /** + * @return void + */ + #[Test] + public function getReturnsServiceFromFactory(): void { - $expected = new \stdClass(); + $expected = new stdClass(); - $this->provider->getFactories()->willReturn(['my.service' => static fn () => $expected]); - $this->provider->getExtensions()->willReturn([]); + $this->provider->getFactories() + ->willReturn([ + 'my.service' => static fn(): stdClass => $expected, + ]); + $this->provider->getExtensions() + ->willReturn([]); $actual = $this->container->get('my.service'); self::assertSame($expected, $actual); } - public function testGetAppliesExtensionIfAvailable(): void + /** + * @return void + */ + #[Test] + public function getAppliesExtensionIfAvailable(): void { - $target = new \stdClass(); + $target = new stdClass(); - $factory = static fn (ContainerInterface $c) => $target; + $factory = static fn(ContainerInterface $c): stdClass => $target; $extension = static function (ContainerInterface $c, object $service): void { $service->extended = true; }; - $this->provider->getFactories()->willReturn(['foo' => $factory]); - $this->provider->getExtensions()->willReturn(['foo' => $extension]); + $this->provider->getFactories() + ->willReturn([ + 'foo' => $factory, + ]); + $this->provider->getExtensions() + ->willReturn([ + 'foo' => $extension, + ]); $service = $this->container->get('foo'); @@ -105,23 +152,34 @@ public function testGetAppliesExtensionIfAvailable(): void self::assertTrue($service->extended); } - public function testGetThrowsNotFoundExceptionForMissingFactory(): void + /** + * @return void + */ + #[Test] + public function getThrowsNotFoundExceptionForMissingFactory(): void { - $this->provider->getFactories()->willReturn([]); + $this->provider->getFactories() + ->willReturn([]); $this->expectException(NotFoundException::class); $this->expectExceptionMessage('Service "invalid" not found.'); $this->container->get('invalid'); } - public function testGetWrapsContainerException(): void + /** + * @return void + */ + #[Test] + public function getWrapsContainerException(): void { - $this->provider->getFactories()->willReturn([ - 'fail.service' => static function (): void { - throw new class('fail') extends \RuntimeException implements ContainerExceptionInterface {}; - }, - ]); - $this->provider->getExtensions()->willReturn([]); + $this->provider->getFactories() + ->willReturn([ + 'fail.service' => static function (): never { + throw new class ('fail') extends RuntimeException implements ContainerExceptionInterface {}; + }, + ]); + $this->provider->getExtensions() + ->willReturn([]); $this->expectException(ContainerException::class); $this->expectExceptionMessage('Invalid service "fail.service".'); @@ -129,18 +187,29 @@ public function testGetWrapsContainerException(): void $this->container->get('fail.service'); } - public function testGetWillReturnFromCacheWhenSericeAlreadyResolved(): void + /** + * @return void + */ + #[Test] + public function getWillReturnFromCacheWhenSericeAlreadyResolved(): void { - $service = new \stdClass(); + $service = new stdClass(); - $this->provider->getFactories()->willReturn(['foo' => static fn () => $service])->shouldBeCalledOnce(); + $this->provider->getFactories() + ->willReturn([ + 'foo' => static fn(): stdClass => $service, + ])->shouldBeCalledOnce(); $this->container->get('foo'); self::assertSame($service, $this->container->get('foo')); } - public function testApplyServiceExtensionByClassName(): void + /** + * @return void + */ + #[Test] + public function applyServiceExtensionByClassName(): void { $service = new class { public bool $extended = false; @@ -150,15 +219,25 @@ public function testApplyServiceExtensionByClassName(): void $service->extended = true; }; - $this->provider->getFactories()->willReturn(['service.id' => static fn () => $service]); - $this->provider->getExtensions()->willReturn([\get_class($service) => $extension]); + $this->provider->getFactories() + ->willReturn([ + 'service.id' => static fn(): object => $service, + ]); + $this->provider->getExtensions() + ->willReturn([ + $service::class => $extension, + ]); $resolved = $this->container->get('service.id'); self::assertTrue($resolved->extended); } - public function testApplyServiceExtensionByIdAndClass(): void + /** + * @return void + */ + #[Test] + public function applyServiceExtensionByIdAndClass(): void { $service = new class { public array $calls = []; @@ -172,26 +251,38 @@ public function testApplyServiceExtensionByIdAndClass(): void $service->calls[] = 'class'; }; - $this->provider->getFactories()->willReturn(['dual' => static fn () => $service]); - $this->provider->getExtensions()->willReturn([ - 'dual' => $byIdExtension, - \get_class($service) => $byClassExtension, - ]); + $this->provider->getFactories() + ->willReturn([ + 'dual' => static fn(): object => $service, + ]); + $this->provider->getExtensions() + ->willReturn([ + 'dual' => $byIdExtension, + $service::class => $byClassExtension, + ]); $resolved = $this->container->get('dual'); self::assertSame(['id', 'class'], $resolved->calls); } - public function testApplyServiceIgnoresNonCallableExtensions(): void + /** + * @return void + */ + #[Test] + public function applyServiceIgnoresNonCallableExtensions(): void { - $service = new \stdClass(); - - $this->provider->getFactories()->willReturn(['not.callable' => static fn () => $service]); - $this->provider->getExtensions()->willReturn([ - 'not.callable' => 'not_a_function', - \get_class($service) => 123, - ]); + $service = new stdClass(); + + $this->provider->getFactories() + ->willReturn([ + 'not.callable' => static fn(): stdClass => $service, + ]); + $this->provider->getExtensions() + ->willReturn([ + 'not.callable' => 'not_a_function', + $service::class => 123, + ]); $resolved = $this->container->get('not.callable');