[
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.php]\nij_php_space_before_short_closure_left_parenthesis = true\nij_php_space_after_type_cast = true\n\n[*.yml]\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Autodetect text files\n* text=auto eol=lf\n\n# ...Unless the name matches the following overriding patterns\n\n# Definitively text files\n*.php  text\n*.css  text\n*.js   text\n*.txt  text\n*.md   text\n*.xml  text\n*.json text\n*.bat  text\n*.sql  text\n*.yml  text\n\n# Ensure those won't be messed up with\n*.png  binary\n*.jpg  binary\n*.gif  binary\n*.ttf  binary\n\n# Ignore some meta files when creating an archive of this repository\n/.github            export-ignore\n/.editorconfig      export-ignore\n/.gitattributes     export-ignore\n/.gitignore         export-ignore\n/phpunit.xml.dist   export-ignore\n/tests              export-ignore\n/docs               export-ignore\n\n# Avoid merge conflicts in CHANGELOG\n# https://about.gitlab.com/2015/02/10/gitlab-reduced-merge-conflicts-by-90-percent-with-changelog-placeholders/\n/CHANGELOG.md\t\tmerge=union\n\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Yii Contributor Code of Conduct\n\n## Our Pledge\n\nAs contributors and maintainers of this project, and in order to keep Yii community open and welcoming, we ask to\nrespect all community members.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our community include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the overall community\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery, and sexual attention or advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Enforcement Responsibilities\n\nCore team members are responsible for clarifying and enforcing our standards of acceptable behavior and will take \nappropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCore team members have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,\nissues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for \nmoderation decisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when an individual is officially representing\nthe community in public spaces. Examples of representing a project or community include using an official e-mail\naddress, posting via an official social media account, within project GitHub, official forum or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting core team members. All\ncomplaints will be reviewed and investigated promptly and fairly.\n\nAll core team members are obligated to respect the privacy and security of the reporter of any incident.\n\n## Enforcement Guidelines\n\nCore team members will follow these Community Impact Guidelines in determining the consequences for any action they\ndeem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in \nthe community.\n\n**Consequence**: A private, written warning from core team members, providing clarity around the nature of the violation\nand an explanation of why the behavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series of actions.\n\n**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including\nunsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding\ninteractions in community spaces as well as external channels like social media. Violating these terms may lead to \na temporary or permanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified\nperiod of time. No public or private interaction with the people involved, including unsolicited interaction with those\nenforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate\nbehavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within the community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at\n[https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Prerequisites\n\n- [Yii goal and values](https://github.com/yiisoft/docs/blob/master/001-yii-values.md)\n- [Namespaces](https://github.com/yiisoft/docs/blob/master/004-namespaces.md)\n- [Git commit messages](https://github.com/yiisoft/docs/blob/master/006-git-commit-messages.md)\n- [Exceptions](https://github.com/yiisoft/docs/blob/master/007-exceptions.md)\n- [Interfaces](https://github.com/yiisoft/docs/blob/master/008-interfaces.md)\n\n# Getting started\n\nSince Yii 3 consists of many packages, we have a [special development tool](https://github.com/yiisoft/docs/blob/master/005-development-tool.md).\n\n1. [Clone the repository](https://github.com/yiisoft/yii-dev-tool).\n\n2. [Set up your own fork](https://github.com/yiisoft/yii-dev-tool#using-your-own-fork).\n\n3. Now you are ready. Fork any package listed in `packages.php` and do `./yii-dev install username/package`.\n\nIf you don't have any particular package in mind to start with:\n\n- [Check roadmap](https://github.com/yiisoft/docs/blob/master/003-roadmap.md).\n- Check package issues at github. Usually there are some.\n- Ask @samdark.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "| Q             | A\n| ------------- | ---\n| Is bugfix?    | ✔️/❌\n| New feature?  | ✔️/❌\n| Breaks BC?    | ✔️/❌\n| Fixed issues  | comma-separated list of tickets # fixed by the PR, if any\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\nPlease use the [security issue form](https://www.yiiframework.com/security) to report to us any security issue you\nfind in Yii. DO NOT use the issue tracker or discuss it in the public forum as it will cause more damage than help.\n\nPlease note that as a non-commercial OpenSource project we are not able to pay bounties at the moment.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n    # Maintain dependencies for GitHub Actions.\n    - package-ecosystem: \"github-actions\"\n      directory: \"/\"\n      schedule:\n          interval: \"daily\"\n      # Too noisy. See https://github.community/t/increase-if-necessary-for-github-actions-in-dependabot/179581\n      open-pull-requests-limit: 0\n\n    # Maintain dependencies for Composer\n    - package-ecosystem: \"composer\"\n      directory: \"/\"\n      schedule:\n          interval: \"daily\"\n      versioning-strategy: increase-if-necessary\n"
  },
  {
    "path": ".github/workflows/bc.yml",
    "content": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'infection.json.dist'\n      - 'phpunit.xml.dist'\n      - 'psalm.xml'\n  push:\n    branches: ['master']\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'infection.json.dist'\n      - 'phpunit.xml.dist'\n      - 'psalm.xml'\n\nname: backwards compatibility\n\njobs:\n  roave_bc_check:\n    uses: yiisoft/actions/.github/workflows/bc.yml@master\n    with:\n      os: >-\n        ['ubuntu-latest']\n      php: >-\n        ['8.4']\n"
  },
  {
    "path": ".github/workflows/bechmark.yml",
    "content": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'infection.json.dist'\n      - 'psalm.xml'\n\n  push:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'infection.json.dist'\n      - 'psalm.xml'\n\nname: bechmark\n\njobs:\n  phpbench:\n    uses: yiisoft/actions/.github/workflows/phpbench.yml@master\n    with:\n      os: >-\n        ['ubuntu-latest', 'windows-latest']\n      php: >-\n        ['8.1']\n\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'infection.json.dist'\n      - 'psalm.xml'\n\n  push:\n    branches: ['master']\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'infection.json.dist'\n      - 'psalm.xml'\n\nname: build\n\njobs:\n  phpunit:\n    uses: yiisoft/actions/.github/workflows/phpunit.yml@master\n    secrets:\n      codecovToken: ${{ secrets.CODECOV_TOKEN }}\n    with:\n      os: >-\n        ['ubuntu-latest', 'windows-latest']\n      php: >-\n        ['8.1', '8.2', '8.3', '8.4', '8.5']\n"
  },
  {
    "path": ".github/workflows/composer-require-checker.yml",
    "content": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'infection.json.dist'\n      - 'phpunit.xml.dist'\n      - 'psalm.xml'\n\n  push:\n    branches: ['master']\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'infection.json.dist'\n      - 'phpunit.xml.dist'\n      - 'psalm.xml'\n\nname: Composer require checker\n\njobs:\n  composer-require-checker:\n    uses: yiisoft/actions/.github/workflows/composer-require-checker.yml@master\n    with:\n      os: >-\n        ['ubuntu-latest']\n      php: >-\n        ['8.1', '8.2', '8.3', '8.4', '8.5']\n"
  },
  {
    "path": ".github/workflows/mutation.yml",
    "content": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'psalm.xml'\n\n  push:\n    branches: ['master']\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'psalm.xml'\n\nname: mutation test\n\njobs:\n  mutation:\n    uses: yiisoft/actions/.github/workflows/infection.yml@master\n    with:\n      os: >-\n        ['ubuntu-latest']\n      php: >-\n        ['8.5']\n      infection-args: \"--ignore-msi-with-no-mutations\"\n    secrets:\n      STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}\n"
  },
  {
    "path": ".github/workflows/rector-cs.yml",
    "content": "name: Rector + PHP CS Fixer\n\non:\n  pull_request_target:\n    paths:\n      - 'src/**'\n      - 'tests/**'\n      - '.github/workflows/rector-cs.yml'\n      - 'composer.json'\n      - 'rector.php'\n      - '.php-cs-fixer.dist.php'\n\npermissions:\n  contents: read\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  rector:\n    uses: yiisoft/actions/.github/workflows/rector-cs.yml@master\n    secrets:\n      token: ${{ secrets.YIISOFT_GITHUB_TOKEN }}\n    with:\n      repository: ${{ github.event.pull_request.head.repo.full_name }}\n      php: '8.1'\n"
  },
  {
    "path": ".github/workflows/static.yml",
    "content": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'infection.json.dist'\n      - 'phpunit.xml.dist'\n\n  push:\n    branches: ['master']\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n      - '.gitattributes'\n      - 'infection.json.dist'\n      - 'phpunit.xml.dist'\n\nname: static analysis\n\njobs:\n  psalm:\n    uses: yiisoft/actions/.github/workflows/psalm.yml@master\n    with:\n      os: >-\n        ['ubuntu-latest']\n      php: >-\n        ['8.1', '8.2', '8.3', '8.4']\n"
  },
  {
    "path": ".gitignore",
    "content": "# phpstorm project files\n.idea\n\n# netbeans project files\nnbproject\n\n# zend studio for eclipse project files\n.buildpath\n.project\n.settings\n\n# windows thumbnail cache\nThumbs.db\n\n# composer vendor dir\n/vendor\n\n/composer.lock\n\n# composer itself is not needed\ncomposer.phar\n\n# Mac DS_Store Files\n.DS_Store\n\n# PhpUnit\n/phpunit.phar\n/phpunit.xml\n/.phpunit.cache\n\n# Static analysis\nanalysis.txt\n\n# Code coverage HTML\n/coverage\n\n# PHP CS Fixer\n/.php-cs-fixer.cache\n/.php-cs-fixer.php\n"
  },
  {
    "path": ".php-cs-fixer.dist.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse PhpCsFixer\\Config;\nuse PhpCsFixer\\Finder;\nuse PhpCsFixer\\Runner\\Parallel\\ParallelConfigFactory;\n\n$finder = (new Finder())->in([\n    __DIR__ . '/src',\n    __DIR__ . '/tests',\n]);\n\nreturn (new Config())\n    ->setRiskyAllowed(true)\n    ->setParallelConfig(ParallelConfigFactory::detect())\n    ->setRules([\n        '@PER-CS3.0' => true,\n        'no_unused_imports' => true,\n        'ordered_class_elements' => true,\n        'class_attributes_separation' => ['elements' => ['method' => 'one']],\n        'declare_strict_types' => true,\n        'native_function_invocation' => true,\n        'native_constant_invocation' => true,\n        'fully_qualified_strict_types' => [\n            'import_symbols' => true\n        ],\n        'global_namespace_import' => [\n            'import_classes' => true,\n            'import_constants' => true,\n            'import_functions' => true,\n        ],\n    ])\n    ->setFinder($finder);\n"
  },
  {
    "path": ".phpstorm.meta.php",
    "content": "<?php\n// .phpstorm.meta.php\n\nnamespace PHPSTORM_META {\n\n    override(\n        \\Psr\\Container\\ContainerInterface::get(0),\n        map([\n            '' => '@',\n        ])\n    );\n}\n"
  },
  {
    "path": ".phpunit-watcher.yml",
    "content": "watch:\n    directories:\n        - src\n        - tests\n    fileMask: '*.php'\nnotifications:\n    passingTests: false\n    failingTests: false\nphpunit:\n    binaryPath: vendor/bin/phpunit\n    timeout: 180\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Yii Dependency Injection Change Log\n\n## 1.4.2 under development\n\n- Enh #397: Explicitly import functions in \"use\" section (@mspirkov)\n\n## 1.4.1 December 01, 2025\n\n- Enh #393: Add PHP 8.5 support (@vjik)\n\n## 1.4.0 May 30, 2025\n\n- New #380: Add `TagReference::id()` method (@vjik)\n- Chg #390: Change PHP constraint in `composer.json` to `8.1 - 8.4` (@vjik)\n- Enh #324: Make `BuildingException` and `NotFoundException` friendly (@np25071984)\n- Enh #384: Make `$config` parameter in `Container` constructor optional (@np25071984)\n- Enh #387: Improve container performance (@samdark)\n- Bug #390: Explicitly mark nullable parameters (@vjik)\n\n## 1.3.0 October 14, 2024\n\n- Enh #353: Add shortcut for tag reference #333 (@xepozz)\n- Enh #356: Improve usage `NotFoundException` for cases with definitions (@vjik)\n- Enh #364: Minor refactoring to improve performance of container (@samdark)\n- Enh #375: Raise minimum PHP version to `^8.1` and refactor code (@vjik)\n- Enh #376: Add default value `true` for parameter of `ContainerConfig::withStrictMode()` and\n `ContainerConfig::withValidate()` methods (@vjik)\n\n## 1.2.1 December 23, 2022\n\n- Chg #316: Fix exception messages (@xepozz)\n- Bug #317: Fix delegated container (@xepozz)\n\n## 1.2.0 November 05, 2022\n\n- Chg #310: Adopt to `yiisoft/definition` version `^3.0` (@vjik)\n- Enh #308: Raise minimum PHP version to `^8.0` and refactor code (@xepozz, @vjik)\n\n## 1.1.0 June 24, 2022\n\n- Chg #263: Raise minimal required version of `psr/container` to `^1.1|^2.0` (@xepozz, @vjik)\n\n## 1.0.3 June 17, 2022\n\n- Enh #302: Improve performance collecting tags (samdark)\n- Enh #303: Add support for `yiisoft/definitions` version `^2.0` (@vjik)\n\n## 1.0.2 February 14, 2022\n\n- Bug #297: Fix method name `TagHelper::extractTagFromAlias` (@rustamwin)\n\n## 1.0.1 December 21, 2021\n\n- Bug #293: Fix `ExtensibleService` normalization bug (@yiiliveext)\n\n## 1.0.0 December 03, 2021\n\n- Initial release.\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright © 2008 by Yii Software (<https://www.yiiframework.com/>)\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n * Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in\n   the documentation and/or other materials provided with the\n   distribution.\n * Neither the name of Yii Software nor the names of its\n   contributors may be used to endorse or promote products derived\n   from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\nFOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\nCOPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\nBUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\nANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n    <a href=\"https://github.com/yiisoft\" target=\"_blank\">\n        <img src=\"https://yiisoft.github.io/docs/images/yii_logo.svg\" height=\"100px\" alt=\"Yii\">\n    </a>\n    <h1 align=\"center\">Yii Dependency Injection</h1>\n    <br>\n</p>\n\n[![Latest Stable Version](https://poser.pugx.org/yiisoft/di/v)](https://packagist.org/packages/yiisoft/di)\n[![Total Downloads](https://poser.pugx.org/yiisoft/di/downloads)](https://packagist.org/packages/yiisoft/di)\n[![Build status](https://github.com/yiisoft/di/actions/workflows/build.yml/badge.svg)](https://github.com/yiisoft/di/actions/workflows/build.yml)\n[![Code coverage](https://codecov.io/gh/yiisoft/di/graph/badge.svg?token=P8W1UTwgQt)](https://codecov.io/gh/yiisoft/di)\n[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fyiisoft%2Fdi%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/di/master)\n[![static analysis](https://github.com/yiisoft/di/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/di/actions?query=workflow%3A%22static+analysis%22)\n[![type-coverage](https://shepherd.dev/github/yiisoft/di/coverage.svg)](https://shepherd.dev/github/yiisoft/di)\n\n[PSR-11](https://www.php-fig.org/psr/psr-11/) compatible\n[dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) container that's able to instantiate\nand configure classes resolving dependencies.\n\n## Features\n\n- [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible.\n- Supports property injection, constructor injection, and method injection.\n- Detects circular references.\n- Accepts array definitions. You can use it with mergeable configs.\n- Provides optional autoload fallback for classes without explicit definition.\n- Allows delegated lookup and has a composite container.\n- Supports aliasing.\n- Supports service providers.\n- Has state resetter for long-running workers serving many requests, such as [RoadRunner](https://roadrunner.dev/)\n  or [Swoole](https://www.swoole.co.uk/).\n- Supports container delegates.\n- Does auto-wiring.\n\n> [!NOTE]\n> The container contains only shared instances. If you need a factory, use the dedicated [yiisoft/factory](https://github.com/yiisoft/factory) package.\n\n## Requirements\n\n- PHP 8.1 - 8.5.\n- `Multibyte String` PHP extension.\n\n## Installation\n\nYou could install the package with composer:\n\n```shell\ncomposer require yiisoft/di\n```\n\n## Using the container\n\nUsage of the DI container is simple: You first initialize it with an\narray of *definitions*. The array keys are usually interface names. It will\nthen use these definitions to create an object whenever the application requests that type.\nThis happens, for example, when fetching a type directly from the container\nsomewhere in the application. But objects are also created implicitly if a\ndefinition has a dependency on another definition.\n\nUsually one uses a single container for the whole application. It's often\nconfigured either in the entry script such as `index.php` or a configuration\nfile:\n\n```php\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$config = ContainerConfig::create()\n    ->withDefinitions($definitions);\n\n$container = new Container($config);\n```\n\nYou could store the definitions in a `.php` file that returns an array:\n\n```php\nreturn [\n    // resolve EngineMarkOne dependencies automatically\n    EngineInterface::class => EngineMarkOne::class,\n    \n    // full definition\n    MyServiceInterface::class => [\n        'class' => MyService::class,\n        \n        // call the constructor, pass named argument \"amount\"\n        '__construct()' => [\n            'amount' => 42,\n            'db' => Reference::to(SecondaryConnection::class), // instance of another dependency\n        ],\n        \n        // set a public property\n        '$name' => 'Alex',\n        \n        // call a public method\n        'setDiscount()' => [10],\n    ],\n    \n    // closure for complicated cases\n    AnotherServiceInterface::class => static function(ConnectionInterface $db) {\n        return new AnotherService($db);\n    },\n    \n    // factory\n    MyObjectInterface::class => fn () => MyFactory::create('args'),\n    \n    // static call\n    MyObjectInterface2::class => [MyFactory::class, 'create'],\n    \n    // direct instance\n    MyInterface::class => new MyClass(),\n];\n```\n\nYou can define an object in several ways:\n\n- In the simple case, an interface definition maps an id to a particular class.\n- A full definition describes how to instantiate a class in more detail:\n  - `class` has the name of the class to instantiate.\n  - `__construct()` holds an array of constructor arguments.\n  - The rest of the config is property values (prefixed with `$`) and method calls, postfixed with `()`. They're\n     set/called in the order they appear in the array.\n- Closures are useful if instantiation is tricky and can be better done in code. When using these, arguments are\n   auto-wired by type. `ContainerInterface` could be used to get current container instance.\n- If it's even more complicated, it's a good idea to move such a code into a\n   factory and reference it as a static call.\n- While it's usually not a good idea, you can also set an already\n   instantiated object into the container.\n\nSee [yiisoft/definitions](https://github.com/yiisoft/definitions) for more information.\n\nAfter you configure the container, you can obtain a service via `get()`:\n\n```php\n/** @var \\Yiisoft\\Di\\Container $container */\n$object = $container->get('interface_name');\n```\n\nNote, however, that it's bad practice using a container directly. It's much\nbetter to rely on auto-wiring as provided by the Injector available from the\n[yiisoft/injector](https://github.com/yiisoft/injector) package.\n\n## Using aliases\n\nThe DI container supports aliases via the `Yiisoft\\Definitions\\Reference` class.\nThis way you can retrieve objects by a more handy name:\n\n```php\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$config = ContainerConfig::create()\n    ->withDefinitions([\n        EngineInterface::class => EngineMarkOne::class,\n        'engine_one' => EngineInterface::class,\n    ]);\n\n$container = new Container($config);\n$object = $container->get('engine_one');\n```\n\n## Using class aliases for specific configuration\n\nTo define another instance of a class with specific configuration, you can\nuse native PHP `class_alias()`:\n\n```php\nclass_alias(Yiisoft\\Db\\Pgsql\\Connection::class, 'MyPgSql');\n\n$config = ContainerConfig::create()                                                                                                                                                     \n    ->withDefinitions([\n        MyPgSql::class => [ ... ]\n    ]);                                                                                                                                                                                 \n\n$container = new Container($config);\n$object = $container->get(MyPgSql::class);\n```\n\nIt could be then conveniently used by type-hinting:\n\n```php\nfinal class MyService\n{\n    public function __construct(MyPgSql $myPgSql)\n    {\n        // ...    \n    }\n} \n```\n\n## Composite containers\n\nA composite container combines many containers in a single container. When\nusing this approach, you should fetch objects only from the composite\ncontainer.\n\n```php\nuse Yiisoft\\Di\\CompositeContainer;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$composite = new CompositeContainer();\n\n$carConfig = ContainerConfig::create()\n    ->withDefinitions([\n        EngineInterface::class => EngineMarkOne::class,\n        CarInterface::class => Car::class\n    ]);\n$carContainer = new Container($carConfig);\n\n$bikeConfig = ContainerConfig::create()\n    ->withDefinitions([\n        BikeInterface::class => Bike::class\n    ]);\n\n$bikeContainer = new Container($bikeConfig);\n$composite->attach($carContainer);\n$composite->attach($bikeContainer);\n\n// Returns an instance of a `Car` class.\n$car = $composite->get(CarInterface::class);\n// Returns an instance of a `Bike` class.\n$bike = $composite->get(BikeInterface::class);\n```\n\nNote that containers attached earlier override dependencies of containers attached later.\n\n```php\nuse Yiisoft\\Di\\CompositeContainer;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$carConfig = ContainerConfig::create()\n    ->withDefinitions([\n        EngineInterface::class => EngineMarkOne::class,\n        CarInterface::class => Car::class\n    ]);\n\n$carContainer = new Container($carConfig);\n\n$composite = new CompositeContainer();\n$composite->attach($carContainer);\n\n// Returns an instance of a `Car` class.\n$car = $composite->get(CarInterface::class);\n// Returns an instance of a `EngineMarkOne` class.\n$engine = $car->getEngine();\n\n$engineConfig = ContainerConfig::create()\n    ->withDefinitions([\n        EngineInterface::class => EngineMarkTwo::class,\n    ]);\n\n$engineContainer = new Container($engineConfig);\n\n$composite = new CompositeContainer();\n$composite->attach($engineContainer);\n$composite->attach($carContainer);\n\n// Returns an instance of a `Car` class.\n$car = $composite->get(CarInterface::class);\n// Returns an instance of a `EngineMarkTwo` class.\n$engine = $composite->get(EngineInterface::class);\n```\n\n## Using service providers\n\nA service provider is a special class that's responsible for providing complex\nservices or groups of dependencies for the container and extensions of existing services.\n\nA provider should extend from `Yiisoft\\Di\\ServiceProviderInterface` and must\ncontain a `getDefinitions()` and `getExtensions()` methods. It should only provide services for the container\nand therefore should only contain code related to this task. It should *never*\nimplement any business logic or other functionality such as environment bootstrap or applying changes to a database.\n\nThe `getExtensions()` method allows implementing the decorator pattern by wrapping existing services\nwith additional functionality.\n\nA typical service provider could look like:\n\n```php\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ServiceProviderInterface;\n\nclass CarFactoryProvider extends ServiceProviderInterface\n{\n    public function getDefinitions(): array\n    {\n        return [\n            CarFactory::class => [\n                'class' => CarFactory::class,\n                '$color' => 'red',\n            ], \n            EngineInterface::class => SolarEngine::class,\n            WheelInterface::class => [\n                'class' => Wheel::class,\n                '$color' => 'black',\n            ],\n            CarInterface::class => [\n                'class' => BMW::class,\n                '$model' => 'X5',\n            ],\n        ];    \n    }\n     \n    public function getExtensions(): array\n    {\n        return [\n            // Note that Garage should already be defined in a container \n            Garage::class => function(ContainerInterface $container, Garage $garage) {\n                $car = $container\n                    ->get(CarFactory::class)\n                    ->create();\n                $garage->setCar($car);\n                \n                return $garage;\n            }\n        ];\n    } \n}\n```\n\nHere you created a service provider responsible for bootstrapping of a car factory with all its dependencies.\n\nAn extension is callable that returns a modified service object.\nIn this case you get existing `Garage` service\nand put a car into the garage by calling the method `setCar()`.\nThus, before applying this provider, you had\nan empty garage and with the help of the extension you fill it.\n\nTo add this service provider to a container, you can pass either its class or a\nconfiguration array in the extra config:\n\n```php\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$config = ContainerConfig::create()\n    ->withProviders([CarFactoryProvider::class]);\n\n$container = new Container($config);\n```\n\nWhen you add a service provider, DI calls its `getDefinitions()` and `getExtensions()` methods\n*immediately* and both services and their extensions get registered into the container.\n\n### Using service providers for decorator pattern\n\nService provider extensions are a powerful feature that allows implementing the decorator pattern.\nThis lets you wrap existing services with additional functionality without modifying their original implementation.\n\nHere's an example of using the decorator pattern to add logging to an existing mailer service:\n\n```php\nuse Psr\\Container\\ContainerInterface;\nuse Psr\\Log\\LoggerInterface;\nuse Yiisoft\\Di\\ServiceProviderInterface;\n\ninterface MailerInterface \n{\n    public function send(string $to, string $subject, string $body): void;\n}\n\nclass Mailer implements MailerInterface \n{\n    public function send(string $to, string $subject, string $body): void \n    {\n        // Original mailer implementation\n        // Sends email via SMTP or external service\n    }\n}\n\nclass LoggingMailerDecorator implements MailerInterface \n{\n    public function __construct(\n        private MailerInterface $mailer,\n        private LoggerInterface $logger\n    ) {\n    }\n    \n    public function send(string $to, string $subject, string $body): void \n    {\n        $this->logger->info(\"Sending email to {$to}\");\n        $this->mailer->send($to, $subject, $body);\n        $this->logger->info(\"Email sent to {$to}\");\n    }\n}\n\nclass MailerDecoratorProvider implements ServiceProviderInterface \n{\n    public function getDefinitions(): array \n    {\n        return [];\n    }\n    \n    public function getExtensions(): array \n    {\n        return [\n            MailerInterface::class => static function (ContainerInterface $container, MailerInterface $mailer) {\n                // Wrap the original mailer with logging decorator\n                return new LoggingMailerDecorator($mailer, $container->get(LoggerInterface::class));\n            }\n        ];\n    }\n}\n```\n\nIn this example, the extension receives the original `MailerInterface` instance and wraps it with\n`LoggingMailerDecorator`, which adds logging before and after sending emails. The decorator pattern\nallows you to add cross-cutting concerns like logging, caching, or monitoring without changing the\noriginal service implementation.\n\n## Container tags\n\nYou can tag services in the following way:\n\n```php\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$config = ContainerConfig::create()\n    ->withDefinitions([  \n        BlueCarService::class => [\n            'class' => BlueCarService::class,\n            'tags' => ['car'], \n        ],\n        RedCarService::class => [\n            'definition' => fn () => new RedCarService(),\n            'tags' => ['car'],\n        ],\n    ]);\n\n$container = new Container($config);\n```\n\nAnother way to tag services is setting tags via container constructor:\n\n```php\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$config = ContainerConfig::create()\n    ->withDefinitions([  \n        BlueCarService::class => [\n            'class' => BlueCarService::class,\n        ],\n        RedCarService::class => fn () => new RedCarService(),\n    ])\n    ->withTags([\n        // \"car\" tag has references to both blue and red cars\n        'car' => [BlueCarService::class, RedCarService::class]\n    ]);\n\n$container = new Container($config);\n```\n\n### Getting tagged services\n\nYou can get tagged services from the container in the following way:\n\n```php\n$container->get(\\Yiisoft\\Di\\Reference\\TagReference::id('car'));\n```\n\nThe result is an array that has two instances: `BlueCarService` and `RedCarService`.\n\n### Using tagged services in configuration\n\nUse `TagReference` to get tagged services in configuration:\n\n```php\n[\n    Garage::class => [\n        '__construct()' => [\n            \\Yiisoft\\Di\\Reference\\TagReference::to('car'),\n        ],    \n    ],\n],\n```\n\n## Resetting services state\n\nDespite stateful services isn't a great practice, these are often inevitable. When you build long-running\napplications with tools like [Swoole](https://www.swoole.co.uk/) or [RoadRunner](https://roadrunner.dev/) you should\nreset the state of such services every request. For this purpose you can use `StateResetter` with resetters callbacks:\n\n```php\n$resetter = new StateResetter($container);\n$resetter->setResetters([\n    MyServiceInterface::class => function () {\n        $this->reset(); // a method of MyServiceInterface\n    },\n]);\n```\n\nThe callback has access to the private and protected properties of the service instance,\nso you can set the initial state of the service efficiently without creating a new instance.\n\nYou should trigger the reset itself after each request-response cycle. For RoadRunner, it would look like the following:\n\n```php\nwhile ($request = $psr7->acceptRequest()) {\n    $response = $application->handle($request);\n    $psr7->respond($response);\n    $application->afterEmit($response);\n    $container\n        ->get(\\Yiisoft\\Di\\StateResetter::class)\n        ->reset();\n    gc_collect_cycles();\n}\n```\n\n### Setting resetters in definitions\n\nYou define the reset state for each service by providing \"reset\" callback in the following way:\n\n```php\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$config = ContainerConfig::create()\n    ->withDefinitions([\n        EngineInterface::class => EngineMarkOne::class,\n        EngineMarkOne::class => [\n            'class' => EngineMarkOne::class,\n            'setNumber()' => [42],\n            'reset' => function () {\n                $this->number = 42;\n            },\n        ],\n    ]);\n\n$container = new Container($config);\n```\n\nNote: resetters from definitions work only if you don't set `StateResetter` in definition or service providers.\n\n### Configuring `StateResetter` manually\n\nTo manually add resetters or in case you use Yii DI composite container with a third party container that doesn't support state reset natively, you could configure state resetter separately. The following example is PHP-DI:\n\n```php\nMyServiceInterface::class => function () {\n    // ...\n},\nStateResetter::class => function (ContainerInterface $container) {\n    $resetter = new StateResetter($container);\n    $resetter->setResetters([\n        MyServiceInterface::class => function () {\n            $this->reset(); // a method of MyServiceInterface\n        },\n    ]);\n    return $resetter;\n}\n```\n\n## Specifying metadata for non-array definitions\n\nTo specify some metadata, such as in cases of \"resetting services state\" or \"container tags,\" for non-array\ndefinitions, you could use the following syntax:\n\n```php\nLogTarget::class => [\n    'definition' => static function (LoggerInterface $logger) use ($params) {\n        $target = ...\n        return $target;\n    },\n    'reset' => function () use ($params) {\n        ...\n    },\n],\n```\n\nNow you've explicitly moved the definition itself to \"definition\" key.\n\n## Delegates\n\nEach delegate is a callable returning a container instance that's used in case DI\ncan't find a service in a primary container:\n\n```php\nfunction (ContainerInterface $container): ContainerInterface\n{\n\n}\n```\n\nTo configure delegates use extra config:\n\n```php\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$config = ContainerConfig::create()\n    ->withDelegates([\n        function (ContainerInterface $container): ContainerInterface {\n            // ...\n        }\n    ]);\n\n\n$container = new Container($config);\n```\n\n## Tuning for production\n\nBy default, the container validates definitions right when they're set. In the production environment, it makes sense to\nturn it off:\n\n```php\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$config = ContainerConfig::create()\n    ->withValidate(false);\n\n$container = new Container($config);\n```\n\n## Strict mode\n\nContainer may work in a strict mode, that's when you should define everything in the container explicitly.\nTo turn it on, use the following code:\n\n```php\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n$config = ContainerConfig::create()\n    ->withStrictMode(true);\n\n$container = new Container($config);\n```\n\n## Documentation\n\n- [Internals](docs/internals.md)\n\nIf you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that.\nYou may also check out other [Yii Community Resources](https://www.yiiframework.com/community).\n\n## License\n\nThe Yii Dependency Injection is free software. It is released under the terms of the BSD License.\nPlease see [`LICENSE`](./LICENSE.md) for more information.\n\nMaintained by [Yii Software](https://www.yiiframework.com/).\n\n## Support the project\n\n[![Open Collective](https://img.shields.io/badge/Open%20Collective-sponsor-7eadf1?logo=open%20collective&logoColor=7eadf1&labelColor=555555)](https://opencollective.com/yiisoft)\n\n## Follow updates\n\n[![Official website](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](https://www.yiiframework.com/)\n[![Twitter](https://img.shields.io/badge/twitter-follow-1DA1F2?logo=twitter&logoColor=1DA1F2&labelColor=555555?style=flat)](https://twitter.com/yiiframework)\n[![Telegram](https://img.shields.io/badge/telegram-join-1DA1F2?style=flat&logo=telegram)](https://t.me/yii3en)\n[![Facebook](https://img.shields.io/badge/facebook-join-1DA1F2?style=flat&logo=facebook&logoColor=ffffff)](https://www.facebook.com/groups/yiitalk)\n[![Slack](https://img.shields.io/badge/slack-join-1DA1F2?style=flat&logo=slack)](https://yiiframework.com/go/slack)\n"
  },
  {
    "path": "benchmarks.md",
    "content": "DI benchmark report\n===================\n\n### suite: 1343bd6191c6668ccf8fb4cf1a51a7b61159c825, date: 2020-04-06, stime: 20:48:28\n\nbenchmark | subject | set | revs | its | mem_peak | best | mean | mode | worst | stdev | rstdev | diff\n --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- \nContainerBench | benchSequentialLookups | 0 | 1000 | 5 | 1,460,456b | 615.235μs | 625.586μs | 621.205μs | 636.719μs | 7.948μs | 1.27% | 1.00x\nContainerBench | benchSequentialLookups | 1 | 1000 | 5 | 1,467,608b | 634.765μs | 645.507μs | 642.453μs | 661.132μs | 8.691μs | 1.35% | 1.03x\nContainerBench | benchSequentialLookups | 2 | 1000 | 5 | 1,467,752b | 632.813μs | 638.086μs | 636.211μs | 646.484μs | 4.605μs | 0.72% | 1.02x\nContainerBench | benchRandomLookups | 0 | 1000 | 5 | 1,460,456b | 609.375μs | 625.000μs | 633.784μs | 639.649μs | 12.628μs | 2.02% | 1.00x\nContainerBench | benchRandomLookups | 1 | 1000 | 5 | 1,467,608b | 644.531μs | 650.391μs | 652.084μs | 657.227μs | 4.744μs | 0.73% | 1.04x\nContainerBench | benchRandomLookups | 2 | 1000 | 5 | 1,467,752b | 638.672μs | 649.219μs | 650.291μs | 658.203μs | 6.250μs | 0.96% | 1.04x\nContainerBench | benchRandomLookupsComposite | 0 | 1000 | 5 | 24,786,136b | 699.219μs | 708.398μs | 711.499μs | 715.820μs | 5.943μs | 0.84% | 1.13x\nContainerBench | benchRandomLookupsComposite | 1 | 1000 | 5 | 24,780,496b | 743.164μs | 753.125μs | 748.496μs | 772.461μs | 10.268μs | 1.36% | 1.20x\nContainerBench | benchRandomLookupsComposite | 2 | 1000 | 5 | 24,780,640b | 739.257μs | 747.071μs | 744.033μs | 755.860μs | 6.020μs | 0.81% | 1.20x\n\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"yiisoft/di\",\n    \"type\": \"library\",\n    \"description\": \"Yii DI container\",\n    \"keywords\": [\n        \"di\",\n        \"dependency\",\n        \"injection\",\n        \"container\",\n        \"injector\",\n        \"autowiring\",\n        \"psr-11\"\n    ],\n    \"homepage\": \"https://www.yiiframework.com/\",\n    \"license\": \"BSD-3-Clause\",\n    \"support\": {\n        \"issues\": \"https://github.com/yiisoft/di/issues?state=open\",\n        \"source\": \"https://github.com/yiisoft/di\",\n        \"forum\": \"https://www.yiiframework.com/forum/\",\n        \"wiki\": \"https://www.yiiframework.com/wiki/\",\n        \"irc\": \"ircs://irc.libera.chat:6697/yii\",\n        \"chat\": \"https://t.me/yii3en\"\n    },\n    \"funding\": [\n        {\n            \"type\": \"opencollective\",\n            \"url\": \"https://opencollective.com/yiisoft\"\n        },\n        {\n            \"type\": \"github\",\n            \"url\": \"https://github.com/sponsors/yiisoft\"\n        }\n    ],\n    \"require\": {\n        \"php\": \"8.1 - 8.5\",\n        \"ext-mbstring\": \"*\",\n        \"psr/container\": \"^1.1 || ^2.0\",\n        \"yiisoft/definitions\": \"^3.0\",\n        \"yiisoft/friendly-exception\": \"^1.1.0\"\n    },\n    \"require-dev\": {\n        \"friendsofphp/php-cs-fixer\": \"^3.92.5\",\n        \"bamarni/composer-bin-plugin\": \"^1.8.3\",\n        \"league/container\": \"^5.1.0\",\n        \"maglnet/composer-require-checker\": \"^4.7.1\",\n        \"phpbench/phpbench\": \"^1.4.1\",\n        \"phpunit/phpunit\": \"^10.5.46\",\n        \"rector/rector\": \"^2.0.17\",\n        \"spatie/phpunit-watcher\": \"^1.24\",\n        \"yiisoft/injector\": \"^1.2.1\",\n        \"yiisoft/test-support\": \"^3.1\"\n    },\n    \"suggest\": {\n        \"yiisoft/injector\": \"^1.0\",\n        \"phpbench/phpbench\": \"To run benchmarks.\"\n    },\n    \"provide\": {\n        \"psr/container-implementation\": \"1.0.0\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"Yiisoft\\\\Di\\\\\": \"src\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Yiisoft\\\\Di\\\\Tests\\\\\": \"tests\"\n        }\n    },\n    \"scripts\": {\n        \"test\": \"phpunit --testdox --no-interaction\",\n        \"test-watch\": \"phpunit-watcher watch\"\n    },\n    \"scripts-descriptions\": {\n        \"test\": \"Run all tests\"\n    },\n    \"extra\": {\n        \"bamarni-bin\": {\n            \"bin-links\": true,\n            \"target-directory\": \"tools\",\n            \"forward-command\": true\n        }\n    },\n    \"config\": {\n        \"sort-packages\": true,\n        \"allow-plugins\": {\n            \"bamarni/composer-bin-plugin\": true,\n            \"composer/package-versions-deprecated\": true\n        }\n    }\n}\n"
  },
  {
    "path": "docs/internals.md",
    "content": "# Internals\n\n## Further reading\n\n- [Martin Fowler's article](https://martinfowler.com/articles/injection.html).\n\n## Benchmarks\n\nTo run benchmarks execute the next command\n\n```shell\n./vendor/bin/phpbench run\n```\n\nResult example\n\n```text\n\\Yiisoft\\Di\\Tests\\Benchmark\\ContainerBench\n\nbenchConstructStupid....................I4 [μ Mo]/r: 438.566 435.190 (μs) [μSD μRSD]/r: 9.080μs 2.07%\nbenchConstructSmart.....................I4 [μ Mo]/r: 470.958 468.942 (μs) [μSD μRSD]/r: 2.848μs 0.60%\nbenchSequentialLookups # 0..............R5 I4 [μ Mo]/r: 2,837.000 2,821.636 (μs) [μSD μRSD]/r: 34.123μs 1.20%\nbenchSequentialLookups # 1..............R1 I0 [μ Mo]/r: 12,253.600 12,278.859 (μs) [μSD μRSD]/r: 69.087μs 0.56%\nbenchRandomLookups # 0..................R5 I4 [μ Mo]/r: 3,142.200 3,111.290 (μs) [μSD μRSD]/r: 87.639μs 2.79%\nbenchRandomLookups # 1..................R1 I2 [μ Mo]/r: 13,298.800 13,337.170 (μs) [μSD μRSD]/r: 103.891μs 0.78%\nbenchRandomLookupsComposite # 0.........R1 I3 [μ Mo]/r: 3,351.600 3,389.104 (μs) [μSD μRSD]/r: 72.516μs 2.16%\nbenchRandomLookupsComposite # 1.........R1 I4 [μ Mo]/r: 13,528.200 13,502.881 (μs) [μSD μRSD]/r: 99.997μs 0.74%\n\\Yiisoft\\Di\\Tests\\Benchmark\\ContainerMethodHasBench\n\nbenchPredefinedExisting.................R1 I4 [μ Mo]/r: 0.115 0.114 (μs) [μSD μRSD]/r: 0.001μs 1.31%\nbenchUndefinedExisting..................R5 I4 [μ Mo]/r: 0.436 0.432 (μs) [μSD μRSD]/r: 0.008μs 1.89%\nbenchUndefinedNonexistent...............R5 I4 [μ Mo]/r: 0.946 0.942 (μs) [μSD μRSD]/r: 0.006μs 0.59%\n8 subjects, 55 iterations, 5,006 revs, 0 rejects, 0 failures, 0 warnings \n(best [mean mode] worst) = 0.113 [4,483.856 4,486.051] 0.117 (μs) \n⅀T: 246,612.096μs μSD/r 43.563μs μRSD/r: 1.336%\n```\n\n> **Warning!**\n> \n> These summary statistics can be misleading.\n> You should always verify the individual subject statistics before drawing any conclusions.\n\n> **Legend**\n>\n> - μ:  Mean time taken by all iterations in variant.\n> - Mo: Mode of all iterations in variant.\n> - μSD: μ standard deviation.\n> - μRSD: μ relative standard deviation.\n> - best: Maximum time of all iterations (minimal of all iterations).\n> - mean: Mean time taken by all iterations.\n> - mode: Mode of all iterations.\n> - worst: Minimum time of all iterations (minimal of all iterations).\n\n### Command examples\n\n- Default report for all benchmarks that outputs the result to `CSV-file`\n\n```shell\n./vendor/bin/phpbench run --report=default --progress=dots  --output=csv_file\n```\n\nGenerated MD-file example\n\n```text\n>DI benchmark report\n>===================\n>\n>### suite: 1343b1dc0589cb4e985036d14b3e12cb430a975b, date: 2020-02-21, stime: 16:02:45\n>\n>benchmark | subject | set | revs | iter | mem_peak | time_rev | comp_z_value | comp_deviation\n> --- | --- | --- | --- | --- | --- | --- | --- | ---\n>ContainerBench | benchConstructStupid | 0 | 1000 | 0 | 1,416,784b | 210.938μs | -1.48σ | -1.1%\n>ContainerBench | benchConstructStupid | 0 | 1000 | 1 | 1,416,784b | 213.867μs | +0.37σ | +0.27%\n>ContainerBench | benchConstructStupid | 0 | 1000 | 2 | 1,416,784b | 212.890μs | -0.25σ | -0.18%\n>ContainerBench | benchConstructStupid | 0 | 1000 | 3 | 1,416,784b | 215.820μs | +1.60σ | +1.19%\n>ContainerBench | benchConstructStupid | 0 | 1000 | 4 | 1,416,784b | 212.891μs | -0.25σ | -0.18%\n>ContainerBench | benchConstructSmart | 0 | 1000 | 0 | 1,426,280b | 232.422μs | -1.03σ | -0.5%\n>ContainerBench | benchConstructSmart | 0 | 1000 | 1 | 1,426,280b | 232.422μs | -1.03σ | -0.5%\n>ContainerBench | benchConstructSmart | 0 | 1000 | 2 | 1,426,280b | 233.398μs | -0.17σ | -0.08%\n>ContainerBench | benchConstructSmart | 0 | 1000 | 3 | 1,426,280b | 234.375μs | +0.69σ | +0.33%\n>ContainerBench | benchConstructSmart | 0 | 1000 | 4 | 1,426,280b | 235.351μs | +1.54σ | +0.75%\n>`... skipped` | `...` | `...` | `...` | `...` | `...` | `...` | `...` | `...`\n>ContainerMethodHasBench | benchPredefinedExisting | 0 | 1000 | 0 | 1,216,144b | 81.055μs | -0.91σ | -1.19%\n>ContainerMethodHasBench | benchPredefinedExisting | 0 | 1000 | 1 | 1,216,144b | 83.985μs | +1.83σ | +2.38%\n>ContainerMethodHasBench | benchPredefinedExisting | 0 | 1000 | 2 | 1,216,144b | 82.032μs | 0.00σ | 0.00%\n>ContainerMethodHasBench | benchPredefinedExisting | 0 | 1000 | 3 | 1,216,144b | 82.031μs | 0.00σ | 0.00%\n>ContainerMethodHasBench | benchPredefinedExisting | 0 | 1000 | 4 | 1,216,144b | 81.055μs | -0.91σ | -1.19%\n>`... skipped` | `...` | `...` | `...` | `...` | `...` | `...` | `...` | `...`\n```\n\n> **Legend**\n>\n> - benchmark: Benchmark class.\n> - subject: Benchmark class method.\n> - set: Set of data (provided by ParamProvider).\n> - revs: Number of revolutions (represent the number of times that the code is executed).\n> - iter: Number of iteration.\n> - mem_peak: (mean) Peak memory used by iteration as retrieved by memory_get_peak_usage.\n> - time_rev:  Mean time taken by all iterations in variant.\n> - comp_z_value: Z-score.\n> - comp_deviation: Relative deviation (margin of error).\n\n- Aggregate report for the `lookup` group that outputs the result to `console` and `CSV-file`\n\n```shell\n./vendor/bin/phpbench run --report=aggregate --progress=dots  --output=csv_file --output=console --group=lookup\n```\n\n> **Notice**\n>\n> Available groups: `construct` `lookup` `has`\n\nGenerated MD-file example\n\n```text\n> DI benchmark report\n> ===================\n>\n>### suite: 1343b1d2654a3819c72a96d236302b70a504dac7, date: 2020-02-21, stime: 13:27:32\n>\n>benchmark | subject | set | revs | its | mem_peak | best | mean | mode | worst | stdev | rstdev | diff\n> --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---\n>ContainerBench | benchSequentialLookups | 0 | 1000 | 5 | 1,454,024b | 168.945μs | 170.117μs | 169.782μs | 171.875μs | 0.957μs | 0.56% | 1.00x\n>ContainerBench | benchSequentialLookups | 1 | 1000 | 5 | 1,445,296b | 3,347.656μs | 3,384.961μs | 3,390.411μs | 3,414.062μs | 21.823μs | 0.64% | 19.90x\n>ContainerBench | benchSequentialLookups | 2 | 1000 | 5 | 1,445,568b | 3,420.898μs | 3,488.477μs | 3,447.260μs | 3,657.227μs | 85.705μs | 2.46% | 20.51x\n>ContainerBench | benchRandomLookups | 0 | 1000 | 5 | 1,454,024b | 169.922μs | 171.875μs | 171.871μs | 173.828μs | 1.381μs | 0.80% | 1.01x\n>ContainerBench | benchRandomLookups | 1 | 1000 | 5 | 1,445,296b | 3,353.515μs | 3,389.844μs | 3,377.299μs | 3,446.289μs | 31.598μs | 0.93% | 19.93x\n>ContainerBench | benchRandomLookups | 2 | 1000 | 5 | 1,445,568b | 3,445.313μs | 3,587.696μs | 3,517.823μs | 3,749.023μs | 115.850μs | 3.23% | 21.09x\n>ContainerBench | benchRandomLookupsComposite | 0 | 1000 | 5 | 1,454,032b | 297.852μs | 299.610μs | 298.855μs | 302.734μs | 1.680μs | 0.56% | 1.76x\n>ContainerBench | benchRandomLookupsComposite | 1 | 1000 | 5 | 1,445,880b | 3,684.570μs | 3,708.984μs | 3,695.731μs | 3,762.695μs | 28.297μs | 0.76% | 21.80x\n>ContainerBench | benchRandomLookupsComposite | 2 | 1000 | 5 | 1,446,152b | 3,668.946μs | 3,721.680μs | 3,727.407μs | 3,765.625μs | 30.881μs | 0.83% | 21.88x\n```\n\n> **Legend**\n>\n>   * benchmark: Benchmark class.\n>   * subject: Benchmark class method.\n>   * set: Set of data (provided by ParamProvider).\n>   * revs: Number of revolutions (represent the number of times that the code is executed).\n>   * its: Number of iterations (one measurement for each iteration).   \n>   * mem_peak: (mean) Peak memory used by each iteration as retrieved by memory_get_peak_usage.\n>   * best: Maximum time of all iterations in variant.\n>   * mean: Mean time taken by all iterations in variant.\n>   * mode: Mode of all iterations in variant.\n>   * worst: Minimum time of all iterations in variant.\n>   * stdev: Standard deviation.\n>   * rstdev: The relative standard deviation.\n>   * diff: Difference between variants in a single group.\n\n## Unit testing\n\nThe package is tested with [PHPUnit](https://phpunit.de/). To run tests:\n\n```shell\n./vendor/bin/phpunit\n```\n\n## Mutation testing\n\nThe package tests are checked with [Infection](https://infection.github.io/) mutation framework with\n[Infection Static Analysis Plugin](https://github.com/Roave/infection-static-analysis-plugin). To run it:\n\n```shell\n./vendor/bin/roave-infection-static-analysis-plugin\n```\n\n## Static analysis\n\nThe code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis:\n\n```shell\n./vendor/bin/psalm\n```\n\n## Code style\n\nUse [Rector](https://github.com/rectorphp/rector) to make codebase follow some specific rules or\nuse either newest or any specific version of PHP:\n\n```shell\n./vendor/bin/rector\n```\n\n## Dependencies\n\nUse [ComposerRequireChecker](https://github.com/maglnet/ComposerRequireChecker) to detect transitive\n[Composer](https://getcomposer.org/) dependencies. To run the checker, execute the following command:\n\n```shell\n./vendor/bin/composer-require-checker\n```\n"
  },
  {
    "path": "infection.json.dist",
    "content": "{\n    \"source\": {\n        \"directories\": [\n            \"src\"\n        ]\n    },\n    \"logs\": {\n        \"text\": \"php:\\/\\/stderr\",\n        \"stryker\": {\n            \"report\": \"master\"\n        }\n    },\n    \"mutators\": {\n        \"@default\": true\n    }\n}\n"
  },
  {
    "path": "phpbench.json",
    "content": "{\n    \"runner.bootstrap\": \"vendor/autoload.php\",\n    \"runner.path\": \"tests/Benchmark\",\n    \"runner.retry_threshold\": 3,\n    \"report.outputs\": {\n        \"csv_file\": {\n            \"extends\": \"delimited\",\n            \"delimiter\": \",\",\n            \"file\": \"benchmarks.csv\"\n        }\n    }\n}\n"
  },
  {
    "path": "phpcs.xml.dist",
    "content": "<?xml version=\"1.0\"?>\n<ruleset>\n    <file>./</file>\n    <exclude-pattern>./vendor/*</exclude-pattern>\n    <rule ref=\"PSR2\" />\n</ruleset>"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n         bootstrap=\"vendor/autoload.php\"\n         cacheDirectory=\".phpunit.cache\"\n         requireCoverageMetadata=\"false\"\n         beStrictAboutCoverageMetadata=\"true\"\n         beStrictAboutOutputDuringTests=\"true\"\n         executionOrder=\"random\"\n         failOnRisky=\"true\"\n         failOnWarning=\"true\"\n         stopOnFailure=\"false\"\n         colors=\"true\"\n         displayDetailsOnPhpunitDeprecations=\"true\"\n>\n    <php>\n        <ini name=\"error_reporting\" value=\"-1\"/>\n    </php>\n\n    <testsuites>\n        <testsuite name=\"Yii DI Container tests\">\n            <directory>./tests/Unit</directory>\n        </testsuite>\n    </testsuites>\n\n    <source>\n        <include>\n            <directory suffix=\".php\">./src</directory>\n        </include>\n    </source>\n</phpunit>\n"
  },
  {
    "path": "psalm.xml",
    "content": "<?xml version=\"1.0\"?>\n<psalm\n    errorLevel=\"1\"\n    findUnusedBaselineEntry=\"true\"\n    findUnusedCode=\"false\"\n    ensureOverrideAttribute=\"false\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns=\"https://getpsalm.org/schema/config\"\n    xsi:schemaLocation=\"https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd\"\n>\n    <projectFiles>\n        <directory name=\"src\" />\n        <ignoreFiles>\n            <directory name=\"vendor\" />\n        </ignoreFiles>\n    </projectFiles>\n    <issueHandlers>\n        <MixedAssignment errorLevel=\"suppress\" />\n    </issueHandlers>\n</psalm>\n"
  },
  {
    "path": "rector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Rector\\CodeQuality\\Rector\\Class_\\InlineConstructorDefaultToPropertyRector;\nuse Rector\\Config\\RectorConfig;\nuse Rector\\Php74\\Rector\\Closure\\ClosureToArrowFunctionRector;\nuse Rector\\Php81\\Rector\\Array_\\ArrayToFirstClassCallableRector;\n\nreturn RectorConfig::configure()\n    ->withPaths([\n        __DIR__ . '/src',\n        __DIR__ . '/tests',\n    ])\n    ->withPhpSets(php81: true)\n    ->withRules([\n        InlineConstructorDefaultToPropertyRector::class,\n    ])\n    ->withSkip([\n        ClosureToArrowFunctionRector::class,\n        ArrayToFirstClassCallableRector::class => [\n            __DIR__ . '/tests/Unit/Helpers/DefinitionParserTest.php',\n        ],\n    ]);\n"
  },
  {
    "path": "src/BuildingException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Exception;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Throwable;\nuse Yiisoft\\FriendlyException\\FriendlyExceptionInterface;\n\nuse function sprintf;\n\n/**\n * It wraps all exceptions that don't implement `ContainerExceptionInterface` during the build process.\n * Also adds building context for more understanding.\n */\nfinal class BuildingException extends Exception implements ContainerExceptionInterface, FriendlyExceptionInterface\n{\n    /**\n     * @param string $id ID of the definition or name of the class that wasn't found.\n     * @param string[] $buildStack Stack of IDs of services requested definition or class that wasn't found.\n     */\n    public function __construct(\n        private readonly string $id,\n        Throwable $error,\n        array $buildStack = [],\n        ?Throwable $previous = null,\n    ) {\n        $message = sprintf(\n            'Caught unhandled error \"%s\" while building \"%s\".',\n            $error->getMessage() === '' ? $error::class : $error->getMessage(),\n            implode('\" -> \"', $buildStack === [] ? [$id] : $buildStack),\n        );\n\n        parent::__construct($message, 0, $previous);\n    }\n\n    public function getName(): string\n    {\n        return sprintf('Unable to build \"%s\" object.', $this->id);\n    }\n\n    public function getSolution(): ?string\n    {\n        $solution = <<<SOLUTION\n            Ensure that either a service with ID \"%1\\$s\" is defined or such class exists and is autoloadable.\n\n            Ensure that configuration for service with ID \"%1\\$s\" is correct.\n            SOLUTION;\n\n        return sprintf($solution, $this->id);\n    }\n}\n"
  },
  {
    "path": "src/CompositeContainer.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse InvalidArgumentException;\nuse Psr\\Container\\ContainerInterface;\nuse RuntimeException;\nuse Throwable;\nuse Yiisoft\\Di\\Reference\\TagReference;\n\nuse function is_string;\nuse function sprintf;\n\n/**\n * A composite container for use with containers that support the delegate lookup feature.\n */\nfinal class CompositeContainer implements ContainerInterface\n{\n    /**\n     * Containers to look into starting from the beginning of the array.\n     *\n     * @var ContainerInterface[] The list of containers.\n     */\n    private array $containers = [];\n\n    /**\n     * @psalm-template T\n     * @psalm-param string|class-string<T> $id\n     * @psalm-return ($id is class-string ? T : mixed)\n     */\n    public function get($id)\n    {\n        /** @psalm-suppress TypeDoesNotContainType */\n        if (!is_string($id)) {\n            throw new InvalidArgumentException(\n                sprintf(\n                    'ID must be a string, %s given.',\n                    get_debug_type($id),\n                ),\n            );\n        }\n\n        if ($id === StateResetter::class) {\n            $resetters = [];\n            foreach ($this->containers as $container) {\n                if ($container->has(StateResetter::class)) {\n                    $resetters[] = $container->get(StateResetter::class);\n                }\n            }\n            $stateResetter = new StateResetter($this);\n            $stateResetter->setResetters($resetters);\n\n            return $stateResetter;\n        }\n\n        if (TagReference::isTagAlias($id)) {\n            $tags = [];\n            foreach ($this->containers as $container) {\n                if (!$container instanceof Container) {\n                    continue;\n                }\n                if ($container->has($id)) {\n                    /** @psalm-suppress MixedArgument `Container::get()` always return array for tag */\n                    array_unshift($tags, $container->get($id));\n                }\n            }\n\n            /** @psalm-suppress MixedArgument `Container::get()` always return array for tag */\n            return array_merge(...$tags);\n        }\n\n        foreach ($this->containers as $container) {\n            if ($container->has($id)) {\n                /** @psalm-suppress MixedReturnStatement */\n                return $container->get($id);\n            }\n        }\n\n        // Collect details from containers\n        $exceptions = [];\n        foreach ($this->containers as $container) {\n            $hasException = false;\n            try {\n                $container->get($id);\n            } catch (Throwable $t) {\n                $hasException = true;\n                $exceptions[] = [$t, $container];\n            } finally {\n                if (!$hasException) {\n                    $exceptions[] = [\n                        new RuntimeException(\n                            'Container \"has()\" returned false, but no exception was thrown from \"get()\".',\n                        ),\n                        $container,\n                    ];\n                }\n            }\n        }\n\n        throw new CompositeNotFoundException($exceptions);\n    }\n\n    public function has($id): bool\n    {\n        /** @psalm-suppress TypeDoesNotContainType */\n        if (!is_string($id)) {\n            throw new InvalidArgumentException(\n                sprintf(\n                    'ID must be a string, %s given.',\n                    get_debug_type($id),\n                ),\n            );\n        }\n\n        if ($id === StateResetter::class) {\n            return true;\n        }\n\n        if (TagReference::isTagAlias($id)) {\n            foreach ($this->containers as $container) {\n                if (!$container instanceof Container) {\n                    continue;\n                }\n                if ($container->has($id)) {\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        foreach ($this->containers as $container) {\n            if ($container->has($id)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Attaches a container to the composite container.\n     */\n    public function attach(ContainerInterface $container): void\n    {\n        $this->containers[] = $container;\n    }\n\n    /**\n     * Removes a container from the list of containers.\n     */\n    public function detach(ContainerInterface $container): void\n    {\n        foreach ($this->containers as $i => $c) {\n            if ($container === $c) {\n                unset($this->containers[$i]);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/CompositeNotFoundException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Exception;\nuse Psr\\Container\\ContainerInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\n\nuse function sprintf;\n\n/**\n * `CompositeNotFoundException` is thrown when no definition or class was found in the composite container\n * for a given ID. It contains all exceptions thrown by containers registered in the composite container.\n */\nfinal class CompositeNotFoundException extends Exception implements NotFoundExceptionInterface\n{\n    /**\n     * @param array $exceptions Container exceptions in [throwable, container] format.\n     *\n     * @psalm-param list<array{\\Throwable,ContainerInterface}> $exceptions\n     */\n    public function __construct(array $exceptions)\n    {\n        $message = '';\n\n        foreach ($exceptions as $i => [$exception, $container]) {\n            $containerClass = $container::class;\n            $containerId = spl_object_id($container);\n            $number = $i + 1;\n\n            $message .= \"\\n    $number. Container $containerClass #$containerId: {$exception->getMessage()}\";\n        }\n\n        parent::__construct(sprintf('No definition or class found or resolvable in composite container:%s', $message));\n    }\n}\n"
  },
  {
    "path": "src/Container.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Closure;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\ContainerInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\nuse Throwable;\nuse Yiisoft\\Definitions\\ArrayDefinition;\nuse Yiisoft\\Definitions\\DefinitionStorage;\nuse Yiisoft\\Definitions\\Exception\\CircularReferenceException;\nuse Yiisoft\\Definitions\\Exception\\InvalidConfigException;\nuse Yiisoft\\Definitions\\Exception\\NotInstantiableException;\nuse Yiisoft\\Definitions\\Helpers\\DefinitionValidator;\nuse Yiisoft\\Di\\Helpers\\DefinitionNormalizer;\nuse Yiisoft\\Di\\Helpers\\DefinitionParser;\nuse Yiisoft\\Di\\Reference\\TagReference;\n\nuse function array_key_exists;\nuse function array_keys;\nuse function implode;\nuse function in_array;\nuse function is_array;\nuse function is_callable;\nuse function is_object;\nuse function is_string;\nuse function sprintf;\n\n/**\n * Container implements a [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) container.\n */\nfinal class Container implements ContainerInterface\n{\n    private const META_TAGS = 'tags';\n    private const META_RESET = 'reset';\n    private const ALLOWED_META = [self::META_TAGS, self::META_RESET];\n\n    /**\n     * @var DefinitionStorage Storage of object definitions.\n     */\n    private readonly DefinitionStorage $definitions;\n\n    /**\n     * @var array Used to collect IDs of objects instantiated during build\n     * to detect circular references.\n     */\n    private array $building = [];\n\n    /**\n     * @var bool $validate If definitions should be validated.\n     */\n    private readonly bool $validate;\n\n    /**\n     * @var array Cached instances.\n     * @psalm-var array<string, mixed>\n     */\n    private array $instances = [];\n\n    private CompositeContainer $delegates;\n\n    /**\n     * @var array Tagged service IDs. The structure is `['tagID' => ['service1', 'service2']]`.\n     * @psalm-var array<string, list<string>>\n     */\n    private array $tags;\n\n    /**\n     * @var Closure[]\n     * @psalm-var array<string, Closure>\n     */\n    private array $resetters = [];\n    private bool $useResettersFromMeta = true;\n\n    /**\n     * @param ?ContainerConfigInterface $config Container configuration.\n     *\n     * @throws InvalidConfigException If configuration is not valid.\n     */\n    public function __construct(?ContainerConfigInterface $config = null)\n    {\n        $config ??= ContainerConfig::create();\n\n        $this->definitions = new DefinitionStorage(\n            [\n                ContainerInterface::class => $this,\n                StateResetter::class => StateResetter::class,\n            ],\n            $config->useStrictMode(),\n        );\n        $this->validate = $config->shouldValidate();\n        $this->setTags($config->getTags());\n        $this->addDefinitions($config->getDefinitions());\n        $this->addProviders($config->getProviders());\n        $this->setDelegates($config->getDelegates());\n    }\n\n    /**\n     * Returns a value indicating whether the container has the definition of the specified name.\n     *\n     * @param string $id Class name, interface name or alias name.\n     *\n     * @return bool Whether the container is able to provide instance of class specified.\n     *\n     * @see addDefinition()\n     */\n    public function has(string $id): bool\n    {\n        try {\n            if ($this->definitions->has($id)) {\n                return true;\n            }\n        } catch (CircularReferenceException) {\n            return true;\n        }\n\n        if (TagReference::isTagAlias($id)) {\n            $tag = TagReference::extractTagFromAlias($id);\n            return isset($this->tags[$tag]);\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns an instance by either interface name or alias.\n     *\n     * The same instance of the class will be returned each time this method is called.\n     *\n     * @param string $id The interface or an alias name that was previously registered.\n     *\n     * @throws CircularReferenceException\n     * @throws InvalidConfigException\n     * @throws NotFoundExceptionInterface\n     * @throws NotInstantiableException\n     * @throws BuildingException\n     *\n     * @return mixed An instance of the requested interface.\n     *\n     * @psalm-template T\n     * @psalm-param string|class-string<T> $id\n     * @psalm-return ($id is class-string ? T : mixed)\n     *\n     * @psalm-suppress MixedReturnStatement `mixed` is a correct return type for this method.\n     */\n    public function get(string $id)\n    {\n        // Fast path: check if instance exists.\n        if (array_key_exists($id, $this->instances)) {\n            if ($id === StateResetter::class) {\n                return $this->prepareStateResetter();\n            }\n            return $this->instances[$id];\n        }\n\n        try {\n            $this->instances[$id] = $this->build($id);\n        } catch (NotFoundException $exception) {\n            // Fast path: if the exception ID matches the requested ID, no need to modify stack.\n            if ($exception->getId() === $id) {\n                // Try delegates before giving up.\n                try {\n                    if ($this->delegates->has($id)) {\n                        return $this->delegates->get($id);\n                    }\n                } catch (Throwable $e) {\n                    throw new BuildingException($id, $e, $this->definitions->getBuildStack(), $e);\n                }\n                throw $exception;\n            }\n\n            // Add current ID to build stack for better error reporting.\n            $buildStack = $exception->getBuildStack();\n            array_unshift($buildStack, $id);\n            throw new NotFoundException($exception->getId(), $buildStack);\n        } catch (NotFoundExceptionInterface $exception) {\n            // Try delegates before giving up\n            try {\n                if ($this->delegates->has($id)) {\n                    return $this->delegates->get($id);\n                }\n            } catch (Throwable $e) {\n                throw new BuildingException($id, $e, $this->definitions->getBuildStack(), $e);\n            }\n\n            throw new NotFoundException($id, [$id], previous: $exception);\n        } catch (ContainerExceptionInterface $e) {\n            if (!$e instanceof InvalidConfigException) {\n                throw $e;\n            }\n            throw new BuildingException($id, $e, $this->definitions->getBuildStack(), $e);\n        } catch (Throwable $e) {\n            throw new BuildingException($id, $e, $this->definitions->getBuildStack(), $e);\n        }\n\n        // Handle StateResetter for newly built instances.\n        if ($id === StateResetter::class) {\n            return $this->prepareStateResetter();\n        }\n\n        return $this->instances[$id];\n    }\n\n    private function prepareStateResetter(): StateResetter\n    {\n        $delegatesResetter = null;\n        if ($this->delegates->has(StateResetter::class)) {\n            $delegatesResetter = $this->delegates->get(StateResetter::class);\n        }\n\n        /** @var StateResetter $mainResetter */\n        $mainResetter = $this->instances[StateResetter::class];\n\n        if ($this->useResettersFromMeta) {\n            /** @var StateResetter[] $resetters */\n            $resetters = [];\n            foreach ($this->resetters as $serviceId => $callback) {\n                if (isset($this->instances[$serviceId])) {\n                    $resetters[$serviceId] = $callback;\n                }\n            }\n            if ($delegatesResetter !== null) {\n                $resetters[] = $delegatesResetter;\n            }\n            $mainResetter->setResetters($resetters);\n        } elseif ($delegatesResetter !== null) {\n            $resetter = new StateResetter($this->get(ContainerInterface::class));\n            $resetter->setResetters([$mainResetter, $delegatesResetter]);\n\n            return $resetter;\n        }\n\n        return $mainResetter;\n    }\n\n    /**\n     * Sets a definition to the container. Definition may be defined multiple ways.\n     *\n     * @param string $id ID to set definition for.\n     * @param mixed $definition Definition to set.\n     *\n     * @throws InvalidConfigException\n     *\n     * @see DefinitionNormalizer::normalize()\n     */\n    private function addDefinition(string $id, mixed $definition): void\n    {\n        [$definition, $meta] = DefinitionParser::parse($definition);\n        if ($this->validate) {\n            $this->validateDefinition($definition, $id);\n            // Only validate meta if it's not empty.\n            if ($meta !== []) {\n                $this->validateMeta($meta);\n            }\n        }\n        /**\n         * @psalm-var array{reset?:Closure,tags?:string[]} $meta\n         */\n\n        // Process meta only if it has tags or reset callback.\n        if (isset($meta[self::META_TAGS])) {\n            $this->setDefinitionTags($id, $meta[self::META_TAGS]);\n        }\n        if (isset($meta[self::META_RESET])) {\n            $this->setDefinitionResetter($id, $meta[self::META_RESET]);\n        }\n\n        unset($this->instances[$id]);\n\n        $this->addDefinitionToStorage($id, $definition);\n    }\n\n    /**\n     * Sets multiple definitions at once.\n     *\n     * @param array $config Definitions indexed by their IDs.\n     *\n     * @throws InvalidConfigException\n     */\n    private function addDefinitions(array $config): void\n    {\n        foreach ($config as $id => $definition) {\n            if ($this->validate && !is_string($id)) {\n                throw new InvalidConfigException(\n                    sprintf(\n                        'Key must be a string. %s given.',\n                        get_debug_type($id),\n                    ),\n                );\n            }\n            /** @var string $id */\n\n            $this->addDefinition($id, $definition);\n        }\n    }\n\n    /**\n     * Set container delegates.\n     *\n     * Each delegate must be a callable in format `function (ContainerInterface $container): ContainerInterface`.\n     * The container instance returned is used in case a service can't be found in primary container.\n     *\n     * @throws InvalidConfigException\n     */\n    private function setDelegates(array $delegates): void\n    {\n        $this->delegates = new CompositeContainer();\n\n        $container = $this->get(ContainerInterface::class);\n\n        foreach ($delegates as $delegate) {\n            if (!$delegate instanceof Closure) {\n                throw new InvalidConfigException(\n                    'Delegate must be callable in format \"function (ContainerInterface $container): ContainerInterface\".',\n                );\n            }\n\n            $delegate = $delegate($container);\n\n            if (!$delegate instanceof ContainerInterface) {\n                throw new InvalidConfigException(\n                    'Delegate callable must return an object that implements ContainerInterface.',\n                );\n            }\n\n            $this->delegates->attach($delegate);\n        }\n        $this->definitions->setDelegateContainer($this->delegates);\n    }\n\n    /**\n     * @param mixed $definition Definition to validate.\n     * @param string|null $id ID of the definition to validate.\n     *\n     * @throws InvalidConfigException\n     */\n    private function validateDefinition(mixed $definition, ?string $id = null): void\n    {\n        // Skip validation for common simple cases.\n        if ($definition instanceof ContainerInterface || $definition instanceof Closure) {\n            return;\n        }\n\n        if (is_array($definition)) {\n            if (isset($definition[DefinitionParser::IS_PREPARED_ARRAY_DEFINITION_DATA])) {\n                $class = $definition['class'];\n                $constructorArguments = $definition['__construct()'];\n\n                /**\n                 * @var array $methodsAndProperties Is always array for prepared array definition data.\n                 * @see DefinitionParser::parse()\n                 * @psalm-var array<string,mixed> $methodsAndProperties\n                 */\n                $methodsAndProperties = $definition['methodsAndProperties'];\n\n                $definition = array_merge(\n                    $class === null ? [] : [ArrayDefinition::CLASS_NAME => $class],\n                    [ArrayDefinition::CONSTRUCTOR => $constructorArguments],\n                    // extract only value from parsed definition method\n                    array_map(static fn(array $data): mixed => $data[2], $methodsAndProperties),\n                );\n            }\n        } elseif ($definition instanceof ExtensibleService) {\n            throw new InvalidConfigException(\n                'Invalid definition. ExtensibleService is only allowed in provider extensions.',\n            );\n        }\n\n        DefinitionValidator::validate($definition, $id);\n    }\n\n    /**\n     * @throws InvalidConfigException\n     */\n    private function validateMeta(array $meta): void\n    {\n        foreach ($meta as $key => $value) {\n            if (!in_array($key, self::ALLOWED_META, true)) {\n                throw new InvalidConfigException(\n                    sprintf(\n                        'Invalid definition: metadata \"%s\" is not allowed. Did you mean \"%s()\" or \"$%s\"?',\n                        $key,\n                        $key,\n                        $key,\n                    ),\n                );\n            }\n\n            if ($key === self::META_TAGS) {\n                $this->validateDefinitionTags($value);\n            }\n\n            if ($key === self::META_RESET) {\n                $this->validateDefinitionReset($value);\n            }\n        }\n    }\n\n    /**\n     * @throws InvalidConfigException\n     */\n    private function validateDefinitionTags(mixed $tags): void\n    {\n        if (!is_array($tags)) {\n            throw new InvalidConfigException(\n                sprintf(\n                    'Invalid definition: tags should be array of strings, %s given.',\n                    get_debug_type($tags),\n                ),\n            );\n        }\n\n        foreach ($tags as $tag) {\n            if (!is_string($tag)) {\n                throw new InvalidConfigException('Invalid tag. Expected a string, got ' . var_export($tag, true) . '.');\n            }\n        }\n    }\n\n    /**\n     * @throws InvalidConfigException\n     */\n    private function validateDefinitionReset(mixed $reset): void\n    {\n        if (!$reset instanceof Closure) {\n            throw new InvalidConfigException(\n                sprintf(\n                    'Invalid definition: \"reset\" should be closure, %s given.',\n                    get_debug_type($reset),\n                ),\n            );\n        }\n    }\n\n    /**\n     * @throws InvalidConfigException\n     */\n    private function setTags(array $tags): void\n    {\n        if ($this->validate) {\n            foreach ($tags as $tag => $services) {\n                if (!is_string($tag)) {\n                    throw new InvalidConfigException(\n                        sprintf(\n                            'Invalid tags configuration: tag should be string, %s given.',\n                            $tag,\n                        ),\n                    );\n                }\n                if (!is_array($services)) {\n                    throw new InvalidConfigException(\n                        sprintf(\n                            'Invalid tags configuration: tag should contain array of service IDs, %s given.',\n                            get_debug_type($services),\n                        ),\n                    );\n                }\n                foreach ($services as $service) {\n                    if (!is_string($service)) {\n                        throw new InvalidConfigException(\n                            sprintf(\n                                'Invalid tags configuration: service should be defined as class string, %s given.',\n                                get_debug_type($service),\n                            ),\n                        );\n                    }\n                }\n            }\n        }\n        /** @psalm-var array<string, list<string>> $tags */\n\n        $this->tags = $tags;\n    }\n\n    /**\n     * @psalm-param string[] $tags\n     */\n    private function setDefinitionTags(string $id, array $tags): void\n    {\n        foreach ($tags as $tag) {\n            if (!isset($this->tags[$tag]) || !in_array($id, $this->tags[$tag], true)) {\n                $this->tags[$tag][] = $id;\n            }\n        }\n    }\n\n    private function setDefinitionResetter(string $id, Closure $resetter): void\n    {\n        $this->resetters[$id] = $resetter;\n    }\n\n    /**\n     * Add definition to storage.\n     *\n     * @param string $id ID to set definition for.\n     * @param mixed|object $definition Definition to set.\n     *\n     * @see $definitions\n     */\n    private function addDefinitionToStorage(string $id, mixed $definition): void\n    {\n        $this->definitions->set($id, $definition);\n\n        if ($id === StateResetter::class) {\n            $this->useResettersFromMeta = false;\n        }\n    }\n\n    /**\n     * Creates new instance by either interface name or alias.\n     *\n     * @param string $id The interface or the alias name that was previously registered.\n     *\n     * @throws InvalidConfigException\n     * @throws NotFoundExceptionInterface\n     * @throws CircularReferenceException\n     *\n     * @return mixed|object New-built instance of the specified class.\n     *\n     * @internal\n     */\n    private function build(string $id): mixed\n    {\n        // Fast path: check for circular reference first as it's the most critical.\n        if (isset($this->building[$id])) {\n            if ($id === ContainerInterface::class) {\n                return $this;\n            }\n            throw new CircularReferenceException(\n                sprintf(\n                    'Circular reference to \"%s\" detected while building: %s.',\n                    $id,\n                    implode(', ', array_keys($this->building)),\n                ),\n            );\n        }\n\n        // Less common case: tag alias.\n        if (TagReference::isTagAlias($id)) {\n            return $this->getTaggedServices($id);\n        }\n\n        // Check if the definition exists.\n        if (!$this->definitions->has($id)) {\n            throw new NotFoundException($id, $this->definitions->getBuildStack());\n        }\n\n        $this->building[$id] = 1;\n        try {\n            $normalizedDefinition = DefinitionNormalizer::normalize($this->definitions->get($id), $id);\n            $object = $normalizedDefinition->resolve($this->get(ContainerInterface::class));\n        } finally {\n            unset($this->building[$id]);\n        }\n\n        return $object;\n    }\n\n    private function getTaggedServices(string $tagAlias): array\n    {\n        $tag = TagReference::extractTagFromAlias($tagAlias);\n        $services = [];\n        if (isset($this->tags[$tag])) {\n            foreach ($this->tags[$tag] as $service) {\n                $services[] = $this->get($service);\n            }\n        }\n\n        return $services;\n    }\n\n    /**\n     * @throws CircularReferenceException\n     * @throws InvalidConfigException\n     */\n    private function addProviders(array $providers): void\n    {\n        $extensions = [];\n        foreach ($providers as $provider) {\n            $providerInstance = $this->buildProvider($provider);\n            $extensions[] = $providerInstance->getExtensions();\n            $this->addDefinitions($providerInstance->getDefinitions());\n        }\n\n        foreach ($extensions as $providerExtensions) {\n            foreach ($providerExtensions as $id => $extension) {\n                if (!is_string($id)) {\n                    throw new InvalidConfigException(\n                        sprintf('Extension key must be a service ID as string, %s given.', $id),\n                    );\n                }\n\n                if ($id === ContainerInterface::class) {\n                    throw new InvalidConfigException('ContainerInterface extensions are not allowed.');\n                }\n\n                if (!$this->definitions->has($id)) {\n                    throw new InvalidConfigException(\"Extended service \\\"$id\\\" doesn't exist.\");\n                }\n\n                if (!is_callable($extension)) {\n                    throw new InvalidConfigException(\n                        sprintf(\n                            'Extension of service should be callable, %s given.',\n                            get_debug_type($extension),\n                        ),\n                    );\n                }\n\n                $definition = $this->definitions->get($id);\n                if (!$definition instanceof ExtensibleService) {\n                    $definition = new ExtensibleService($definition, $id);\n                    $this->addDefinitionToStorage($id, $definition);\n                }\n\n                $definition->addExtension($extension);\n            }\n        }\n    }\n\n    /**\n     * Builds service provider by definition.\n     *\n     * @param mixed $provider Class name or instance of provider.\n     *\n     * @throws InvalidConfigException If provider argument is not valid.\n     *\n     * @return ServiceProviderInterface Instance of service provider.\n     */\n    private function buildProvider(mixed $provider): ServiceProviderInterface\n    {\n        if ($this->validate && !(is_string($provider) || $provider instanceof ServiceProviderInterface)) {\n            throw new InvalidConfigException(\n                sprintf(\n                    'Service provider should be a class name or an instance of %s. %s given.',\n                    ServiceProviderInterface::class,\n                    get_debug_type($provider),\n                ),\n            );\n        }\n\n        /**\n         * @psalm-suppress MixedMethodCall Service provider defined as class string\n         * should container public constructor, otherwise throws error.\n         */\n        $providerInstance = is_object($provider) ? $provider : new $provider();\n        if (!$providerInstance instanceof ServiceProviderInterface) {\n            throw new InvalidConfigException(\n                sprintf(\n                    'Service provider should be an instance of %s. %s given.',\n                    ServiceProviderInterface::class,\n                    get_debug_type($providerInstance),\n                ),\n            );\n        }\n\n        return $providerInstance;\n    }\n}\n"
  },
  {
    "path": "src/ContainerConfig.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\n/**\n * Container configuration.\n */\nfinal class ContainerConfig implements ContainerConfigInterface\n{\n    private array $definitions = [];\n    private array $providers = [];\n    private array $tags = [];\n    private bool $validate = true;\n    private array $delegates = [];\n    private bool $useStrictMode = false;\n\n    private function __construct() {}\n\n    public static function create(): self\n    {\n        return new self();\n    }\n\n    /**\n     * @param array $definitions Definitions to put into container.\n     */\n    public function withDefinitions(array $definitions): self\n    {\n        $new = clone $this;\n        $new->definitions = $definitions;\n        return $new;\n    }\n\n    public function getDefinitions(): array\n    {\n        return $this->definitions;\n    }\n\n    /**\n     * @param array $providers Service providers to get definitions from.\n     */\n    public function withProviders(array $providers): self\n    {\n        $new = clone $this;\n        $new->providers = $providers;\n        return $new;\n    }\n\n    public function getProviders(): array\n    {\n        return $this->providers;\n    }\n\n    /**\n     * @param array $tags Tagged service IDs. The structure is `['tagID' => ['service1', 'service2']]`.\n     */\n    public function withTags(array $tags): self\n    {\n        $new = clone $this;\n        $new->tags = $tags;\n        return $new;\n    }\n\n    public function getTags(): array\n    {\n        return $this->tags;\n    }\n\n    /**\n     * @param bool $validate Whether definitions should be validated immediately.\n     */\n    public function withValidate(bool $validate = true): self\n    {\n        $new = clone $this;\n        $new->validate = $validate;\n        return $new;\n    }\n\n    public function shouldValidate(): bool\n    {\n        return $this->validate;\n    }\n\n    /**\n     * @param array $delegates Container delegates. Each delegate is a callable in format\n     * `function (ContainerInterface $container): ContainerInterface`. The container instance returned is used\n     * in case a service can't be found in primary container.\n     */\n    public function withDelegates(array $delegates): self\n    {\n        $new = clone $this;\n        $new->delegates = $delegates;\n        return $new;\n    }\n\n    public function getDelegates(): array\n    {\n        return $this->delegates;\n    }\n\n    /**\n     * @param bool $useStrictMode If the automatic addition of definition when class exists and can be resolved\n     * is disabled.\n     */\n    public function withStrictMode(bool $useStrictMode = true): self\n    {\n        $new = clone $this;\n        $new->useStrictMode = $useStrictMode;\n        return $new;\n    }\n\n    public function useStrictMode(): bool\n    {\n        return $this->useStrictMode;\n    }\n}\n"
  },
  {
    "path": "src/ContainerConfigInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\n/**\n * Container configuration.\n */\ninterface ContainerConfigInterface\n{\n    /**\n     * @return array Definitions to put into container.\n     */\n    public function getDefinitions(): array;\n\n    /**\n     * @return array Service providers to get definitions from.\n     */\n    public function getProviders(): array;\n\n    /**\n     * @return array Tagged service IDs. The structure is `['tagID' => ['service1', 'service2']]`.\n     */\n    public function getTags(): array;\n\n    /**\n     * @return bool Whether definitions should be validated immediately.\n     */\n    public function shouldValidate(): bool;\n\n    /**\n     * @return array Container delegates. Each delegate is a callable in format\n     * `function (ContainerInterface $container): ContainerInterface`. The container instance returned is used\n     * in case a service can't be found in primary container.\n     */\n    public function getDelegates(): array;\n\n    /**\n     * @return bool If the automatic addition of definition when class exists and can be resolved is disabled.\n     */\n    public function useStrictMode(): bool;\n}\n"
  },
  {
    "path": "src/ExtensibleService.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Definitions\\Contract\\DefinitionInterface;\nuse Yiisoft\\Di\\Helpers\\DefinitionNormalizer;\n\n/**\n * @internal A wrapper for a service definition that allows registering extensions.\n * An extension is callable that returns a modified service object:\n *\n * ```php\n * static function (ContainerInterface $container, $service) {\n *     return $service->withAnotherOption(42);\n * }\n * ```\n */\nfinal class ExtensibleService implements DefinitionInterface\n{\n    /**\n     * @var callable[]\n     */\n    private array $extensions = [];\n\n    /**\n     * @param mixed $definition Definition to allow registering extensions for.\n     */\n    public function __construct(\n        private readonly mixed $definition,\n        private readonly string $id,\n    ) {}\n\n    /**\n     * Add an extension.\n     *\n     * An extension is callable that returns a modified service object:\n     *\n     * ```php\n     * static function (ContainerInterface $container, $service) {\n     *     return $service->withAnotherOption(42);\n     * }\n     * ```\n     *\n     * @param callable $closure An extension to register.\n     */\n    public function addExtension(callable $closure): void\n    {\n        $this->extensions[] = $closure;\n    }\n\n    public function resolve(ContainerInterface $container): mixed\n    {\n        $service = DefinitionNormalizer::normalize($this->definition, $this->id)\n            ->resolve($container);\n\n        foreach ($this->extensions as $extension) {\n            $result = $extension($container->get(ContainerInterface::class), $service);\n            if ($result === null) {\n                continue;\n            }\n\n            $service = $result;\n        }\n\n        return $service;\n    }\n}\n"
  },
  {
    "path": "src/Helpers/DefinitionNormalizer.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Helpers;\n\nuse Yiisoft\\Definitions\\ArrayDefinition;\nuse Yiisoft\\Definitions\\Contract\\DefinitionInterface;\nuse Yiisoft\\Definitions\\Exception\\InvalidConfigException;\nuse Yiisoft\\Definitions\\Helpers\\Normalizer;\nuse Yiisoft\\Di\\ExtensibleService;\n\nuse function is_array;\n\n/**\n * @internal Normalizes a definition.\n */\nfinal class DefinitionNormalizer\n{\n    /**\n     * @param mixed $definition Definition to normalize.\n     * @param string $id Service ID.\n     *\n     * @throws InvalidConfigException If configuration is not valid.\n     */\n    public static function normalize(mixed $definition, string $id): DefinitionInterface\n    {\n        if (is_array($definition) && isset($definition[DefinitionParser::IS_PREPARED_ARRAY_DEFINITION_DATA])) {\n            /** @psalm-suppress MixedArgument Definition should be valid {@see Container::$validate} */\n            return ArrayDefinition::fromPreparedData(\n                $definition['class'] ?? $id,\n                $definition['__construct()'],\n                $definition['methodsAndProperties'],\n            );\n        }\n\n        if ($definition instanceof ExtensibleService) {\n            return $definition;\n        }\n\n        return Normalizer::normalize($definition, $id);\n    }\n}\n"
  },
  {
    "path": "src/Helpers/DefinitionParser.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Helpers;\n\nuse Yiisoft\\Definitions\\ArrayDefinition;\n\nuse function count;\nuse function is_array;\nuse function is_callable;\nuse function is_string;\n\n/**\n * @internal Splits metadata and definition.\n *\n * Supports the following configuration:\n *\n * 1) With a dedicated definition:\n *\n * ```php\n * Engine::class => [\n *     'definition' => [\n *         '__class' => BigEngine::class,\n *         'setNumber()' => [42],\n *     ],\n *     'tags' => ['a', 'b'],\n *     'reset' => function () {\n *         $this->number = 42;\n *      },\n * ]\n * ```\n *\n * 2) Mixed in array definition:\n *\n * ```php\n * Engine::class => [\n *     '__class' => BigEngine::class,\n *     'setNumber()' => [42],\n *     'tags' => ['a', 'b'],\n *     'reset' => function () {\n *         $this->number = 42;\n *      },\n * ]\n * ```\n */\nfinal class DefinitionParser\n{\n    public const IS_PREPARED_ARRAY_DEFINITION_DATA = 'isPreparedArrayDefinitionData';\n    private const DEFINITION_META = 'definition';\n\n    /**\n     * @param mixed $definition Definition to parse.\n     *\n     * @return array Definition parsed into an array of a special structure.\n     * @psalm-return array{mixed,array}\n     */\n    public static function parse(mixed $definition): array\n    {\n        if (!is_array($definition)) {\n            return [$definition, []];\n        }\n\n        // Dedicated definition\n        if (isset($definition[self::DEFINITION_META])) {\n            $newDefinition = $definition[self::DEFINITION_META];\n            unset($definition[self::DEFINITION_META]);\n\n            return [$newDefinition, $definition];\n        }\n\n        // Callable definition\n        if (is_callable($definition, true)) {\n            return [$definition, []];\n        }\n\n        // Array definition\n        $meta = [];\n        $class = null;\n        $constructorArguments = [];\n        $methodsAndProperties = [];\n        foreach ($definition as $key => $value) {\n            if (is_string($key)) {\n                // Class\n                if ($key === ArrayDefinition::CLASS_NAME) {\n                    $class = $value;\n                    continue;\n                }\n\n                // Constructor arguments\n                if ($key === ArrayDefinition::CONSTRUCTOR) {\n                    $constructorArguments = $value;\n                    continue;\n                }\n\n                // Methods and properties\n                if (count($methodArray = explode('()', $key, 2)) === 2) {\n                    $methodsAndProperties[$key] = [ArrayDefinition::TYPE_METHOD, $methodArray[0], $value];\n                    continue;\n                }\n                if (count($propertyArray = explode('$', $key, 2)) === 2) {\n                    $methodsAndProperties[$key] = [ArrayDefinition::TYPE_PROPERTY, $propertyArray[1], $value];\n                    continue;\n                }\n            }\n\n            $meta[$key] = $value;\n        }\n        return [\n            [\n                'class' => $class,\n                '__construct()' => $constructorArguments,\n                'methodsAndProperties' => $methodsAndProperties,\n                self::IS_PREPARED_ARRAY_DEFINITION_DATA => true,\n            ],\n            $meta,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/NotFoundException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Exception;\nuse Psr\\Container\\NotFoundExceptionInterface;\nuse Throwable;\nuse Yiisoft\\FriendlyException\\FriendlyExceptionInterface;\n\nuse function sprintf;\n\n/**\n * `NotFoundException` is thrown when no definition or class was found in the container for a given ID.\n */\nfinal class NotFoundException extends Exception implements NotFoundExceptionInterface, FriendlyExceptionInterface\n{\n    /**\n     * @param string $id ID of the definition or name of the class that was not found.\n     * @param string[] $buildStack Stack of IDs of services requested definition or class that was not found.\n     */\n    public function __construct(\n        private readonly string $id,\n        private array $buildStack = [],\n        ?Throwable $previous = null,\n    ) {\n        if (empty($this->buildStack)) {\n            $message = sprintf('No definition or class found or resolvable for \"%s\".', $id);\n        } elseif ($this->buildStack === [$id]) {\n            $message = sprintf('No definition or class found or resolvable for \"%s\" while building it.', $id);\n        } else {\n            $message = sprintf(\n                'No definition or class found or resolvable for \"%s\" while building \"%s\".',\n                end($this->buildStack),\n                implode('\" -> \"', $buildStack),\n            );\n        }\n\n        parent::__construct($message, previous: $previous);\n    }\n\n    public function getId(): string\n    {\n        return $this->id;\n    }\n\n    /**\n     * @return string[]\n     */\n    public function getBuildStack(): array\n    {\n        return $this->buildStack;\n    }\n\n    public function getName(): string\n    {\n        return sprintf('No definition or class found for \"%s\" ID.', $this->id);\n    }\n\n    public function getSolution(): ?string\n    {\n        $solution = <<<SOLUTION\n            Ensure that either a service with ID \"%1\\$s\" is defined or such class exists and is autoloadable.\n            SOLUTION;\n\n        return sprintf($solution, $this->id);\n    }\n}\n"
  },
  {
    "path": "src/Reference/TagReference.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Reference;\n\nuse InvalidArgumentException;\nuse Yiisoft\\Definitions\\Reference;\n\nuse function sprintf;\n\n/**\n * Helper class used to specify a reference to a tag.\n * For example, `TagReference::to('my-tag')` specifies a reference to all services that are tagged with `tag@my-tag`.\n */\nfinal class TagReference\n{\n    private const PREFIX = 'tag@';\n\n    private function __construct() {}\n\n    public static function to(string $tag): Reference\n    {\n        return Reference::to(self::id($tag));\n    }\n\n    public static function id(string $tag): string\n    {\n        return self::PREFIX . $tag;\n    }\n\n    public static function extractTagFromAlias(string $alias): string\n    {\n        if (!str_starts_with($alias, self::PREFIX)) {\n            throw new InvalidArgumentException(sprintf('Alias \"%s\" is not a tag alias.', $alias));\n        }\n        return substr($alias, 4);\n    }\n\n    public static function isTagAlias(string $id): bool\n    {\n        return str_starts_with($id, self::PREFIX);\n    }\n}\n"
  },
  {
    "path": "src/ServiceProviderInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\n/**\n * Represents a component responsible for class registration in the Container.\n *\n * The goal of service providers is to centralize and organize in one place\n * registration of classes bound by any logic or classes with complex dependencies.\n *\n * You can organize registration of a service and its dependencies in a single\n * provider class except for creating a bootstrap file or configuration array for the Container.\n *\n * Example:\n *\n * ```php\n * class CarProvider implements ServiceProviderInterface\n * {\n *    public function getDefinitions(): array\n *    {\n *        return [\n *            'car' => ['class' => Car::class],\n *            'car-factory' => CarFactory::class,\n *            EngineInterface::class => EngineMarkOne::class,\n *        ];\n *    }\n * }\n * ```\n */\ninterface ServiceProviderInterface\n{\n    /**\n     * Returns definitions for the container.\n     *\n     * This method:\n     *\n     * - Should only return definitions for the Container preventing any side effects.\n     * - Should be idempotent.\n     *\n     * @return array Definitions for the container. Each array key is the name of the service (usually it is\n     * an interface name), and a corresponding value is a service definition.\n     */\n    public function getDefinitions(): array;\n\n    /**\n     * Returns an array of service extensions.\n     *\n     * An extension is callable that returns a modified service object:\n     *\n     * ```php\n     * static function (ContainerInterface $container, $service) {\n     *     return $service->withAnotherOption(42);\n     * }\n     * ```\n     *\n     * @return array Extensions for the container services. Each array key is the name of the service to be modified\n     * and a corresponding value is callable doing the job.\n     */\n    public function getExtensions(): array;\n}\n"
  },
  {
    "path": "src/StateResetter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Closure;\nuse InvalidArgumentException;\nuse Psr\\Container\\ContainerInterface;\n\nuse function is_int;\nuse function is_object;\nuse function sprintf;\n\n/**\n * State resetter allows resetting state of the services that are currently stored in the container and have \"reset\"\n * callback defined. The reset should be triggered after each request-response cycle in case you build long-running\n * applications with tools like [Swoole](https://www.swoole.co.uk/) or [RoadRunner](https://roadrunner.dev/).\n */\nfinal class StateResetter\n{\n    /**\n     * @var Closure[]|self[]\n     */\n    private array $resetters = [];\n\n    /**\n     * @param ContainerInterface $container Container to reset.\n     */\n    public function __construct(\n        private readonly ContainerInterface $container,\n    ) {}\n\n    /**\n     * Reset the container.\n     */\n    public function reset(): void\n    {\n        foreach ($this->resetters as $resetter) {\n            if ($resetter instanceof self) {\n                $resetter->reset();\n                continue;\n            }\n            $resetter($this->container);\n        }\n    }\n\n    /**\n     * @param Closure[]|self[] $resetters Array of reset callbacks. Each callback has access to the private and\n     * protected properties of the service instance, so you can set the initial state of the service efficiently\n     * without creating a new instance.\n     */\n    public function setResetters(array $resetters): void\n    {\n        $this->resetters = [];\n        foreach ($resetters as $serviceId => $callback) {\n            if (is_int($serviceId)) {\n                if (!$callback instanceof self) {\n                    throw new InvalidArgumentException(sprintf(\n                        'State resetter object should be instance of \"%s\", \"%s\" given.',\n                        self::class,\n                        get_debug_type($callback),\n                    ));\n                }\n                $this->resetters[] = $callback;\n                continue;\n            }\n\n            if (!$callback instanceof Closure) {\n                throw new InvalidArgumentException(\n                    'Callback for state resetter should be closure in format '\n                    . '`function (ContainerInterface $container): void`. '\n                    . 'Got \"' . get_debug_type($callback) . '\".',\n                );\n            }\n\n            $instance = $this->container->get($serviceId);\n            if (!is_object($instance)) {\n                throw new InvalidArgumentException(\n                    'State resetter supports resetting objects only. Container returned '\n                    . get_debug_type($instance)\n                    . '.',\n                );\n            }\n\n            /** @var Closure */\n            $this->resetters[] = $callback->bindTo($instance, $instance::class);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Benchmark/ContainerBench.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Benchmark;\n\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\BeforeMethods;\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\Groups;\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\Iterations;\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\ParamProviders;\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\Revs;\nuse Yiisoft\\Di\\CompositeContainer;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\nuse Yiisoft\\Di\\Tests\\Support\\Car;\nuse Yiisoft\\Di\\Tests\\Support\\EngineInterface;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkOne;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkTwo;\nuse Yiisoft\\Di\\Tests\\Support\\NullableConcreteDependency;\nuse Yiisoft\\Di\\Tests\\Support\\PropertyTestClass;\nuse Yiisoft\\Definitions\\Reference;\nuse Yiisoft\\Definitions\\Exception\\InvalidConfigException;\nuse Yiisoft\\Definitions\\Exception\\NotInstantiableException;\n\n/**\n * @Iterations(5)\n * @Revs(1000)\n * @BeforeMethods({\"before\"})\n */\nclass ContainerBench\n{\n    public const SERVICE_COUNT = 200;\n\n    private CompositeContainer $composite;\n\n    /** @var int[] */\n    private array $indexes = [];\n\n    /** @var int[] */\n    private array $randomIndexes = [];\n\n    public function provideDefinitions(): array\n    {\n        return [\n            ['serviceClass' => PropertyTestClass::class],\n            [\n                'serviceClass' => NullableConcreteDependency::class,\n                'otherDefinitions' => [\n                    EngineInterface::class => EngineMarkOne::class,\n                    Car::class => Car::class,\n                    EngineMarkOne::class => EngineMarkOne::class,\n                ],\n            ],\n            [\n                'serviceClass' => NullableConcreteDependency::class,\n                'otherDefinitions' => [\n                    EngineInterface::class => EngineMarkTwo::class,\n                ],\n            ],\n        ];\n    }\n\n    /**\n     * Load the bulk of the definitions.\n     * These all refer to a service that is not yet defined but must be defined in the benchmark.\n     */\n    public function before(): void\n    {\n        $definitions3 = [];\n        $definitions2 = [];\n        $definitions3['service'] = PropertyTestClass::class;\n        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {\n            $this->indexes[] = $i;\n            $definitions2[\"second$i\"] = Reference::to('service');\n            $definitions3[\"third$i\"] = Reference::to('service');\n        }\n        $this->randomIndexes = $this->indexes;\n        shuffle($this->randomIndexes);\n\n        $this->composite = new CompositeContainer();\n        // Attach the dummy containers multiple times, to see what would happen if there are lots of them.\n        $this->composite->attach(\n            new Container(\n                ContainerConfig::create()\n                    ->withDefinitions($definitions2),\n            ),\n        );\n        $this->composite->attach(\n            new Container(\n                ContainerConfig::create()\n                    ->withDefinitions($definitions3),\n            ),\n        );\n        $this->composite->attach(\n            new Container(\n                ContainerConfig::create()\n                    ->withDefinitions($definitions2),\n            ),\n        );\n        $this->composite->attach(\n            new Container(\n                ContainerConfig::create()\n                    ->withDefinitions($definitions3),\n            ),\n        );\n        $this->composite->attach(\n            new Container(\n                ContainerConfig::create()\n                    ->withDefinitions($definitions2),\n            ),\n        );\n        $this->composite->attach(\n            new Container(\n                ContainerConfig::create()\n                    ->withDefinitions($definitions3),\n            ),\n        );\n        $this->composite->attach(\n            new Container(\n                ContainerConfig::create()\n                    ->withDefinitions($definitions2),\n            ),\n        );\n        $this->composite->attach(\n            new Container(\n                ContainerConfig::create()\n                    ->withDefinitions($definitions3),\n            ),\n        );\n    }\n\n    /**\n     * @Groups({\"construct\"})\n     *\n     * @throws InvalidConfigException\n     * @throws NotInstantiableException\n     */\n    public function benchConstruct(): void\n    {\n        $definitions = [];\n        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {\n            $definitions[\"service$i\"] = PropertyTestClass::class;\n        }\n        $container = new Container(\n            ContainerConfig::create()\n                ->withDefinitions($definitions),\n        );\n    }\n\n    /**\n     * @Groups({\"lookup\"})\n     * @ParamProviders({\"provideDefinitions\"})\n     */\n    public function benchSequentialLookups($params): void\n    {\n        $definitions = [];\n        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {\n            $definitions[\"service$i\"] = $params['serviceClass'];\n        }\n        if (isset($params['otherDefinitions'])) {\n            $definitions = array_merge($definitions, $params['otherDefinitions']);\n        }\n        $container = new Container(\n            ContainerConfig::create()\n                ->withDefinitions($definitions),\n        );\n        for ($i = 0; $i < self::SERVICE_COUNT / 2; $i++) {\n            // Do array lookup.\n            $index = $this->indexes[$i];\n            $container->get(\"service$index\");\n        }\n    }\n\n    /**\n     * @Groups({\"lookup\"})\n     * @ParamProviders({\"provideDefinitions\"})\n     */\n    public function benchRandomLookups($params): void\n    {\n        $definitions = [];\n        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {\n            $definitions[\"service$i\"] = $params['serviceClass'];\n        }\n        if (isset($params['otherDefinitions'])) {\n            $definitions = array_merge($definitions, $params['otherDefinitions']);\n        }\n        $container = new Container(\n            ContainerConfig::create()\n                ->withDefinitions($definitions),\n        );\n        for ($i = 0; $i < self::SERVICE_COUNT / 2; $i++) {\n            // Do array lookup.\n            $index = $this->randomIndexes[$i];\n            $container->get(\"service$index\");\n        }\n    }\n\n    /**\n     * @Groups({\"lookup\"})\n     * @ParamProviders({\"provideDefinitions\"})\n     */\n    public function benchRandomLookupsComposite($params): void\n    {\n        $definitions = [];\n        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {\n            $definitions[\"service$i\"] = $params['serviceClass'];\n        }\n        if (isset($params['otherDefinitions'])) {\n            $definitions = array_merge($definitions, $params['otherDefinitions']);\n        }\n        $container = new Container(\n            ContainerConfig::create()\n                ->withDefinitions($definitions),\n        );\n        $this->composite->attach($container);\n        for ($i = 0; $i < self::SERVICE_COUNT / 2; $i++) {\n            // Do array lookup.\n            $index = $this->randomIndexes[$i];\n            $this->composite->get(\"service$index\");\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Benchmark/ContainerMethodHasBench.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Benchmark;\n\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\BeforeMethods;\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\Groups;\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\Iterations;\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\Revs;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\nuse Yiisoft\\Di\\Tests\\Support\\GearBox;\nuse Yiisoft\\Di\\Tests\\Support\\PropertyTestClass;\nuse Yiisoft\\Definitions\\Reference;\n\n/**\n * @Iterations(5)\n * @Revs(1000)\n * @Groups({\"has\"})\n * @BeforeMethods({\"before\"})\n */\nclass ContainerMethodHasBench\n{\n    private const SERVICE_COUNT = 200;\n\n    private Container $container;\n\n    /**\n     * Load the bulk of the definitions.\n     */\n    public function before(): void\n    {\n        $definitions = [];\n        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {\n            $definitions[\"service$i\"] = Reference::to('service');\n        }\n        $definitions['service'] = PropertyTestClass::class;\n\n        $this->container = new Container(\n            ContainerConfig::create()\n                ->withDefinitions($definitions),\n        );\n    }\n\n    public function benchPredefinedExisting(): void\n    {\n        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {\n            $this->container->has(\"service$i\");\n        }\n    }\n\n    public function benchUndefinedExisting(): void\n    {\n        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {\n            $this->container->has(GearBox::class);\n        }\n    }\n\n    public function benchUndefinedNonexistent(): void\n    {\n        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {\n            $this->container->has('NonexistentNamespace\\NonexistentClass');\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Support/A.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nclass A\n{\n    public function __construct(public ?B $b = null) {}\n}\n"
  },
  {
    "path": "tests/Support/B.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nclass B\n{\n    public function __construct(public ?A $a = null) {}\n}\n"
  },
  {
    "path": "tests/Support/Car.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * A car\n */\nclass Car\n{\n    public ColorInterface $color;\n\n    public function __construct(\n        private readonly EngineInterface $engine,\n        private readonly array $moreEngines = [],\n    ) {}\n\n    public function setColor(ColorInterface $color): self\n    {\n        $this->color = $color;\n\n        return $this;\n    }\n\n    public function getColor(): ColorInterface\n    {\n        return $this->color;\n    }\n\n    public function getEngine(): EngineInterface\n    {\n        return $this->engine;\n    }\n\n    public function getEngineName(): string\n    {\n        return $this->engine->getName();\n    }\n\n    public function getMoreEngines(): array\n    {\n        return $this->moreEngines;\n    }\n}\n"
  },
  {
    "path": "tests/Support/CarExtensionProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\ServiceProviderInterface;\n\nfinal class CarExtensionProvider implements ServiceProviderInterface\n{\n    public function getDefinitions(): array\n    {\n        return [];\n    }\n\n    public function getExtensions(): array\n    {\n        return [\n            Car::class => static function (ContainerInterface $container, Car $car) {\n                $car->setColor(new ColorRed());\n                return $car;\n            },\n            EngineInterface::class => static fn(ContainerInterface $container, EngineInterface $engine) => $container->get(EngineMarkTwo::class),\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Support/CarFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * Produces cars\n */\nclass CarFactory\n{\n    public static function create(EngineInterface $engine): Car\n    {\n        return new Car($engine);\n    }\n\n    public function createByEngineName(EngineFactory $factory, $name): Car\n    {\n        return new Car($factory->createByName($name));\n    }\n\n    public function createWithColor(ColorInterface $color): Car\n    {\n        $car = new Car(EngineFactory::createDefault());\n\n        return $car->setColor($color);\n    }\n}\n"
  },
  {
    "path": "tests/Support/CarProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\ServiceProviderInterface;\n\nfinal class CarProvider implements ServiceProviderInterface\n{\n    public function getDefinitions(): array\n    {\n        return [\n            'car' => Car::class,\n            EngineInterface::class => EngineMarkOne::class,\n        ];\n    }\n\n    public function getExtensions(): array\n    {\n        return [\n            Car::class => static function (ContainerInterface $container, Car $car) {\n                $car->setColor(new ColorPink());\n                return $car;\n            },\n            'sport_car' => static function (ContainerInterface $container, SportCar $car) {\n                $car->setColor(new ColorPink());\n                return $car;\n            },\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Support/ColorInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * Interface ColorInterface defines car color\n */\ninterface ColorInterface\n{\n    public function getColor(): string;\n}\n"
  },
  {
    "path": "tests/Support/ColorPink.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * Class ColorPink\n */\nfinal class ColorPink implements ColorInterface\n{\n    private const COLOR_PINK = 'pink';\n\n    public function getColor(): string\n    {\n        return self::COLOR_PINK;\n    }\n}\n"
  },
  {
    "path": "tests/Support/ColorRed.php",
    "content": "<?php\n\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * Class ColorRed\n */\nfinal class ColorRed implements ColorInterface\n{\n    private const COLOR_PINK = 'red';\n\n    public function getColor(): string\n    {\n        return self::COLOR_PINK;\n    }\n}\n"
  },
  {
    "path": "tests/Support/ConstructorTestClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse function func_get_args;\n\n/**\n * ConstructorTestClass\n */\nclass ConstructorTestClass\n{\n    private readonly array $allParameters;\n\n    /**\n     * ConstructorTestClass constructor.\n     *\n     * @param $parameter\n     */\n    public function __construct(private $parameter)\n    {\n        $this->allParameters = func_get_args();\n    }\n\n    /**\n     * @return mixed\n     */\n    public function getParameter()\n    {\n        return $this->parameter;\n    }\n\n    public function getAllParameters(): array\n    {\n        return $this->allParameters;\n    }\n}\n"
  },
  {
    "path": "tests/Support/ContainerInterfaceExtensionProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\ServiceProviderInterface;\n\nfinal class ContainerInterfaceExtensionProvider implements ServiceProviderInterface\n{\n    public function getDefinitions(): array\n    {\n        return [];\n    }\n\n    public function getExtensions(): array\n    {\n        return [\n            ContainerInterface::class => static fn(ContainerInterface $container, ContainerInterface $extended) => $container,\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Support/Cycle/Chicken.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support\\Cycle;\n\nclass Chicken\n{\n    public function __construct(Egg $egg) {}\n}\n"
  },
  {
    "path": "tests/Support/Cycle/Egg.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support\\Cycle;\n\nclass Egg\n{\n    public function __construct(Chicken $chicken) {}\n}\n"
  },
  {
    "path": "tests/Support/EngineFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Exception;\nuse Psr\\Container\\ContainerInterface;\n\n/**\n * EngineFactory\n */\nclass EngineFactory\n{\n    public function __construct(private readonly ContainerInterface $container) {}\n\n    public function createByName(?string $name = null): EngineInterface\n    {\n        if ($name === EngineMarkOne::NAME) {\n            return $this->container->get(EngineMarkOne::class);\n        }\n        if ($name === EngineMarkTwo::NAME) {\n            return $this->container->get(EngineMarkTwo::class);\n        }\n\n        throw new Exception('unknown engine name: ' . $name);\n    }\n\n    public static function createDefault(): EngineInterface\n    {\n        return new EngineMarkOne();\n    }\n}\n"
  },
  {
    "path": "tests/Support/EngineInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * EngineInterface defines car engine interface\n */\ninterface EngineInterface\n{\n    public function getName(): string;\n\n    public function setNumber(int $value): void;\n\n    public function getNumber(): int;\n}\n"
  },
  {
    "path": "tests/Support/EngineMarkOne.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * EngineMarkOne\n */\nclass EngineMarkOne implements EngineInterface\n{\n    public const NAME = 'Mark One';\n    public const NUMBER = 1;\n\n    public function __construct(private int $number = self::NUMBER) {}\n\n    public function getName(): string\n    {\n        return static::NAME;\n    }\n\n    public function setNumber(int $value): void\n    {\n        $this->number = $value;\n    }\n\n    public function getNumber(): int\n    {\n        return $this->number;\n    }\n}\n"
  },
  {
    "path": "tests/Support/EngineMarkTwo.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * EngineMarkTwo\n */\nclass EngineMarkTwo implements EngineInterface\n{\n    public const NAME = 'Mark Two';\n    public const NUMBER = 2;\n\n    public function __construct(private int $number = self::NUMBER) {}\n\n    public function getName(): string\n    {\n        return static::NAME;\n    }\n\n    public function setNumber(int $value): void\n    {\n        $this->number = $value;\n    }\n\n    public function getNumber(): int\n    {\n        return $this->number;\n    }\n}\n"
  },
  {
    "path": "tests/Support/EngineStorage.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class EngineStorage\n{\n    private readonly array $engines;\n\n    public function __construct(EngineInterface ...$engines)\n    {\n        $this->engines = $engines;\n    }\n\n    public function getEngines(): array\n    {\n        return $this->engines;\n    }\n}\n"
  },
  {
    "path": "tests/Support/Garage.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * A garage\n */\nfinal class Garage\n{\n    public function __construct(private readonly SportCar $car) {}\n\n    public function getCar(): SportCar\n    {\n        return $this->car;\n    }\n}\n"
  },
  {
    "path": "tests/Support/GearBox.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * A gear box.\n */\nclass GearBox\n{\n    public function __construct(private readonly int $maxGear = 5) {}\n}\n"
  },
  {
    "path": "tests/Support/InvokableCarFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\n\nclass InvokableCarFactory\n{\n    public function __invoke(ContainerInterface $container): Car\n    {\n        /** @var EngineInterface $engine */\n        $engine = $container->get('engine');\n        return new Car($engine);\n    }\n}\n"
  },
  {
    "path": "tests/Support/MethodTestClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * MethodTestClass\n */\nclass MethodTestClass\n{\n    private $value;\n\n    /**\n     * @return mixed\n     */\n    public function getValue()\n    {\n        return $this->value;\n    }\n\n    public function setValue(mixed $value): void\n    {\n        $this->value = $value;\n    }\n}\n"
  },
  {
    "path": "tests/Support/NonPsrContainer.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\nuse stdClass;\n\nfinal class NonPsrContainer implements ContainerInterface\n{\n    public function get(string $id)\n    {\n        return new stdClass();\n    }\n\n    public function has(string $id): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "tests/Support/NullCarExtensionProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\ServiceProviderInterface;\n\nfinal class NullCarExtensionProvider implements ServiceProviderInterface\n{\n    public function getDefinitions(): array\n    {\n        return [\n        ];\n    }\n\n    public function getExtensions(): array\n    {\n        return [\n            Car::class => static fn(ContainerInterface $container, Car $car) => null,\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Support/NullableConcreteDependency.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nclass NullableConcreteDependency\n{\n    public function __construct(?Car $car) {}\n}\n"
  },
  {
    "path": "tests/Support/OptionalConcreteDependency.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nclass OptionalConcreteDependency\n{\n    public function __construct(private readonly ?Car $car = null) {}\n\n    public function getCar(): ?Car\n    {\n        return $this->car;\n    }\n}\n"
  },
  {
    "path": "tests/Support/PropertyTestClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * PropertyTestClass\n */\nclass PropertyTestClass\n{\n    public $property;\n}\n"
  },
  {
    "path": "tests/Support/SportCar.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * A sport car\n */\nclass SportCar\n{\n    public ColorInterface $color;\n\n    public function __construct(\n        private readonly EngineInterface $engine,\n        private readonly int $maxSpeed,\n    ) {}\n\n    public function setColor(ColorInterface $color): self\n    {\n        $this->color = $color;\n\n        return $this;\n    }\n\n    public function getColor(): ColorInterface\n    {\n        return $this->color;\n    }\n\n    public function getEngine(): EngineInterface\n    {\n        return $this->engine;\n    }\n\n    public function getEngineName(): string\n    {\n        return $this->engine->getName();\n    }\n\n    public function getMaxSpeed(): int\n    {\n        return $this->maxSpeed;\n    }\n}\n"
  },
  {
    "path": "tests/Support/StaticFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse stdClass;\n\nfinal class StaticFactory\n{\n    public static function create(): stdClass\n    {\n        return new stdClass();\n    }\n}\n"
  },
  {
    "path": "tests/Support/TreeItem.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * TreeItem\n */\nclass TreeItem\n{\n    public function __construct(private readonly self $treeItem) {}\n}\n"
  },
  {
    "path": "tests/Support/UnionTypeInConstructorFirstTypeInParamResolvable.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class UnionTypeInConstructorFirstTypeInParamResolvable\n{\n    public function __construct(private readonly EngineMarkOne|EngineInterface $engine) {}\n}\n"
  },
  {
    "path": "tests/Support/UnionTypeInConstructorParamNotResolvable.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class UnionTypeInConstructorParamNotResolvable\n{\n    public function __construct(private readonly EngineInterface|ColorInterface $param) {}\n}\n"
  },
  {
    "path": "tests/Support/UnionTypeInConstructorSecondParamNotResolvable.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class UnionTypeInConstructorSecondParamNotResolvable\n{\n    public function __construct(EngineMarkOne|EngineInterface $engine, string $name) {}\n}\n"
  },
  {
    "path": "tests/Support/UnionTypeInConstructorSecondTypeInParamResolvable.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class UnionTypeInConstructorSecondTypeInParamResolvable\n{\n    public function __construct(private readonly EngineInterface|EngineMarkOne $engine) {}\n}\n"
  },
  {
    "path": "tests/Support/VariadicConstructor.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class VariadicConstructor\n{\n    private readonly array $parameters;\n\n    public function __construct(\n        private $first,\n        private readonly EngineInterface $engine,\n        ...$parameters,\n    ) {\n        $this->parameters = $parameters;\n    }\n\n    public function getFirst()\n    {\n        return $this->first;\n    }\n\n    public function getEngine(): EngineInterface\n    {\n        return $this->engine;\n    }\n\n    public function getParameters(): array\n    {\n        return $this->parameters;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/BuildingExceptionTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\nuse RuntimeException;\nuse Yiisoft\\Di\\BuildingException;\n\nfinal class BuildingExceptionTest extends TestCase\n{\n    public function testMessage(): void\n    {\n        $exception = new BuildingException('test', new RuntimeException('i am angry'));\n\n        $this->assertSame('Caught unhandled error \"i am angry\" while building \"test\".', $exception->getMessage());\n        $this->assertSame('Unable to build \"test\" object.', $exception->getName());\n        $this->assertSame(\n            <<<SOLUTION\n            Ensure that either a service with ID \"test\" is defined or such class exists and is autoloadable.\n\n            Ensure that configuration for service with ID \"test\" is correct.\n            SOLUTION,\n            $exception->getSolution(),\n        );\n    }\n\n    public function testEmptyMessage(): void\n    {\n        $exception = new BuildingException('test', new RuntimeException());\n\n        $this->assertSame('Caught unhandled error \"RuntimeException\" while building \"test\".', $exception->getMessage());\n    }\n\n    public function testBuildStack(): void\n    {\n        $exception = new BuildingException('test', new RuntimeException('i am angry'), ['a', 'b', 'test']);\n\n        $this->assertSame('Caught unhandled error \"i am angry\" while building \"a\" -> \"b\" -> \"test\".', $exception->getMessage());\n    }\n\n    public function testCode(): void\n    {\n        $exception = new BuildingException('test', new RuntimeException());\n\n        $this->assertSame(0, $exception->getCode());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/CompositeContainerTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\Attributes\\TestWith;\nuse PHPUnit\\Framework\\TestCase;\nuse Yiisoft\\Di\\CompositeContainer;\nuse Yiisoft\\Di\\CompositeNotFoundException;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkOne;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkTwo;\nuse Yiisoft\\Di\\Tests\\Support\\NonPsrContainer;\nuse Yiisoft\\Test\\Support\\Container\\SimpleContainer;\n\nuse function PHPUnit\\Framework\\assertFalse;\nuse function PHPUnit\\Framework\\assertSame;\n\nfinal class CompositeContainerTest extends TestCase\n{\n    public function testGetNonString(): void\n    {\n        $container = new CompositeContainer();\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessageMatches(\n            '/^ID must be a string, (integer|int) given\\.$/',\n        );\n        $container->get(42);\n    }\n\n    public function testTagsWithYiiAndNotYiiContainers(): void\n    {\n        $compositeContainer = new CompositeContainer();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'tags' => ['engine'],\n                ],\n                EngineMarkTwo::class => [\n                    'class' => EngineMarkTwo::class,\n                    'tags' => ['engine'],\n                ],\n            ]);\n        $firstContainer = new Container($config);\n\n        $secondContainer = new \\League\\Container\\Container();\n\n        $compositeContainer->attach($firstContainer);\n        $compositeContainer->attach($secondContainer);\n\n        $engines = $compositeContainer->get('tag@engine');\n\n        $this->assertIsArray($engines);\n        $this->assertCount(2, $engines);\n        $this->assertInstanceOf(EngineMarkOne::class, $engines[0]);\n        $this->assertInstanceOf(EngineMarkTwo::class, $engines[1]);\n    }\n\n    public function testNonPsrContainer(): void\n    {\n        $compositeContainer = new CompositeContainer();\n\n        $compositeContainer->attach(new NonPsrContainer());\n\n        $this->expectException(CompositeNotFoundException::class);\n        $this->expectExceptionMessageMatches(\n            '/No definition or class found or resolvable in composite container/',\n        );\n        $this->expectExceptionMessageMatches(\n            '/Container \"has\\(\\)\" returned false, but no exception was thrown from \"get\\(\\)\"\\./',\n        );\n        $compositeContainer->get('test');\n    }\n\n    public function testHasNoString(): void\n    {\n        $container = new CompositeContainer();\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('ID must be a string, bool given.');\n        $container->has(true);\n    }\n\n    #[TestWith([true, 'engine'])]\n    #[TestWith([false, 'other'])]\n    public function testHasTag(bool $expected, string $tag): void\n    {\n        $container = new CompositeContainer();\n\n        $container->attach(\n            new Container(\n                ContainerConfig::create()->withTags(['engine' => []]),\n            ),\n        );\n\n        assertSame($expected, $container->has('tag@' . $tag));\n    }\n\n    public function testHasTagWithoutYiiContainer(): void\n    {\n        $container = new CompositeContainer();\n\n        $container->attach(new SimpleContainer());\n\n        assertFalse($container->has('tag@engine'));\n    }\n}\n"
  },
  {
    "path": "tests/Unit/CompositePsrContainerOverLeagueTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse League\\Container\\Container;\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\CompositeContainer;\nuse Yiisoft\\Di\\CompositeNotFoundException;\n\n/**\n * Test the CompositeContainer over League Container.\n */\nfinal class CompositePsrContainerOverLeagueTest extends CompositePsrContainerTestAbstract\n{\n    public function createContainer(iterable $definitions = []): ContainerInterface\n    {\n        $container = $this->setupContainer(new Container(), $definitions);\n        return $this->createCompositeContainer($container);\n    }\n\n    public function setupContainer(ContainerInterface $container, iterable $definitions = []): ContainerInterface\n    {\n        foreach ($definitions as $id => $definition) {\n            $container->add($id, $definition);\n        }\n\n        return $container;\n    }\n\n    public function testNotFoundException(): void\n    {\n        $compositeContainer = new CompositeContainer();\n\n        $container1 = new Container();\n        $container1Id = spl_object_id($container1);\n        $container2 = new Container();\n        $container2Id = spl_object_id($container2);\n\n        $compositeContainer->attach($container1);\n        $compositeContainer->attach($container2);\n\n        $this->expectException(CompositeNotFoundException::class);\n        $this->expectExceptionMessage(\"No definition or class found or resolvable in composite container:\\n    1. Container League\\Container\\Container #$container1Id: Alias (test) is not being managed by the container or delegates\\n    2. Container League\\Container\\Container #$container2Id: Alias (test) is not being managed by the container or delegates\");\n        $compositeContainer->get('test');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/CompositePsrContainerOverYiisoftTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\CompositeContainer;\nuse Yiisoft\\Di\\CompositeNotFoundException;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\nuse Yiisoft\\Di\\StateResetter;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkOne;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkTwo;\n\n/**\n * Test the CompositeContainer over Yiisoft Container.\n */\nfinal class CompositePsrContainerOverYiisoftTest extends CompositePsrContainerTestAbstract\n{\n    public function createContainer(iterable $definitions = []): ContainerInterface\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions($definitions);\n        $container = new Container($config);\n        return $this->createCompositeContainer($container);\n    }\n\n    public function testResetterInCompositeContainerWithExternalResetter(): void\n    {\n        $composite = $this->createContainer([\n            StateResetter::class => function (ContainerInterface $container) {\n                $resetter = new StateResetter($container);\n                $resetter->setResetters([\n                    'engineMarkOne' => function () {\n                        $this->number = 42;\n                    },\n                ]);\n                return $resetter;\n            },\n            'engineMarkOne' => function () {\n                $engine = new EngineMarkOne();\n                $engine->setNumber(42);\n                return $engine;\n            },\n        ]);\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engineMarkTwo' => ['class' => EngineMarkTwo::class,\n                    'setNumber()' => [43],\n                    'reset' => function () {\n                        $this->number = 43;\n                    },\n                ],\n            ]);\n        $secondContainer = new Container($config);\n        $composite->attach($secondContainer);\n\n        $engineMarkOne = $composite->get('engineMarkOne');\n        $engineMarkTwo = $composite->get('engineMarkTwo');\n        $this->assertSame(\n            42,\n            $composite\n                ->get('engineMarkOne')\n                ->getNumber(),\n        );\n        $this->assertSame(\n            43,\n            $composite\n                ->get('engineMarkTwo')\n                ->getNumber(),\n        );\n\n        $engineMarkOne->setNumber(45);\n        $engineMarkTwo->setNumber(46);\n        $this->assertSame(\n            45,\n            $composite\n                ->get('engineMarkOne')\n                ->getNumber(),\n        );\n        $this->assertSame(\n            46,\n            $composite\n                ->get('engineMarkTwo')\n                ->getNumber(),\n        );\n\n        $composite\n            ->get(StateResetter::class)\n            ->reset();\n\n        $this->assertSame($engineMarkOne, $composite->get('engineMarkOne'));\n        $this->assertSame($engineMarkTwo, $composite->get('engineMarkTwo'));\n        $this->assertSame(\n            42,\n            $composite\n                ->get('engineMarkOne')\n                ->getNumber(),\n        );\n        $this->assertSame(\n            43,\n            $composite\n                ->get('engineMarkTwo')\n                ->getNumber(),\n        );\n    }\n\n    public function testNotFoundException(): void\n    {\n        $compositeContainer = new CompositeContainer();\n\n        $container1 = new Container();\n        $container1Id = spl_object_id($container1);\n        $container2 = new Container();\n        $container2Id = spl_object_id($container2);\n\n        $compositeContainer->attach($container1);\n        $compositeContainer->attach($container2);\n\n        $this->expectException(CompositeNotFoundException::class);\n        $this->expectExceptionMessage(\"No definition or class found or resolvable in composite container:\\n    1. Container Yiisoft\\Di\\Container #$container1Id: No definition or class found or resolvable for \\\"test\\\" while building it.\\n    2. Container Yiisoft\\Di\\Container #$container2Id: No definition or class found or resolvable for \\\"test\\\" while building it.\");\n        $compositeContainer->get('test');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/CompositePsrContainerTestAbstract.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse Psr\\Container\\ContainerInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\nuse Yiisoft\\Di\\CompositeContainer;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\nuse Yiisoft\\Di\\Tests\\Support\\Car;\nuse Yiisoft\\Di\\Tests\\Support\\UnionTypeInConstructorParamNotResolvable;\nuse Yiisoft\\Di\\Tests\\Support\\EngineInterface;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkOne;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkTwo;\n\n/**\n * General tests for PSR-11 composite container.\n * To be extended for specific containers.\n */\nabstract class CompositePsrContainerTestAbstract extends PsrContainerTestAbstract\n{\n    public function createCompositeContainer(ContainerInterface $attachedContainer): ContainerInterface\n    {\n        $compositeContainer = new CompositeContainer();\n        $compositeContainer->attach($attachedContainer);\n\n        return $compositeContainer;\n    }\n\n    public function testAttach(): void\n    {\n        $compositeContainer = new CompositeContainer();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'test' => EngineMarkOne::class,\n            ]);\n        $container = new Container($config);\n        $compositeContainer->attach($container);\n        $this->assertTrue($compositeContainer->has('test'));\n        $this->assertInstanceOf(EngineMarkOne::class, $compositeContainer->get('test'));\n    }\n\n    public function testDetach(): void\n    {\n        $compositeContainer = new CompositeContainer();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'test' => EngineMarkOne::class,\n            ]);\n        $container = new Container($config);\n        $compositeContainer->attach($container);\n        $this->assertInstanceOf(EngineMarkOne::class, $compositeContainer->get('test'));\n\n        $compositeContainer->detach($container);\n        $this->expectException(NotFoundExceptionInterface::class);\n        $this->assertInstanceOf(EngineMarkOne::class, $compositeContainer->get('test'));\n    }\n\n    public function testHasDefinition(): void\n    {\n        $compositeContainer = $this->createContainer([EngineInterface::class => EngineMarkOne::class]);\n        $this->assertTrue($compositeContainer->has(EngineInterface::class));\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'test' => EngineMarkTwo::class,\n            ]);\n        $container = new Container($config);\n        $compositeContainer->attach($container);\n        $this->assertTrue($compositeContainer->has('test'));\n    }\n\n    public function testGetPriority(): void\n    {\n        $compositeContainer = $this->createContainer([EngineInterface::class => EngineMarkOne::class]);\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkTwo::class,\n            ]);\n        $container = new Container($config);\n        $compositeContainer->attach($container);\n        $this->assertInstanceOf(EngineMarkOne::class, $compositeContainer->get(EngineInterface::class));\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([EngineInterface::class => EngineMarkOne::class]);\n        $containerOne = new Container($config);\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([EngineInterface::class => EngineMarkTwo::class]);\n        $containerTwo = new Container($config);\n\n        $compositeContainer = new CompositeContainer();\n        $compositeContainer->attach($containerOne);\n        $compositeContainer->attach($containerTwo);\n        $this->assertInstanceOf(EngineMarkOne::class, $compositeContainer->get(EngineInterface::class));\n    }\n\n    public function testTags(): void\n    {\n        $compositeContainer = new CompositeContainer();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'tags' => ['engine'],\n                ],\n            ]);\n        $firstContainer = new Container($config);\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkTwo::class => [\n                    'class' => EngineMarkTwo::class,\n                    'tags' => ['engine'],\n                ],\n            ]);\n        $secondContainer = new Container($config);\n\n        $compositeContainer->attach($firstContainer);\n        $compositeContainer->attach($secondContainer);\n\n        $engines = $compositeContainer->get('tag@engine');\n\n        $this->assertIsArray($engines);\n        $this->assertCount(2, $engines);\n        $this->assertSame(EngineMarkOne::class, $engines[1]::class);\n        $this->assertSame(EngineMarkTwo::class, $engines[0]::class);\n    }\n\n    public function testDelegateLookup(): void\n    {\n        $compositeContainer = new CompositeContainer();\n        $firstContainer = new Container();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n            ]);\n        $secondContainer = new Container($config);\n\n        $compositeContainer->attach($firstContainer);\n        $compositeContainer->attach($secondContainer);\n\n        $car = $compositeContainer->get(Car::class);\n\n        $this->assertInstanceOf(Car::class, $car);\n    }\n\n    public function testDelegateLookupUnionTypes(): void\n    {\n        $compositeContainer = new CompositeContainer();\n        $firstContainer = new Container();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n            ]);\n        $secondContainer = new Container($config);\n\n        $compositeContainer->attach($firstContainer);\n        $compositeContainer->attach($secondContainer);\n\n        $car = $compositeContainer->get(UnionTypeInConstructorParamNotResolvable::class);\n\n        $this->assertInstanceOf(UnionTypeInConstructorParamNotResolvable::class, $car);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Container/DependencyFromDelegate/Car.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Container\\DependencyFromDelegate;\n\nfinal class Car\n{\n    public function __construct(\n        public readonly EngineInterface $engine,\n    ) {}\n}\n"
  },
  {
    "path": "tests/Unit/Container/DependencyFromDelegate/DependencyFromDelegateTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Container\\DependencyFromDelegate;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Container\\ContainerInterface;\nuse Throwable;\nuse Yiisoft\\Di\\BuildingException;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\nuse Yiisoft\\Di\\NotFoundException;\nuse Yiisoft\\Test\\Support\\Container\\SimpleContainer;\n\nuse function PHPUnit\\Framework\\assertInstanceOf;\nuse function PHPUnit\\Framework\\assertSame;\nuse function sprintf;\n\nfinal class DependencyFromDelegateTest extends TestCase\n{\n    public function testAnotherContainer(): void\n    {\n        $container = new Container(\n            ContainerConfig::create()\n                ->withDefinitions([\n                    ContainerInterface::class => new SimpleContainer(),\n                    Car::class => Car::class,\n                ])\n                ->withDelegates([\n                    static fn() => new SimpleContainer([\n                        Car::class => new Car(new Engine()),\n                    ]),\n                ]),\n        );\n\n        $car = $container->get(Car::class);\n\n        assertInstanceOf(Car::class, $car);\n        assertInstanceOf(Engine::class, $car->engine);\n    }\n\n    public function testNotFoundInDelegate(): void\n    {\n        $container = new Container(\n            ContainerConfig::create()\n                ->withDefinitions([\n                    ContainerInterface::class => new SimpleContainer(),\n                    'car' => Car::class,\n                ])\n                ->withDelegates([\n                    static fn() => new Container(\n                        ContainerConfig::create()\n                            ->withDefinitions([\n                                'car' => Car::class,\n                            ]),\n                    ),\n                ]),\n        );\n\n        $exception = null;\n        try {\n            $container->get('car');\n        } catch (Throwable $exception) {\n        }\n\n        assertInstanceOf(BuildingException::class, $exception);\n        assertSame(\n            sprintf(\n                'Caught unhandled error \"No definition or class found or resolvable for \"%2$s\" while building \"%1$s\" -> \"%3$s\" -> \"%2$s\".\" while building \"%1$s\".',\n                'car',\n                EngineInterface::class,\n                Car::class,\n            ),\n            $exception->getMessage(),\n        );\n\n        $previous = $exception->getPrevious();\n        assertInstanceOf(NotFoundException::class, $previous);\n        $this->assertSame(\n            sprintf(\n                'No definition or class found or resolvable for \"%2$s\" while building \"%1$s\" -> \"%3$s\" -> \"%2$s\".',\n                'car',\n                EngineInterface::class,\n                Car::class,\n            ),\n            $previous->getMessage(),\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Container/DependencyFromDelegate/Engine.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Container\\DependencyFromDelegate;\n\nfinal class Engine implements EngineInterface {}\n"
  },
  {
    "path": "tests/Unit/Container/DependencyFromDelegate/EngineInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Container\\DependencyFromDelegate;\n\ninterface EngineInterface {}\n"
  },
  {
    "path": "tests/Unit/ContainerTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse ArrayIterator;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Container\\ContainerInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\nuse RuntimeException;\nuse stdClass;\nuse Throwable;\nuse Yiisoft\\Di\\BuildingException;\nuse Yiisoft\\Di\\CompositeContainer;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\nuse Yiisoft\\Di\\ExtensibleService;\nuse Yiisoft\\Di\\NotFoundException;\nuse Yiisoft\\Di\\StateResetter;\nuse Yiisoft\\Di\\ServiceProviderInterface;\nuse Yiisoft\\Di\\Tests\\Support\\A;\nuse Yiisoft\\Di\\Tests\\Support\\B;\nuse Yiisoft\\Di\\Tests\\Support\\Car;\nuse Yiisoft\\Di\\Tests\\Support\\CarFactory;\nuse Yiisoft\\Di\\Tests\\Support\\ColorInterface;\nuse Yiisoft\\Di\\Tests\\Support\\ColorPink;\nuse Yiisoft\\Di\\Tests\\Support\\ColorRed;\nuse Yiisoft\\Di\\Tests\\Support\\ConstructorTestClass;\nuse Yiisoft\\Di\\Tests\\Support\\Cycle\\Chicken;\nuse Yiisoft\\Di\\Tests\\Support\\Cycle\\Egg;\nuse Yiisoft\\Di\\Tests\\Support\\EngineFactory;\nuse Yiisoft\\Di\\Tests\\Support\\EngineInterface;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkOne;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkTwo;\nuse Yiisoft\\Di\\Tests\\Support\\EngineStorage;\nuse Yiisoft\\Di\\Tests\\Support\\Garage;\nuse Yiisoft\\Di\\Tests\\Support\\InvokableCarFactory;\nuse Yiisoft\\Di\\Tests\\Support\\MethodTestClass;\nuse Yiisoft\\Di\\Tests\\Support\\NullableConcreteDependency;\nuse Yiisoft\\Di\\Tests\\Support\\OptionalConcreteDependency;\nuse Yiisoft\\Di\\Tests\\Support\\PropertyTestClass;\nuse Yiisoft\\Di\\Tests\\Support\\SportCar;\nuse Yiisoft\\Di\\Tests\\Support\\TreeItem;\nuse Yiisoft\\Di\\Tests\\Support\\UnionTypeInConstructorSecondTypeInParamResolvable;\nuse Yiisoft\\Di\\Tests\\Support\\UnionTypeInConstructorSecondParamNotResolvable;\nuse Yiisoft\\Di\\Tests\\Support\\UnionTypeInConstructorParamNotResolvable;\nuse Yiisoft\\Di\\Tests\\Support\\UnionTypeInConstructorFirstTypeInParamResolvable;\nuse Yiisoft\\Di\\Tests\\Support\\VariadicConstructor;\nuse Yiisoft\\Definitions\\DynamicReference;\nuse Yiisoft\\Definitions\\Exception\\CircularReferenceException;\nuse Yiisoft\\Definitions\\Exception\\InvalidConfigException;\nuse Yiisoft\\Definitions\\Reference;\nuse Yiisoft\\Injector\\Injector;\nuse Yiisoft\\Test\\Support\\Container\\SimpleContainer;\n\nuse function PHPUnit\\Framework\\assertInstanceOf;\nuse function PHPUnit\\Framework\\assertSame;\n\n/**\n * ContainerTest contains tests for \\Yiisoft\\Di\\Container\n */\nfinal class ContainerTest extends TestCase\n{\n    public function testCanCreateWihtoutConfig(): void\n    {\n        $this->expectNotToPerformAssertions();\n\n        new Container();\n    }\n\n    public function testSettingScalars(): void\n    {\n        $this->expectException(InvalidConfigException::class);\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'scalar' => 123,\n            ]);\n        $container = new Container($config);\n\n        $container->get('scalar');\n    }\n\n    public function testIntegerKeys(): void\n    {\n        $this->expectException(InvalidConfigException::class);\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class,\n                EngineMarkTwo::class,\n            ]);\n        $container = new Container($config);\n\n        $container->get(Car::class);\n    }\n\n    public function testNullableClassDependency(): void\n    {\n        $container = new Container();\n\n        $this->expectException(NotFoundException::class);\n        $container->get(NullableConcreteDependency::class);\n    }\n\n    public function testOptionalResolvableClassDependency(): void\n    {\n        $container = new Container(\n            ContainerConfig::create()\n                ->withDefinitions([\n                    EngineInterface::class => EngineMarkOne::class,\n                ]),\n        );\n\n        $this->assertTrue($container->has(OptionalConcreteDependency::class));\n        $service = $container->get(OptionalConcreteDependency::class);\n        $this->assertInstanceOf(Car::class, $service->getCar());\n    }\n\n    public function testOptionalNotResolvableClassDependency(): void\n    {\n        $container = new Container();\n\n        $this->assertTrue($container->has(OptionalConcreteDependency::class));\n        $service = $container->get(OptionalConcreteDependency::class);\n        $this->assertNull($service->getCar());\n    }\n\n    public function testOptionalCircularClassDependency(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                A::class => A::class,\n                B::class => B::class,\n            ]);\n        $container = new Container($config);\n        $a = $container->get(A::class);\n        $this->assertInstanceOf(B::class, $a->b);\n        $this->assertNull($a->b->a);\n    }\n\n    public static function dataHas(): array\n    {\n        return [\n            [false, 'non_existing'],\n            [false, ColorInterface::class],\n            [true, Car::class],\n            [true, EngineMarkOne::class],\n            [true, EngineInterface::class],\n            [true, EngineStorage::class],\n            [true, Chicken::class],\n            [true, TreeItem::class],\n        ];\n    }\n\n    #[DataProvider('dataHas')]\n    public function testHas(bool $expected, $id): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n            ]);\n        $container = new Container($config);\n\n        $this->assertSame($expected, $container->has($id));\n    }\n\n    public static function dataUnionTypes(): array\n    {\n        return [\n            [UnionTypeInConstructorSecondTypeInParamResolvable::class],\n            [UnionTypeInConstructorFirstTypeInParamResolvable::class],\n        ];\n    }\n\n    #[DataProvider('dataUnionTypes')]\n    public function testUnionTypes(string $class): void\n    {\n        $container = new Container();\n\n        $this->assertTrue($container->has($class));\n    }\n\n    public function testClassExistsButIsNotResolvable(): void\n    {\n        $container = new Container();\n\n        $this->assertFalse($container->has('non_existing'));\n        $this->assertFalse($container->has(Car::class));\n        $this->assertFalse($container->has(SportCar::class));\n        $this->assertFalse($container->has(NullableConcreteDependency::class));\n        $this->assertFalse($container->has(ColorInterface::class));\n    }\n\n    public static function dataClassExistButIsNotResolvableWithUnionTypes(): array\n    {\n        return [\n            [UnionTypeInConstructorParamNotResolvable::class],\n            [UnionTypeInConstructorSecondParamNotResolvable::class],\n        ];\n    }\n\n    #[DataProvider('dataClassExistButIsNotResolvableWithUnionTypes')]\n    public function testClassExistButIsNotResolvableWithUnionTypes(string $class): void\n    {\n        $container = new Container();\n\n        $this->assertFalse($container->has($class));\n    }\n\n    public function testWithoutDefinition(): void\n    {\n        $container = new Container();\n\n        $hasEngine = $container->has(EngineMarkOne::class);\n        $this->assertTrue($hasEngine);\n\n        $engine = $container->get(EngineMarkOne::class);\n        $this->assertInstanceOf(EngineMarkOne::class, $engine);\n    }\n\n    public function testCircularClassDependencyWithoutDefinition(): void\n    {\n        $container = new Container();\n        $this->expectException(CircularReferenceException::class);\n        $container->get(Chicken::class);\n    }\n\n    public function testTrivialDefinition(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => EngineMarkOne::class,\n            ]);\n        $container = new Container($config);\n\n        $one = $container->get(EngineMarkOne::class);\n        $two = $container->get(EngineMarkOne::class);\n        $this->assertInstanceOf(EngineMarkOne::class, $one);\n        $this->assertSame($one, $two);\n    }\n\n    public function testCircularClassDependency(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                Chicken::class => Chicken::class,\n                Egg::class => Egg::class,\n            ]);\n        $container = new Container($config);\n\n        $this->expectException(CircularReferenceException::class);\n        $container->get(Chicken::class);\n    }\n\n    public function testClassSimple(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engine' => EngineMarkOne::class,\n            ]);\n        $container = new Container($config);\n        $this->assertInstanceOf(EngineMarkOne::class, $container->get('engine'));\n    }\n\n    public function testSetAll(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engine1' => EngineMarkOne::class,\n                'engine2' => EngineMarkTwo::class,\n            ]);\n        $container = new Container($config);\n\n        $this->assertInstanceOf(EngineMarkOne::class, $container->get('engine1'));\n        $this->assertInstanceOf(EngineMarkTwo::class, $container->get('engine2'));\n    }\n\n    public function testClassConstructor(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'constructor_test' => [\n                    'class' => ConstructorTestClass::class,\n                    '__construct()' => [42],\n                ],\n            ]);\n        $container = new Container($config);\n\n        /** @var ConstructorTestClass $object */\n        $object = $container->get('constructor_test');\n        $this->assertSame(42, $object->getParameter());\n    }\n\n    // See https://github.com/yiisoft/di/issues/157#issuecomment-701458616\n    public function testIntegerIndexedConstructorArguments(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'items' => [\n                    'class' => ArrayIterator::class,\n                    '__construct()' => [\n                        [],\n                        ArrayIterator::STD_PROP_LIST,\n                    ],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $items = $container->get('items');\n\n        $this->assertInstanceOf(ArrayIterator::class, $items);\n        $this->assertSame(ArrayIterator::STD_PROP_LIST, $items->getFlags());\n    }\n\n    public function testExcessiveConstructorParametersIgnored(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'constructor_test' => [\n                    'class' => ConstructorTestClass::class,\n                    '__construct()' => [\n                        'parameter' => 42,\n                        'surplus1' => 43,\n                    ],\n                ],\n            ]);\n        $container = new Container($config);\n\n        /** @var ConstructorTestClass $object */\n        $object = $container->get('constructor_test');\n        $this->assertSame([42], $object->getAllParameters());\n    }\n\n    public function testVariadicConstructorParameters(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                'stringIndexed' => [\n                    'class' => VariadicConstructor::class,\n                    '__construct()' => [\n                        'first' => 1,\n                        'parameters' => [42, 43, 44],\n                    ],\n                ],\n                'integerIndexed' => [\n                    'class' => VariadicConstructor::class,\n                    '__construct()' => [1, new EngineMarkOne(), 42, 43, 44],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $object = $container->get('stringIndexed');\n        $this->assertSame(1, $object->getFirst());\n        $this->assertSame([42, 43, 44], $object->getParameters());\n        $this->assertInstanceOf(EngineMarkOne::class, $object->getEngine());\n\n        $object = $container->get('integerIndexed');\n        $this->assertSame(1, $object->getFirst());\n        $this->assertInstanceOf(EngineMarkOne::class, $object->getEngine());\n        $this->assertSame([42, 43, 44], $object->getParameters());\n    }\n\n    public function testMixedIndexedConstructorParametersAreNotAllowed(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'test' => [\n                    'class' => VariadicConstructor::class,\n                    '__construct()' => [\n                        'parameters' => 42,\n                        43,\n                    ],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $this->expectException(BuildingException::class);\n        $this->expectExceptionMessage(\n            'Caught unhandled error \"Arguments indexed both by name and by position are not allowed in the same array.\" while building \"test\".',\n        );\n        $container->get('test');\n    }\n\n    public function testClassProperties(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'property_test' => [\n                    'class' => PropertyTestClass::class,\n                    '$property' => 42,\n                ],\n            ]);\n        $container = new Container($config);\n\n        /** @var PropertyTestClass $object */\n        $object = $container->get('property_test');\n        $this->assertSame(42, $object->property);\n    }\n\n    public function testClassMethods(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'method_test' => [\n                    'class' => MethodTestClass::class,\n                    'setValue()' => [42],\n                ],\n            ]);\n        $container = new Container($config);\n\n        /** @var MethodTestClass $object */\n        $object = $container->get('method_test');\n        $this->assertSame(42, $object->getValue());\n    }\n\n    public function testClosureInConstructor(): void\n    {\n        $color = static fn() => new ColorPink();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                ConstructorTestClass::class => [\n                    'class' => ConstructorTestClass::class,\n                    '__construct()' => [$color],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $testClass = $container->get(ConstructorTestClass::class);\n        $this->assertSame($color, $testClass->getParameter());\n    }\n\n    public function testDynamicClosureInConstruct(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'car' => [\n                    'class' => Car::class,\n                    '__construct()' => [\n                        DynamicReference::to(static fn(EngineInterface $engine) => $engine),\n                    ],\n                ],\n                EngineInterface::class => EngineMarkTwo::class,\n            ]);\n        $container = new Container($config);\n\n        $car = $container->get('car');\n        $engine = $container->get(EngineInterface::class);\n        $this->assertSame($engine, $car->getEngine());\n    }\n\n    public function testKeepClosureDefinition(): void\n    {\n        $engine = new EngineMarkOne();\n        $closure = static fn(EngineInterface $engine) => $engine;\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => $engine,\n                'closure' => DynamicReference::to($closure),\n                'engine' => $closure,\n            ]);\n        $container = new Container($config);\n\n        $closure = $container->get('closure');\n        $this->assertSame($closure, $container->get('closure'));\n        $this->assertSame($engine, $container->get('engine'));\n    }\n\n    public function testClosureInProperty(): void\n    {\n        $color = static fn() => new ColorPink();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                PropertyTestClass::class => [\n                    'class' => PropertyTestClass::class,\n                    '$property' => $color,\n                ],\n            ]);\n        $container = new Container($config);\n\n        $testClass = $container->get(PropertyTestClass::class);\n        $this->assertSame($color, $testClass->property);\n    }\n\n    public function testDynamicClosureInProperty(): void\n    {\n        $color = new ColorPink();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                ColorInterface::class => $color,\n                'car' => [\n                    'class' => Car::class,\n                    '$color' => DynamicReference::to(fn() => $color),\n                ],\n            ]);\n        $container = new Container($config);\n\n        $car = $container->get('car');\n        $this->assertSame($color, $car->getColor());\n    }\n\n    public function testClosureInMethodCall(): void\n    {\n        $color = static fn() => new ColorPink();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                MethodTestClass::class => [\n                    'class' => MethodTestClass::class,\n                    'setValue()' => [$color],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $testClass = $container->get(MethodTestClass::class);\n        $this->assertSame($color, $testClass->getValue());\n    }\n\n    public function testDynamicClosureInMethodCall(): void\n    {\n        $color = new ColorPink();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                ColorInterface::class => $color,\n                'car' => [\n                    'class' => Car::class,\n                    'setColor()' => [DynamicReference::to(fn() => $color)],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $car = $container->get('car');\n        $this->assertSame($color, $car->getColor());\n    }\n\n    public function testAlias(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => Reference::to('engine'),\n                'engine' => Reference::to('engine-mark-one'),\n                'engine-mark-one' => EngineMarkOne::class,\n            ]);\n        $container = new Container($config);\n\n        $engine1 = $container->get('engine-mark-one');\n        $engine2 = $container->get('engine');\n        $engine3 = $container->get(EngineInterface::class);\n        $this->assertInstanceOf(EngineMarkOne::class, $engine1);\n        $this->assertSame($engine1, $engine2);\n        $this->assertSame($engine2, $engine3);\n    }\n\n    public function testCircularAlias(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engine-1' => Reference::to('engine-2'),\n                'engine-2' => Reference::to('engine-3'),\n                'engine-3' => Reference::to('engine-1'),\n            ]);\n        $container = new Container($config);\n\n        $this->expectException(CircularReferenceException::class);\n        $container->get('engine-1');\n    }\n\n    public function testUndefinedDependencies(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'car' => Car::class,\n            ]);\n        $container = new Container($config);\n\n        $this->expectException(NotFoundException::class);\n        $container->get('car');\n    }\n\n    public function testDependencies(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'car' => Car::class,\n                EngineInterface::class => EngineMarkTwo::class,\n            ]);\n        $container = new Container($config);\n\n        /** @var Car $car */\n        $car = $container->get('car');\n        $this->assertEquals(EngineMarkTwo::NAME, $car->getEngineName());\n    }\n\n    public function testCircularReference(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                TreeItem::class => TreeItem::class,\n            ]);\n        $container = new Container($config);\n\n        $this->expectException(CircularReferenceException::class);\n        $container->get(TreeItem::class);\n    }\n\n    /**\n     * @link https://github.com/yiisoft/di/pull/189\n     */\n    public function testFalsePositiveCircularReferenceWithClassID(): void\n    {\n        $this->expectNotToPerformAssertions();\n\n        $container = new Container();\n\n        // Build an object\n        $container->get(ColorPink::class);\n\n        // set definition to container\n        (fn(string $id, $definition) => $this->addDefinition($id, $definition))->call(\n            $container,\n            ColorPink::class,\n            ColorPink::class,\n        );\n\n        try {\n            // Build an object\n            $container->get(ColorPink::class);\n        } catch (CircularReferenceException) {\n            $this->fail('Circular reference detected false positively.');\n        }\n    }\n\n    /**\n     * @link https://github.com/yiisoft/di/pull/189\n     */\n    public function testFalsePositiveCircularReferenceWithStringID(): void\n    {\n        $this->expectNotToPerformAssertions();\n\n        $container = new Container();\n        try {\n            // Build an object\n            $container->get('test');\n        } catch (NotFoundException) {\n            // It is expected\n        }\n\n        // set definition to container\n        (fn(string $id, $definition) => $this->addDefinition($id, $definition))->call(\n            $container,\n            'test',\n            ColorPink::class,\n        );\n\n        try {\n            // Build an object\n            $container->get('test');\n        } catch (CircularReferenceException) {\n            $this->fail('Circular reference detected false positively.');\n        }\n    }\n\n    public function testCallable(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                'test' => fn(ContainerInterface $container) => $container->get(EngineInterface::class),\n            ]);\n        $container = new Container($config);\n\n        $object = $container->get('test');\n        $this->assertInstanceOf(EngineMarkOne::class, $object);\n    }\n\n    public function testCallableWithInjector(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                'car' => fn(CarFactory $factory, Injector $injector) => $injector->invoke($factory->create(...)),\n            ]);\n        $container = new Container($config);\n\n        $engine = $container->get(EngineInterface::class);\n        $car = $container->get('car');\n        $this->assertInstanceOf(Car::class, $car);\n        $this->assertSame($engine, $car->getEngine());\n    }\n\n    public function testCallableWithArgs(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engine1' => fn(EngineFactory $factory) => $factory->createByName(EngineMarkOne::NAME),\n                'engine2' => fn(EngineFactory $factory) => $factory->createByName(EngineMarkTwo::NAME),\n            ]);\n        $container = new Container($config);\n        $engine1 = $container->get('engine1');\n        $this->assertInstanceOf(EngineMarkOne::class, $engine1);\n        $this->assertSame(EngineMarkOne::NUMBER, $engine1->getNumber());\n        $engine2 = $container->get('engine2');\n        $this->assertInstanceOf(EngineMarkTwo::class, $engine2);\n        $this->assertSame(EngineMarkTwo::NUMBER, $engine2->getNumber());\n    }\n\n    public function testCallableWithDependencies(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'car1' => fn(CarFactory $carFactory, EngineFactory $engineFactory) => $carFactory->createByEngineName(\n                    $engineFactory,\n                    EngineMarkOne::NAME,\n                ),\n                'car2' => fn(CarFactory $carFactory, EngineFactory $engineFactory) => $carFactory->createByEngineName(\n                    $engineFactory,\n                    EngineMarkTwo::NAME,\n                ),\n            ]);\n        $container = new Container($config);\n        $car1 = $container->get('car1');\n        $this->assertInstanceOf(Car::class, $car1);\n        $this->assertInstanceOf(EngineMarkOne::class, $car1->getEngine());\n        $car2 = $container->get('car2');\n        $this->assertInstanceOf(Car::class, $car2);\n        $this->assertInstanceOf(EngineMarkTwo::class, $car2->getEngine());\n    }\n\n    public function testObject(): void\n    {\n        $engine = new EngineMarkOne();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engine' => $engine,\n            ]);\n        $container = new Container($config);\n\n        $object = $container->get('engine');\n        $this->assertSame($engine, $object);\n    }\n\n    public function testArrayStaticCall(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                'car' => CarFactory::create(...),\n            ]);\n        $container = new Container($config);\n\n        $car = $container->get('car');\n        $this->assertInstanceOf(Car::class, $car);\n        $this->assertInstanceOf(EngineMarkOne::class, $car->getEngine());\n    }\n\n    public function testArrayDynamicCall(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                ColorInterface::class => ColorPink::class,\n                'car' => [CarFactory::class, 'createWithColor'],\n            ]);\n        $container = new Container($config);\n\n        $car = $container->get('car');\n        $this->assertInstanceOf(Car::class, $car);\n        $this->assertInstanceOf(ColorPink::class, $car->getColor());\n    }\n\n    public function testArrayDynamicCallWithObject(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                ColorInterface::class => ColorPink::class,\n                'car' => [new CarFactory(), 'createWithColor'],\n            ]);\n        $container = new Container($config);\n\n        $car = $container->get('car');\n        $this->assertInstanceOf(Car::class, $car);\n        $this->assertInstanceOf(ColorPink::class, $car->getColor());\n    }\n\n    public function testInvokeable(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engine' => EngineMarkOne::class,\n                'invokeable' => new InvokableCarFactory(),\n            ]);\n        $container = new Container($config);\n\n        $object = $container->get('invokeable');\n        $this->assertInstanceOf(Car::class, $object);\n    }\n\n    public function testReference(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engine' => EngineMarkOne::class,\n                'color' => ColorPink::class,\n                'car' => [\n                    'class' => Car::class,\n                    '__construct()' => [\n                        Reference::to('engine'),\n                    ],\n                    '$color' => Reference::to('color'),\n                ],\n            ]);\n        $container = new Container($config);\n        $object = $container->get('car');\n        $this->assertInstanceOf(Car::class, $object);\n        $this->assertInstanceOf(ColorPink::class, $object->color);\n    }\n\n    public function testReferencesInArrayInDependencies(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engine1' => EngineMarkOne::class,\n                'engine2' => EngineMarkTwo::class,\n                'engine3' => EngineMarkTwo::class,\n                'engine4' => EngineMarkTwo::class,\n                'car' => [\n                    'class' => Car::class,\n                    '__construct()' => [\n                        Reference::to('engine1'),\n                        [\n                            'engine2' => Reference::to('engine2'),\n                            'more' => [\n                                'engine3' => Reference::to('engine3'),\n                                'more' => [\n                                    'engine4' => Reference::to('engine4'),\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ]);\n        $container = new Container($config);\n        $car = $container->get('car');\n        $this->assertInstanceOf(Car::class, $car);\n        $moreEngines = $car->getMoreEngines();\n        $this->assertSame($container->get('engine2'), $moreEngines['engine2']);\n        $this->assertSame($container->get('engine3'), $moreEngines['more']['engine3']);\n        $this->assertSame($container->get('engine4'), $moreEngines['more']['more']['engine4']);\n    }\n\n    public function testReferencesInProperties(): void\n    {\n        $color = new ColorPink();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                ColorInterface::class => $color,\n                'car' => [\n                    'class' => Car::class,\n                    '$color' => Reference::to(ColorInterface::class),\n                ],\n            ]);\n\n        $container = new Container($config);\n        $car = $container->get('car');\n        $this->assertInstanceOf(Car::class, $car);\n        $this->assertSame($color, $car->getColor());\n    }\n\n    public function testReferencesInMethodCall(): void\n    {\n        $color = new ColorPink();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                ColorInterface::class => $color,\n                'car' => [\n                    'class' => Car::class,\n                    'setColor()' => [Reference::to(ColorInterface::class)],\n                ],\n            ]);\n        $container = new Container($config);\n        $car = $container->get('car');\n        $this->assertInstanceOf(Car::class, $car);\n        $this->assertSame($color, $car->getColor());\n    }\n\n    public function testCallableArrayValueInConstructor(): void\n    {\n        $array = [\n            [EngineMarkTwo::class, 'getNumber'],\n        ];\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                Car::class => [\n                    'class' => Car::class,\n                    '__construct()' => [\n                        Reference::to(EngineInterface::class),\n                        $array,\n                    ],\n                ],\n            ]);\n        $container = new Container($config);\n\n        /** @var Car $object */\n        $object = $container->get(Car::class);\n        $this->assertSame($array, $object->getMoreEngines());\n    }\n\n    public function testSameInstance(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engine' => EngineMarkOne::class,\n            ]);\n        $container = new Container($config);\n\n        $one = $container->get('engine');\n        $two = $container->get('engine');\n        $this->assertSame($one, $two);\n    }\n\n    public function testGetByClassIndirectly(): void\n    {\n        $number = 42;\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                EngineMarkOne::class => [\n                    'setNumber()' => [$number],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $engine = $container->get(EngineInterface::class);\n        $this->assertInstanceOf(EngineMarkOne::class, $engine);\n        $this->assertSame($number, $engine->getNumber());\n    }\n\n    public function testThrowingNotFoundException(): void\n    {\n        $this->expectException(NotFoundException::class);\n\n        $container = new Container();\n        $container->get('non_existing');\n    }\n\n    public function testContainerInContainer(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'container' => static fn(ContainerInterface $container) => $container,\n            ]);\n        $container = new Container($config);\n\n        $this->assertSame($container, $container->get('container'));\n        $this->assertSame($container, $container->get(ContainerInterface::class));\n    }\n\n    public function testTagsInArrayDefinition(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'tags' => ['engine'],\n                ],\n                EngineMarkTwo::class => [\n                    'class' => EngineMarkTwo::class,\n                    'tags' => ['engine'],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $engines = $container->get('tag@engine');\n\n        $this->assertIsArray($engines);\n        $this->assertSame(EngineMarkOne::class, $engines[0]::class);\n        $this->assertSame(EngineMarkTwo::class, $engines[1]::class);\n    }\n\n    public function testTagsInClosureDefinition(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'definition' => fn() => new EngineMarkOne(),\n                    'tags' => ['engine'],\n                ],\n                EngineMarkTwo::class => [\n                    'definition' => fn() => new EngineMarkTwo(),\n                    'tags' => ['engine'],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $engines = $container->get('tag@engine');\n\n        $this->assertIsArray($engines);\n        $this->assertSame(EngineMarkOne::class, $engines[0]::class);\n        $this->assertSame(EngineMarkTwo::class, $engines[1]::class);\n    }\n\n    public function testTagsMultiple(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'tags' => ['engine', 'mark_one'],\n                ],\n                EngineMarkTwo::class => [\n                    'class' => EngineMarkTwo::class,\n                    'tags' => ['engine'],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $engines = $container->get('tag@engine');\n        $markOneEngines = $container->get('tag@mark_one');\n\n        $this->assertIsArray($engines);\n        $this->assertSame(EngineMarkOne::class, $engines[0]::class);\n        $this->assertSame(EngineMarkTwo::class, $engines[1]::class);\n        $this->assertIsArray($markOneEngines);\n        $this->assertSame(EngineMarkOne::class, $markOneEngines[0]::class);\n        $this->assertCount(1, $markOneEngines);\n    }\n\n    public function testTagsEmpty(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                ],\n                EngineMarkTwo::class => [\n                    'class' => EngineMarkTwo::class,\n                ],\n            ]);\n        $container = new Container($config);\n\n        $engines = $container->get('tag@engine');\n\n        $this->assertIsArray($engines);\n        $this->assertCount(0, $engines);\n    }\n\n    public function testTagsWithExternalDefinition(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'tags' => ['engine'],\n                ],\n                EngineMarkTwo::class => [\n                    'class' => EngineMarkTwo::class,\n                ],\n            ])\n            ->withTags(['engine' => [EngineMarkTwo::class]]);\n        $container = new Container($config);\n\n        $engines = $container->get('tag@engine');\n\n        $this->assertIsArray($engines);\n        $this->assertCount(2, $engines);\n        $this->assertSame(EngineMarkOne::class, $engines[1]::class);\n        $this->assertSame(EngineMarkTwo::class, $engines[0]::class);\n    }\n\n    public function testTagsWithExternalDefinitionMerge(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'tags' => ['engine'],\n                ],\n                EngineMarkTwo::class => [\n                    'class' => EngineMarkTwo::class,\n                    'tags' => ['engine'],\n                ],\n            ])\n            ->withTags(['mark_two' => [EngineMarkTwo::class]]);\n        $container = new Container($config);\n\n        $engines = $container->get('tag@engine');\n        $markTwoEngines = $container->get('tag@mark_two');\n\n        $this->assertIsArray($engines);\n        $this->assertCount(2, $engines);\n        $this->assertSame(EngineMarkOne::class, $engines[0]::class);\n        $this->assertSame(EngineMarkTwo::class, $engines[1]::class);\n        $this->assertIsArray($markTwoEngines);\n        $this->assertCount(1, $markTwoEngines);\n        $this->assertSame(EngineMarkTwo::class, $markTwoEngines[0]::class);\n    }\n\n    public function testTagsAsArrayInConstructor(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'tags' => ['engine'],\n                ],\n                EngineMarkTwo::class => [\n                    'class' => EngineMarkTwo::class,\n                    'tags' => ['engine'],\n                ],\n                Car::class => [\n                    '__construct()' => ['moreEngines' => Reference::to('tag@engine')],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $engines = $container\n            ->get(Car::class)\n            ->getMoreEngines();\n\n        $this->assertIsArray($engines);\n        $this->assertCount(2, $engines);\n        $this->assertSame(EngineMarkOne::class, $engines[0]::class);\n        $this->assertSame(EngineMarkTwo::class, $engines[1]::class);\n    }\n\n    public static function dataResetter(): array\n    {\n        return [\n            'strict-mode' => [true],\n            'non-strict-mode' => [false],\n        ];\n    }\n\n    #[DataProvider('dataResetter')]\n    public function testResetter(bool $strictMode): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'setNumber()' => [42],\n                    'reset' => function () {\n                        $this->number = 42;\n                    },\n                ],\n            ])\n            ->withStrictMode($strictMode);\n        $container = new Container($config);\n\n        $engine = $container->get(EngineInterface::class);\n        $this->assertSame(\n            42,\n            $container\n                ->get(EngineInterface::class)\n                ->getNumber(),\n        );\n\n        $engine->setNumber(45);\n        $this->assertSame(\n            45,\n            $container\n                ->get(EngineInterface::class)\n                ->getNumber(),\n        );\n\n        $container\n            ->get(StateResetter::class)\n            ->reset();\n\n        $this->assertSame($engine, $container->get(EngineInterface::class));\n        $this->assertSame(42, $engine->getNumber());\n    }\n\n    public function testResetterInDelegates(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDelegates([\n                static function (ContainerInterface $container) {\n                    $config = ContainerConfig::create()\n                        ->withDefinitions([\n                            EngineInterface::class => [\n                                'class' => EngineMarkOne::class,\n                                'setNumber()' => [42],\n                                'reset' => function () {\n                                    $this->number = 42;\n                                },\n                            ],\n                        ]);\n                    return new Container($config);\n                },\n            ]);\n        $container = new Container($config);\n\n        $engine = $container->get(EngineInterface::class);\n        $this->assertSame(\n            42,\n            $container\n                ->get(EngineInterface::class)\n                ->getNumber(),\n        );\n\n        $engine->setNumber(45);\n        $this->assertSame(\n            45,\n            $container\n                ->get(EngineInterface::class)\n                ->getNumber(),\n        );\n\n        $container\n            ->get(StateResetter::class)\n            ->reset();\n\n        $this->assertSame($engine, $container->get(EngineInterface::class));\n        $this->assertSame(42, $engine->getNumber());\n    }\n\n    public function testNewContainerDefinitionInDelegates(): void\n    {\n        $firstContainer = null;\n        $secondContainer = null;\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                ContainerInterface::class => new Container(),\n            ])\n            ->withDelegates([\n                function (ContainerInterface $container) use (&$firstContainer): ContainerInterface {\n                    $firstContainer = $container;\n                    return new Container();\n                },\n                function (ContainerInterface $container) use (&$secondContainer): ContainerInterface {\n                    $secondContainer = $container;\n                    return new Container();\n                },\n            ]);\n        $originalContainer = new Container($config);\n\n        $container = $originalContainer->get(ContainerInterface::class);\n\n        $this->assertNotSame($container, $originalContainer);\n        $this->assertSame($container, $firstContainer);\n        $this->assertSame($container, $secondContainer);\n    }\n\n    public function testResetterInDelegatesWithCustomResetter(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDelegates([\n                static function (ContainerInterface $container) {\n                    $config = ContainerConfig::create()\n                        ->withDefinitions([\n                            EngineInterface::class => [\n                                'class' => EngineMarkOne::class,\n                                'setNumber()' => [42],\n                                'reset' => function () {\n                                    $this->number = 42;\n                                },\n                            ],\n                        ]);\n                    return new Container($config);\n                },\n            ])\n            ->withDefinitions([\n                Car::class => [\n                    'class' => Car::class,\n                    'setColor()' => [new ColorPink()],\n                ],\n                StateResetter::class => [\n                    'class' => StateResetter::class,\n                    'setResetters()' => [\n                        [\n                            Car::class => function () {\n                                $this->color = new ColorPink();\n                            },\n                        ],\n                    ],\n                ],\n            ]);\n        $container = new Container($config);\n\n        $engine = $container->get(EngineInterface::class);\n        $this->assertSame(\n            42,\n            $container\n                ->get(EngineInterface::class)\n                ->getNumber(),\n        );\n\n        $car = $container->get(Car::class);\n        $this->assertInstanceOf(\n            ColorPink::class,\n            $container\n                ->get(Car::class)\n                ->getColor(),\n        );\n\n        $engine->setNumber(45);\n        $this->assertSame(\n            45,\n            $container\n                ->get(EngineInterface::class)\n                ->getNumber(),\n        );\n\n        $car->setColor(new ColorRed());\n        $this->assertInstanceOf(\n            ColorRed::class,\n            $container\n                ->get(Car::class)\n                ->getColor(),\n        );\n\n        $container\n            ->get(StateResetter::class)\n            ->reset();\n\n        $this->assertSame($engine, $container->get(EngineInterface::class));\n        $this->assertSame(42, $engine->getNumber());\n\n        $this->assertSame($car, $container->get(Car::class));\n        $this->assertInstanceOf(\n            ColorPink::class,\n            $container\n                ->get(Car::class)\n                ->getColor(),\n        );\n    }\n\n    public static function dataResetterInProviderDefinitions(): array\n    {\n        return [\n            'strict-mode' => [true],\n            'non-strict-mode' => [false],\n        ];\n    }\n\n    #[DataProvider('dataResetterInProviderDefinitions')]\n    public function testResetterInProviderDefinitions(bool $strictMode): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => [\n                    'class' => EngineMarkOne::class,\n                    'setNumber()' => [42],\n                ],\n            ])\n            ->withProviders([\n                new class implements ServiceProviderInterface {\n                    public function getDefinitions(): array\n                    {\n                        return [\n                            StateResetter::class => static function (ContainerInterface $container) {\n                                $resetter = new StateResetter($container);\n                                $resetter->setResetters([\n                                    EngineInterface::class => function () {\n                                        $this->number = 42;\n                                    },\n                                ]);\n                                return $resetter;\n                            },\n                        ];\n                    }\n\n                    public function getExtensions(): array\n                    {\n                        return [];\n                    }\n                },\n            ])\n            ->withStrictMode($strictMode);\n        $container = new Container($config);\n\n        $engine = $container->get(EngineInterface::class);\n        $engine->setNumber(45);\n        $container\n            ->get(StateResetter::class)\n            ->reset();\n\n        $this->assertSame($engine, $container->get(EngineInterface::class));\n        $this->assertSame(42, $engine->getNumber());\n    }\n\n    public function testResetterInProviderExtensions(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => [\n                    'class' => EngineMarkOne::class,\n                    'setNumber()' => [42],\n                ],\n            ])\n            ->withProviders([\n                new class implements ServiceProviderInterface {\n                    public function getDefinitions(): array\n                    {\n                        return [];\n                    }\n\n                    public function getExtensions(): array\n                    {\n                        return [\n                            StateResetter::class => static function (\n                                ContainerInterface $container,\n                                StateResetter $resetter,\n                            ) {\n                                $resetter->setResetters([\n                                    EngineInterface::class => function () {\n                                        $this->number = 42;\n                                    },\n                                ]);\n                                return $resetter;\n                            },\n                        ];\n                    }\n                },\n            ]);\n        $container = new Container($config);\n\n        $engine = $container->get(EngineInterface::class);\n        $engine->setNumber(45);\n        $container\n            ->get(StateResetter::class)\n            ->reset();\n\n        $this->assertSame($engine, $container->get(EngineInterface::class));\n        $this->assertSame(42, $engine->getNumber());\n    }\n\n    public function testNestedResetter(): void\n    {\n        $color = new ColorPink();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => EngineMarkOne::class,\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'setNumber()' => [42],\n                    'reset' => function () {\n                        $this->number = 42;\n                    },\n                ],\n                ColorInterface::class => $color,\n                Car::class => [\n                    'class' => Car::class,\n                    'setColor()' => [DynamicReference::to(fn() => $color)],\n                    'reset' => function (ContainerInterface $container) {\n                        $this->color = $container->get(ColorInterface::class);\n                    },\n                ],\n            ]);\n        $container = new Container($config);\n\n        $engine = $container->get(EngineInterface::class);\n        $car = $container->get(Car::class);\n        $this->assertSame($engine, $car->getEngine());\n        $this->assertInstanceOf(EngineMarkOne::class, $car->getEngine());\n\n        $engine->setNumber(45);\n        $car->setColor(new ColorRed());\n        $this->assertSame(\n            45,\n            $container\n                ->get(Car::class)\n                ->getEngine()\n                ->getNumber(),\n        );\n        $this->assertSame(\n            'red',\n            $container\n                ->get(Car::class)\n                ->getColor()\n                ->getColor(),\n        );\n\n        $container\n            ->get(StateResetter::class)\n            ->reset();\n\n        $this->assertSame($engine, $container->get(EngineInterface::class));\n        $this->assertSame($car, $container->get(Car::class));\n        $this->assertSame(\n            42,\n            $car\n                ->getEngine()\n                ->getNumber(),\n        );\n        $this->assertSame($color, $car->getColor());\n    }\n\n    public function testResetterInCompositeContainer(): void\n    {\n        $composite = new CompositeContainer();\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engineMarkOne' => [\n                    'class' => EngineMarkOne::class,\n                    'setNumber()' => [42],\n                    'reset' => function () {\n                        $this->number = 42;\n                    },\n                ],\n            ]);\n        $firstContainer = new Container($config);\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'engineMarkTwo' => [\n                    'class' => EngineMarkTwo::class,\n                    'setNumber()' => [43],\n                    'reset' => function () {\n                        $this->number = 43;\n                    },\n                ],\n            ]);\n        $secondContainer = new Container($config);\n        $composite->attach($firstContainer);\n        $composite->attach($secondContainer);\n\n        $engineMarkOne = $composite->get('engineMarkOne');\n        $engineMarkTwo = $composite->get('engineMarkTwo');\n        $this->assertSame(\n            42,\n            $composite\n                ->get('engineMarkOne')\n                ->getNumber(),\n        );\n        $this->assertSame(\n            43,\n            $composite\n                ->get('engineMarkTwo')\n                ->getNumber(),\n        );\n\n        $engineMarkOne->setNumber(45);\n        $engineMarkTwo->setNumber(46);\n        $this->assertSame(\n            45,\n            $composite\n                ->get('engineMarkOne')\n                ->getNumber(),\n        );\n        $this->assertSame(\n            46,\n            $composite\n                ->get('engineMarkTwo')\n                ->getNumber(),\n        );\n\n        $composite\n            ->get(StateResetter::class)\n            ->reset();\n\n        $this->assertSame($engineMarkOne, $composite->get('engineMarkOne'));\n        $this->assertSame($engineMarkTwo, $composite->get('engineMarkTwo'));\n        $this->assertSame(\n            42,\n            $composite\n                ->get('engineMarkOne')\n                ->getNumber(),\n        );\n        $this->assertSame(\n            43,\n            $composite\n                ->get('engineMarkTwo')\n                ->getNumber(),\n        );\n    }\n\n    public function testCircularReferenceExceptionWhileResolvingProviders(): void\n    {\n        $provider = new class implements ServiceProviderInterface {\n            public function getDefinitions(): array\n            {\n                return [\n                    // wrapping container with proxy class\n                    ContainerInterface::class => static fn(ContainerInterface $container) => $container,\n                ];\n            }\n\n            public function getExtensions(): array\n            {\n                return [];\n            }\n        };\n\n        $this->expectException(BuildingException::class);\n        $this->expectExceptionMessage(\n            'Caught unhandled error \"RuntimeException\" while building \"Yiisoft\\Di\\Tests\\Support\\B\".',\n        );\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                B::class => function () {\n                    throw new RuntimeException();\n                },\n            ])\n            ->withProviders([$provider]);\n        $container = new Container($config);\n        $container->get(B::class);\n    }\n\n    public function testDifferentContainerWithProviders(): void\n    {\n        $provider = new class implements ServiceProviderInterface {\n            public function getDefinitions(): array\n            {\n                return [\n                    ContainerInterface::class => static fn(ContainerInterface $container) => new Container(),\n                ];\n            }\n\n            public function getExtensions(): array\n            {\n                return [];\n            }\n        };\n\n        $config = ContainerConfig::create()\n            ->withProviders([$provider]);\n        $originalContainer = new Container($config);\n\n        $container = $originalContainer->get(ContainerInterface::class);\n\n        $this->assertNotSame($originalContainer, $container);\n    }\n\n    public function testErrorOnMethodTypo(): void\n    {\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Invalid definition: metadata \"setId\" is not allowed. Did you mean \"setId()\" or \"$setId\"?',\n        );\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => [\n                    'class' => EngineMarkOne::class,\n                    'setId' => [42],\n                ],\n            ]);\n        new Container($config);\n    }\n\n    public function testErrorOnPropertyTypo(): void\n    {\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Invalid definition: metadata \"dev\" is not allowed. Did you mean \"dev()\" or \"$dev\"?',\n        );\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => [\n                    'class' => EngineMarkOne::class,\n                    'dev' => true,\n                ],\n            ]);\n        new Container($config);\n    }\n\n    public function testErrorOnDisallowMeta(): void\n    {\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Invalid definition: metadata \"dev\" is not allowed. Did you mean \"dev()\" or \"$dev\"?',\n        );\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineInterface::class => [\n                    'class' => EngineMarkOne::class,\n                    'tags' => ['a', 'b'],\n                    'dev' => 42,\n                ],\n            ]);\n        new Container($config);\n    }\n\n    public function testDelegateLookup(): void\n    {\n        $delegate = static function (ContainerInterface $container) {\n            $config = ContainerConfig::create()\n                ->withDefinitions([\n                    EngineInterface::class => EngineMarkOne::class,\n                    SportCar::class => ['__construct()' => ['maxSpeed' => 300]],\n                ]);\n            return new Container($config);\n        };\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                Garage::class => Garage::class,\n                EngineInterface::class => EngineMarkTwo::class,\n            ])\n            ->withValidate(true)\n            ->withDelegates([$delegate]);\n        $container = new Container($config);\n\n        $garage = $container->get(Garage::class);\n\n        $this->assertInstanceOf(Garage::class, $garage);\n        $this->assertInstanceOf(\n            EngineMarkOne::class,\n            $garage\n                ->getCar()\n                ->getEngine(),\n        );\n    }\n\n    public function testNonClosureDelegate(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDelegates([42]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Delegate must be callable in format \"function (ContainerInterface $container): ContainerInterface\".',\n        );\n        new Container($config);\n    }\n\n    public function testNonContainerDelegate(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDelegates([\n                static fn(ContainerInterface $container) => 42,\n            ]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Delegate callable must return an object that implements ContainerInterface.',\n        );\n        new Container($config);\n    }\n\n    public function testExtensibleServiceDefinition(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'test' => new ExtensibleService([], 'test'),\n            ]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Invalid definition. ExtensibleService is only allowed in provider extensions.',\n        );\n        new Container($config);\n    }\n\n    public function testWrongTag(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'tags' => ['engine', 42],\n                ],\n            ]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Invalid tag. Expected a string, got 42.',\n        );\n        new Container($config);\n    }\n\n    public function testNumberProvider(): void\n    {\n        $config = ContainerConfig::create()\n            ->withProviders([42]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessageMatches(\n            '/^Service provider should be a class name or an instance of '\n            . preg_quote(ServiceProviderInterface::class, '/')\n            . '\\. (integer|int) given\\.$/',\n        );\n        new Container($config);\n    }\n\n    public function testNonServiceProviderInterfaceProvider(): void\n    {\n        $config = ContainerConfig::create()\n            ->withProviders([stdClass::class]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Service provider should be an instance of ' . ServiceProviderInterface::class . '.'\n            . ' stdClass given.',\n        );\n        new Container($config);\n    }\n\n    public function testStrictModeDisabled(): void\n    {\n        $config = ContainerConfig::create()\n            ->withStrictMode(false);\n        $container = new Container($config);\n        $this->assertTrue($container->has(EngineMarkOne::class));\n\n        $engine = $container->get(EngineMarkOne::class);\n        $this->assertInstanceOf(EngineMarkOne::class, $engine);\n    }\n\n    public function testStrictModeEnabled(): void\n    {\n        $config = ContainerConfig::create()\n            ->withStrictMode(true);\n        $container = new Container($config);\n        $this->assertFalse($container->has(EngineMarkOne::class));\n\n        $this->expectException(NotFoundExceptionInterface::class);\n        $container->get(EngineMarkOne::class);\n    }\n\n    public function testIntegerKeyInExtensions(): void\n    {\n        $config = ContainerConfig::create()\n            ->withProviders([\n                new class implements ServiceProviderInterface {\n                    public function getDefinitions(): array\n                    {\n                        return [];\n                    }\n\n                    public function getExtensions(): array\n                    {\n                        return [\n                            23 => static fn(ContainerInterface $container, StateResetter $resetter) => $resetter,\n                        ];\n                    }\n                },\n            ]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage('Extension key must be a service ID as string, 23 given.');\n        new Container($config);\n    }\n\n    public function testNonCallableExtension(): void\n    {\n        $config = ContainerConfig::create()\n            ->withProviders([\n                new class implements ServiceProviderInterface {\n                    public function getDefinitions(): array\n                    {\n                        return [];\n                    }\n\n                    public function getExtensions(): array\n                    {\n                        return [\n                            ColorPink::class => [],\n                        ];\n                    }\n                },\n            ]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage('Extension of service should be callable, array given.');\n        new Container($config);\n    }\n\n    public function testNonArrayReset(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'setNumber()' => [42],\n                    'reset' => 42,\n                ],\n            ]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Invalid definition: \"reset\" should be closure, int given.',\n        );\n        new Container($config);\n    }\n\n    public function testNonArrayTags(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'setNumber()' => [42],\n                    'tags' => 'hello',\n                ],\n            ]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Invalid definition: tags should be array of strings, string given.',\n        );\n        new Container($config);\n    }\n\n    public function testNonArrayArguments(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                EngineMarkOne::class => [\n                    'class' => EngineMarkOne::class,\n                    'setNumber()' => 42,\n                ],\n            ]);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessage(\n            'Invalid definition: incorrect method \"setNumber()\" arguments. Expected array, got \"int\". Probably you should wrap them into square brackets.',\n        );\n        $container = new Container($config);\n    }\n\n    public static function dataInvalidTags(): array\n    {\n        return [\n            [\n                '/^Invalid tags configuration: tag should be string, 42 given\\.$/',\n                [42 => [EngineMarkTwo::class]],\n            ],\n            [\n                '/^Invalid tags configuration: tag should contain array of service IDs, (integer|int) given\\.$/',\n                ['engine' => 42],\n            ],\n            [\n                '/^Invalid tags configuration: service should be defined as class string, (integer|int) given\\.$/',\n                ['engine' => [42]],\n            ],\n        ];\n    }\n\n    #[DataProvider('dataInvalidTags')]\n    public function testInvalidTags(string $message, array $tags): void\n    {\n        $config = ContainerConfig::create()\n            ->withTags($tags);\n\n        $this->expectException(InvalidConfigException::class);\n        $this->expectExceptionMessageMatches($message);\n        new Container($config);\n    }\n\n    public static function dataNotFoundExceptionMessageWithDefinitions(): array\n    {\n        return [\n            'without-definition' => [[]],\n            'with-definition' => [[SportCar::class => SportCar::class]],\n        ];\n    }\n\n    #[DataProvider('dataNotFoundExceptionMessageWithDefinitions')]\n    public function testNotFoundExceptionMessageWithDefinitions(array $definitions): void\n    {\n        $config = ContainerConfig::create()->withDefinitions($definitions);\n        $container = new Container($config);\n\n        $this->expectException(NotFoundException::class);\n        $this->expectExceptionMessage(\n            'No definition or class found or resolvable for \"'\n            . EngineInterface::class\n            . '\" while building \"'\n            . SportCar::class\n            . '\" -> \"'\n            . EngineInterface::class\n            . '\".',\n        );\n        $container->get(SportCar::class);\n    }\n\n    public function testNotFoundExceptionWithNotYiiContainer(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                ContainerInterface::class => new SimpleContainer(),\n                SportCar::class => SportCar::class,\n            ]);\n        $container = new Container($config);\n\n        $exception = null;\n        try {\n            $container->get(SportCar::class);\n        } catch (Throwable $e) {\n            $exception = $e;\n        }\n\n        $this->assertInstanceOf(NotFoundException::class, $exception);\n        $this->assertSame(\n            'No definition or class found or resolvable for \"' . SportCar::class . '\" while building it.',\n            $exception->getMessage(),\n        );\n        $this->assertInstanceOf(\n            \\Yiisoft\\Test\\Support\\Container\\Exception\\NotFoundException::class,\n            $exception->getPrevious(),\n        );\n    }\n\n    public function testExceptionOnGetInDelegate(): void\n    {\n        $container = new Container(\n            ContainerConfig::create()->withDelegates([\n                static fn() => new SimpleContainer(\n                    factory: static fn() => throw new RuntimeException('Error in delegate'),\n                ),\n            ]),\n        );\n\n        $exception = null;\n        try {\n            $container->get('identifier');\n        } catch (Throwable $exception) {\n        }\n\n        $this->assertInstanceOf(BuildingException::class, $exception);\n        $this->assertSame(\n            'Caught unhandled error \"Error in delegate\" while building \"identifier\".',\n            $exception->getMessage(),\n        );\n\n        $previous = $exception->getPrevious();\n        $this->assertInstanceOf(RuntimeException::class, $previous);\n        $this->assertSame('Error in delegate', $previous->getMessage());\n    }\n\n    public function testExceptionOnHasInDelegate(): void\n    {\n        $container = new Container(\n            ContainerConfig::create()->withDelegates([\n                static fn() => new SimpleContainer(\n                    hasCallback: static fn() => throw new RuntimeException('Error in delegate'),\n                ),\n            ]),\n        );\n\n        $exception = null;\n        try {\n            $container->get('identifier');\n        } catch (Throwable $exception) {\n        }\n\n        $this->assertInstanceOf(BuildingException::class, $exception);\n        $this->assertSame(\n            'Caught unhandled error \"Error in delegate\" while building \"identifier\".',\n            $exception->getMessage(),\n        );\n\n        $previous = $exception->getPrevious();\n        $this->assertInstanceOf(RuntimeException::class, $previous);\n        $this->assertSame('Error in delegate', $previous->getMessage());\n    }\n\n    public function testGetStateResetterTwice(): void\n    {\n        $container = new Container();\n\n        $resetter1 = $container->get(StateResetter::class);\n        $resetter2 = $container->get(StateResetter::class);\n\n        assertInstanceOf(StateResetter::class, $resetter1);\n        assertSame($resetter1, $resetter2);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Helpers/DefinitionParserTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Helpers;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Yiisoft\\Di\\Helpers\\DefinitionParser;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkOne;\nuse Yiisoft\\Di\\Tests\\Support\\StaticFactory;\n\nfinal class DefinitionParserTest extends TestCase\n{\n    public function testParseCallableDefinition(): void\n    {\n        $fn = static fn() => new EngineMarkOne();\n        $definition = [\n            'definition' => $fn,\n            'tags' => ['one', 'two'],\n        ];\n        [$definition, $meta] = DefinitionParser::parse($definition);\n        $this->assertSame($fn, $definition);\n        $this->assertSame(['tags' => ['one', 'two']], $meta);\n    }\n\n    public function testParseArrayCallableDefinition(): void\n    {\n        $definition = [\n            'definition' => [StaticFactory::class, 'create'],\n            'tags' => ['one', 'two'],\n        ];\n        [$definition, $meta] = DefinitionParser::parse($definition);\n        $this->assertSame([StaticFactory::class, 'create'], $definition);\n        $this->assertSame(['tags' => ['one', 'two']], $meta);\n    }\n\n    public function testParseArrayDefinition(): void\n    {\n        $definition = [\n            'class' => EngineMarkOne::class,\n            '__construct()' => [42],\n            'tags' => ['one', 'two'],\n        ];\n        [$definition, $meta] = DefinitionParser::parse($definition);\n        $this->assertSame([\n            'class' => EngineMarkOne::class,\n            '__construct()' => [42],\n            'methodsAndProperties' => [],\n            DefinitionParser::IS_PREPARED_ARRAY_DEFINITION_DATA => true,\n        ], $definition);\n        $this->assertSame(['tags' => ['one', 'two']], $meta);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/LeaguePsrContainerTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse League\\Container\\Container;\nuse Psr\\Container\\ContainerInterface;\n\n/**\n * Test the League PSR-11 Container.\n */\nfinal class LeaguePsrContainerTest extends PsrContainerTestAbstract\n{\n    public function createContainer(array $definitions = []): ContainerInterface\n    {\n        return $this->setupContainer(new Container(), $definitions);\n    }\n\n    public function setupContainer(ContainerInterface $container, iterable $definitions = []): ContainerInterface\n    {\n        foreach ($definitions as $id => $definition) {\n            $container->add($id, $definition);\n        }\n\n        return $container;\n    }\n}\n"
  },
  {
    "path": "tests/Unit/NotFoundExceptionTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Yiisoft\\Di\\NotFoundException;\n\nfinal class NotFoundExceptionTest extends TestCase\n{\n    public function testGetId(): void\n    {\n        $exception = new NotFoundException('test');\n\n        $this->assertSame('test', $exception->getId());\n        $this->assertSame('No definition or class found for \"test\" ID.', $exception->getName());\n        $this->assertSame(\n            <<<SOLUTION\n            Ensure that either a service with ID \"test\" is defined or such class exists and is autoloadable.\n            SOLUTION,\n            $exception->getSolution(),\n        );\n    }\n\n    public function testMessage(): void\n    {\n        $exception = new NotFoundException('test');\n\n        $this->assertSame('No definition or class found or resolvable for \"test\".', $exception->getMessage());\n    }\n\n    public function testBuildStack(): void\n    {\n        $exception = new NotFoundException('test', ['a', 'b', 'test']);\n\n        $this->assertSame(\n            'No definition or class found or resolvable for \"test\" while building \"a\" -> \"b\" -> \"test\".',\n            $exception->getMessage(),\n        );\n    }\n\n    public function testCode(): void\n    {\n        $exception = new NotFoundException('test');\n\n        $this->assertSame(0, $exception->getCode());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/PsrContainerTestAbstract.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse Psr\\Container\\ContainerInterface;\nuse Psr\\Container\\NotFoundExceptionInterface;\nuse Yiisoft\\Di\\Tests\\Support\\Cycle\\Chicken;\nuse Yiisoft\\Di\\Tests\\Support\\EngineInterface;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkOne;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkTwo;\n\n/**\n * General tests for PSR-11 container.\n * To be extended for specific containers.\n */\nabstract class PsrContainerTestAbstract extends TestCase\n{\n    abstract public function createContainer(array $definitions = []): ContainerInterface;\n\n    public function testCircularClassDependencyWithoutDefinition(): void\n    {\n        $container = $this->createContainer();\n        $this->expectException(ContainerExceptionInterface::class);\n        $container->get(Chicken::class);\n    }\n\n    public function testSimpleDefinition(): void\n    {\n        $container = $this->createContainer([\n            EngineInterface::class => EngineMarkOne::class,\n        ]);\n        $one = $container->get(EngineInterface::class);\n        $this->assertInstanceOf(EngineMarkOne::class, $one);\n    }\n\n    public function testClassSimple(): void\n    {\n        $container = $this->createContainer(['engine' => EngineMarkOne::class]);\n        $this->assertInstanceOf(EngineMarkOne::class, $container->get('engine'));\n    }\n\n    public function testSetAll(): void\n    {\n        $container = $this->createContainer([\n            'engine1' => EngineMarkOne::class,\n            'engine2' => EngineMarkTwo::class,\n        ]);\n        $this->assertInstanceOf(EngineMarkOne::class, $container->get('engine1'));\n        $this->assertInstanceOf(EngineMarkTwo::class, $container->get('engine2'));\n    }\n\n    public function testObject(): void\n    {\n        $container = $this->createContainer([\n            'engine' => new EngineMarkOne(),\n        ]);\n        $object = $container->get('engine');\n        $this->assertInstanceOf(EngineMarkOne::class, $object);\n    }\n\n    public function testThrowingNotFoundException(): void\n    {\n        $this->expectException(NotFoundExceptionInterface::class);\n\n        $container = $this->createContainer();\n        $container->get('non_existing');\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Reference/TagReference/Resolve/A.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Reference\\TagReference\\Resolve;\n\nfinal class A {}\n"
  },
  {
    "path": "tests/Unit/Reference/TagReference/Resolve/B.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Reference\\TagReference\\Resolve;\n\nfinal class B {}\n"
  },
  {
    "path": "tests/Unit/Reference/TagReference/Resolve/Main.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Reference\\TagReference\\Resolve;\n\nfinal class Main\n{\n    public array $data = [];\n}\n"
  },
  {
    "path": "tests/Unit/Reference/TagReference/Resolve/TagReferenceResolveTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Reference\\TagReference\\Resolve;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\nuse Yiisoft\\Di\\Reference\\TagReference;\n\nfinal class TagReferenceResolveTest extends TestCase\n{\n    public function testBase(): void\n    {\n        $container = new Container(\n            ContainerConfig::create()\n                ->withDefinitions([\n                    Main::class => [\n                        '$data' => TagReference::to('letters'),\n                    ],\n                ])\n                ->withTags([\n                    'letters' => [A::class, B::class],\n                ]),\n        );\n\n        $main = $container->get(Main::class);\n\n        $this->assertCount(2, $main->data);\n        $this->assertInstanceOf(A::class, $main->data[0]);\n        $this->assertInstanceOf(B::class, $main->data[1]);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Reference/TagReference/TagReferenceTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Reference\\TagReference;\n\nuse Error;\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\Reference\\TagReference;\nuse ReflectionClass;\n\nfinal class TagReferenceTest extends TestCase\n{\n    public function testConstructorIsPrivate(): void\n    {\n        $this->expectException(Error::class);\n        new TagReference();\n    }\n\n    public function testConstructor(): void\n    {\n        $reflection = new ReflectionClass(TagReference::class);\n        $reflectionMethod = $reflection->getConstructor();\n        $this->assertTrue($reflectionMethod->isPrivate());\n        $reflectionMethod->invoke($reflection->newInstanceWithoutConstructor());\n    }\n\n    public function testAliases(): void\n    {\n        $this->assertFalse(TagReference::isTagAlias('test'));\n        $this->assertFalse(TagReference::isTagAlias('tag#test'));\n        $this->assertTrue(TagReference::isTagAlias('tag@test'));\n    }\n\n    public function testExtractTag(): void\n    {\n        $this->assertEquals('test', TagReference::extractTagFromAlias('tag@test'));\n    }\n\n    public function testExtractWrongTagDelimiter(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        TagReference::extractTagFromAlias('tag#test');\n    }\n\n    public function testExtractWrongTagFormat(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        TagReference::extractTagFromAlias('test');\n    }\n\n    public function testReference(): void\n    {\n        $reference = TagReference::to('test');\n        $spyContainer = new class implements ContainerInterface {\n            public function get($id)\n            {\n                return $id;\n            }\n\n            public function has($id): bool\n            {\n                return true;\n            }\n        };\n\n        $result = $reference->resolve($spyContainer);\n\n        $this->assertEquals('tag@test', $result);\n    }\n\n    public function testId(): void\n    {\n        $this->assertSame('tag@test', TagReference::id('test'));\n    }\n}\n"
  },
  {
    "path": "tests/Unit/ServiceProviderTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\nuse Yiisoft\\Di\\ServiceProviderInterface;\nuse Yiisoft\\Di\\Tests\\Support\\Car;\nuse Yiisoft\\Di\\Tests\\Support\\CarProvider;\nuse Yiisoft\\Di\\Tests\\Support\\CarExtensionProvider;\nuse Yiisoft\\Di\\Tests\\Support\\ContainerInterfaceExtensionProvider;\nuse Yiisoft\\Di\\Tests\\Support\\ColorRed;\nuse Yiisoft\\Di\\Tests\\Support\\EngineInterface;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkOne;\nuse Yiisoft\\Di\\Tests\\Support\\EngineMarkTwo;\nuse Yiisoft\\Di\\Tests\\Support\\MethodTestClass;\nuse Yiisoft\\Di\\Tests\\Support\\NullCarExtensionProvider;\nuse Yiisoft\\Di\\Tests\\Support\\SportCar;\nuse Yiisoft\\Definitions\\Exception\\InvalidConfigException;\n\nfinal class ServiceProviderTest extends TestCase\n{\n    public function testAddProviderByClassName(): void\n    {\n        $this->ensureProviderRegisterDefinitions(CarProvider::class);\n        $this->ensureProviderRegisterExtensions(CarExtensionProvider::class);\n    }\n\n    public function testAddProviderByInstance(): void\n    {\n        $this->ensureProviderRegisterDefinitions(new CarProvider());\n        $this->ensureProviderRegisterExtensions(new CarExtensionProvider());\n    }\n\n    public function testNotExistedExtension(): void\n    {\n        $this->expectException(InvalidConfigException::class);\n        $config = ContainerConfig::create()\n            ->withProviders([\n                CarProvider::class,\n            ]);\n        new Container($config);\n    }\n\n    public function testContainerInterfaceExtension(): void\n    {\n        $this->expectException(InvalidConfigException::class);\n        $config = ContainerConfig::create()\n            ->withProviders([\n                ContainerInterfaceExtensionProvider::class,\n            ]);\n        new Container($config);\n    }\n\n    public function testExtensionOverride(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                Car::class => Car::class,\n                'sport_car' => SportCar::class,\n            ])\n            ->withProviders([\n                CarProvider::class,\n                CarExtensionProvider::class,\n            ]);\n        $container = new Container($config);\n\n        $this->assertInstanceOf(\n            ColorRed::class,\n            $container\n                ->get(Car::class)\n                ->getColor(),\n        );\n    }\n\n    public function testExtensionReturnedNull(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                Car::class => Car::class,\n                'sport_car' => SportCar::class,\n            ])\n            ->withProviders([\n                CarProvider::class,\n                NullCarExtensionProvider::class,\n                CarExtensionProvider::class,\n            ]);\n        $container = new Container($config);\n\n        $this->assertInstanceOf(\n            ColorRed::class,\n            $container\n                ->get(Car::class)\n                ->getColor(),\n        );\n    }\n\n    public function testClassMethodsWithExtensible(): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                'method_test' => [\n                    'class' => MethodTestClass::class,\n                    'setValue()' => [42],\n                ],\n            ])\n            ->withProviders([\n                new class implements ServiceProviderInterface {\n                    public function getDefinitions(): array\n                    {\n                        return [];\n                    }\n\n                    public function getExtensions(): array\n                    {\n                        return [\n                            'method_test' => static fn(ContainerInterface $container, MethodTestClass $class) => $class,\n                        ];\n                    }\n                },\n            ]);\n\n        $container = new Container($config);\n\n        /** @var MethodTestClass $object */\n        $object = $container->get('method_test');\n        $this->assertSame(42, $object->getValue());\n    }\n\n    private function ensureProviderRegisterExtensions($provider): void\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                Car::class => Car::class,\n                EngineInterface::class => EngineMarkOne::class,\n                'sport_car' => SportCar::class,\n            ])\n            ->withProviders([$provider]);\n        $container = new Container($config);\n\n        $this->assertTrue($container->has(Car::class));\n        $this->assertTrue($container->has(EngineInterface::class));\n        $this->assertInstanceOf(Car::class, $container->get(Car::class));\n        $this->assertInstanceOf(\n            ColorRed::class,\n            $container\n                ->get(Car::class)\n                ->getColor(),\n        );\n        $this->assertInstanceOf(\n            EngineMarkTwo::class,\n            $container\n                ->get(Car::class)\n                ->getEngine(),\n        );\n    }\n\n    private function ensureProviderRegisterDefinitions($provider): void\n    {\n        $container = new Container();\n\n        $this->assertFalse(\n            $container->has(Car::class),\n            'Container should not have Car registered before service provider added due to autoload fallback.',\n        );\n        $this->assertFalse(\n            $container->has('car'),\n            'Container should not have \"car\" registered before service provider added.',\n        );\n        $this->assertFalse(\n            $container->has(EngineInterface::class),\n            'Container should not have EngineInterface registered before service provider added.',\n        );\n\n        $config = ContainerConfig::create()\n            ->withDefinitions([\n                Car::class => Car::class,\n                'sport_car' => SportCar::class,\n            ])\n            ->withProviders([$provider]);\n        $container = new Container($config);\n\n        // ensure addProvider invoked ServiceProviderInterface::register\n        $this->assertTrue(\n            $container->has('car'),\n            'CarProvider should have registered \"car\" once it was added to container.',\n        );\n        $this->assertTrue(\n            $container->has(EngineInterface::class),\n            'CarProvider should have registered EngineInterface once it was added to container.',\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/StateResetterTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\nuse Yiisoft\\Di\\StateResetter;\nuse Yiisoft\\Di\\Tests\\Support\\Car;\nuse Yiisoft\\Test\\Support\\Container\\SimpleContainer;\n\nfinal class StateResetterTest extends TestCase\n{\n    public function testNonStateResetterObject(): void\n    {\n        $resetter = new StateResetter(new SimpleContainer());\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage(\n            'State resetter object should be instance of \"' . StateResetter::class . '\", \"stdClass\" given.',\n        );\n        $resetter->setResetters([\n            new stdClass(),\n        ]);\n    }\n\n    public function testStateResetterObjectForService(): void\n    {\n        $resetter = new StateResetter(new SimpleContainer());\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage(\n            'Callback for state resetter should be closure in format '\n            . '`function (ContainerInterface $container): void`. '\n            . 'Got \"' . StateResetter::class . '\".',\n        );\n        $resetter->setResetters([\n            Car::class => $resetter,\n        ]);\n    }\n\n    public function testResetNonObject(): void\n    {\n        $resetter = new StateResetter(\n            new SimpleContainer([\n                'value' => 42,\n            ]),\n        );\n\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessageMatches(\n            '/^State resetter supports resetting objects only\\. Container returned (integer|int)\\.$/',\n        );\n        $resetter->setResetters([\n            'value' => static function () {},\n        ]);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/YiisoftPsrContainerTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\Container;\nuse Yiisoft\\Di\\ContainerConfig;\n\n/**\n * Test the Yiisoft PSR-11 Container.\n */\nfinal class YiisoftPsrContainerTest extends PsrContainerTestAbstract\n{\n    public function createContainer(iterable $definitions = []): ContainerInterface\n    {\n        $config = ContainerConfig::create()\n            ->withDefinitions($definitions);\n        return new Container($config);\n    }\n}\n"
  },
  {
    "path": "tools/.gitignore",
    "content": "/*/vendor\n/*/composer.lock\n"
  },
  {
    "path": "tools/infection/composer.json",
    "content": "{\n    \"require-dev\": {\n        \"infection/infection\": \"^0.26 || ^0.31.9\"\n    },\n    \"config\": {\n        \"allow-plugins\": {\n            \"infection/extension-installer\": true\n        }\n    }\n}\n"
  },
  {
    "path": "tools/psalm/composer.json",
    "content": "{\n    \"require-dev\": {\n        \"vimeo/psalm\": \"^5.26.1 || ^6.12\"\n    }\n}\n"
  }
]