Full Code of yiisoft/di for AI

master 10ffafe94415 cached
105 files
227.9 KB
55.2k tokens
387 symbols
1 requests
Download .txt
Showing preview only (253K chars total). Download the full file or copy to clipboard to get everything.
Repository: yiisoft/di
Branch: master
Commit: 10ffafe94415
Files: 105
Total size: 227.9 KB

Directory structure:
gitextract_8vkzrttm/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── SECURITY.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── bc.yml
│       ├── bechmark.yml
│       ├── build.yml
│       ├── composer-require-checker.yml
│       ├── mutation.yml
│       ├── rector-cs.yml
│       └── static.yml
├── .gitignore
├── .php-cs-fixer.dist.php
├── .phpstorm.meta.php
├── .phpunit-watcher.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── benchmarks.md
├── composer.json
├── docs/
│   └── internals.md
├── infection.json.dist
├── phpbench.json
├── phpcs.xml.dist
├── phpunit.xml.dist
├── psalm.xml
├── rector.php
├── src/
│   ├── BuildingException.php
│   ├── CompositeContainer.php
│   ├── CompositeNotFoundException.php
│   ├── Container.php
│   ├── ContainerConfig.php
│   ├── ContainerConfigInterface.php
│   ├── ExtensibleService.php
│   ├── Helpers/
│   │   ├── DefinitionNormalizer.php
│   │   └── DefinitionParser.php
│   ├── NotFoundException.php
│   ├── Reference/
│   │   └── TagReference.php
│   ├── ServiceProviderInterface.php
│   └── StateResetter.php
├── tests/
│   ├── Benchmark/
│   │   ├── ContainerBench.php
│   │   └── ContainerMethodHasBench.php
│   ├── Support/
│   │   ├── A.php
│   │   ├── B.php
│   │   ├── Car.php
│   │   ├── CarExtensionProvider.php
│   │   ├── CarFactory.php
│   │   ├── CarProvider.php
│   │   ├── ColorInterface.php
│   │   ├── ColorPink.php
│   │   ├── ColorRed.php
│   │   ├── ConstructorTestClass.php
│   │   ├── ContainerInterfaceExtensionProvider.php
│   │   ├── Cycle/
│   │   │   ├── Chicken.php
│   │   │   └── Egg.php
│   │   ├── EngineFactory.php
│   │   ├── EngineInterface.php
│   │   ├── EngineMarkOne.php
│   │   ├── EngineMarkTwo.php
│   │   ├── EngineStorage.php
│   │   ├── Garage.php
│   │   ├── GearBox.php
│   │   ├── InvokableCarFactory.php
│   │   ├── MethodTestClass.php
│   │   ├── NonPsrContainer.php
│   │   ├── NullCarExtensionProvider.php
│   │   ├── NullableConcreteDependency.php
│   │   ├── OptionalConcreteDependency.php
│   │   ├── PropertyTestClass.php
│   │   ├── SportCar.php
│   │   ├── StaticFactory.php
│   │   ├── TreeItem.php
│   │   ├── UnionTypeInConstructorFirstTypeInParamResolvable.php
│   │   ├── UnionTypeInConstructorParamNotResolvable.php
│   │   ├── UnionTypeInConstructorSecondParamNotResolvable.php
│   │   ├── UnionTypeInConstructorSecondTypeInParamResolvable.php
│   │   └── VariadicConstructor.php
│   └── Unit/
│       ├── BuildingExceptionTest.php
│       ├── CompositeContainerTest.php
│       ├── CompositePsrContainerOverLeagueTest.php
│       ├── CompositePsrContainerOverYiisoftTest.php
│       ├── CompositePsrContainerTestAbstract.php
│       ├── Container/
│       │   └── DependencyFromDelegate/
│       │       ├── Car.php
│       │       ├── DependencyFromDelegateTest.php
│       │       ├── Engine.php
│       │       └── EngineInterface.php
│       ├── ContainerTest.php
│       ├── Helpers/
│       │   └── DefinitionParserTest.php
│       ├── LeaguePsrContainerTest.php
│       ├── NotFoundExceptionTest.php
│       ├── PsrContainerTestAbstract.php
│       ├── Reference/
│       │   └── TagReference/
│       │       ├── Resolve/
│       │       │   ├── A.php
│       │       │   ├── B.php
│       │       │   ├── Main.php
│       │       │   └── TagReferenceResolveTest.php
│       │       └── TagReferenceTest.php
│       ├── ServiceProviderTest.php
│       ├── StateResetterTest.php
│       └── YiisoftPsrContainerTest.php
└── tools/
    ├── .gitignore
    ├── infection/
    │   └── composer.json
    └── psalm/
        └── composer.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.php]
ij_php_space_before_short_closure_left_parenthesis = true
ij_php_space_after_type_cast = true

[*.yml]
indent_size = 2


================================================
FILE: .gitattributes
================================================
# Autodetect text files
* text=auto eol=lf

# ...Unless the name matches the following overriding patterns

# Definitively text files
*.php  text
*.css  text
*.js   text
*.txt  text
*.md   text
*.xml  text
*.json text
*.bat  text
*.sql  text
*.yml  text

# Ensure those won't be messed up with
*.png  binary
*.jpg  binary
*.gif  binary
*.ttf  binary

# Ignore some meta files when creating an archive of this repository
/.github            export-ignore
/.editorconfig      export-ignore
/.gitattributes     export-ignore
/.gitignore         export-ignore
/phpunit.xml.dist   export-ignore
/tests              export-ignore
/docs               export-ignore

# Avoid merge conflicts in CHANGELOG
# https://about.gitlab.com/2015/02/10/gitlab-reduced-merge-conflicts-by-90-percent-with-changelog-placeholders/
/CHANGELOG.md		merge=union



================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Yii Contributor Code of Conduct

## Our Pledge

As contributors and maintainers of this project, and in order to keep Yii community open and welcoming, we ask to
respect all community members.

## Our Standards

Examples of behavior that contributes to a positive environment for our community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall community

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery, and sexual attention or advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Enforcement Responsibilities

Core team members are responsible for clarifying and enforcing our standards of acceptable behavior and will take 
appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Core team members have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,
issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for 
moderation decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing
the community in public spaces. Examples of representing a project or community include using an official e-mail
address, posting via an official social media account, within project GitHub, official forum or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting core team members. All
complaints will be reviewed and investigated promptly and fairly.

All core team members are obligated to respect the privacy and security of the reporter of any incident.

## Enforcement Guidelines

Core team members will follow these Community Impact Guidelines in determining the consequences for any action they
deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in 
the community.

**Consequence**: A private, written warning from core team members, providing clarity around the nature of the violation
and an explanation of why the behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series of actions.

**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including
unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding
interactions in community spaces as well as external channels like social media. Violating these terms may lead to 
a temporary or permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified
period of time. No public or private interaction with the people involved, including unsolicited interaction with those
enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate
behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].

Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].

For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].

[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Prerequisites

- [Yii goal and values](https://github.com/yiisoft/docs/blob/master/001-yii-values.md)
- [Namespaces](https://github.com/yiisoft/docs/blob/master/004-namespaces.md)
- [Git commit messages](https://github.com/yiisoft/docs/blob/master/006-git-commit-messages.md)
- [Exceptions](https://github.com/yiisoft/docs/blob/master/007-exceptions.md)
- [Interfaces](https://github.com/yiisoft/docs/blob/master/008-interfaces.md)

# Getting started

Since Yii 3 consists of many packages, we have a [special development tool](https://github.com/yiisoft/docs/blob/master/005-development-tool.md).

1. [Clone the repository](https://github.com/yiisoft/yii-dev-tool).

2. [Set up your own fork](https://github.com/yiisoft/yii-dev-tool#using-your-own-fork).

3. Now you are ready. Fork any package listed in `packages.php` and do `./yii-dev install username/package`.

If you don't have any particular package in mind to start with:

- [Check roadmap](https://github.com/yiisoft/docs/blob/master/003-roadmap.md).
- Check package issues at github. Usually there are some.
- Ask @samdark.


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
| Q             | A
| ------------- | ---
| Is bugfix?    | ✔️/❌
| New feature?  | ✔️/❌
| Breaks BC?    | ✔️/❌
| Fixed issues  | comma-separated list of tickets # fixed by the PR, if any


================================================
FILE: .github/SECURITY.md
================================================
# Security Policy

Please use the [security issue form](https://www.yiiframework.com/security) to report to us any security issue you
find in Yii. DO NOT use the issue tracker or discuss it in the public forum as it will cause more damage than help.

Please note that as a non-commercial OpenSource project we are not able to pay bounties at the moment.


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
    # Maintain dependencies for GitHub Actions.
    - package-ecosystem: "github-actions"
      directory: "/"
      schedule:
          interval: "daily"
      # Too noisy. See https://github.community/t/increase-if-necessary-for-github-actions-in-dependabot/179581
      open-pull-requests-limit: 0

    # Maintain dependencies for Composer
    - package-ecosystem: "composer"
      directory: "/"
      schedule:
          interval: "daily"
      versioning-strategy: increase-if-necessary


================================================
FILE: .github/workflows/bc.yml
================================================
on:
  pull_request:
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'infection.json.dist'
      - 'phpunit.xml.dist'
      - 'psalm.xml'
  push:
    branches: ['master']
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'infection.json.dist'
      - 'phpunit.xml.dist'
      - 'psalm.xml'

name: backwards compatibility

jobs:
  roave_bc_check:
    uses: yiisoft/actions/.github/workflows/bc.yml@master
    with:
      os: >-
        ['ubuntu-latest']
      php: >-
        ['8.4']


================================================
FILE: .github/workflows/bechmark.yml
================================================
on:
  pull_request:
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'infection.json.dist'
      - 'psalm.xml'

  push:
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'infection.json.dist'
      - 'psalm.xml'

name: bechmark

jobs:
  phpbench:
    uses: yiisoft/actions/.github/workflows/phpbench.yml@master
    with:
      os: >-
        ['ubuntu-latest', 'windows-latest']
      php: >-
        ['8.1']



================================================
FILE: .github/workflows/build.yml
================================================
on:
  pull_request:
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'infection.json.dist'
      - 'psalm.xml'

  push:
    branches: ['master']
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'infection.json.dist'
      - 'psalm.xml'

name: build

jobs:
  phpunit:
    uses: yiisoft/actions/.github/workflows/phpunit.yml@master
    secrets:
      codecovToken: ${{ secrets.CODECOV_TOKEN }}
    with:
      os: >-
        ['ubuntu-latest', 'windows-latest']
      php: >-
        ['8.1', '8.2', '8.3', '8.4', '8.5']


================================================
FILE: .github/workflows/composer-require-checker.yml
================================================
on:
  pull_request:
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'infection.json.dist'
      - 'phpunit.xml.dist'
      - 'psalm.xml'

  push:
    branches: ['master']
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'infection.json.dist'
      - 'phpunit.xml.dist'
      - 'psalm.xml'

name: Composer require checker

jobs:
  composer-require-checker:
    uses: yiisoft/actions/.github/workflows/composer-require-checker.yml@master
    with:
      os: >-
        ['ubuntu-latest']
      php: >-
        ['8.1', '8.2', '8.3', '8.4', '8.5']


================================================
FILE: .github/workflows/mutation.yml
================================================
on:
  pull_request:
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'psalm.xml'

  push:
    branches: ['master']
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'psalm.xml'

name: mutation test

jobs:
  mutation:
    uses: yiisoft/actions/.github/workflows/infection.yml@master
    with:
      os: >-
        ['ubuntu-latest']
      php: >-
        ['8.5']
      infection-args: "--ignore-msi-with-no-mutations"
    secrets:
      STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}


================================================
FILE: .github/workflows/rector-cs.yml
================================================
name: Rector + PHP CS Fixer

on:
  pull_request_target:
    paths:
      - 'src/**'
      - 'tests/**'
      - '.github/workflows/rector-cs.yml'
      - 'composer.json'
      - 'rector.php'
      - '.php-cs-fixer.dist.php'

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  rector:
    uses: yiisoft/actions/.github/workflows/rector-cs.yml@master
    secrets:
      token: ${{ secrets.YIISOFT_GITHUB_TOKEN }}
    with:
      repository: ${{ github.event.pull_request.head.repo.full_name }}
      php: '8.1'


================================================
FILE: .github/workflows/static.yml
================================================
on:
  pull_request:
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'infection.json.dist'
      - 'phpunit.xml.dist'

  push:
    branches: ['master']
    paths-ignore:
      - 'docs/**'
      - 'README.md'
      - 'CHANGELOG.md'
      - '.gitignore'
      - '.gitattributes'
      - 'infection.json.dist'
      - 'phpunit.xml.dist'

name: static analysis

jobs:
  psalm:
    uses: yiisoft/actions/.github/workflows/psalm.yml@master
    with:
      os: >-
        ['ubuntu-latest']
      php: >-
        ['8.1', '8.2', '8.3', '8.4']


================================================
FILE: .gitignore
================================================
# phpstorm project files
.idea

# netbeans project files
nbproject

# zend studio for eclipse project files
.buildpath
.project
.settings

# windows thumbnail cache
Thumbs.db

# composer vendor dir
/vendor

/composer.lock

# composer itself is not needed
composer.phar

# Mac DS_Store Files
.DS_Store

# PhpUnit
/phpunit.phar
/phpunit.xml
/.phpunit.cache

# Static analysis
analysis.txt

# Code coverage HTML
/coverage

# PHP CS Fixer
/.php-cs-fixer.cache
/.php-cs-fixer.php


================================================
FILE: .php-cs-fixer.dist.php
================================================
<?php

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;

$finder = (new Finder())->in([
    __DIR__ . '/src',
    __DIR__ . '/tests',
]);

return (new Config())
    ->setRiskyAllowed(true)
    ->setParallelConfig(ParallelConfigFactory::detect())
    ->setRules([
        '@PER-CS3.0' => true,
        'no_unused_imports' => true,
        'ordered_class_elements' => true,
        'class_attributes_separation' => ['elements' => ['method' => 'one']],
        'declare_strict_types' => true,
        'native_function_invocation' => true,
        'native_constant_invocation' => true,
        'fully_qualified_strict_types' => [
            'import_symbols' => true
        ],
        'global_namespace_import' => [
            'import_classes' => true,
            'import_constants' => true,
            'import_functions' => true,
        ],
    ])
    ->setFinder($finder);


================================================
FILE: .phpstorm.meta.php
================================================
<?php
// .phpstorm.meta.php

namespace PHPSTORM_META {

    override(
        \Psr\Container\ContainerInterface::get(0),
        map([
            '' => '@',
        ])
    );
}


================================================
FILE: .phpunit-watcher.yml
================================================
watch:
    directories:
        - src
        - tests
    fileMask: '*.php'
notifications:
    passingTests: false
    failingTests: false
phpunit:
    binaryPath: vendor/bin/phpunit
    timeout: 180


================================================
FILE: CHANGELOG.md
================================================
# Yii Dependency Injection Change Log

## 1.4.2 under development

- Enh #397: Explicitly import functions in "use" section (@mspirkov)

## 1.4.1 December 01, 2025

- Enh #393: Add PHP 8.5 support (@vjik)

## 1.4.0 May 30, 2025

- New #380: Add `TagReference::id()` method (@vjik)
- Chg #390: Change PHP constraint in `composer.json` to `8.1 - 8.4` (@vjik)
- Enh #324: Make `BuildingException` and `NotFoundException` friendly (@np25071984)
- Enh #384: Make `$config` parameter in `Container` constructor optional (@np25071984)
- Enh #387: Improve container performance (@samdark)
- Bug #390: Explicitly mark nullable parameters (@vjik)

## 1.3.0 October 14, 2024

- Enh #353: Add shortcut for tag reference #333 (@xepozz)
- Enh #356: Improve usage `NotFoundException` for cases with definitions (@vjik)
- Enh #364: Minor refactoring to improve performance of container (@samdark)
- Enh #375: Raise minimum PHP version to `^8.1` and refactor code (@vjik)
- Enh #376: Add default value `true` for parameter of `ContainerConfig::withStrictMode()` and
 `ContainerConfig::withValidate()` methods (@vjik)

## 1.2.1 December 23, 2022

- Chg #316: Fix exception messages (@xepozz)
- Bug #317: Fix delegated container (@xepozz)

## 1.2.0 November 05, 2022

- Chg #310: Adopt to `yiisoft/definition` version `^3.0` (@vjik)
- Enh #308: Raise minimum PHP version to `^8.0` and refactor code (@xepozz, @vjik)

## 1.1.0 June 24, 2022

- Chg #263: Raise minimal required version of `psr/container` to `^1.1|^2.0` (@xepozz, @vjik)

## 1.0.3 June 17, 2022

- Enh #302: Improve performance collecting tags (samdark)
- Enh #303: Add support for `yiisoft/definitions` version `^2.0` (@vjik)

## 1.0.2 February 14, 2022

- Bug #297: Fix method name `TagHelper::extractTagFromAlias` (@rustamwin)

## 1.0.1 December 21, 2021

- Bug #293: Fix `ExtensibleService` normalization bug (@yiiliveext)

## 1.0.0 December 03, 2021

- Initial release.


================================================
FILE: LICENSE.md
================================================
Copyright © 2008 by Yii Software (<https://www.yiiframework.com/>)
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

 * Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in
   the documentation and/or other materials provided with the
   distribution.
 * Neither the name of Yii Software nor the names of its
   contributors may be used to endorse or promote products derived
   from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
<p align="center">
    <a href="https://github.com/yiisoft" target="_blank">
        <img src="https://yiisoft.github.io/docs/images/yii_logo.svg" height="100px" alt="Yii">
    </a>
    <h1 align="center">Yii Dependency Injection</h1>
    <br>
</p>

[![Latest Stable Version](https://poser.pugx.org/yiisoft/di/v)](https://packagist.org/packages/yiisoft/di)
[![Total Downloads](https://poser.pugx.org/yiisoft/di/downloads)](https://packagist.org/packages/yiisoft/di)
[![Build status](https://github.com/yiisoft/di/actions/workflows/build.yml/badge.svg)](https://github.com/yiisoft/di/actions/workflows/build.yml)
[![Code coverage](https://codecov.io/gh/yiisoft/di/graph/badge.svg?token=P8W1UTwgQt)](https://codecov.io/gh/yiisoft/di)
[![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)
[![static analysis](https://github.com/yiisoft/di/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/di/actions?query=workflow%3A%22static+analysis%22)
[![type-coverage](https://shepherd.dev/github/yiisoft/di/coverage.svg)](https://shepherd.dev/github/yiisoft/di)

[PSR-11](https://www.php-fig.org/psr/psr-11/) compatible
[dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) container that's able to instantiate
and configure classes resolving dependencies.

## Features

- [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible.
- Supports property injection, constructor injection, and method injection.
- Detects circular references.
- Accepts array definitions. You can use it with mergeable configs.
- Provides optional autoload fallback for classes without explicit definition.
- Allows delegated lookup and has a composite container.
- Supports aliasing.
- Supports service providers.
- Has state resetter for long-running workers serving many requests, such as [RoadRunner](https://roadrunner.dev/)
  or [Swoole](https://www.swoole.co.uk/).
- Supports container delegates.
- Does auto-wiring.

> [!NOTE]
> The container contains only shared instances. If you need a factory, use the dedicated [yiisoft/factory](https://github.com/yiisoft/factory) package.

## Requirements

- PHP 8.1 - 8.5.
- `Multibyte String` PHP extension.

## Installation

You could install the package with composer:

```shell
composer require yiisoft/di
```

## Using the container

Usage of the DI container is simple: You first initialize it with an
array of *definitions*. The array keys are usually interface names. It will
then use these definitions to create an object whenever the application requests that type.
This happens, for example, when fetching a type directly from the container
somewhere in the application. But objects are also created implicitly if a
definition has a dependency on another definition.

Usually one uses a single container for the whole application. It's often
configured either in the entry script such as `index.php` or a configuration
file:

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions($definitions);

$container = new Container($config);
```

You could store the definitions in a `.php` file that returns an array:

```php
return [
    // resolve EngineMarkOne dependencies automatically
    EngineInterface::class => EngineMarkOne::class,
    
    // full definition
    MyServiceInterface::class => [
        'class' => MyService::class,
        
        // call the constructor, pass named argument "amount"
        '__construct()' => [
            'amount' => 42,
            'db' => Reference::to(SecondaryConnection::class), // instance of another dependency
        ],
        
        // set a public property
        '$name' => 'Alex',
        
        // call a public method
        'setDiscount()' => [10],
    ],
    
    // closure for complicated cases
    AnotherServiceInterface::class => static function(ConnectionInterface $db) {
        return new AnotherService($db);
    },
    
    // factory
    MyObjectInterface::class => fn () => MyFactory::create('args'),
    
    // static call
    MyObjectInterface2::class => [MyFactory::class, 'create'],
    
    // direct instance
    MyInterface::class => new MyClass(),
];
```

You can define an object in several ways:

- In the simple case, an interface definition maps an id to a particular class.
- A full definition describes how to instantiate a class in more detail:
  - `class` has the name of the class to instantiate.
  - `__construct()` holds an array of constructor arguments.
  - The rest of the config is property values (prefixed with `$`) and method calls, postfixed with `()`. They're
     set/called in the order they appear in the array.
- Closures are useful if instantiation is tricky and can be better done in code. When using these, arguments are
   auto-wired by type. `ContainerInterface` could be used to get current container instance.
- If it's even more complicated, it's a good idea to move such a code into a
   factory and reference it as a static call.
- While it's usually not a good idea, you can also set an already
   instantiated object into the container.

See [yiisoft/definitions](https://github.com/yiisoft/definitions) for more information.

After you configure the container, you can obtain a service via `get()`:

```php
/** @var \Yiisoft\Di\Container $container */
$object = $container->get('interface_name');
```

Note, however, that it's bad practice using a container directly. It's much
better to rely on auto-wiring as provided by the Injector available from the
[yiisoft/injector](https://github.com/yiisoft/injector) package.

## Using aliases

The DI container supports aliases via the `Yiisoft\Definitions\Reference` class.
This way you can retrieve objects by a more handy name:

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        'engine_one' => EngineInterface::class,
    ]);

$container = new Container($config);
$object = $container->get('engine_one');
```

## Using class aliases for specific configuration

To define another instance of a class with specific configuration, you can
use native PHP `class_alias()`:

```php
class_alias(Yiisoft\Db\Pgsql\Connection::class, 'MyPgSql');

$config = ContainerConfig::create()                                                                                                                                                     
    ->withDefinitions([
        MyPgSql::class => [ ... ]
    ]);                                                                                                                                                                                 

$container = new Container($config);
$object = $container->get(MyPgSql::class);
```

It could be then conveniently used by type-hinting:

```php
final class MyService
{
    public function __construct(MyPgSql $myPgSql)
    {
        // ...    
    }
} 
```

## Composite containers

A composite container combines many containers in a single container. When
using this approach, you should fetch objects only from the composite
container.

```php
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$composite = new CompositeContainer();

$carConfig = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        CarInterface::class => Car::class
    ]);
$carContainer = new Container($carConfig);

$bikeConfig = ContainerConfig::create()
    ->withDefinitions([
        BikeInterface::class => Bike::class
    ]);

$bikeContainer = new Container($bikeConfig);
$composite->attach($carContainer);
$composite->attach($bikeContainer);

// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `Bike` class.
$bike = $composite->get(BikeInterface::class);
```

Note that containers attached earlier override dependencies of containers attached later.

```php
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$carConfig = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        CarInterface::class => Car::class
    ]);

$carContainer = new Container($carConfig);

$composite = new CompositeContainer();
$composite->attach($carContainer);

// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `EngineMarkOne` class.
$engine = $car->getEngine();

$engineConfig = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkTwo::class,
    ]);

$engineContainer = new Container($engineConfig);

$composite = new CompositeContainer();
$composite->attach($engineContainer);
$composite->attach($carContainer);

// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `EngineMarkTwo` class.
$engine = $composite->get(EngineInterface::class);
```

## Using service providers

A service provider is a special class that's responsible for providing complex
services or groups of dependencies for the container and extensions of existing services.

A provider should extend from `Yiisoft\Di\ServiceProviderInterface` and must
contain a `getDefinitions()` and `getExtensions()` methods. It should only provide services for the container
and therefore should only contain code related to this task. It should *never*
implement any business logic or other functionality such as environment bootstrap or applying changes to a database.

The `getExtensions()` method allows implementing the decorator pattern by wrapping existing services
with additional functionality.

A typical service provider could look like:

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ServiceProviderInterface;

class CarFactoryProvider extends ServiceProviderInterface
{
    public function getDefinitions(): array
    {
        return [
            CarFactory::class => [
                'class' => CarFactory::class,
                '$color' => 'red',
            ], 
            EngineInterface::class => SolarEngine::class,
            WheelInterface::class => [
                'class' => Wheel::class,
                '$color' => 'black',
            ],
            CarInterface::class => [
                'class' => BMW::class,
                '$model' => 'X5',
            ],
        ];    
    }
     
    public function getExtensions(): array
    {
        return [
            // Note that Garage should already be defined in a container 
            Garage::class => function(ContainerInterface $container, Garage $garage) {
                $car = $container
                    ->get(CarFactory::class)
                    ->create();
                $garage->setCar($car);
                
                return $garage;
            }
        ];
    } 
}
```

Here you created a service provider responsible for bootstrapping of a car factory with all its dependencies.

An extension is callable that returns a modified service object.
In this case you get existing `Garage` service
and put a car into the garage by calling the method `setCar()`.
Thus, before applying this provider, you had
an empty garage and with the help of the extension you fill it.

To add this service provider to a container, you can pass either its class or a
configuration array in the extra config:

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withProviders([CarFactoryProvider::class]);

$container = new Container($config);
```

When you add a service provider, DI calls its `getDefinitions()` and `getExtensions()` methods
*immediately* and both services and their extensions get registered into the container.

### Using service providers for decorator pattern

Service provider extensions are a powerful feature that allows implementing the decorator pattern.
This lets you wrap existing services with additional functionality without modifying their original implementation.

Here's an example of using the decorator pattern to add logging to an existing mailer service:

```php
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Yiisoft\Di\ServiceProviderInterface;

interface MailerInterface 
{
    public function send(string $to, string $subject, string $body): void;
}

class Mailer implements MailerInterface 
{
    public function send(string $to, string $subject, string $body): void 
    {
        // Original mailer implementation
        // Sends email via SMTP or external service
    }
}

class LoggingMailerDecorator implements MailerInterface 
{
    public function __construct(
        private MailerInterface $mailer,
        private LoggerInterface $logger
    ) {
    }
    
    public function send(string $to, string $subject, string $body): void 
    {
        $this->logger->info("Sending email to {$to}");
        $this->mailer->send($to, $subject, $body);
        $this->logger->info("Email sent to {$to}");
    }
}

class MailerDecoratorProvider implements ServiceProviderInterface 
{
    public function getDefinitions(): array 
    {
        return [];
    }
    
    public function getExtensions(): array 
    {
        return [
            MailerInterface::class => static function (ContainerInterface $container, MailerInterface $mailer) {
                // Wrap the original mailer with logging decorator
                return new LoggingMailerDecorator($mailer, $container->get(LoggerInterface::class));
            }
        ];
    }
}
```

In this example, the extension receives the original `MailerInterface` instance and wraps it with
`LoggingMailerDecorator`, which adds logging before and after sending emails. The decorator pattern
allows you to add cross-cutting concerns like logging, caching, or monitoring without changing the
original service implementation.

## Container tags

You can tag services in the following way:

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([  
        BlueCarService::class => [
            'class' => BlueCarService::class,
            'tags' => ['car'], 
        ],
        RedCarService::class => [
            'definition' => fn () => new RedCarService(),
            'tags' => ['car'],
        ],
    ]);

$container = new Container($config);
```

Another way to tag services is setting tags via container constructor:

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([  
        BlueCarService::class => [
            'class' => BlueCarService::class,
        ],
        RedCarService::class => fn () => new RedCarService(),
    ])
    ->withTags([
        // "car" tag has references to both blue and red cars
        'car' => [BlueCarService::class, RedCarService::class]
    ]);

$container = new Container($config);
```

### Getting tagged services

You can get tagged services from the container in the following way:

```php
$container->get(\Yiisoft\Di\Reference\TagReference::id('car'));
```

The result is an array that has two instances: `BlueCarService` and `RedCarService`.

### Using tagged services in configuration

Use `TagReference` to get tagged services in configuration:

```php
[
    Garage::class => [
        '__construct()' => [
            \Yiisoft\Di\Reference\TagReference::to('car'),
        ],    
    ],
],
```

## Resetting services state

Despite stateful services isn't a great practice, these are often inevitable. When you build long-running
applications with tools like [Swoole](https://www.swoole.co.uk/) or [RoadRunner](https://roadrunner.dev/) you should
reset the state of such services every request. For this purpose you can use `StateResetter` with resetters callbacks:

```php
$resetter = new StateResetter($container);
$resetter->setResetters([
    MyServiceInterface::class => function () {
        $this->reset(); // a method of MyServiceInterface
    },
]);
```

The callback has access to the private and protected properties of the service instance,
so you can set the initial state of the service efficiently without creating a new instance.

You should trigger the reset itself after each request-response cycle. For RoadRunner, it would look like the following:

```php
while ($request = $psr7->acceptRequest()) {
    $response = $application->handle($request);
    $psr7->respond($response);
    $application->afterEmit($response);
    $container
        ->get(\Yiisoft\Di\StateResetter::class)
        ->reset();
    gc_collect_cycles();
}
```

### Setting resetters in definitions

You define the reset state for each service by providing "reset" callback in the following way:

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        EngineMarkOne::class => [
            'class' => EngineMarkOne::class,
            'setNumber()' => [42],
            'reset' => function () {
                $this->number = 42;
            },
        ],
    ]);

$container = new Container($config);
```

Note: resetters from definitions work only if you don't set `StateResetter` in definition or service providers.

### Configuring `StateResetter` manually

To 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:

```php
MyServiceInterface::class => function () {
    // ...
},
StateResetter::class => function (ContainerInterface $container) {
    $resetter = new StateResetter($container);
    $resetter->setResetters([
        MyServiceInterface::class => function () {
            $this->reset(); // a method of MyServiceInterface
        },
    ]);
    return $resetter;
}
```

## Specifying metadata for non-array definitions

To specify some metadata, such as in cases of "resetting services state" or "container tags," for non-array
definitions, you could use the following syntax:

```php
LogTarget::class => [
    'definition' => static function (LoggerInterface $logger) use ($params) {
        $target = ...
        return $target;
    },
    'reset' => function () use ($params) {
        ...
    },
],
```

Now you've explicitly moved the definition itself to "definition" key.

## Delegates

Each delegate is a callable returning a container instance that's used in case DI
can't find a service in a primary container:

```php
function (ContainerInterface $container): ContainerInterface
{

}
```

To configure delegates use extra config:

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDelegates([
        function (ContainerInterface $container): ContainerInterface {
            // ...
        }
    ]);


$container = new Container($config);
```

## Tuning for production

By default, the container validates definitions right when they're set. In the production environment, it makes sense to
turn it off:

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withValidate(false);

$container = new Container($config);
```

## Strict mode

Container may work in a strict mode, that's when you should define everything in the container explicitly.
To turn it on, use the following code:

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withStrictMode(true);

$container = new Container($config);
```

## Documentation

- [Internals](docs/internals.md)

If 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.
You may also check out other [Yii Community Resources](https://www.yiiframework.com/community).

## License

The Yii Dependency Injection is free software. It is released under the terms of the BSD License.
Please see [`LICENSE`](./LICENSE.md) for more information.

Maintained by [Yii Software](https://www.yiiframework.com/).

## Support the project

[![Open Collective](https://img.shields.io/badge/Open%20Collective-sponsor-7eadf1?logo=open%20collective&logoColor=7eadf1&labelColor=555555)](https://opencollective.com/yiisoft)

## Follow updates

[![Official website](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](https://www.yiiframework.com/)
[![Twitter](https://img.shields.io/badge/twitter-follow-1DA1F2?logo=twitter&logoColor=1DA1F2&labelColor=555555?style=flat)](https://twitter.com/yiiframework)
[![Telegram](https://img.shields.io/badge/telegram-join-1DA1F2?style=flat&logo=telegram)](https://t.me/yii3en)
[![Facebook](https://img.shields.io/badge/facebook-join-1DA1F2?style=flat&logo=facebook&logoColor=ffffff)](https://www.facebook.com/groups/yiitalk)
[![Slack](https://img.shields.io/badge/slack-join-1DA1F2?style=flat&logo=slack)](https://yiiframework.com/go/slack)


================================================
FILE: benchmarks.md
================================================
DI benchmark report
===================

### suite: 1343bd6191c6668ccf8fb4cf1a51a7b61159c825, date: 2020-04-06, stime: 20:48:28

benchmark | subject | set | revs | its | mem_peak | best | mean | mode | worst | stdev | rstdev | diff
 --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- 
ContainerBench | 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
ContainerBench | 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
ContainerBench | 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
ContainerBench | 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
ContainerBench | 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
ContainerBench | 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
ContainerBench | 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
ContainerBench | 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
ContainerBench | 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



================================================
FILE: composer.json
================================================
{
    "name": "yiisoft/di",
    "type": "library",
    "description": "Yii DI container",
    "keywords": [
        "di",
        "dependency",
        "injection",
        "container",
        "injector",
        "autowiring",
        "psr-11"
    ],
    "homepage": "https://www.yiiframework.com/",
    "license": "BSD-3-Clause",
    "support": {
        "issues": "https://github.com/yiisoft/di/issues?state=open",
        "source": "https://github.com/yiisoft/di",
        "forum": "https://www.yiiframework.com/forum/",
        "wiki": "https://www.yiiframework.com/wiki/",
        "irc": "ircs://irc.libera.chat:6697/yii",
        "chat": "https://t.me/yii3en"
    },
    "funding": [
        {
            "type": "opencollective",
            "url": "https://opencollective.com/yiisoft"
        },
        {
            "type": "github",
            "url": "https://github.com/sponsors/yiisoft"
        }
    ],
    "require": {
        "php": "8.1 - 8.5",
        "ext-mbstring": "*",
        "psr/container": "^1.1 || ^2.0",
        "yiisoft/definitions": "^3.0",
        "yiisoft/friendly-exception": "^1.1.0"
    },
    "require-dev": {
        "friendsofphp/php-cs-fixer": "^3.92.5",
        "bamarni/composer-bin-plugin": "^1.8.3",
        "league/container": "^5.1.0",
        "maglnet/composer-require-checker": "^4.7.1",
        "phpbench/phpbench": "^1.4.1",
        "phpunit/phpunit": "^10.5.46",
        "rector/rector": "^2.0.17",
        "spatie/phpunit-watcher": "^1.24",
        "yiisoft/injector": "^1.2.1",
        "yiisoft/test-support": "^3.1"
    },
    "suggest": {
        "yiisoft/injector": "^1.0",
        "phpbench/phpbench": "To run benchmarks."
    },
    "provide": {
        "psr/container-implementation": "1.0.0"
    },
    "autoload": {
        "psr-4": {
            "Yiisoft\\Di\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Yiisoft\\Di\\Tests\\": "tests"
        }
    },
    "scripts": {
        "test": "phpunit --testdox --no-interaction",
        "test-watch": "phpunit-watcher watch"
    },
    "scripts-descriptions": {
        "test": "Run all tests"
    },
    "extra": {
        "bamarni-bin": {
            "bin-links": true,
            "target-directory": "tools",
            "forward-command": true
        }
    },
    "config": {
        "sort-packages": true,
        "allow-plugins": {
            "bamarni/composer-bin-plugin": true,
            "composer/package-versions-deprecated": true
        }
    }
}


================================================
FILE: docs/internals.md
================================================
# Internals

## Further reading

- [Martin Fowler's article](https://martinfowler.com/articles/injection.html).

## Benchmarks

To run benchmarks execute the next command

```shell
./vendor/bin/phpbench run
```

Result example

```text
\Yiisoft\Di\Tests\Benchmark\ContainerBench

benchConstructStupid....................I4 [μ Mo]/r: 438.566 435.190 (μs) [μSD μRSD]/r: 9.080μs 2.07%
benchConstructSmart.....................I4 [μ Mo]/r: 470.958 468.942 (μs) [μSD μRSD]/r: 2.848μs 0.60%
benchSequentialLookups # 0..............R5 I4 [μ Mo]/r: 2,837.000 2,821.636 (μs) [μSD μRSD]/r: 34.123μs 1.20%
benchSequentialLookups # 1..............R1 I0 [μ Mo]/r: 12,253.600 12,278.859 (μs) [μSD μRSD]/r: 69.087μs 0.56%
benchRandomLookups # 0..................R5 I4 [μ Mo]/r: 3,142.200 3,111.290 (μs) [μSD μRSD]/r: 87.639μs 2.79%
benchRandomLookups # 1..................R1 I2 [μ Mo]/r: 13,298.800 13,337.170 (μs) [μSD μRSD]/r: 103.891μs 0.78%
benchRandomLookupsComposite # 0.........R1 I3 [μ Mo]/r: 3,351.600 3,389.104 (μs) [μSD μRSD]/r: 72.516μs 2.16%
benchRandomLookupsComposite # 1.........R1 I4 [μ Mo]/r: 13,528.200 13,502.881 (μs) [μSD μRSD]/r: 99.997μs 0.74%
\Yiisoft\Di\Tests\Benchmark\ContainerMethodHasBench

benchPredefinedExisting.................R1 I4 [μ Mo]/r: 0.115 0.114 (μs) [μSD μRSD]/r: 0.001μs 1.31%
benchUndefinedExisting..................R5 I4 [μ Mo]/r: 0.436 0.432 (μs) [μSD μRSD]/r: 0.008μs 1.89%
benchUndefinedNonexistent...............R5 I4 [μ Mo]/r: 0.946 0.942 (μs) [μSD μRSD]/r: 0.006μs 0.59%
8 subjects, 55 iterations, 5,006 revs, 0 rejects, 0 failures, 0 warnings 
(best [mean mode] worst) = 0.113 [4,483.856 4,486.051] 0.117 (μs) 
⅀T: 246,612.096μs μSD/r 43.563μs μRSD/r: 1.336%
```

> **Warning!**
> 
> These summary statistics can be misleading.
> You should always verify the individual subject statistics before drawing any conclusions.

> **Legend**
>
> - μ:  Mean time taken by all iterations in variant.
> - Mo: Mode of all iterations in variant.
> - μSD: μ standard deviation.
> - μRSD: μ relative standard deviation.
> - best: Maximum time of all iterations (minimal of all iterations).
> - mean: Mean time taken by all iterations.
> - mode: Mode of all iterations.
> - worst: Minimum time of all iterations (minimal of all iterations).

### Command examples

- Default report for all benchmarks that outputs the result to `CSV-file`

```shell
./vendor/bin/phpbench run --report=default --progress=dots  --output=csv_file
```

Generated MD-file example

```text
>DI benchmark report
>===================
>
>### suite: 1343b1dc0589cb4e985036d14b3e12cb430a975b, date: 2020-02-21, stime: 16:02:45
>
>benchmark | subject | set | revs | iter | mem_peak | time_rev | comp_z_value | comp_deviation
> --- | --- | --- | --- | --- | --- | --- | --- | ---
>ContainerBench | benchConstructStupid | 0 | 1000 | 0 | 1,416,784b | 210.938μs | -1.48σ | -1.1%
>ContainerBench | benchConstructStupid | 0 | 1000 | 1 | 1,416,784b | 213.867μs | +0.37σ | +0.27%
>ContainerBench | benchConstructStupid | 0 | 1000 | 2 | 1,416,784b | 212.890μs | -0.25σ | -0.18%
>ContainerBench | benchConstructStupid | 0 | 1000 | 3 | 1,416,784b | 215.820μs | +1.60σ | +1.19%
>ContainerBench | benchConstructStupid | 0 | 1000 | 4 | 1,416,784b | 212.891μs | -0.25σ | -0.18%
>ContainerBench | benchConstructSmart | 0 | 1000 | 0 | 1,426,280b | 232.422μs | -1.03σ | -0.5%
>ContainerBench | benchConstructSmart | 0 | 1000 | 1 | 1,426,280b | 232.422μs | -1.03σ | -0.5%
>ContainerBench | benchConstructSmart | 0 | 1000 | 2 | 1,426,280b | 233.398μs | -0.17σ | -0.08%
>ContainerBench | benchConstructSmart | 0 | 1000 | 3 | 1,426,280b | 234.375μs | +0.69σ | +0.33%
>ContainerBench | benchConstructSmart | 0 | 1000 | 4 | 1,426,280b | 235.351μs | +1.54σ | +0.75%
>`... skipped` | `...` | `...` | `...` | `...` | `...` | `...` | `...` | `...`
>ContainerMethodHasBench | benchPredefinedExisting | 0 | 1000 | 0 | 1,216,144b | 81.055μs | -0.91σ | -1.19%
>ContainerMethodHasBench | benchPredefinedExisting | 0 | 1000 | 1 | 1,216,144b | 83.985μs | +1.83σ | +2.38%
>ContainerMethodHasBench | benchPredefinedExisting | 0 | 1000 | 2 | 1,216,144b | 82.032μs | 0.00σ | 0.00%
>ContainerMethodHasBench | benchPredefinedExisting | 0 | 1000 | 3 | 1,216,144b | 82.031μs | 0.00σ | 0.00%
>ContainerMethodHasBench | benchPredefinedExisting | 0 | 1000 | 4 | 1,216,144b | 81.055μs | -0.91σ | -1.19%
>`... skipped` | `...` | `...` | `...` | `...` | `...` | `...` | `...` | `...`
```

> **Legend**
>
> - benchmark: Benchmark class.
> - subject: Benchmark class method.
> - set: Set of data (provided by ParamProvider).
> - revs: Number of revolutions (represent the number of times that the code is executed).
> - iter: Number of iteration.
> - mem_peak: (mean) Peak memory used by iteration as retrieved by memory_get_peak_usage.
> - time_rev:  Mean time taken by all iterations in variant.
> - comp_z_value: Z-score.
> - comp_deviation: Relative deviation (margin of error).

- Aggregate report for the `lookup` group that outputs the result to `console` and `CSV-file`

```shell
./vendor/bin/phpbench run --report=aggregate --progress=dots  --output=csv_file --output=console --group=lookup
```

> **Notice**
>
> Available groups: `construct` `lookup` `has`

Generated MD-file example

```text
> DI benchmark report
> ===================
>
>### suite: 1343b1d2654a3819c72a96d236302b70a504dac7, date: 2020-02-21, stime: 13:27:32
>
>benchmark | subject | set | revs | its | mem_peak | best | mean | mode | worst | stdev | rstdev | diff
> --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
>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
>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
>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
>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
>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
>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
>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
>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
>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
```

> **Legend**
>
>   * benchmark: Benchmark class.
>   * subject: Benchmark class method.
>   * set: Set of data (provided by ParamProvider).
>   * revs: Number of revolutions (represent the number of times that the code is executed).
>   * its: Number of iterations (one measurement for each iteration).   
>   * mem_peak: (mean) Peak memory used by each iteration as retrieved by memory_get_peak_usage.
>   * best: Maximum time of all iterations in variant.
>   * mean: Mean time taken by all iterations in variant.
>   * mode: Mode of all iterations in variant.
>   * worst: Minimum time of all iterations in variant.
>   * stdev: Standard deviation.
>   * rstdev: The relative standard deviation.
>   * diff: Difference between variants in a single group.

## Unit testing

The package is tested with [PHPUnit](https://phpunit.de/). To run tests:

```shell
./vendor/bin/phpunit
```

## Mutation testing

The package tests are checked with [Infection](https://infection.github.io/) mutation framework with
[Infection Static Analysis Plugin](https://github.com/Roave/infection-static-analysis-plugin). To run it:

```shell
./vendor/bin/roave-infection-static-analysis-plugin
```

## Static analysis

The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis:

```shell
./vendor/bin/psalm
```

## Code style

Use [Rector](https://github.com/rectorphp/rector) to make codebase follow some specific rules or
use either newest or any specific version of PHP:

```shell
./vendor/bin/rector
```

## Dependencies

Use [ComposerRequireChecker](https://github.com/maglnet/ComposerRequireChecker) to detect transitive
[Composer](https://getcomposer.org/) dependencies. To run the checker, execute the following command:

```shell
./vendor/bin/composer-require-checker
```


================================================
FILE: infection.json.dist
================================================
{
    "source": {
        "directories": [
            "src"
        ]
    },
    "logs": {
        "text": "php:\/\/stderr",
        "stryker": {
            "report": "master"
        }
    },
    "mutators": {
        "@default": true
    }
}


================================================
FILE: phpbench.json
================================================
{
    "runner.bootstrap": "vendor/autoload.php",
    "runner.path": "tests/Benchmark",
    "runner.retry_threshold": 3,
    "report.outputs": {
        "csv_file": {
            "extends": "delimited",
            "delimiter": ",",
            "file": "benchmarks.csv"
        }
    }
}


================================================
FILE: phpcs.xml.dist
================================================
<?xml version="1.0"?>
<ruleset>
    <file>./</file>
    <exclude-pattern>./vendor/*</exclude-pattern>
    <rule ref="PSR2" />
</ruleset>

================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         cacheDirectory=".phpunit.cache"
         requireCoverageMetadata="false"
         beStrictAboutCoverageMetadata="true"
         beStrictAboutOutputDuringTests="true"
         executionOrder="random"
         failOnRisky="true"
         failOnWarning="true"
         stopOnFailure="false"
         colors="true"
         displayDetailsOnPhpunitDeprecations="true"
>
    <php>
        <ini name="error_reporting" value="-1"/>
    </php>

    <testsuites>
        <testsuite name="Yii DI Container tests">
            <directory>./tests/Unit</directory>
        </testsuite>
    </testsuites>

    <source>
        <include>
            <directory suffix=".php">./src</directory>
        </include>
    </source>
</phpunit>


================================================
FILE: psalm.xml
================================================
<?xml version="1.0"?>
<psalm
    errorLevel="1"
    findUnusedBaselineEntry="true"
    findUnusedCode="false"
    ensureOverrideAttribute="false"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="src" />
        <ignoreFiles>
            <directory name="vendor" />
        </ignoreFiles>
    </projectFiles>
    <issueHandlers>
        <MixedAssignment errorLevel="suppress" />
    </issueHandlers>
</psalm>


================================================
FILE: rector.php
================================================
<?php

declare(strict_types=1);

use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
use Rector\Config\RectorConfig;
use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector;
use Rector\Php81\Rector\Array_\ArrayToFirstClassCallableRector;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ])
    ->withPhpSets(php81: true)
    ->withRules([
        InlineConstructorDefaultToPropertyRector::class,
    ])
    ->withSkip([
        ClosureToArrowFunctionRector::class,
        ArrayToFirstClassCallableRector::class => [
            __DIR__ . '/tests/Unit/Helpers/DefinitionParserTest.php',
        ],
    ]);


================================================
FILE: src/BuildingException.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

use Exception;
use Psr\Container\ContainerExceptionInterface;
use Throwable;
use Yiisoft\FriendlyException\FriendlyExceptionInterface;

use function sprintf;

/**
 * It wraps all exceptions that don't implement `ContainerExceptionInterface` during the build process.
 * Also adds building context for more understanding.
 */
final class BuildingException extends Exception implements ContainerExceptionInterface, FriendlyExceptionInterface
{
    /**
     * @param string $id ID of the definition or name of the class that wasn't found.
     * @param string[] $buildStack Stack of IDs of services requested definition or class that wasn't found.
     */
    public function __construct(
        private readonly string $id,
        Throwable $error,
        array $buildStack = [],
        ?Throwable $previous = null,
    ) {
        $message = sprintf(
            'Caught unhandled error "%s" while building "%s".',
            $error->getMessage() === '' ? $error::class : $error->getMessage(),
            implode('" -> "', $buildStack === [] ? [$id] : $buildStack),
        );

        parent::__construct($message, 0, $previous);
    }

    public function getName(): string
    {
        return sprintf('Unable to build "%s" object.', $this->id);
    }

    public function getSolution(): ?string
    {
        $solution = <<<SOLUTION
            Ensure that either a service with ID "%1\$s" is defined or such class exists and is autoloadable.

            Ensure that configuration for service with ID "%1\$s" is correct.
            SOLUTION;

        return sprintf($solution, $this->id);
    }
}


================================================
FILE: src/CompositeContainer.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use RuntimeException;
use Throwable;
use Yiisoft\Di\Reference\TagReference;

use function is_string;
use function sprintf;

/**
 * A composite container for use with containers that support the delegate lookup feature.
 */
final class CompositeContainer implements ContainerInterface
{
    /**
     * Containers to look into starting from the beginning of the array.
     *
     * @var ContainerInterface[] The list of containers.
     */
    private array $containers = [];

    /**
     * @psalm-template T
     * @psalm-param string|class-string<T> $id
     * @psalm-return ($id is class-string ? T : mixed)
     */
    public function get($id)
    {
        /** @psalm-suppress TypeDoesNotContainType */
        if (!is_string($id)) {
            throw new InvalidArgumentException(
                sprintf(
                    'ID must be a string, %s given.',
                    get_debug_type($id),
                ),
            );
        }

        if ($id === StateResetter::class) {
            $resetters = [];
            foreach ($this->containers as $container) {
                if ($container->has(StateResetter::class)) {
                    $resetters[] = $container->get(StateResetter::class);
                }
            }
            $stateResetter = new StateResetter($this);
            $stateResetter->setResetters($resetters);

            return $stateResetter;
        }

        if (TagReference::isTagAlias($id)) {
            $tags = [];
            foreach ($this->containers as $container) {
                if (!$container instanceof Container) {
                    continue;
                }
                if ($container->has($id)) {
                    /** @psalm-suppress MixedArgument `Container::get()` always return array for tag */
                    array_unshift($tags, $container->get($id));
                }
            }

            /** @psalm-suppress MixedArgument `Container::get()` always return array for tag */
            return array_merge(...$tags);
        }

        foreach ($this->containers as $container) {
            if ($container->has($id)) {
                /** @psalm-suppress MixedReturnStatement */
                return $container->get($id);
            }
        }

        // Collect details from containers
        $exceptions = [];
        foreach ($this->containers as $container) {
            $hasException = false;
            try {
                $container->get($id);
            } catch (Throwable $t) {
                $hasException = true;
                $exceptions[] = [$t, $container];
            } finally {
                if (!$hasException) {
                    $exceptions[] = [
                        new RuntimeException(
                            'Container "has()" returned false, but no exception was thrown from "get()".',
                        ),
                        $container,
                    ];
                }
            }
        }

        throw new CompositeNotFoundException($exceptions);
    }

    public function has($id): bool
    {
        /** @psalm-suppress TypeDoesNotContainType */
        if (!is_string($id)) {
            throw new InvalidArgumentException(
                sprintf(
                    'ID must be a string, %s given.',
                    get_debug_type($id),
                ),
            );
        }

        if ($id === StateResetter::class) {
            return true;
        }

        if (TagReference::isTagAlias($id)) {
            foreach ($this->containers as $container) {
                if (!$container instanceof Container) {
                    continue;
                }
                if ($container->has($id)) {
                    return true;
                }
            }
            return false;
        }

        foreach ($this->containers as $container) {
            if ($container->has($id)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Attaches a container to the composite container.
     */
    public function attach(ContainerInterface $container): void
    {
        $this->containers[] = $container;
    }

    /**
     * Removes a container from the list of containers.
     */
    public function detach(ContainerInterface $container): void
    {
        foreach ($this->containers as $i => $c) {
            if ($container === $c) {
                unset($this->containers[$i]);
            }
        }
    }
}


================================================
FILE: src/CompositeNotFoundException.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

use Exception;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;

use function sprintf;

/**
 * `CompositeNotFoundException` is thrown when no definition or class was found in the composite container
 * for a given ID. It contains all exceptions thrown by containers registered in the composite container.
 */
final class CompositeNotFoundException extends Exception implements NotFoundExceptionInterface
{
    /**
     * @param array $exceptions Container exceptions in [throwable, container] format.
     *
     * @psalm-param list<array{\Throwable,ContainerInterface}> $exceptions
     */
    public function __construct(array $exceptions)
    {
        $message = '';

        foreach ($exceptions as $i => [$exception, $container]) {
            $containerClass = $container::class;
            $containerId = spl_object_id($container);
            $number = $i + 1;

            $message .= "\n    $number. Container $containerClass #$containerId: {$exception->getMessage()}";
        }

        parent::__construct(sprintf('No definition or class found or resolvable in composite container:%s', $message));
    }
}


================================================
FILE: src/Container.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

use Closure;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
use Yiisoft\Definitions\ArrayDefinition;
use Yiisoft\Definitions\DefinitionStorage;
use Yiisoft\Definitions\Exception\CircularReferenceException;
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Definitions\Exception\NotInstantiableException;
use Yiisoft\Definitions\Helpers\DefinitionValidator;
use Yiisoft\Di\Helpers\DefinitionNormalizer;
use Yiisoft\Di\Helpers\DefinitionParser;
use Yiisoft\Di\Reference\TagReference;

use function array_key_exists;
use function array_keys;
use function implode;
use function in_array;
use function is_array;
use function is_callable;
use function is_object;
use function is_string;
use function sprintf;

/**
 * Container implements a [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) container.
 */
final class Container implements ContainerInterface
{
    private const META_TAGS = 'tags';
    private const META_RESET = 'reset';
    private const ALLOWED_META = [self::META_TAGS, self::META_RESET];

    /**
     * @var DefinitionStorage Storage of object definitions.
     */
    private readonly DefinitionStorage $definitions;

    /**
     * @var array Used to collect IDs of objects instantiated during build
     * to detect circular references.
     */
    private array $building = [];

    /**
     * @var bool $validate If definitions should be validated.
     */
    private readonly bool $validate;

    /**
     * @var array Cached instances.
     * @psalm-var array<string, mixed>
     */
    private array $instances = [];

    private CompositeContainer $delegates;

    /**
     * @var array Tagged service IDs. The structure is `['tagID' => ['service1', 'service2']]`.
     * @psalm-var array<string, list<string>>
     */
    private array $tags;

    /**
     * @var Closure[]
     * @psalm-var array<string, Closure>
     */
    private array $resetters = [];
    private bool $useResettersFromMeta = true;

    /**
     * @param ?ContainerConfigInterface $config Container configuration.
     *
     * @throws InvalidConfigException If configuration is not valid.
     */
    public function __construct(?ContainerConfigInterface $config = null)
    {
        $config ??= ContainerConfig::create();

        $this->definitions = new DefinitionStorage(
            [
                ContainerInterface::class => $this,
                StateResetter::class => StateResetter::class,
            ],
            $config->useStrictMode(),
        );
        $this->validate = $config->shouldValidate();
        $this->setTags($config->getTags());
        $this->addDefinitions($config->getDefinitions());
        $this->addProviders($config->getProviders());
        $this->setDelegates($config->getDelegates());
    }

    /**
     * Returns a value indicating whether the container has the definition of the specified name.
     *
     * @param string $id Class name, interface name or alias name.
     *
     * @return bool Whether the container is able to provide instance of class specified.
     *
     * @see addDefinition()
     */
    public function has(string $id): bool
    {
        try {
            if ($this->definitions->has($id)) {
                return true;
            }
        } catch (CircularReferenceException) {
            return true;
        }

        if (TagReference::isTagAlias($id)) {
            $tag = TagReference::extractTagFromAlias($id);
            return isset($this->tags[$tag]);
        }

        return false;
    }

    /**
     * Returns an instance by either interface name or alias.
     *
     * The same instance of the class will be returned each time this method is called.
     *
     * @param string $id The interface or an alias name that was previously registered.
     *
     * @throws CircularReferenceException
     * @throws InvalidConfigException
     * @throws NotFoundExceptionInterface
     * @throws NotInstantiableException
     * @throws BuildingException
     *
     * @return mixed An instance of the requested interface.
     *
     * @psalm-template T
     * @psalm-param string|class-string<T> $id
     * @psalm-return ($id is class-string ? T : mixed)
     *
     * @psalm-suppress MixedReturnStatement `mixed` is a correct return type for this method.
     */
    public function get(string $id)
    {
        // Fast path: check if instance exists.
        if (array_key_exists($id, $this->instances)) {
            if ($id === StateResetter::class) {
                return $this->prepareStateResetter();
            }
            return $this->instances[$id];
        }

        try {
            $this->instances[$id] = $this->build($id);
        } catch (NotFoundException $exception) {
            // Fast path: if the exception ID matches the requested ID, no need to modify stack.
            if ($exception->getId() === $id) {
                // Try delegates before giving up.
                try {
                    if ($this->delegates->has($id)) {
                        return $this->delegates->get($id);
                    }
                } catch (Throwable $e) {
                    throw new BuildingException($id, $e, $this->definitions->getBuildStack(), $e);
                }
                throw $exception;
            }

            // Add current ID to build stack for better error reporting.
            $buildStack = $exception->getBuildStack();
            array_unshift($buildStack, $id);
            throw new NotFoundException($exception->getId(), $buildStack);
        } catch (NotFoundExceptionInterface $exception) {
            // Try delegates before giving up
            try {
                if ($this->delegates->has($id)) {
                    return $this->delegates->get($id);
                }
            } catch (Throwable $e) {
                throw new BuildingException($id, $e, $this->definitions->getBuildStack(), $e);
            }

            throw new NotFoundException($id, [$id], previous: $exception);
        } catch (ContainerExceptionInterface $e) {
            if (!$e instanceof InvalidConfigException) {
                throw $e;
            }
            throw new BuildingException($id, $e, $this->definitions->getBuildStack(), $e);
        } catch (Throwable $e) {
            throw new BuildingException($id, $e, $this->definitions->getBuildStack(), $e);
        }

        // Handle StateResetter for newly built instances.
        if ($id === StateResetter::class) {
            return $this->prepareStateResetter();
        }

        return $this->instances[$id];
    }

    private function prepareStateResetter(): StateResetter
    {
        $delegatesResetter = null;
        if ($this->delegates->has(StateResetter::class)) {
            $delegatesResetter = $this->delegates->get(StateResetter::class);
        }

        /** @var StateResetter $mainResetter */
        $mainResetter = $this->instances[StateResetter::class];

        if ($this->useResettersFromMeta) {
            /** @var StateResetter[] $resetters */
            $resetters = [];
            foreach ($this->resetters as $serviceId => $callback) {
                if (isset($this->instances[$serviceId])) {
                    $resetters[$serviceId] = $callback;
                }
            }
            if ($delegatesResetter !== null) {
                $resetters[] = $delegatesResetter;
            }
            $mainResetter->setResetters($resetters);
        } elseif ($delegatesResetter !== null) {
            $resetter = new StateResetter($this->get(ContainerInterface::class));
            $resetter->setResetters([$mainResetter, $delegatesResetter]);

            return $resetter;
        }

        return $mainResetter;
    }

    /**
     * Sets a definition to the container. Definition may be defined multiple ways.
     *
     * @param string $id ID to set definition for.
     * @param mixed $definition Definition to set.
     *
     * @throws InvalidConfigException
     *
     * @see DefinitionNormalizer::normalize()
     */
    private function addDefinition(string $id, mixed $definition): void
    {
        [$definition, $meta] = DefinitionParser::parse($definition);
        if ($this->validate) {
            $this->validateDefinition($definition, $id);
            // Only validate meta if it's not empty.
            if ($meta !== []) {
                $this->validateMeta($meta);
            }
        }
        /**
         * @psalm-var array{reset?:Closure,tags?:string[]} $meta
         */

        // Process meta only if it has tags or reset callback.
        if (isset($meta[self::META_TAGS])) {
            $this->setDefinitionTags($id, $meta[self::META_TAGS]);
        }
        if (isset($meta[self::META_RESET])) {
            $this->setDefinitionResetter($id, $meta[self::META_RESET]);
        }

        unset($this->instances[$id]);

        $this->addDefinitionToStorage($id, $definition);
    }

    /**
     * Sets multiple definitions at once.
     *
     * @param array $config Definitions indexed by their IDs.
     *
     * @throws InvalidConfigException
     */
    private function addDefinitions(array $config): void
    {
        foreach ($config as $id => $definition) {
            if ($this->validate && !is_string($id)) {
                throw new InvalidConfigException(
                    sprintf(
                        'Key must be a string. %s given.',
                        get_debug_type($id),
                    ),
                );
            }
            /** @var string $id */

            $this->addDefinition($id, $definition);
        }
    }

    /**
     * Set container delegates.
     *
     * Each delegate must be a callable in format `function (ContainerInterface $container): ContainerInterface`.
     * The container instance returned is used in case a service can't be found in primary container.
     *
     * @throws InvalidConfigException
     */
    private function setDelegates(array $delegates): void
    {
        $this->delegates = new CompositeContainer();

        $container = $this->get(ContainerInterface::class);

        foreach ($delegates as $delegate) {
            if (!$delegate instanceof Closure) {
                throw new InvalidConfigException(
                    'Delegate must be callable in format "function (ContainerInterface $container): ContainerInterface".',
                );
            }

            $delegate = $delegate($container);

            if (!$delegate instanceof ContainerInterface) {
                throw new InvalidConfigException(
                    'Delegate callable must return an object that implements ContainerInterface.',
                );
            }

            $this->delegates->attach($delegate);
        }
        $this->definitions->setDelegateContainer($this->delegates);
    }

    /**
     * @param mixed $definition Definition to validate.
     * @param string|null $id ID of the definition to validate.
     *
     * @throws InvalidConfigException
     */
    private function validateDefinition(mixed $definition, ?string $id = null): void
    {
        // Skip validation for common simple cases.
        if ($definition instanceof ContainerInterface || $definition instanceof Closure) {
            return;
        }

        if (is_array($definition)) {
            if (isset($definition[DefinitionParser::IS_PREPARED_ARRAY_DEFINITION_DATA])) {
                $class = $definition['class'];
                $constructorArguments = $definition['__construct()'];

                /**
                 * @var array $methodsAndProperties Is always array for prepared array definition data.
                 * @see DefinitionParser::parse()
                 * @psalm-var array<string,mixed> $methodsAndProperties
                 */
                $methodsAndProperties = $definition['methodsAndProperties'];

                $definition = array_merge(
                    $class === null ? [] : [ArrayDefinition::CLASS_NAME => $class],
                    [ArrayDefinition::CONSTRUCTOR => $constructorArguments],
                    // extract only value from parsed definition method
                    array_map(static fn(array $data): mixed => $data[2], $methodsAndProperties),
                );
            }
        } elseif ($definition instanceof ExtensibleService) {
            throw new InvalidConfigException(
                'Invalid definition. ExtensibleService is only allowed in provider extensions.',
            );
        }

        DefinitionValidator::validate($definition, $id);
    }

    /**
     * @throws InvalidConfigException
     */
    private function validateMeta(array $meta): void
    {
        foreach ($meta as $key => $value) {
            if (!in_array($key, self::ALLOWED_META, true)) {
                throw new InvalidConfigException(
                    sprintf(
                        'Invalid definition: metadata "%s" is not allowed. Did you mean "%s()" or "$%s"?',
                        $key,
                        $key,
                        $key,
                    ),
                );
            }

            if ($key === self::META_TAGS) {
                $this->validateDefinitionTags($value);
            }

            if ($key === self::META_RESET) {
                $this->validateDefinitionReset($value);
            }
        }
    }

    /**
     * @throws InvalidConfigException
     */
    private function validateDefinitionTags(mixed $tags): void
    {
        if (!is_array($tags)) {
            throw new InvalidConfigException(
                sprintf(
                    'Invalid definition: tags should be array of strings, %s given.',
                    get_debug_type($tags),
                ),
            );
        }

        foreach ($tags as $tag) {
            if (!is_string($tag)) {
                throw new InvalidConfigException('Invalid tag. Expected a string, got ' . var_export($tag, true) . '.');
            }
        }
    }

    /**
     * @throws InvalidConfigException
     */
    private function validateDefinitionReset(mixed $reset): void
    {
        if (!$reset instanceof Closure) {
            throw new InvalidConfigException(
                sprintf(
                    'Invalid definition: "reset" should be closure, %s given.',
                    get_debug_type($reset),
                ),
            );
        }
    }

    /**
     * @throws InvalidConfigException
     */
    private function setTags(array $tags): void
    {
        if ($this->validate) {
            foreach ($tags as $tag => $services) {
                if (!is_string($tag)) {
                    throw new InvalidConfigException(
                        sprintf(
                            'Invalid tags configuration: tag should be string, %s given.',
                            $tag,
                        ),
                    );
                }
                if (!is_array($services)) {
                    throw new InvalidConfigException(
                        sprintf(
                            'Invalid tags configuration: tag should contain array of service IDs, %s given.',
                            get_debug_type($services),
                        ),
                    );
                }
                foreach ($services as $service) {
                    if (!is_string($service)) {
                        throw new InvalidConfigException(
                            sprintf(
                                'Invalid tags configuration: service should be defined as class string, %s given.',
                                get_debug_type($service),
                            ),
                        );
                    }
                }
            }
        }
        /** @psalm-var array<string, list<string>> $tags */

        $this->tags = $tags;
    }

    /**
     * @psalm-param string[] $tags
     */
    private function setDefinitionTags(string $id, array $tags): void
    {
        foreach ($tags as $tag) {
            if (!isset($this->tags[$tag]) || !in_array($id, $this->tags[$tag], true)) {
                $this->tags[$tag][] = $id;
            }
        }
    }

    private function setDefinitionResetter(string $id, Closure $resetter): void
    {
        $this->resetters[$id] = $resetter;
    }

    /**
     * Add definition to storage.
     *
     * @param string $id ID to set definition for.
     * @param mixed|object $definition Definition to set.
     *
     * @see $definitions
     */
    private function addDefinitionToStorage(string $id, mixed $definition): void
    {
        $this->definitions->set($id, $definition);

        if ($id === StateResetter::class) {
            $this->useResettersFromMeta = false;
        }
    }

    /**
     * Creates new instance by either interface name or alias.
     *
     * @param string $id The interface or the alias name that was previously registered.
     *
     * @throws InvalidConfigException
     * @throws NotFoundExceptionInterface
     * @throws CircularReferenceException
     *
     * @return mixed|object New-built instance of the specified class.
     *
     * @internal
     */
    private function build(string $id): mixed
    {
        // Fast path: check for circular reference first as it's the most critical.
        if (isset($this->building[$id])) {
            if ($id === ContainerInterface::class) {
                return $this;
            }
            throw new CircularReferenceException(
                sprintf(
                    'Circular reference to "%s" detected while building: %s.',
                    $id,
                    implode(', ', array_keys($this->building)),
                ),
            );
        }

        // Less common case: tag alias.
        if (TagReference::isTagAlias($id)) {
            return $this->getTaggedServices($id);
        }

        // Check if the definition exists.
        if (!$this->definitions->has($id)) {
            throw new NotFoundException($id, $this->definitions->getBuildStack());
        }

        $this->building[$id] = 1;
        try {
            $normalizedDefinition = DefinitionNormalizer::normalize($this->definitions->get($id), $id);
            $object = $normalizedDefinition->resolve($this->get(ContainerInterface::class));
        } finally {
            unset($this->building[$id]);
        }

        return $object;
    }

    private function getTaggedServices(string $tagAlias): array
    {
        $tag = TagReference::extractTagFromAlias($tagAlias);
        $services = [];
        if (isset($this->tags[$tag])) {
            foreach ($this->tags[$tag] as $service) {
                $services[] = $this->get($service);
            }
        }

        return $services;
    }

    /**
     * @throws CircularReferenceException
     * @throws InvalidConfigException
     */
    private function addProviders(array $providers): void
    {
        $extensions = [];
        foreach ($providers as $provider) {
            $providerInstance = $this->buildProvider($provider);
            $extensions[] = $providerInstance->getExtensions();
            $this->addDefinitions($providerInstance->getDefinitions());
        }

        foreach ($extensions as $providerExtensions) {
            foreach ($providerExtensions as $id => $extension) {
                if (!is_string($id)) {
                    throw new InvalidConfigException(
                        sprintf('Extension key must be a service ID as string, %s given.', $id),
                    );
                }

                if ($id === ContainerInterface::class) {
                    throw new InvalidConfigException('ContainerInterface extensions are not allowed.');
                }

                if (!$this->definitions->has($id)) {
                    throw new InvalidConfigException("Extended service \"$id\" doesn't exist.");
                }

                if (!is_callable($extension)) {
                    throw new InvalidConfigException(
                        sprintf(
                            'Extension of service should be callable, %s given.',
                            get_debug_type($extension),
                        ),
                    );
                }

                $definition = $this->definitions->get($id);
                if (!$definition instanceof ExtensibleService) {
                    $definition = new ExtensibleService($definition, $id);
                    $this->addDefinitionToStorage($id, $definition);
                }

                $definition->addExtension($extension);
            }
        }
    }

    /**
     * Builds service provider by definition.
     *
     * @param mixed $provider Class name or instance of provider.
     *
     * @throws InvalidConfigException If provider argument is not valid.
     *
     * @return ServiceProviderInterface Instance of service provider.
     */
    private function buildProvider(mixed $provider): ServiceProviderInterface
    {
        if ($this->validate && !(is_string($provider) || $provider instanceof ServiceProviderInterface)) {
            throw new InvalidConfigException(
                sprintf(
                    'Service provider should be a class name or an instance of %s. %s given.',
                    ServiceProviderInterface::class,
                    get_debug_type($provider),
                ),
            );
        }

        /**
         * @psalm-suppress MixedMethodCall Service provider defined as class string
         * should container public constructor, otherwise throws error.
         */
        $providerInstance = is_object($provider) ? $provider : new $provider();
        if (!$providerInstance instanceof ServiceProviderInterface) {
            throw new InvalidConfigException(
                sprintf(
                    'Service provider should be an instance of %s. %s given.',
                    ServiceProviderInterface::class,
                    get_debug_type($providerInstance),
                ),
            );
        }

        return $providerInstance;
    }
}


================================================
FILE: src/ContainerConfig.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

/**
 * Container configuration.
 */
final class ContainerConfig implements ContainerConfigInterface
{
    private array $definitions = [];
    private array $providers = [];
    private array $tags = [];
    private bool $validate = true;
    private array $delegates = [];
    private bool $useStrictMode = false;

    private function __construct() {}

    public static function create(): self
    {
        return new self();
    }

    /**
     * @param array $definitions Definitions to put into container.
     */
    public function withDefinitions(array $definitions): self
    {
        $new = clone $this;
        $new->definitions = $definitions;
        return $new;
    }

    public function getDefinitions(): array
    {
        return $this->definitions;
    }

    /**
     * @param array $providers Service providers to get definitions from.
     */
    public function withProviders(array $providers): self
    {
        $new = clone $this;
        $new->providers = $providers;
        return $new;
    }

    public function getProviders(): array
    {
        return $this->providers;
    }

    /**
     * @param array $tags Tagged service IDs. The structure is `['tagID' => ['service1', 'service2']]`.
     */
    public function withTags(array $tags): self
    {
        $new = clone $this;
        $new->tags = $tags;
        return $new;
    }

    public function getTags(): array
    {
        return $this->tags;
    }

    /**
     * @param bool $validate Whether definitions should be validated immediately.
     */
    public function withValidate(bool $validate = true): self
    {
        $new = clone $this;
        $new->validate = $validate;
        return $new;
    }

    public function shouldValidate(): bool
    {
        return $this->validate;
    }

    /**
     * @param array $delegates Container delegates. Each delegate is a callable in format
     * `function (ContainerInterface $container): ContainerInterface`. The container instance returned is used
     * in case a service can't be found in primary container.
     */
    public function withDelegates(array $delegates): self
    {
        $new = clone $this;
        $new->delegates = $delegates;
        return $new;
    }

    public function getDelegates(): array
    {
        return $this->delegates;
    }

    /**
     * @param bool $useStrictMode If the automatic addition of definition when class exists and can be resolved
     * is disabled.
     */
    public function withStrictMode(bool $useStrictMode = true): self
    {
        $new = clone $this;
        $new->useStrictMode = $useStrictMode;
        return $new;
    }

    public function useStrictMode(): bool
    {
        return $this->useStrictMode;
    }
}


================================================
FILE: src/ContainerConfigInterface.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

/**
 * Container configuration.
 */
interface ContainerConfigInterface
{
    /**
     * @return array Definitions to put into container.
     */
    public function getDefinitions(): array;

    /**
     * @return array Service providers to get definitions from.
     */
    public function getProviders(): array;

    /**
     * @return array Tagged service IDs. The structure is `['tagID' => ['service1', 'service2']]`.
     */
    public function getTags(): array;

    /**
     * @return bool Whether definitions should be validated immediately.
     */
    public function shouldValidate(): bool;

    /**
     * @return array Container delegates. Each delegate is a callable in format
     * `function (ContainerInterface $container): ContainerInterface`. The container instance returned is used
     * in case a service can't be found in primary container.
     */
    public function getDelegates(): array;

    /**
     * @return bool If the automatic addition of definition when class exists and can be resolved is disabled.
     */
    public function useStrictMode(): bool;
}


================================================
FILE: src/ExtensibleService.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

use Psr\Container\ContainerInterface;
use Yiisoft\Definitions\Contract\DefinitionInterface;
use Yiisoft\Di\Helpers\DefinitionNormalizer;

/**
 * @internal A wrapper for a service definition that allows registering extensions.
 * An extension is callable that returns a modified service object:
 *
 * ```php
 * static function (ContainerInterface $container, $service) {
 *     return $service->withAnotherOption(42);
 * }
 * ```
 */
final class ExtensibleService implements DefinitionInterface
{
    /**
     * @var callable[]
     */
    private array $extensions = [];

    /**
     * @param mixed $definition Definition to allow registering extensions for.
     */
    public function __construct(
        private readonly mixed $definition,
        private readonly string $id,
    ) {}

    /**
     * Add an extension.
     *
     * An extension is callable that returns a modified service object:
     *
     * ```php
     * static function (ContainerInterface $container, $service) {
     *     return $service->withAnotherOption(42);
     * }
     * ```
     *
     * @param callable $closure An extension to register.
     */
    public function addExtension(callable $closure): void
    {
        $this->extensions[] = $closure;
    }

    public function resolve(ContainerInterface $container): mixed
    {
        $service = DefinitionNormalizer::normalize($this->definition, $this->id)
            ->resolve($container);

        foreach ($this->extensions as $extension) {
            $result = $extension($container->get(ContainerInterface::class), $service);
            if ($result === null) {
                continue;
            }

            $service = $result;
        }

        return $service;
    }
}


================================================
FILE: src/Helpers/DefinitionNormalizer.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Helpers;

use Yiisoft\Definitions\ArrayDefinition;
use Yiisoft\Definitions\Contract\DefinitionInterface;
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Definitions\Helpers\Normalizer;
use Yiisoft\Di\ExtensibleService;

use function is_array;

/**
 * @internal Normalizes a definition.
 */
final class DefinitionNormalizer
{
    /**
     * @param mixed $definition Definition to normalize.
     * @param string $id Service ID.
     *
     * @throws InvalidConfigException If configuration is not valid.
     */
    public static function normalize(mixed $definition, string $id): DefinitionInterface
    {
        if (is_array($definition) && isset($definition[DefinitionParser::IS_PREPARED_ARRAY_DEFINITION_DATA])) {
            /** @psalm-suppress MixedArgument Definition should be valid {@see Container::$validate} */
            return ArrayDefinition::fromPreparedData(
                $definition['class'] ?? $id,
                $definition['__construct()'],
                $definition['methodsAndProperties'],
            );
        }

        if ($definition instanceof ExtensibleService) {
            return $definition;
        }

        return Normalizer::normalize($definition, $id);
    }
}


================================================
FILE: src/Helpers/DefinitionParser.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Helpers;

use Yiisoft\Definitions\ArrayDefinition;

use function count;
use function is_array;
use function is_callable;
use function is_string;

/**
 * @internal Splits metadata and definition.
 *
 * Supports the following configuration:
 *
 * 1) With a dedicated definition:
 *
 * ```php
 * Engine::class => [
 *     'definition' => [
 *         '__class' => BigEngine::class,
 *         'setNumber()' => [42],
 *     ],
 *     'tags' => ['a', 'b'],
 *     'reset' => function () {
 *         $this->number = 42;
 *      },
 * ]
 * ```
 *
 * 2) Mixed in array definition:
 *
 * ```php
 * Engine::class => [
 *     '__class' => BigEngine::class,
 *     'setNumber()' => [42],
 *     'tags' => ['a', 'b'],
 *     'reset' => function () {
 *         $this->number = 42;
 *      },
 * ]
 * ```
 */
final class DefinitionParser
{
    public const IS_PREPARED_ARRAY_DEFINITION_DATA = 'isPreparedArrayDefinitionData';
    private const DEFINITION_META = 'definition';

    /**
     * @param mixed $definition Definition to parse.
     *
     * @return array Definition parsed into an array of a special structure.
     * @psalm-return array{mixed,array}
     */
    public static function parse(mixed $definition): array
    {
        if (!is_array($definition)) {
            return [$definition, []];
        }

        // Dedicated definition
        if (isset($definition[self::DEFINITION_META])) {
            $newDefinition = $definition[self::DEFINITION_META];
            unset($definition[self::DEFINITION_META]);

            return [$newDefinition, $definition];
        }

        // Callable definition
        if (is_callable($definition, true)) {
            return [$definition, []];
        }

        // Array definition
        $meta = [];
        $class = null;
        $constructorArguments = [];
        $methodsAndProperties = [];
        foreach ($definition as $key => $value) {
            if (is_string($key)) {
                // Class
                if ($key === ArrayDefinition::CLASS_NAME) {
                    $class = $value;
                    continue;
                }

                // Constructor arguments
                if ($key === ArrayDefinition::CONSTRUCTOR) {
                    $constructorArguments = $value;
                    continue;
                }

                // Methods and properties
                if (count($methodArray = explode('()', $key, 2)) === 2) {
                    $methodsAndProperties[$key] = [ArrayDefinition::TYPE_METHOD, $methodArray[0], $value];
                    continue;
                }
                if (count($propertyArray = explode('$', $key, 2)) === 2) {
                    $methodsAndProperties[$key] = [ArrayDefinition::TYPE_PROPERTY, $propertyArray[1], $value];
                    continue;
                }
            }

            $meta[$key] = $value;
        }
        return [
            [
                'class' => $class,
                '__construct()' => $constructorArguments,
                'methodsAndProperties' => $methodsAndProperties,
                self::IS_PREPARED_ARRAY_DEFINITION_DATA => true,
            ],
            $meta,
        ];
    }
}


================================================
FILE: src/NotFoundException.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

use Exception;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
use Yiisoft\FriendlyException\FriendlyExceptionInterface;

use function sprintf;

/**
 * `NotFoundException` is thrown when no definition or class was found in the container for a given ID.
 */
final class NotFoundException extends Exception implements NotFoundExceptionInterface, FriendlyExceptionInterface
{
    /**
     * @param string $id ID of the definition or name of the class that was not found.
     * @param string[] $buildStack Stack of IDs of services requested definition or class that was not found.
     */
    public function __construct(
        private readonly string $id,
        private array $buildStack = [],
        ?Throwable $previous = null,
    ) {
        if (empty($this->buildStack)) {
            $message = sprintf('No definition or class found or resolvable for "%s".', $id);
        } elseif ($this->buildStack === [$id]) {
            $message = sprintf('No definition or class found or resolvable for "%s" while building it.', $id);
        } else {
            $message = sprintf(
                'No definition or class found or resolvable for "%s" while building "%s".',
                end($this->buildStack),
                implode('" -> "', $buildStack),
            );
        }

        parent::__construct($message, previous: $previous);
    }

    public function getId(): string
    {
        return $this->id;
    }

    /**
     * @return string[]
     */
    public function getBuildStack(): array
    {
        return $this->buildStack;
    }

    public function getName(): string
    {
        return sprintf('No definition or class found for "%s" ID.', $this->id);
    }

    public function getSolution(): ?string
    {
        $solution = <<<SOLUTION
            Ensure that either a service with ID "%1\$s" is defined or such class exists and is autoloadable.
            SOLUTION;

        return sprintf($solution, $this->id);
    }
}


================================================
FILE: src/Reference/TagReference.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Reference;

use InvalidArgumentException;
use Yiisoft\Definitions\Reference;

use function sprintf;

/**
 * Helper class used to specify a reference to a tag.
 * For example, `TagReference::to('my-tag')` specifies a reference to all services that are tagged with `tag@my-tag`.
 */
final class TagReference
{
    private const PREFIX = 'tag@';

    private function __construct() {}

    public static function to(string $tag): Reference
    {
        return Reference::to(self::id($tag));
    }

    public static function id(string $tag): string
    {
        return self::PREFIX . $tag;
    }

    public static function extractTagFromAlias(string $alias): string
    {
        if (!str_starts_with($alias, self::PREFIX)) {
            throw new InvalidArgumentException(sprintf('Alias "%s" is not a tag alias.', $alias));
        }
        return substr($alias, 4);
    }

    public static function isTagAlias(string $id): bool
    {
        return str_starts_with($id, self::PREFIX);
    }
}


================================================
FILE: src/ServiceProviderInterface.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

/**
 * Represents a component responsible for class registration in the Container.
 *
 * The goal of service providers is to centralize and organize in one place
 * registration of classes bound by any logic or classes with complex dependencies.
 *
 * You can organize registration of a service and its dependencies in a single
 * provider class except for creating a bootstrap file or configuration array for the Container.
 *
 * Example:
 *
 * ```php
 * class CarProvider implements ServiceProviderInterface
 * {
 *    public function getDefinitions(): array
 *    {
 *        return [
 *            'car' => ['class' => Car::class],
 *            'car-factory' => CarFactory::class,
 *            EngineInterface::class => EngineMarkOne::class,
 *        ];
 *    }
 * }
 * ```
 */
interface ServiceProviderInterface
{
    /**
     * Returns definitions for the container.
     *
     * This method:
     *
     * - Should only return definitions for the Container preventing any side effects.
     * - Should be idempotent.
     *
     * @return array Definitions for the container. Each array key is the name of the service (usually it is
     * an interface name), and a corresponding value is a service definition.
     */
    public function getDefinitions(): array;

    /**
     * Returns an array of service extensions.
     *
     * An extension is callable that returns a modified service object:
     *
     * ```php
     * static function (ContainerInterface $container, $service) {
     *     return $service->withAnotherOption(42);
     * }
     * ```
     *
     * @return array Extensions for the container services. Each array key is the name of the service to be modified
     * and a corresponding value is callable doing the job.
     */
    public function getExtensions(): array;
}


================================================
FILE: src/StateResetter.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

use Closure;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;

use function is_int;
use function is_object;
use function sprintf;

/**
 * State resetter allows resetting state of the services that are currently stored in the container and have "reset"
 * callback defined. The reset should be triggered after each request-response cycle in case you build long-running
 * applications with tools like [Swoole](https://www.swoole.co.uk/) or [RoadRunner](https://roadrunner.dev/).
 */
final class StateResetter
{
    /**
     * @var Closure[]|self[]
     */
    private array $resetters = [];

    /**
     * @param ContainerInterface $container Container to reset.
     */
    public function __construct(
        private readonly ContainerInterface $container,
    ) {}

    /**
     * Reset the container.
     */
    public function reset(): void
    {
        foreach ($this->resetters as $resetter) {
            if ($resetter instanceof self) {
                $resetter->reset();
                continue;
            }
            $resetter($this->container);
        }
    }

    /**
     * @param Closure[]|self[] $resetters Array of reset callbacks. Each callback has access to the private and
     * protected properties of the service instance, so you can set the initial state of the service efficiently
     * without creating a new instance.
     */
    public function setResetters(array $resetters): void
    {
        $this->resetters = [];
        foreach ($resetters as $serviceId => $callback) {
            if (is_int($serviceId)) {
                if (!$callback instanceof self) {
                    throw new InvalidArgumentException(sprintf(
                        'State resetter object should be instance of "%s", "%s" given.',
                        self::class,
                        get_debug_type($callback),
                    ));
                }
                $this->resetters[] = $callback;
                continue;
            }

            if (!$callback instanceof Closure) {
                throw new InvalidArgumentException(
                    'Callback for state resetter should be closure in format '
                    . '`function (ContainerInterface $container): void`. '
                    . 'Got "' . get_debug_type($callback) . '".',
                );
            }

            $instance = $this->container->get($serviceId);
            if (!is_object($instance)) {
                throw new InvalidArgumentException(
                    'State resetter supports resetting objects only. Container returned '
                    . get_debug_type($instance)
                    . '.',
                );
            }

            /** @var Closure */
            $this->resetters[] = $callback->bindTo($instance, $instance::class);
        }
    }
}


================================================
FILE: tests/Benchmark/ContainerBench.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Benchmark;

use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
use PhpBench\Benchmark\Metadata\Annotations\Groups;
use PhpBench\Benchmark\Metadata\Annotations\Iterations;
use PhpBench\Benchmark\Metadata\Annotations\ParamProviders;
use PhpBench\Benchmark\Metadata\Annotations\Revs;
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
use Yiisoft\Di\Tests\Support\Car;
use Yiisoft\Di\Tests\Support\EngineInterface;
use Yiisoft\Di\Tests\Support\EngineMarkOne;
use Yiisoft\Di\Tests\Support\EngineMarkTwo;
use Yiisoft\Di\Tests\Support\NullableConcreteDependency;
use Yiisoft\Di\Tests\Support\PropertyTestClass;
use Yiisoft\Definitions\Reference;
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Definitions\Exception\NotInstantiableException;

/**
 * @Iterations(5)
 * @Revs(1000)
 * @BeforeMethods({"before"})
 */
class ContainerBench
{
    public const SERVICE_COUNT = 200;

    private CompositeContainer $composite;

    /** @var int[] */
    private array $indexes = [];

    /** @var int[] */
    private array $randomIndexes = [];

    public function provideDefinitions(): array
    {
        return [
            ['serviceClass' => PropertyTestClass::class],
            [
                'serviceClass' => NullableConcreteDependency::class,
                'otherDefinitions' => [
                    EngineInterface::class => EngineMarkOne::class,
                    Car::class => Car::class,
                    EngineMarkOne::class => EngineMarkOne::class,
                ],
            ],
            [
                'serviceClass' => NullableConcreteDependency::class,
                'otherDefinitions' => [
                    EngineInterface::class => EngineMarkTwo::class,
                ],
            ],
        ];
    }

    /**
     * Load the bulk of the definitions.
     * These all refer to a service that is not yet defined but must be defined in the benchmark.
     */
    public function before(): void
    {
        $definitions3 = [];
        $definitions2 = [];
        $definitions3['service'] = PropertyTestClass::class;
        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {
            $this->indexes[] = $i;
            $definitions2["second$i"] = Reference::to('service');
            $definitions3["third$i"] = Reference::to('service');
        }
        $this->randomIndexes = $this->indexes;
        shuffle($this->randomIndexes);

        $this->composite = new CompositeContainer();
        // Attach the dummy containers multiple times, to see what would happen if there are lots of them.
        $this->composite->attach(
            new Container(
                ContainerConfig::create()
                    ->withDefinitions($definitions2),
            ),
        );
        $this->composite->attach(
            new Container(
                ContainerConfig::create()
                    ->withDefinitions($definitions3),
            ),
        );
        $this->composite->attach(
            new Container(
                ContainerConfig::create()
                    ->withDefinitions($definitions2),
            ),
        );
        $this->composite->attach(
            new Container(
                ContainerConfig::create()
                    ->withDefinitions($definitions3),
            ),
        );
        $this->composite->attach(
            new Container(
                ContainerConfig::create()
                    ->withDefinitions($definitions2),
            ),
        );
        $this->composite->attach(
            new Container(
                ContainerConfig::create()
                    ->withDefinitions($definitions3),
            ),
        );
        $this->composite->attach(
            new Container(
                ContainerConfig::create()
                    ->withDefinitions($definitions2),
            ),
        );
        $this->composite->attach(
            new Container(
                ContainerConfig::create()
                    ->withDefinitions($definitions3),
            ),
        );
    }

    /**
     * @Groups({"construct"})
     *
     * @throws InvalidConfigException
     * @throws NotInstantiableException
     */
    public function benchConstruct(): void
    {
        $definitions = [];
        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {
            $definitions["service$i"] = PropertyTestClass::class;
        }
        $container = new Container(
            ContainerConfig::create()
                ->withDefinitions($definitions),
        );
    }

    /**
     * @Groups({"lookup"})
     * @ParamProviders({"provideDefinitions"})
     */
    public function benchSequentialLookups($params): void
    {
        $definitions = [];
        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {
            $definitions["service$i"] = $params['serviceClass'];
        }
        if (isset($params['otherDefinitions'])) {
            $definitions = array_merge($definitions, $params['otherDefinitions']);
        }
        $container = new Container(
            ContainerConfig::create()
                ->withDefinitions($definitions),
        );
        for ($i = 0; $i < self::SERVICE_COUNT / 2; $i++) {
            // Do array lookup.
            $index = $this->indexes[$i];
            $container->get("service$index");
        }
    }

    /**
     * @Groups({"lookup"})
     * @ParamProviders({"provideDefinitions"})
     */
    public function benchRandomLookups($params): void
    {
        $definitions = [];
        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {
            $definitions["service$i"] = $params['serviceClass'];
        }
        if (isset($params['otherDefinitions'])) {
            $definitions = array_merge($definitions, $params['otherDefinitions']);
        }
        $container = new Container(
            ContainerConfig::create()
                ->withDefinitions($definitions),
        );
        for ($i = 0; $i < self::SERVICE_COUNT / 2; $i++) {
            // Do array lookup.
            $index = $this->randomIndexes[$i];
            $container->get("service$index");
        }
    }

    /**
     * @Groups({"lookup"})
     * @ParamProviders({"provideDefinitions"})
     */
    public function benchRandomLookupsComposite($params): void
    {
        $definitions = [];
        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {
            $definitions["service$i"] = $params['serviceClass'];
        }
        if (isset($params['otherDefinitions'])) {
            $definitions = array_merge($definitions, $params['otherDefinitions']);
        }
        $container = new Container(
            ContainerConfig::create()
                ->withDefinitions($definitions),
        );
        $this->composite->attach($container);
        for ($i = 0; $i < self::SERVICE_COUNT / 2; $i++) {
            // Do array lookup.
            $index = $this->randomIndexes[$i];
            $this->composite->get("service$index");
        }
    }
}


================================================
FILE: tests/Benchmark/ContainerMethodHasBench.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Benchmark;

use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
use PhpBench\Benchmark\Metadata\Annotations\Groups;
use PhpBench\Benchmark\Metadata\Annotations\Iterations;
use PhpBench\Benchmark\Metadata\Annotations\Revs;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
use Yiisoft\Di\Tests\Support\GearBox;
use Yiisoft\Di\Tests\Support\PropertyTestClass;
use Yiisoft\Definitions\Reference;

/**
 * @Iterations(5)
 * @Revs(1000)
 * @Groups({"has"})
 * @BeforeMethods({"before"})
 */
class ContainerMethodHasBench
{
    private const SERVICE_COUNT = 200;

    private Container $container;

    /**
     * Load the bulk of the definitions.
     */
    public function before(): void
    {
        $definitions = [];
        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {
            $definitions["service$i"] = Reference::to('service');
        }
        $definitions['service'] = PropertyTestClass::class;

        $this->container = new Container(
            ContainerConfig::create()
                ->withDefinitions($definitions),
        );
    }

    public function benchPredefinedExisting(): void
    {
        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {
            $this->container->has("service$i");
        }
    }

    public function benchUndefinedExisting(): void
    {
        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {
            $this->container->has(GearBox::class);
        }
    }

    public function benchUndefinedNonexistent(): void
    {
        for ($i = 0; $i < self::SERVICE_COUNT; $i++) {
            $this->container->has('NonexistentNamespace\NonexistentClass');
        }
    }
}


================================================
FILE: tests/Support/A.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

class A
{
    public function __construct(public ?B $b = null) {}
}


================================================
FILE: tests/Support/B.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

class B
{
    public function __construct(public ?A $a = null) {}
}


================================================
FILE: tests/Support/Car.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * A car
 */
class Car
{
    public ColorInterface $color;

    public function __construct(
        private readonly EngineInterface $engine,
        private readonly array $moreEngines = [],
    ) {}

    public function setColor(ColorInterface $color): self
    {
        $this->color = $color;

        return $this;
    }

    public function getColor(): ColorInterface
    {
        return $this->color;
    }

    public function getEngine(): EngineInterface
    {
        return $this->engine;
    }

    public function getEngineName(): string
    {
        return $this->engine->getName();
    }

    public function getMoreEngines(): array
    {
        return $this->moreEngines;
    }
}


================================================
FILE: tests/Support/CarExtensionProvider.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

use Psr\Container\ContainerInterface;
use Yiisoft\Di\ServiceProviderInterface;

final class CarExtensionProvider implements ServiceProviderInterface
{
    public function getDefinitions(): array
    {
        return [];
    }

    public function getExtensions(): array
    {
        return [
            Car::class => static function (ContainerInterface $container, Car $car) {
                $car->setColor(new ColorRed());
                return $car;
            },
            EngineInterface::class => static fn(ContainerInterface $container, EngineInterface $engine) => $container->get(EngineMarkTwo::class),
        ];
    }
}


================================================
FILE: tests/Support/CarFactory.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * Produces cars
 */
class CarFactory
{
    public static function create(EngineInterface $engine): Car
    {
        return new Car($engine);
    }

    public function createByEngineName(EngineFactory $factory, $name): Car
    {
        return new Car($factory->createByName($name));
    }

    public function createWithColor(ColorInterface $color): Car
    {
        $car = new Car(EngineFactory::createDefault());

        return $car->setColor($color);
    }
}


================================================
FILE: tests/Support/CarProvider.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

use Psr\Container\ContainerInterface;
use Yiisoft\Di\ServiceProviderInterface;

final class CarProvider implements ServiceProviderInterface
{
    public function getDefinitions(): array
    {
        return [
            'car' => Car::class,
            EngineInterface::class => EngineMarkOne::class,
        ];
    }

    public function getExtensions(): array
    {
        return [
            Car::class => static function (ContainerInterface $container, Car $car) {
                $car->setColor(new ColorPink());
                return $car;
            },
            'sport_car' => static function (ContainerInterface $container, SportCar $car) {
                $car->setColor(new ColorPink());
                return $car;
            },
        ];
    }
}


================================================
FILE: tests/Support/ColorInterface.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * Interface ColorInterface defines car color
 */
interface ColorInterface
{
    public function getColor(): string;
}


================================================
FILE: tests/Support/ColorPink.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * Class ColorPink
 */
final class ColorPink implements ColorInterface
{
    private const COLOR_PINK = 'pink';

    public function getColor(): string
    {
        return self::COLOR_PINK;
    }
}


================================================
FILE: tests/Support/ColorRed.php
================================================
<?php


declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * Class ColorRed
 */
final class ColorRed implements ColorInterface
{
    private const COLOR_PINK = 'red';

    public function getColor(): string
    {
        return self::COLOR_PINK;
    }
}


================================================
FILE: tests/Support/ConstructorTestClass.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

use function func_get_args;

/**
 * ConstructorTestClass
 */
class ConstructorTestClass
{
    private readonly array $allParameters;

    /**
     * ConstructorTestClass constructor.
     *
     * @param $parameter
     */
    public function __construct(private $parameter)
    {
        $this->allParameters = func_get_args();
    }

    /**
     * @return mixed
     */
    public function getParameter()
    {
        return $this->parameter;
    }

    public function getAllParameters(): array
    {
        return $this->allParameters;
    }
}


================================================
FILE: tests/Support/ContainerInterfaceExtensionProvider.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

use Psr\Container\ContainerInterface;
use Yiisoft\Di\ServiceProviderInterface;

final class ContainerInterfaceExtensionProvider implements ServiceProviderInterface
{
    public function getDefinitions(): array
    {
        return [];
    }

    public function getExtensions(): array
    {
        return [
            ContainerInterface::class => static fn(ContainerInterface $container, ContainerInterface $extended) => $container,
        ];
    }
}


================================================
FILE: tests/Support/Cycle/Chicken.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support\Cycle;

class Chicken
{
    public function __construct(Egg $egg) {}
}


================================================
FILE: tests/Support/Cycle/Egg.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support\Cycle;

class Egg
{
    public function __construct(Chicken $chicken) {}
}


================================================
FILE: tests/Support/EngineFactory.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

use Exception;
use Psr\Container\ContainerInterface;

/**
 * EngineFactory
 */
class EngineFactory
{
    public function __construct(private readonly ContainerInterface $container) {}

    public function createByName(?string $name = null): EngineInterface
    {
        if ($name === EngineMarkOne::NAME) {
            return $this->container->get(EngineMarkOne::class);
        }
        if ($name === EngineMarkTwo::NAME) {
            return $this->container->get(EngineMarkTwo::class);
        }

        throw new Exception('unknown engine name: ' . $name);
    }

    public static function createDefault(): EngineInterface
    {
        return new EngineMarkOne();
    }
}


================================================
FILE: tests/Support/EngineInterface.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * EngineInterface defines car engine interface
 */
interface EngineInterface
{
    public function getName(): string;

    public function setNumber(int $value): void;

    public function getNumber(): int;
}


================================================
FILE: tests/Support/EngineMarkOne.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * EngineMarkOne
 */
class EngineMarkOne implements EngineInterface
{
    public const NAME = 'Mark One';
    public const NUMBER = 1;

    public function __construct(private int $number = self::NUMBER) {}

    public function getName(): string
    {
        return static::NAME;
    }

    public function setNumber(int $value): void
    {
        $this->number = $value;
    }

    public function getNumber(): int
    {
        return $this->number;
    }
}


================================================
FILE: tests/Support/EngineMarkTwo.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * EngineMarkTwo
 */
class EngineMarkTwo implements EngineInterface
{
    public const NAME = 'Mark Two';
    public const NUMBER = 2;

    public function __construct(private int $number = self::NUMBER) {}

    public function getName(): string
    {
        return static::NAME;
    }

    public function setNumber(int $value): void
    {
        $this->number = $value;
    }

    public function getNumber(): int
    {
        return $this->number;
    }
}


================================================
FILE: tests/Support/EngineStorage.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

final class EngineStorage
{
    private readonly array $engines;

    public function __construct(EngineInterface ...$engines)
    {
        $this->engines = $engines;
    }

    public function getEngines(): array
    {
        return $this->engines;
    }
}


================================================
FILE: tests/Support/Garage.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * A garage
 */
final class Garage
{
    public function __construct(private readonly SportCar $car) {}

    public function getCar(): SportCar
    {
        return $this->car;
    }
}


================================================
FILE: tests/Support/GearBox.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * A gear box.
 */
class GearBox
{
    public function __construct(private readonly int $maxGear = 5) {}
}


================================================
FILE: tests/Support/InvokableCarFactory.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

use Psr\Container\ContainerInterface;

class InvokableCarFactory
{
    public function __invoke(ContainerInterface $container): Car
    {
        /** @var EngineInterface $engine */
        $engine = $container->get('engine');
        return new Car($engine);
    }
}


================================================
FILE: tests/Support/MethodTestClass.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * MethodTestClass
 */
class MethodTestClass
{
    private $value;

    /**
     * @return mixed
     */
    public function getValue()
    {
        return $this->value;
    }

    public function setValue(mixed $value): void
    {
        $this->value = $value;
    }
}


================================================
FILE: tests/Support/NonPsrContainer.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

use Psr\Container\ContainerInterface;
use stdClass;

final class NonPsrContainer implements ContainerInterface
{
    public function get(string $id)
    {
        return new stdClass();
    }

    public function has(string $id): bool
    {
        return false;
    }
}


================================================
FILE: tests/Support/NullCarExtensionProvider.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

use Psr\Container\ContainerInterface;
use Yiisoft\Di\ServiceProviderInterface;

final class NullCarExtensionProvider implements ServiceProviderInterface
{
    public function getDefinitions(): array
    {
        return [
        ];
    }

    public function getExtensions(): array
    {
        return [
            Car::class => static fn(ContainerInterface $container, Car $car) => null,
        ];
    }
}


================================================
FILE: tests/Support/NullableConcreteDependency.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

class NullableConcreteDependency
{
    public function __construct(?Car $car) {}
}


================================================
FILE: tests/Support/OptionalConcreteDependency.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

class OptionalConcreteDependency
{
    public function __construct(private readonly ?Car $car = null) {}

    public function getCar(): ?Car
    {
        return $this->car;
    }
}


================================================
FILE: tests/Support/PropertyTestClass.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * PropertyTestClass
 */
class PropertyTestClass
{
    public $property;
}


================================================
FILE: tests/Support/SportCar.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * A sport car
 */
class SportCar
{
    public ColorInterface $color;

    public function __construct(
        private readonly EngineInterface $engine,
        private readonly int $maxSpeed,
    ) {}

    public function setColor(ColorInterface $color): self
    {
        $this->color = $color;

        return $this;
    }

    public function getColor(): ColorInterface
    {
        return $this->color;
    }

    public function getEngine(): EngineInterface
    {
        return $this->engine;
    }

    public function getEngineName(): string
    {
        return $this->engine->getName();
    }

    public function getMaxSpeed(): int
    {
        return $this->maxSpeed;
    }
}


================================================
FILE: tests/Support/StaticFactory.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

use stdClass;

final class StaticFactory
{
    public static function create(): stdClass
    {
        return new stdClass();
    }
}


================================================
FILE: tests/Support/TreeItem.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

/**
 * TreeItem
 */
class TreeItem
{
    public function __construct(private readonly self $treeItem) {}
}


================================================
FILE: tests/Support/UnionTypeInConstructorFirstTypeInParamResolvable.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

final class UnionTypeInConstructorFirstTypeInParamResolvable
{
    public function __construct(private readonly EngineMarkOne|EngineInterface $engine) {}
}


================================================
FILE: tests/Support/UnionTypeInConstructorParamNotResolvable.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

final class UnionTypeInConstructorParamNotResolvable
{
    public function __construct(private readonly EngineInterface|ColorInterface $param) {}
}


================================================
FILE: tests/Support/UnionTypeInConstructorSecondParamNotResolvable.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

final class UnionTypeInConstructorSecondParamNotResolvable
{
    public function __construct(EngineMarkOne|EngineInterface $engine, string $name) {}
}


================================================
FILE: tests/Support/UnionTypeInConstructorSecondTypeInParamResolvable.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

final class UnionTypeInConstructorSecondTypeInParamResolvable
{
    public function __construct(private readonly EngineInterface|EngineMarkOne $engine) {}
}


================================================
FILE: tests/Support/VariadicConstructor.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Support;

final class VariadicConstructor
{
    private readonly array $parameters;

    public function __construct(
        private $first,
        private readonly EngineInterface $engine,
        ...$parameters,
    ) {
        $this->parameters = $parameters;
    }

    public function getFirst()
    {
        return $this->first;
    }

    public function getEngine(): EngineInterface
    {
        return $this->engine;
    }

    public function getParameters(): array
    {
        return $this->parameters;
    }
}


================================================
FILE: tests/Unit/BuildingExceptionTest.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit;

use PHPUnit\Framework\TestCase;
use RuntimeException;
use Yiisoft\Di\BuildingException;

final class BuildingExceptionTest extends TestCase
{
    public function testMessage(): void
    {
        $exception = new BuildingException('test', new RuntimeException('i am angry'));

        $this->assertSame('Caught unhandled error "i am angry" while building "test".', $exception->getMessage());
        $this->assertSame('Unable to build "test" object.', $exception->getName());
        $this->assertSame(
            <<<SOLUTION
            Ensure that either a service with ID "test" is defined or such class exists and is autoloadable.

            Ensure that configuration for service with ID "test" is correct.
            SOLUTION,
            $exception->getSolution(),
        );
    }

    public function testEmptyMessage(): void
    {
        $exception = new BuildingException('test', new RuntimeException());

        $this->assertSame('Caught unhandled error "RuntimeException" while building "test".', $exception->getMessage());
    }

    public function testBuildStack(): void
    {
        $exception = new BuildingException('test', new RuntimeException('i am angry'), ['a', 'b', 'test']);

        $this->assertSame('Caught unhandled error "i am angry" while building "a" -> "b" -> "test".', $exception->getMessage());
    }

    public function testCode(): void
    {
        $exception = new BuildingException('test', new RuntimeException());

        $this->assertSame(0, $exception->getCode());
    }
}


================================================
FILE: tests/Unit/CompositeContainerTest.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit;

use InvalidArgumentException;
use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\TestCase;
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\CompositeNotFoundException;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
use Yiisoft\Di\Tests\Support\EngineMarkOne;
use Yiisoft\Di\Tests\Support\EngineMarkTwo;
use Yiisoft\Di\Tests\Support\NonPsrContainer;
use Yiisoft\Test\Support\Container\SimpleContainer;

use function PHPUnit\Framework\assertFalse;
use function PHPUnit\Framework\assertSame;

final class CompositeContainerTest extends TestCase
{
    public function testGetNonString(): void
    {
        $container = new CompositeContainer();

        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessageMatches(
            '/^ID must be a string, (integer|int) given\.$/',
        );
        $container->get(42);
    }

    public function testTagsWithYiiAndNotYiiContainers(): void
    {
        $compositeContainer = new CompositeContainer();

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkOne::class => [
                    'class' => EngineMarkOne::class,
                    'tags' => ['engine'],
                ],
                EngineMarkTwo::class => [
                    'class' => EngineMarkTwo::class,
                    'tags' => ['engine'],
                ],
            ]);
        $firstContainer = new Container($config);

        $secondContainer = new \League\Container\Container();

        $compositeContainer->attach($firstContainer);
        $compositeContainer->attach($secondContainer);

        $engines = $compositeContainer->get('tag@engine');

        $this->assertIsArray($engines);
        $this->assertCount(2, $engines);
        $this->assertInstanceOf(EngineMarkOne::class, $engines[0]);
        $this->assertInstanceOf(EngineMarkTwo::class, $engines[1]);
    }

    public function testNonPsrContainer(): void
    {
        $compositeContainer = new CompositeContainer();

        $compositeContainer->attach(new NonPsrContainer());

        $this->expectException(CompositeNotFoundException::class);
        $this->expectExceptionMessageMatches(
            '/No definition or class found or resolvable in composite container/',
        );
        $this->expectExceptionMessageMatches(
            '/Container "has\(\)" returned false, but no exception was thrown from "get\(\)"\./',
        );
        $compositeContainer->get('test');
    }

    public function testHasNoString(): void
    {
        $container = new CompositeContainer();

        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('ID must be a string, bool given.');
        $container->has(true);
    }

    #[TestWith([true, 'engine'])]
    #[TestWith([false, 'other'])]
    public function testHasTag(bool $expected, string $tag): void
    {
        $container = new CompositeContainer();

        $container->attach(
            new Container(
                ContainerConfig::create()->withTags(['engine' => []]),
            ),
        );

        assertSame($expected, $container->has('tag@' . $tag));
    }

    public function testHasTagWithoutYiiContainer(): void
    {
        $container = new CompositeContainer();

        $container->attach(new SimpleContainer());

        assertFalse($container->has('tag@engine'));
    }
}


================================================
FILE: tests/Unit/CompositePsrContainerOverLeagueTest.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit;

use League\Container\Container;
use Psr\Container\ContainerInterface;
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\CompositeNotFoundException;

/**
 * Test the CompositeContainer over League Container.
 */
final class CompositePsrContainerOverLeagueTest extends CompositePsrContainerTestAbstract
{
    public function createContainer(iterable $definitions = []): ContainerInterface
    {
        $container = $this->setupContainer(new Container(), $definitions);
        return $this->createCompositeContainer($container);
    }

    public function setupContainer(ContainerInterface $container, iterable $definitions = []): ContainerInterface
    {
        foreach ($definitions as $id => $definition) {
            $container->add($id, $definition);
        }

        return $container;
    }

    public function testNotFoundException(): void
    {
        $compositeContainer = new CompositeContainer();

        $container1 = new Container();
        $container1Id = spl_object_id($container1);
        $container2 = new Container();
        $container2Id = spl_object_id($container2);

        $compositeContainer->attach($container1);
        $compositeContainer->attach($container2);

        $this->expectException(CompositeNotFoundException::class);
        $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");
        $compositeContainer->get('test');
    }
}


================================================
FILE: tests/Unit/CompositePsrContainerOverYiisoftTest.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit;

use Psr\Container\ContainerInterface;
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\CompositeNotFoundException;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
use Yiisoft\Di\StateResetter;
use Yiisoft\Di\Tests\Support\EngineMarkOne;
use Yiisoft\Di\Tests\Support\EngineMarkTwo;

/**
 * Test the CompositeContainer over Yiisoft Container.
 */
final class CompositePsrContainerOverYiisoftTest extends CompositePsrContainerTestAbstract
{
    public function createContainer(iterable $definitions = []): ContainerInterface
    {
        $config = ContainerConfig::create()
            ->withDefinitions($definitions);
        $container = new Container($config);
        return $this->createCompositeContainer($container);
    }

    public function testResetterInCompositeContainerWithExternalResetter(): void
    {
        $composite = $this->createContainer([
            StateResetter::class => function (ContainerInterface $container) {
                $resetter = new StateResetter($container);
                $resetter->setResetters([
                    'engineMarkOne' => function () {
                        $this->number = 42;
                    },
                ]);
                return $resetter;
            },
            'engineMarkOne' => function () {
                $engine = new EngineMarkOne();
                $engine->setNumber(42);
                return $engine;
            },
        ]);
        $config = ContainerConfig::create()
            ->withDefinitions([
                'engineMarkTwo' => ['class' => EngineMarkTwo::class,
                    'setNumber()' => [43],
                    'reset' => function () {
                        $this->number = 43;
                    },
                ],
            ]);
        $secondContainer = new Container($config);
        $composite->attach($secondContainer);

        $engineMarkOne = $composite->get('engineMarkOne');
        $engineMarkTwo = $composite->get('engineMarkTwo');
        $this->assertSame(
            42,
            $composite
                ->get('engineMarkOne')
                ->getNumber(),
        );
        $this->assertSame(
            43,
            $composite
                ->get('engineMarkTwo')
                ->getNumber(),
        );

        $engineMarkOne->setNumber(45);
        $engineMarkTwo->setNumber(46);
        $this->assertSame(
            45,
            $composite
                ->get('engineMarkOne')
                ->getNumber(),
        );
        $this->assertSame(
            46,
            $composite
                ->get('engineMarkTwo')
                ->getNumber(),
        );

        $composite
            ->get(StateResetter::class)
            ->reset();

        $this->assertSame($engineMarkOne, $composite->get('engineMarkOne'));
        $this->assertSame($engineMarkTwo, $composite->get('engineMarkTwo'));
        $this->assertSame(
            42,
            $composite
                ->get('engineMarkOne')
                ->getNumber(),
        );
        $this->assertSame(
            43,
            $composite
                ->get('engineMarkTwo')
                ->getNumber(),
        );
    }

    public function testNotFoundException(): void
    {
        $compositeContainer = new CompositeContainer();

        $container1 = new Container();
        $container1Id = spl_object_id($container1);
        $container2 = new Container();
        $container2Id = spl_object_id($container2);

        $compositeContainer->attach($container1);
        $compositeContainer->attach($container2);

        $this->expectException(CompositeNotFoundException::class);
        $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.");
        $compositeContainer->get('test');
    }
}


================================================
FILE: tests/Unit/CompositePsrContainerTestAbstract.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit;

use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
use Yiisoft\Di\Tests\Support\Car;
use Yiisoft\Di\Tests\Support\UnionTypeInConstructorParamNotResolvable;
use Yiisoft\Di\Tests\Support\EngineInterface;
use Yiisoft\Di\Tests\Support\EngineMarkOne;
use Yiisoft\Di\Tests\Support\EngineMarkTwo;

/**
 * General tests for PSR-11 composite container.
 * To be extended for specific containers.
 */
abstract class CompositePsrContainerTestAbstract extends PsrContainerTestAbstract
{
    public function createCompositeContainer(ContainerInterface $attachedContainer): ContainerInterface
    {
        $compositeContainer = new CompositeContainer();
        $compositeContainer->attach($attachedContainer);

        return $compositeContainer;
    }

    public function testAttach(): void
    {
        $compositeContainer = new CompositeContainer();

        $config = ContainerConfig::create()
            ->withDefinitions([
                'test' => EngineMarkOne::class,
            ]);
        $container = new Container($config);
        $compositeContainer->attach($container);
        $this->assertTrue($compositeContainer->has('test'));
        $this->assertInstanceOf(EngineMarkOne::class, $compositeContainer->get('test'));
    }

    public function testDetach(): void
    {
        $compositeContainer = new CompositeContainer();

        $config = ContainerConfig::create()
            ->withDefinitions([
                'test' => EngineMarkOne::class,
            ]);
        $container = new Container($config);
        $compositeContainer->attach($container);
        $this->assertInstanceOf(EngineMarkOne::class, $compositeContainer->get('test'));

        $compositeContainer->detach($container);
        $this->expectException(NotFoundExceptionInterface::class);
        $this->assertInstanceOf(EngineMarkOne::class, $compositeContainer->get('test'));
    }

    public function testHasDefinition(): void
    {
        $compositeContainer = $this->createContainer([EngineInterface::class => EngineMarkOne::class]);
        $this->assertTrue($compositeContainer->has(EngineInterface::class));

        $config = ContainerConfig::create()
            ->withDefinitions([
                'test' => EngineMarkTwo::class,
            ]);
        $container = new Container($config);
        $compositeContainer->attach($container);
        $this->assertTrue($compositeContainer->has('test'));
    }

    public function testGetPriority(): void
    {
        $compositeContainer = $this->createContainer([EngineInterface::class => EngineMarkOne::class]);

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkTwo::class,
            ]);
        $container = new Container($config);
        $compositeContainer->attach($container);
        $this->assertInstanceOf(EngineMarkOne::class, $compositeContainer->get(EngineInterface::class));

        $config = ContainerConfig::create()
            ->withDefinitions([EngineInterface::class => EngineMarkOne::class]);
        $containerOne = new Container($config);

        $config = ContainerConfig::create()
            ->withDefinitions([EngineInterface::class => EngineMarkTwo::class]);
        $containerTwo = new Container($config);

        $compositeContainer = new CompositeContainer();
        $compositeContainer->attach($containerOne);
        $compositeContainer->attach($containerTwo);
        $this->assertInstanceOf(EngineMarkOne::class, $compositeContainer->get(EngineInterface::class));
    }

    public function testTags(): void
    {
        $compositeContainer = new CompositeContainer();

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkOne::class => [
                    'class' => EngineMarkOne::class,
                    'tags' => ['engine'],
                ],
            ]);
        $firstContainer = new Container($config);

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkTwo::class => [
                    'class' => EngineMarkTwo::class,
                    'tags' => ['engine'],
                ],
            ]);
        $secondContainer = new Container($config);

        $compositeContainer->attach($firstContainer);
        $compositeContainer->attach($secondContainer);

        $engines = $compositeContainer->get('tag@engine');

        $this->assertIsArray($engines);
        $this->assertCount(2, $engines);
        $this->assertSame(EngineMarkOne::class, $engines[1]::class);
        $this->assertSame(EngineMarkTwo::class, $engines[0]::class);
    }

    public function testDelegateLookup(): void
    {
        $compositeContainer = new CompositeContainer();
        $firstContainer = new Container();

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
            ]);
        $secondContainer = new Container($config);

        $compositeContainer->attach($firstContainer);
        $compositeContainer->attach($secondContainer);

        $car = $compositeContainer->get(Car::class);

        $this->assertInstanceOf(Car::class, $car);
    }

    public function testDelegateLookupUnionTypes(): void
    {
        $compositeContainer = new CompositeContainer();
        $firstContainer = new Container();

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
            ]);
        $secondContainer = new Container($config);

        $compositeContainer->attach($firstContainer);
        $compositeContainer->attach($secondContainer);

        $car = $compositeContainer->get(UnionTypeInConstructorParamNotResolvable::class);

        $this->assertInstanceOf(UnionTypeInConstructorParamNotResolvable::class, $car);
    }
}


================================================
FILE: tests/Unit/Container/DependencyFromDelegate/Car.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit\Container\DependencyFromDelegate;

final class Car
{
    public function __construct(
        public readonly EngineInterface $engine,
    ) {}
}


================================================
FILE: tests/Unit/Container/DependencyFromDelegate/DependencyFromDelegateTest.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit\Container\DependencyFromDelegate;

use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Throwable;
use Yiisoft\Di\BuildingException;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
use Yiisoft\Di\NotFoundException;
use Yiisoft\Test\Support\Container\SimpleContainer;

use function PHPUnit\Framework\assertInstanceOf;
use function PHPUnit\Framework\assertSame;
use function sprintf;

final class DependencyFromDelegateTest extends TestCase
{
    public function testAnotherContainer(): void
    {
        $container = new Container(
            ContainerConfig::create()
                ->withDefinitions([
                    ContainerInterface::class => new SimpleContainer(),
                    Car::class => Car::class,
                ])
                ->withDelegates([
                    static fn() => new SimpleContainer([
                        Car::class => new Car(new Engine()),
                    ]),
                ]),
        );

        $car = $container->get(Car::class);

        assertInstanceOf(Car::class, $car);
        assertInstanceOf(Engine::class, $car->engine);
    }

    public function testNotFoundInDelegate(): void
    {
        $container = new Container(
            ContainerConfig::create()
                ->withDefinitions([
                    ContainerInterface::class => new SimpleContainer(),
                    'car' => Car::class,
                ])
                ->withDelegates([
                    static fn() => new Container(
                        ContainerConfig::create()
                            ->withDefinitions([
                                'car' => Car::class,
                            ]),
                    ),
                ]),
        );

        $exception = null;
        try {
            $container->get('car');
        } catch (Throwable $exception) {
        }

        assertInstanceOf(BuildingException::class, $exception);
        assertSame(
            sprintf(
                '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".',
                'car',
                EngineInterface::class,
                Car::class,
            ),
            $exception->getMessage(),
        );

        $previous = $exception->getPrevious();
        assertInstanceOf(NotFoundException::class, $previous);
        $this->assertSame(
            sprintf(
                'No definition or class found or resolvable for "%2$s" while building "%1$s" -> "%3$s" -> "%2$s".',
                'car',
                EngineInterface::class,
                Car::class,
            ),
            $previous->getMessage(),
        );
    }
}


================================================
FILE: tests/Unit/Container/DependencyFromDelegate/Engine.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit\Container\DependencyFromDelegate;

final class Engine implements EngineInterface {}


================================================
FILE: tests/Unit/Container/DependencyFromDelegate/EngineInterface.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit\Container\DependencyFromDelegate;

interface EngineInterface {}


================================================
FILE: tests/Unit/ContainerTest.php
================================================
<?php

declare(strict_types=1);

namespace Yiisoft\Di\Tests\Unit;

use ArrayIterator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use RuntimeException;
use stdClass;
use Throwable;
use Yiisoft\Di\BuildingException;
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
use Yiisoft\Di\ExtensibleService;
use Yiisoft\Di\NotFoundException;
use Yiisoft\Di\StateResetter;
use Yiisoft\Di\ServiceProviderInterface;
use Yiisoft\Di\Tests\Support\A;
use Yiisoft\Di\Tests\Support\B;
use Yiisoft\Di\Tests\Support\Car;
use Yiisoft\Di\Tests\Support\CarFactory;
use Yiisoft\Di\Tests\Support\ColorInterface;
use Yiisoft\Di\Tests\Support\ColorPink;
use Yiisoft\Di\Tests\Support\ColorRed;
use Yiisoft\Di\Tests\Support\ConstructorTestClass;
use Yiisoft\Di\Tests\Support\Cycle\Chicken;
use Yiisoft\Di\Tests\Support\Cycle\Egg;
use Yiisoft\Di\Tests\Support\EngineFactory;
use Yiisoft\Di\Tests\Support\EngineInterface;
use Yiisoft\Di\Tests\Support\EngineMarkOne;
use Yiisoft\Di\Tests\Support\EngineMarkTwo;
use Yiisoft\Di\Tests\Support\EngineStorage;
use Yiisoft\Di\Tests\Support\Garage;
use Yiisoft\Di\Tests\Support\InvokableCarFactory;
use Yiisoft\Di\Tests\Support\MethodTestClass;
use Yiisoft\Di\Tests\Support\NullableConcreteDependency;
use Yiisoft\Di\Tests\Support\OptionalConcreteDependency;
use Yiisoft\Di\Tests\Support\PropertyTestClass;
use Yiisoft\Di\Tests\Support\SportCar;
use Yiisoft\Di\Tests\Support\TreeItem;
use Yiisoft\Di\Tests\Support\UnionTypeInConstructorSecondTypeInParamResolvable;
use Yiisoft\Di\Tests\Support\UnionTypeInConstructorSecondParamNotResolvable;
use Yiisoft\Di\Tests\Support\UnionTypeInConstructorParamNotResolvable;
use Yiisoft\Di\Tests\Support\UnionTypeInConstructorFirstTypeInParamResolvable;
use Yiisoft\Di\Tests\Support\VariadicConstructor;
use Yiisoft\Definitions\DynamicReference;
use Yiisoft\Definitions\Exception\CircularReferenceException;
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Definitions\Reference;
use Yiisoft\Injector\Injector;
use Yiisoft\Test\Support\Container\SimpleContainer;

use function PHPUnit\Framework\assertInstanceOf;
use function PHPUnit\Framework\assertSame;

/**
 * ContainerTest contains tests for \Yiisoft\Di\Container
 */
final class ContainerTest extends TestCase
{
    public function testCanCreateWihtoutConfig(): void
    {
        $this->expectNotToPerformAssertions();

        new Container();
    }

    public function testSettingScalars(): void
    {
        $this->expectException(InvalidConfigException::class);

        $config = ContainerConfig::create()
            ->withDefinitions([
                'scalar' => 123,
            ]);
        $container = new Container($config);

        $container->get('scalar');
    }

    public function testIntegerKeys(): void
    {
        $this->expectException(InvalidConfigException::class);

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkOne::class,
                EngineMarkTwo::class,
            ]);
        $container = new Container($config);

        $container->get(Car::class);
    }

    public function testNullableClassDependency(): void
    {
        $container = new Container();

        $this->expectException(NotFoundException::class);
        $container->get(NullableConcreteDependency::class);
    }

    public function testOptionalResolvableClassDependency(): void
    {
        $container = new Container(
            ContainerConfig::create()
                ->withDefinitions([
                    EngineInterface::class => EngineMarkOne::class,
                ]),
        );

        $this->assertTrue($container->has(OptionalConcreteDependency::class));
        $service = $container->get(OptionalConcreteDependency::class);
        $this->assertInstanceOf(Car::class, $service->getCar());
    }

    public function testOptionalNotResolvableClassDependency(): void
    {
        $container = new Container();

        $this->assertTrue($container->has(OptionalConcreteDependency::class));
        $service = $container->get(OptionalConcreteDependency::class);
        $this->assertNull($service->getCar());
    }

    public function testOptionalCircularClassDependency(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                A::class => A::class,
                B::class => B::class,
            ]);
        $container = new Container($config);
        $a = $container->get(A::class);
        $this->assertInstanceOf(B::class, $a->b);
        $this->assertNull($a->b->a);
    }

    public static function dataHas(): array
    {
        return [
            [false, 'non_existing'],
            [false, ColorInterface::class],
            [true, Car::class],
            [true, EngineMarkOne::class],
            [true, EngineInterface::class],
            [true, EngineStorage::class],
            [true, Chicken::class],
            [true, TreeItem::class],
        ];
    }

    #[DataProvider('dataHas')]
    public function testHas(bool $expected, $id): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
            ]);
        $container = new Container($config);

        $this->assertSame($expected, $container->has($id));
    }

    public static function dataUnionTypes(): array
    {
        return [
            [UnionTypeInConstructorSecondTypeInParamResolvable::class],
            [UnionTypeInConstructorFirstTypeInParamResolvable::class],
        ];
    }

    #[DataProvider('dataUnionTypes')]
    public function testUnionTypes(string $class): void
    {
        $container = new Container();

        $this->assertTrue($container->has($class));
    }

    public function testClassExistsButIsNotResolvable(): void
    {
        $container = new Container();

        $this->assertFalse($container->has('non_existing'));
        $this->assertFalse($container->has(Car::class));
        $this->assertFalse($container->has(SportCar::class));
        $this->assertFalse($container->has(NullableConcreteDependency::class));
        $this->assertFalse($container->has(ColorInterface::class));
    }

    public static function dataClassExistButIsNotResolvableWithUnionTypes(): array
    {
        return [
            [UnionTypeInConstructorParamNotResolvable::class],
            [UnionTypeInConstructorSecondParamNotResolvable::class],
        ];
    }

    #[DataProvider('dataClassExistButIsNotResolvableWithUnionTypes')]
    public function testClassExistButIsNotResolvableWithUnionTypes(string $class): void
    {
        $container = new Container();

        $this->assertFalse($container->has($class));
    }

    public function testWithoutDefinition(): void
    {
        $container = new Container();

        $hasEngine = $container->has(EngineMarkOne::class);
        $this->assertTrue($hasEngine);

        $engine = $container->get(EngineMarkOne::class);
        $this->assertInstanceOf(EngineMarkOne::class, $engine);
    }

    public function testCircularClassDependencyWithoutDefinition(): void
    {
        $container = new Container();
        $this->expectException(CircularReferenceException::class);
        $container->get(Chicken::class);
    }

    public function testTrivialDefinition(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkOne::class => EngineMarkOne::class,
            ]);
        $container = new Container($config);

        $one = $container->get(EngineMarkOne::class);
        $two = $container->get(EngineMarkOne::class);
        $this->assertInstanceOf(EngineMarkOne::class, $one);
        $this->assertSame($one, $two);
    }

    public function testCircularClassDependency(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                Chicken::class => Chicken::class,
                Egg::class => Egg::class,
            ]);
        $container = new Container($config);

        $this->expectException(CircularReferenceException::class);
        $container->get(Chicken::class);
    }

    public function testClassSimple(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'engine' => EngineMarkOne::class,
            ]);
        $container = new Container($config);
        $this->assertInstanceOf(EngineMarkOne::class, $container->get('engine'));
    }

    public function testSetAll(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'engine1' => EngineMarkOne::class,
                'engine2' => EngineMarkTwo::class,
            ]);
        $container = new Container($config);

        $this->assertInstanceOf(EngineMarkOne::class, $container->get('engine1'));
        $this->assertInstanceOf(EngineMarkTwo::class, $container->get('engine2'));
    }

    public function testClassConstructor(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'constructor_test' => [
                    'class' => ConstructorTestClass::class,
                    '__construct()' => [42],
                ],
            ]);
        $container = new Container($config);

        /** @var ConstructorTestClass $object */
        $object = $container->get('constructor_test');
        $this->assertSame(42, $object->getParameter());
    }

    // See https://github.com/yiisoft/di/issues/157#issuecomment-701458616
    public function testIntegerIndexedConstructorArguments(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'items' => [
                    'class' => ArrayIterator::class,
                    '__construct()' => [
                        [],
                        ArrayIterator::STD_PROP_LIST,
                    ],
                ],
            ]);
        $container = new Container($config);

        $items = $container->get('items');

        $this->assertInstanceOf(ArrayIterator::class, $items);
        $this->assertSame(ArrayIterator::STD_PROP_LIST, $items->getFlags());
    }

    public function testExcessiveConstructorParametersIgnored(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'constructor_test' => [
                    'class' => ConstructorTestClass::class,
                    '__construct()' => [
                        'parameter' => 42,
                        'surplus1' => 43,
                    ],
                ],
            ]);
        $container = new Container($config);

        /** @var ConstructorTestClass $object */
        $object = $container->get('constructor_test');
        $this->assertSame([42], $object->getAllParameters());
    }

    public function testVariadicConstructorParameters(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                'stringIndexed' => [
                    'class' => VariadicConstructor::class,
                    '__construct()' => [
                        'first' => 1,
                        'parameters' => [42, 43, 44],
                    ],
                ],
                'integerIndexed' => [
                    'class' => VariadicConstructor::class,
                    '__construct()' => [1, new EngineMarkOne(), 42, 43, 44],
                ],
            ]);
        $container = new Container($config);

        $object = $container->get('stringIndexed');
        $this->assertSame(1, $object->getFirst());
        $this->assertSame([42, 43, 44], $object->getParameters());
        $this->assertInstanceOf(EngineMarkOne::class, $object->getEngine());

        $object = $container->get('integerIndexed');
        $this->assertSame(1, $object->getFirst());
        $this->assertInstanceOf(EngineMarkOne::class, $object->getEngine());
        $this->assertSame([42, 43, 44], $object->getParameters());
    }

    public function testMixedIndexedConstructorParametersAreNotAllowed(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'test' => [
                    'class' => VariadicConstructor::class,
                    '__construct()' => [
                        'parameters' => 42,
                        43,
                    ],
                ],
            ]);
        $container = new Container($config);

        $this->expectException(BuildingException::class);
        $this->expectExceptionMessage(
            'Caught unhandled error "Arguments indexed both by name and by position are not allowed in the same array." while building "test".',
        );
        $container->get('test');
    }

    public function testClassProperties(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'property_test' => [
                    'class' => PropertyTestClass::class,
                    '$property' => 42,
                ],
            ]);
        $container = new Container($config);

        /** @var PropertyTestClass $object */
        $object = $container->get('property_test');
        $this->assertSame(42, $object->property);
    }

    public function testClassMethods(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'method_test' => [
                    'class' => MethodTestClass::class,
                    'setValue()' => [42],
                ],
            ]);
        $container = new Container($config);

        /** @var MethodTestClass $object */
        $object = $container->get('method_test');
        $this->assertSame(42, $object->getValue());
    }

    public function testClosureInConstructor(): void
    {
        $color = static fn() => new ColorPink();

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                ConstructorTestClass::class => [
                    'class' => ConstructorTestClass::class,
                    '__construct()' => [$color],
                ],
            ]);
        $container = new Container($config);

        $testClass = $container->get(ConstructorTestClass::class);
        $this->assertSame($color, $testClass->getParameter());
    }

    public function testDynamicClosureInConstruct(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'car' => [
                    'class' => Car::class,
                    '__construct()' => [
                        DynamicReference::to(static fn(EngineInterface $engine) => $engine),
                    ],
                ],
                EngineInterface::class => EngineMarkTwo::class,
            ]);
        $container = new Container($config);

        $car = $container->get('car');
        $engine = $container->get(EngineInterface::class);
        $this->assertSame($engine, $car->getEngine());
    }

    public function testKeepClosureDefinition(): void
    {
        $engine = new EngineMarkOne();
        $closure = static fn(EngineInterface $engine) => $engine;

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => $engine,
                'closure' => DynamicReference::to($closure),
                'engine' => $closure,
            ]);
        $container = new Container($config);

        $closure = $container->get('closure');
        $this->assertSame($closure, $container->get('closure'));
        $this->assertSame($engine, $container->get('engine'));
    }

    public function testClosureInProperty(): void
    {
        $color = static fn() => new ColorPink();

        $config = ContainerConfig::create()
            ->withDefinitions([
                PropertyTestClass::class => [
                    'class' => PropertyTestClass::class,
                    '$property' => $color,
                ],
            ]);
        $container = new Container($config);

        $testClass = $container->get(PropertyTestClass::class);
        $this->assertSame($color, $testClass->property);
    }

    public function testDynamicClosureInProperty(): void
    {
        $color = new ColorPink();

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                ColorInterface::class => $color,
                'car' => [
                    'class' => Car::class,
                    '$color' => DynamicReference::to(fn() => $color),
                ],
            ]);
        $container = new Container($config);

        $car = $container->get('car');
        $this->assertSame($color, $car->getColor());
    }

    public function testClosureInMethodCall(): void
    {
        $color = static fn() => new ColorPink();

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                MethodTestClass::class => [
                    'class' => MethodTestClass::class,
                    'setValue()' => [$color],
                ],
            ]);
        $container = new Container($config);

        $testClass = $container->get(MethodTestClass::class);
        $this->assertSame($color, $testClass->getValue());
    }

    public function testDynamicClosureInMethodCall(): void
    {
        $color = new ColorPink();

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                ColorInterface::class => $color,
                'car' => [
                    'class' => Car::class,
                    'setColor()' => [DynamicReference::to(fn() => $color)],
                ],
            ]);
        $container = new Container($config);

        $car = $container->get('car');
        $this->assertSame($color, $car->getColor());
    }

    public function testAlias(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => Reference::to('engine'),
                'engine' => Reference::to('engine-mark-one'),
                'engine-mark-one' => EngineMarkOne::class,
            ]);
        $container = new Container($config);

        $engine1 = $container->get('engine-mark-one');
        $engine2 = $container->get('engine');
        $engine3 = $container->get(EngineInterface::class);
        $this->assertInstanceOf(EngineMarkOne::class, $engine1);
        $this->assertSame($engine1, $engine2);
        $this->assertSame($engine2, $engine3);
    }

    public function testCircularAlias(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'engine-1' => Reference::to('engine-2'),
                'engine-2' => Reference::to('engine-3'),
                'engine-3' => Reference::to('engine-1'),
            ]);
        $container = new Container($config);

        $this->expectException(CircularReferenceException::class);
        $container->get('engine-1');
    }

    public function testUndefinedDependencies(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'car' => Car::class,
            ]);
        $container = new Container($config);

        $this->expectException(NotFoundException::class);
        $container->get('car');
    }

    public function testDependencies(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'car' => Car::class,
                EngineInterface::class => EngineMarkTwo::class,
            ]);
        $container = new Container($config);

        /** @var Car $car */
        $car = $container->get('car');
        $this->assertEquals(EngineMarkTwo::NAME, $car->getEngineName());
    }

    public function testCircularReference(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                TreeItem::class => TreeItem::class,
            ]);
        $container = new Container($config);

        $this->expectException(CircularReferenceException::class);
        $container->get(TreeItem::class);
    }

    /**
     * @link https://github.com/yiisoft/di/pull/189
     */
    public function testFalsePositiveCircularReferenceWithClassID(): void
    {
        $this->expectNotToPerformAssertions();

        $container = new Container();

        // Build an object
        $container->get(ColorPink::class);

        // set definition to container
        (fn(string $id, $definition) => $this->addDefinition($id, $definition))->call(
            $container,
            ColorPink::class,
            ColorPink::class,
        );

        try {
            // Build an object
            $container->get(ColorPink::class);
        } catch (CircularReferenceException) {
            $this->fail('Circular reference detected false positively.');
        }
    }

    /**
     * @link https://github.com/yiisoft/di/pull/189
     */
    public function testFalsePositiveCircularReferenceWithStringID(): void
    {
        $this->expectNotToPerformAssertions();

        $container = new Container();
        try {
            // Build an object
            $container->get('test');
        } catch (NotFoundException) {
            // It is expected
        }

        // set definition to container
        (fn(string $id, $definition) => $this->addDefinition($id, $definition))->call(
            $container,
            'test',
            ColorPink::class,
        );

        try {
            // Build an object
            $container->get('test');
        } catch (CircularReferenceException) {
            $this->fail('Circular reference detected false positively.');
        }
    }

    public function testCallable(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                'test' => fn(ContainerInterface $container) => $container->get(EngineInterface::class),
            ]);
        $container = new Container($config);

        $object = $container->get('test');
        $this->assertInstanceOf(EngineMarkOne::class, $object);
    }

    public function testCallableWithInjector(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                'car' => fn(CarFactory $factory, Injector $injector) => $injector->invoke($factory->create(...)),
            ]);
        $container = new Container($config);

        $engine = $container->get(EngineInterface::class);
        $car = $container->get('car');
        $this->assertInstanceOf(Car::class, $car);
        $this->assertSame($engine, $car->getEngine());
    }

    public function testCallableWithArgs(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'engine1' => fn(EngineFactory $factory) => $factory->createByName(EngineMarkOne::NAME),
                'engine2' => fn(EngineFactory $factory) => $factory->createByName(EngineMarkTwo::NAME),
            ]);
        $container = new Container($config);
        $engine1 = $container->get('engine1');
        $this->assertInstanceOf(EngineMarkOne::class, $engine1);
        $this->assertSame(EngineMarkOne::NUMBER, $engine1->getNumber());
        $engine2 = $container->get('engine2');
        $this->assertInstanceOf(EngineMarkTwo::class, $engine2);
        $this->assertSame(EngineMarkTwo::NUMBER, $engine2->getNumber());
    }

    public function testCallableWithDependencies(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'car1' => fn(CarFactory $carFactory, EngineFactory $engineFactory) => $carFactory->createByEngineName(
                    $engineFactory,
                    EngineMarkOne::NAME,
                ),
                'car2' => fn(CarFactory $carFactory, EngineFactory $engineFactory) => $carFactory->createByEngineName(
                    $engineFactory,
                    EngineMarkTwo::NAME,
                ),
            ]);
        $container = new Container($config);
        $car1 = $container->get('car1');
        $this->assertInstanceOf(Car::class, $car1);
        $this->assertInstanceOf(EngineMarkOne::class, $car1->getEngine());
        $car2 = $container->get('car2');
        $this->assertInstanceOf(Car::class, $car2);
        $this->assertInstanceOf(EngineMarkTwo::class, $car2->getEngine());
    }

    public function testObject(): void
    {
        $engine = new EngineMarkOne();

        $config = ContainerConfig::create()
            ->withDefinitions([
                'engine' => $engine,
            ]);
        $container = new Container($config);

        $object = $container->get('engine');
        $this->assertSame($engine, $object);
    }

    public function testArrayStaticCall(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                'car' => CarFactory::create(...),
            ]);
        $container = new Container($config);

        $car = $container->get('car');
        $this->assertInstanceOf(Car::class, $car);
        $this->assertInstanceOf(EngineMarkOne::class, $car->getEngine());
    }

    public function testArrayDynamicCall(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                ColorInterface::class => ColorPink::class,
                'car' => [CarFactory::class, 'createWithColor'],
            ]);
        $container = new Container($config);

        $car = $container->get('car');
        $this->assertInstanceOf(Car::class, $car);
        $this->assertInstanceOf(ColorPink::class, $car->getColor());
    }

    public function testArrayDynamicCallWithObject(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                ColorInterface::class => ColorPink::class,
                'car' => [new CarFactory(), 'createWithColor'],
            ]);
        $container = new Container($config);

        $car = $container->get('car');
        $this->assertInstanceOf(Car::class, $car);
        $this->assertInstanceOf(ColorPink::class, $car->getColor());
    }

    public function testInvokeable(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'engine' => EngineMarkOne::class,
                'invokeable' => new InvokableCarFactory(),
            ]);
        $container = new Container($config);

        $object = $container->get('invokeable');
        $this->assertInstanceOf(Car::class, $object);
    }

    public function testReference(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'engine' => EngineMarkOne::class,
                'color' => ColorPink::class,
                'car' => [
                    'class' => Car::class,
                    '__construct()' => [
                        Reference::to('engine'),
                    ],
                    '$color' => Reference::to('color'),
                ],
            ]);
        $container = new Container($config);
        $object = $container->get('car');
        $this->assertInstanceOf(Car::class, $object);
        $this->assertInstanceOf(ColorPink::class, $object->color);
    }

    public function testReferencesInArrayInDependencies(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'engine1' => EngineMarkOne::class,
                'engine2' => EngineMarkTwo::class,
                'engine3' => EngineMarkTwo::class,
                'engine4' => EngineMarkTwo::class,
                'car' => [
                    'class' => Car::class,
                    '__construct()' => [
                        Reference::to('engine1'),
                        [
                            'engine2' => Reference::to('engine2'),
                            'more' => [
                                'engine3' => Reference::to('engine3'),
                                'more' => [
                                    'engine4' => Reference::to('engine4'),
                                ],
                            ],
                        ],
                    ],
                ],
            ]);
        $container = new Container($config);
        $car = $container->get('car');
        $this->assertInstanceOf(Car::class, $car);
        $moreEngines = $car->getMoreEngines();
        $this->assertSame($container->get('engine2'), $moreEngines['engine2']);
        $this->assertSame($container->get('engine3'), $moreEngines['more']['engine3']);
        $this->assertSame($container->get('engine4'), $moreEngines['more']['more']['engine4']);
    }

    public function testReferencesInProperties(): void
    {
        $color = new ColorPink();

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                ColorInterface::class => $color,
                'car' => [
                    'class' => Car::class,
                    '$color' => Reference::to(ColorInterface::class),
                ],
            ]);

        $container = new Container($config);
        $car = $container->get('car');
        $this->assertInstanceOf(Car::class, $car);
        $this->assertSame($color, $car->getColor());
    }

    public function testReferencesInMethodCall(): void
    {
        $color = new ColorPink();

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                ColorInterface::class => $color,
                'car' => [
                    'class' => Car::class,
                    'setColor()' => [Reference::to(ColorInterface::class)],
                ],
            ]);
        $container = new Container($config);
        $car = $container->get('car');
        $this->assertInstanceOf(Car::class, $car);
        $this->assertSame($color, $car->getColor());
    }

    public function testCallableArrayValueInConstructor(): void
    {
        $array = [
            [EngineMarkTwo::class, 'getNumber'],
        ];

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                Car::class => [
                    'class' => Car::class,
                    '__construct()' => [
                        Reference::to(EngineInterface::class),
                        $array,
                    ],
                ],
            ]);
        $container = new Container($config);

        /** @var Car $object */
        $object = $container->get(Car::class);
        $this->assertSame($array, $object->getMoreEngines());
    }

    public function testSameInstance(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'engine' => EngineMarkOne::class,
            ]);
        $container = new Container($config);

        $one = $container->get('engine');
        $two = $container->get('engine');
        $this->assertSame($one, $two);
    }

    public function testGetByClassIndirectly(): void
    {
        $number = 42;

        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                EngineMarkOne::class => [
                    'setNumber()' => [$number],
                ],
            ]);
        $container = new Container($config);

        $engine = $container->get(EngineInterface::class);
        $this->assertInstanceOf(EngineMarkOne::class, $engine);
        $this->assertSame($number, $engine->getNumber());
    }

    public function testThrowingNotFoundException(): void
    {
        $this->expectException(NotFoundException::class);

        $container = new Container();
        $container->get('non_existing');
    }

    public function testContainerInContainer(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                'container' => static fn(ContainerInterface $container) => $container,
            ]);
        $container = new Container($config);

        $this->assertSame($container, $container->get('container'));
        $this->assertSame($container, $container->get(ContainerInterface::class));
    }

    public function testTagsInArrayDefinition(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkOne::class => [
                    'class' => EngineMarkOne::class,
                    'tags' => ['engine'],
                ],
                EngineMarkTwo::class => [
                    'class' => EngineMarkTwo::class,
                    'tags' => ['engine'],
                ],
            ]);
        $container = new Container($config);

        $engines = $container->get('tag@engine');

        $this->assertIsArray($engines);
        $this->assertSame(EngineMarkOne::class, $engines[0]::class);
        $this->assertSame(EngineMarkTwo::class, $engines[1]::class);
    }

    public function testTagsInClosureDefinition(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkOne::class => [
                    'definition' => fn() => new EngineMarkOne(),
                    'tags' => ['engine'],
                ],
                EngineMarkTwo::class => [
                    'definition' => fn() => new EngineMarkTwo(),
                    'tags' => ['engine'],
                ],
            ]);
        $container = new Container($config);

        $engines = $container->get('tag@engine');

        $this->assertIsArray($engines);
        $this->assertSame(EngineMarkOne::class, $engines[0]::class);
        $this->assertSame(EngineMarkTwo::class, $engines[1]::class);
    }

    public function testTagsMultiple(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkOne::class => [
                    'class' => EngineMarkOne::class,
                    'tags' => ['engine', 'mark_one'],
                ],
                EngineMarkTwo::class => [
                    'class' => EngineMarkTwo::class,
                    'tags' => ['engine'],
                ],
            ]);
        $container = new Container($config);

        $engines = $container->get('tag@engine');
        $markOneEngines = $container->get('tag@mark_one');

        $this->assertIsArray($engines);
        $this->assertSame(EngineMarkOne::class, $engines[0]::class);
        $this->assertSame(EngineMarkTwo::class, $engines[1]::class);
        $this->assertIsArray($markOneEngines);
        $this->assertSame(EngineMarkOne::class, $markOneEngines[0]::class);
        $this->assertCount(1, $markOneEngines);
    }

    public function testTagsEmpty(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkOne::class => [
                    'class' => EngineMarkOne::class,
                ],
                EngineMarkTwo::class => [
                    'class' => EngineMarkTwo::class,
                ],
            ]);
        $container = new Container($config);

        $engines = $container->get('tag@engine');

        $this->assertIsArray($engines);
        $this->assertCount(0, $engines);
    }

    public function testTagsWithExternalDefinition(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkOne::class => [
                    'class' => EngineMarkOne::class,
                    'tags' => ['engine'],
                ],
                EngineMarkTwo::class => [
                    'class' => EngineMarkTwo::class,
                ],
            ])
            ->withTags(['engine' => [EngineMarkTwo::class]]);
        $container = new Container($config);

        $engines = $container->get('tag@engine');

        $this->assertIsArray($engines);
        $this->assertCount(2, $engines);
        $this->assertSame(EngineMarkOne::class, $engines[1]::class);
        $this->assertSame(EngineMarkTwo::class, $engines[0]::class);
    }

    public function testTagsWithExternalDefinitionMerge(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineMarkOne::class => [
                    'class' => EngineMarkOne::class,
                    'tags' => ['engine'],
                ],
                EngineMarkTwo::class => [
                    'class' => EngineMarkTwo::class,
                    'tags' => ['engine'],
                ],
            ])
            ->withTags(['mark_two' => [EngineMarkTwo::class]]);
        $container = new Container($config);

        $engines = $container->get('tag@engine');
        $markTwoEngines = $container->get('tag@mark_two');

        $this->assertIsArray($engines);
        $this->assertCount(2, $engines);
        $this->assertSame(EngineMarkOne::class, $engines[0]::class);
        $this->assertSame(EngineMarkTwo::class, $engines[1]::class);
        $this->assertIsArray($markTwoEngines);
        $this->assertCount(1, $markTwoEngines);
        $this->assertSame(EngineMarkTwo::class, $markTwoEngines[0]::class);
    }

    public function testTagsAsArrayInConstructor(): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                EngineMarkOne::class => [
                    'class' => EngineMarkOne::class,
                    'tags' => ['engine'],
                ],
                EngineMarkTwo::class => [
                    'class' => EngineMarkTwo::class,
                    'tags' => ['engine'],
                ],
                Car::class => [
                    '__construct()' => ['moreEngines' => Reference::to('tag@engine')],
                ],
            ]);
        $container = new Container($config);

        $engines = $container
            ->get(Car::class)
            ->getMoreEngines();

        $this->assertIsArray($engines);
        $this->assertCount(2, $engines);
        $this->assertSame(EngineMarkOne::class, $engines[0]::class);
        $this->assertSame(EngineMarkTwo::class, $engines[1]::class);
    }

    public static function dataResetter(): array
    {
        return [
            'strict-mode' => [true],
            'non-strict-mode' => [false],
        ];
    }

    #[DataProvider('dataResetter')]
    public function testResetter(bool $strictMode): void
    {
        $config = ContainerConfig::create()
            ->withDefinitions([
                EngineInterface::class => EngineMarkOne::class,
                EngineMarkOne::class => [
                    'class' => EngineMarkOne::class,
                    'setNumber()' => [42],
                    'reset' => function () {
                        $this->number = 42;
                    },
                ],
            ])
            ->withStrictMode($strictMode);
        $container = new Container($config);

        $engine = $container->get(EngineInterface::class);
        $this->assertSame(
            42,
            $container
                ->get(EngineInterface::class)
                ->getNumber(),
        );

        $engine->setNumber(45);
        $this->assertSame(
            45,
            $container
                ->get(EngineInterface::class)
                ->getNumber(),
        );

        $container
            ->get(StateResetter::class)
            ->reset();

        $this->assertSame($engine, $container->get(EngineInterface::class));
        $this->assertSame(42, $engine->getNumber());
    }

    public function testResetterInDelegates(): void
    {
        $config = ContainerConfig::create()
            ->withDelegates([
                static function (ContainerInterface $container) {
                    $config = ContainerConfig::create()
                        ->withDefinitions([
                            EngineInterface::class => [
                                'class' => EngineMarkOne::class,
                                'setNumber()' => [42],
                                'reset' => function () {
                                    $this->number = 42;
                                },
                            ],
                        ]);
                    return new Container($config);
                },
            ]);
        $container = new Container($config);

        $engine = $container->get(EngineInterface::class);
        $this->assertSame(
            42,
            $container
                ->get(EngineInterface::class)
                ->getNumber(),
        );

        $engine->setNumber(45);
        $this->assertSame(
            45,
            $container
                ->get(EngineInterface::class)
                ->getNumber(),
        );

        $container
            ->get(StateResetter::class)
            ->reset();

        $this->assertSame($engine, $container->get(EngineInterface::class));
        $this->assertSame(42, $engine->getNumber());
    }

    public function testNewContainerDefinitionInDelegates(): void
    {
        $firstContainer = null;
        $secondContainer = null;

        $config = ContainerConfig::create()
            ->withDefinitions([
                ContainerInterface::class => new Container(),
            ])
            ->withDelegates([
                function (ContainerInterface $container) use (&$firstContainer): ContainerInterface {
                    $firstContainer = $container;
                    return new Container();
                },
                function (ContainerInterface $container) use (&$secondContainer): ContainerInterface {
                    $secondContainer = $container;
                    return new Container();
                },
            ]);
        $originalContainer = new Container($config);

        $container = $origin
Download .txt
gitextract_8vkzrttm/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── SECURITY.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── bc.yml
│       ├── bechmark.yml
│       ├── build.yml
│       ├── composer-require-checker.yml
│       ├── mutation.yml
│       ├── rector-cs.yml
│       └── static.yml
├── .gitignore
├── .php-cs-fixer.dist.php
├── .phpstorm.meta.php
├── .phpunit-watcher.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── benchmarks.md
├── composer.json
├── docs/
│   └── internals.md
├── infection.json.dist
├── phpbench.json
├── phpcs.xml.dist
├── phpunit.xml.dist
├── psalm.xml
├── rector.php
├── src/
│   ├── BuildingException.php
│   ├── CompositeContainer.php
│   ├── CompositeNotFoundException.php
│   ├── Container.php
│   ├── ContainerConfig.php
│   ├── ContainerConfigInterface.php
│   ├── ExtensibleService.php
│   ├── Helpers/
│   │   ├── DefinitionNormalizer.php
│   │   └── DefinitionParser.php
│   ├── NotFoundException.php
│   ├── Reference/
│   │   └── TagReference.php
│   ├── ServiceProviderInterface.php
│   └── StateResetter.php
├── tests/
│   ├── Benchmark/
│   │   ├── ContainerBench.php
│   │   └── ContainerMethodHasBench.php
│   ├── Support/
│   │   ├── A.php
│   │   ├── B.php
│   │   ├── Car.php
│   │   ├── CarExtensionProvider.php
│   │   ├── CarFactory.php
│   │   ├── CarProvider.php
│   │   ├── ColorInterface.php
│   │   ├── ColorPink.php
│   │   ├── ColorRed.php
│   │   ├── ConstructorTestClass.php
│   │   ├── ContainerInterfaceExtensionProvider.php
│   │   ├── Cycle/
│   │   │   ├── Chicken.php
│   │   │   └── Egg.php
│   │   ├── EngineFactory.php
│   │   ├── EngineInterface.php
│   │   ├── EngineMarkOne.php
│   │   ├── EngineMarkTwo.php
│   │   ├── EngineStorage.php
│   │   ├── Garage.php
│   │   ├── GearBox.php
│   │   ├── InvokableCarFactory.php
│   │   ├── MethodTestClass.php
│   │   ├── NonPsrContainer.php
│   │   ├── NullCarExtensionProvider.php
│   │   ├── NullableConcreteDependency.php
│   │   ├── OptionalConcreteDependency.php
│   │   ├── PropertyTestClass.php
│   │   ├── SportCar.php
│   │   ├── StaticFactory.php
│   │   ├── TreeItem.php
│   │   ├── UnionTypeInConstructorFirstTypeInParamResolvable.php
│   │   ├── UnionTypeInConstructorParamNotResolvable.php
│   │   ├── UnionTypeInConstructorSecondParamNotResolvable.php
│   │   ├── UnionTypeInConstructorSecondTypeInParamResolvable.php
│   │   └── VariadicConstructor.php
│   └── Unit/
│       ├── BuildingExceptionTest.php
│       ├── CompositeContainerTest.php
│       ├── CompositePsrContainerOverLeagueTest.php
│       ├── CompositePsrContainerOverYiisoftTest.php
│       ├── CompositePsrContainerTestAbstract.php
│       ├── Container/
│       │   └── DependencyFromDelegate/
│       │       ├── Car.php
│       │       ├── DependencyFromDelegateTest.php
│       │       ├── Engine.php
│       │       └── EngineInterface.php
│       ├── ContainerTest.php
│       ├── Helpers/
│       │   └── DefinitionParserTest.php
│       ├── LeaguePsrContainerTest.php
│       ├── NotFoundExceptionTest.php
│       ├── PsrContainerTestAbstract.php
│       ├── Reference/
│       │   └── TagReference/
│       │       ├── Resolve/
│       │       │   ├── A.php
│       │       │   ├── B.php
│       │       │   ├── Main.php
│       │       │   └── TagReferenceResolveTest.php
│       │       └── TagReferenceTest.php
│       ├── ServiceProviderTest.php
│       ├── StateResetterTest.php
│       └── YiisoftPsrContainerTest.php
└── tools/
    ├── .gitignore
    ├── infection/
    │   └── composer.json
    └── psalm/
        └── composer.json
Download .txt
SYMBOL INDEX (387 symbols across 72 files)

FILE: src/BuildingException.php
  class BuildingException (line 18) | final class BuildingException extends Exception implements ContainerExce...
    method __construct (line 24) | public function __construct(
    method getName (line 39) | public function getName(): string
    method getSolution (line 44) | public function getSolution(): ?string

FILE: src/CompositeContainer.php
  class CompositeContainer (line 19) | final class CompositeContainer implements ContainerInterface
    method get (line 33) | public function get($id)
    method has (line 105) | public function has($id): bool
    method attach (line 145) | public function attach(ContainerInterface $container): void
    method detach (line 153) | public function detach(ContainerInterface $container): void

FILE: src/CompositeNotFoundException.php
  class CompositeNotFoundException (line 17) | final class CompositeNotFoundException extends Exception implements NotF...
    method __construct (line 24) | public function __construct(array $exceptions)

FILE: src/Container.php
  class Container (line 35) | final class Container implements ContainerInterface
    method __construct (line 83) | public function __construct(?ContainerConfigInterface $config = null)
    method has (line 110) | public function has(string $id): bool
    method get (line 149) | public function get(string $id)
    method prepareStateResetter (line 207) | private function prepareStateResetter(): StateResetter
    method addDefinition (line 249) | private function addDefinition(string $id, mixed $definition): void
    method addDefinitions (line 283) | private function addDefinitions(array $config): void
    method setDelegates (line 308) | private function setDelegates(array $delegates): void
    method validateDefinition (line 340) | private function validateDefinition(mixed $definition, ?string $id = n...
    method validateMeta (line 378) | private function validateMeta(array $meta): void
    method validateDefinitionTags (line 405) | private function validateDefinitionTags(mixed $tags): void
    method validateDefinitionReset (line 426) | private function validateDefinitionReset(mixed $reset): void
    method setTags (line 441) | private function setTags(array $tags): void
    method setDefinitionTags (line 481) | private function setDefinitionTags(string $id, array $tags): void
    method setDefinitionResetter (line 490) | private function setDefinitionResetter(string $id, Closure $resetter):...
    method addDefinitionToStorage (line 503) | private function addDefinitionToStorage(string $id, mixed $definition)...
    method build (line 525) | private function build(string $id): mixed
    method getTaggedServices (line 562) | private function getTaggedServices(string $tagAlias): array
    method addProviders (line 579) | private function addProviders(array $providers): void
    method buildProvider (line 633) | private function buildProvider(mixed $provider): ServiceProviderInterface

FILE: src/ContainerConfig.php
  class ContainerConfig (line 10) | final class ContainerConfig implements ContainerConfigInterface
    method __construct (line 19) | private function __construct() {}
    method create (line 21) | public static function create(): self
    method withDefinitions (line 29) | public function withDefinitions(array $definitions): self
    method getDefinitions (line 36) | public function getDefinitions(): array
    method withProviders (line 44) | public function withProviders(array $providers): self
    method getProviders (line 51) | public function getProviders(): array
    method withTags (line 59) | public function withTags(array $tags): self
    method getTags (line 66) | public function getTags(): array
    method withValidate (line 74) | public function withValidate(bool $validate = true): self
    method shouldValidate (line 81) | public function shouldValidate(): bool
    method withDelegates (line 91) | public function withDelegates(array $delegates): self
    method getDelegates (line 98) | public function getDelegates(): array
    method withStrictMode (line 107) | public function withStrictMode(bool $useStrictMode = true): self
    method useStrictMode (line 114) | public function useStrictMode(): bool

FILE: src/ContainerConfigInterface.php
  type ContainerConfigInterface (line 10) | interface ContainerConfigInterface
    method getDefinitions (line 15) | public function getDefinitions(): array;
    method getProviders (line 20) | public function getProviders(): array;
    method getTags (line 25) | public function getTags(): array;
    method shouldValidate (line 30) | public function shouldValidate(): bool;
    method getDelegates (line 37) | public function getDelegates(): array;
    method useStrictMode (line 42) | public function useStrictMode(): bool;

FILE: src/ExtensibleService.php
  class ExtensibleService (line 21) | final class ExtensibleService implements DefinitionInterface
    method __construct (line 31) | public function __construct(
    method addExtension (line 49) | public function addExtension(callable $closure): void
    method resolve (line 54) | public function resolve(ContainerInterface $container): mixed

FILE: src/Helpers/DefinitionNormalizer.php
  class DefinitionNormalizer (line 18) | final class DefinitionNormalizer
    method normalize (line 26) | public static function normalize(mixed $definition, string $id): Defin...

FILE: src/Helpers/DefinitionParser.php
  class DefinitionParser (line 47) | final class DefinitionParser
    method parse (line 58) | public static function parse(mixed $definition): array

FILE: src/NotFoundException.php
  class NotFoundException (line 17) | final class NotFoundException extends Exception implements NotFoundExcep...
    method __construct (line 23) | public function __construct(
    method getId (line 43) | public function getId(): string
    method getBuildStack (line 51) | public function getBuildStack(): array
    method getName (line 56) | public function getName(): string
    method getSolution (line 61) | public function getSolution(): ?string

FILE: src/Reference/TagReference.php
  class TagReference (line 16) | final class TagReference
    method __construct (line 20) | private function __construct() {}
    method to (line 22) | public static function to(string $tag): Reference
    method id (line 27) | public static function id(string $tag): string
    method extractTagFromAlias (line 32) | public static function extractTagFromAlias(string $alias): string
    method isTagAlias (line 40) | public static function isTagAlias(string $id): bool

FILE: src/ServiceProviderInterface.php
  type ServiceProviderInterface (line 32) | interface ServiceProviderInterface
    method getDefinitions (line 45) | public function getDefinitions(): array;
    method getExtensions (line 61) | public function getExtensions(): array;

FILE: src/StateResetter.php
  class StateResetter (line 20) | final class StateResetter
    method __construct (line 30) | public function __construct(
    method reset (line 37) | public function reset(): void
    method setResetters (line 53) | public function setResetters(array $resetters): void

FILE: tests/Benchmark/ContainerBench.php
  class ContainerBench (line 30) | class ContainerBench
    method provideDefinitions (line 42) | public function provideDefinitions(): array
    method before (line 67) | public function before(): void
    method benchConstruct (line 138) | public function benchConstruct(): void
    method benchSequentialLookups (line 154) | public function benchSequentialLookups($params): void
    method benchRandomLookups (line 178) | public function benchRandomLookups($params): void
    method benchRandomLookupsComposite (line 202) | public function benchRandomLookupsComposite($params): void

FILE: tests/Benchmark/ContainerMethodHasBench.php
  class ContainerMethodHasBench (line 23) | class ContainerMethodHasBench
    method before (line 32) | public function before(): void
    method benchPredefinedExisting (line 46) | public function benchPredefinedExisting(): void
    method benchUndefinedExisting (line 53) | public function benchUndefinedExisting(): void
    method benchUndefinedNonexistent (line 60) | public function benchUndefinedNonexistent(): void

FILE: tests/Support/A.php
  class A (line 7) | class A
    method __construct (line 9) | public function __construct(public ?B $b = null) {}

FILE: tests/Support/B.php
  class B (line 7) | class B
    method __construct (line 9) | public function __construct(public ?A $a = null) {}

FILE: tests/Support/Car.php
  class Car (line 10) | class Car
    method __construct (line 14) | public function __construct(
    method setColor (line 19) | public function setColor(ColorInterface $color): self
    method getColor (line 26) | public function getColor(): ColorInterface
    method getEngine (line 31) | public function getEngine(): EngineInterface
    method getEngineName (line 36) | public function getEngineName(): string
    method getMoreEngines (line 41) | public function getMoreEngines(): array

FILE: tests/Support/CarExtensionProvider.php
  class CarExtensionProvider (line 10) | final class CarExtensionProvider implements ServiceProviderInterface
    method getDefinitions (line 12) | public function getDefinitions(): array
    method getExtensions (line 17) | public function getExtensions(): array

FILE: tests/Support/CarFactory.php
  class CarFactory (line 10) | class CarFactory
    method create (line 12) | public static function create(EngineInterface $engine): Car
    method createByEngineName (line 17) | public function createByEngineName(EngineFactory $factory, $name): Car
    method createWithColor (line 22) | public function createWithColor(ColorInterface $color): Car

FILE: tests/Support/CarProvider.php
  class CarProvider (line 10) | final class CarProvider implements ServiceProviderInterface
    method getDefinitions (line 12) | public function getDefinitions(): array
    method getExtensions (line 20) | public function getExtensions(): array

FILE: tests/Support/ColorInterface.php
  type ColorInterface (line 10) | interface ColorInterface
    method getColor (line 12) | public function getColor(): string;

FILE: tests/Support/ColorPink.php
  class ColorPink (line 10) | final class ColorPink implements ColorInterface
    method getColor (line 14) | public function getColor(): string

FILE: tests/Support/ColorRed.php
  class ColorRed (line 11) | final class ColorRed implements ColorInterface
    method getColor (line 15) | public function getColor(): string

FILE: tests/Support/ConstructorTestClass.php
  class ConstructorTestClass (line 12) | class ConstructorTestClass
    method __construct (line 21) | public function __construct(private $parameter)
    method getParameter (line 29) | public function getParameter()
    method getAllParameters (line 34) | public function getAllParameters(): array

FILE: tests/Support/ContainerInterfaceExtensionProvider.php
  class ContainerInterfaceExtensionProvider (line 10) | final class ContainerInterfaceExtensionProvider implements ServiceProvid...
    method getDefinitions (line 12) | public function getDefinitions(): array
    method getExtensions (line 17) | public function getExtensions(): array

FILE: tests/Support/Cycle/Chicken.php
  class Chicken (line 7) | class Chicken
    method __construct (line 9) | public function __construct(Egg $egg) {}

FILE: tests/Support/Cycle/Egg.php
  class Egg (line 7) | class Egg
    method __construct (line 9) | public function __construct(Chicken $chicken) {}

FILE: tests/Support/EngineFactory.php
  class EngineFactory (line 13) | class EngineFactory
    method __construct (line 15) | public function __construct(private readonly ContainerInterface $conta...
    method createByName (line 17) | public function createByName(?string $name = null): EngineInterface
    method createDefault (line 29) | public static function createDefault(): EngineInterface

FILE: tests/Support/EngineInterface.php
  type EngineInterface (line 10) | interface EngineInterface
    method getName (line 12) | public function getName(): string;
    method setNumber (line 14) | public function setNumber(int $value): void;
    method getNumber (line 16) | public function getNumber(): int;

FILE: tests/Support/EngineMarkOne.php
  class EngineMarkOne (line 10) | class EngineMarkOne implements EngineInterface
    method __construct (line 15) | public function __construct(private int $number = self::NUMBER) {}
    method getName (line 17) | public function getName(): string
    method setNumber (line 22) | public function setNumber(int $value): void
    method getNumber (line 27) | public function getNumber(): int

FILE: tests/Support/EngineMarkTwo.php
  class EngineMarkTwo (line 10) | class EngineMarkTwo implements EngineInterface
    method __construct (line 15) | public function __construct(private int $number = self::NUMBER) {}
    method getName (line 17) | public function getName(): string
    method setNumber (line 22) | public function setNumber(int $value): void
    method getNumber (line 27) | public function getNumber(): int

FILE: tests/Support/EngineStorage.php
  class EngineStorage (line 7) | final class EngineStorage
    method __construct (line 11) | public function __construct(EngineInterface ...$engines)
    method getEngines (line 16) | public function getEngines(): array

FILE: tests/Support/Garage.php
  class Garage (line 10) | final class Garage
    method __construct (line 12) | public function __construct(private readonly SportCar $car) {}
    method getCar (line 14) | public function getCar(): SportCar

FILE: tests/Support/GearBox.php
  class GearBox (line 10) | class GearBox
    method __construct (line 12) | public function __construct(private readonly int $maxGear = 5) {}

FILE: tests/Support/InvokableCarFactory.php
  class InvokableCarFactory (line 9) | class InvokableCarFactory
    method __invoke (line 11) | public function __invoke(ContainerInterface $container): Car

FILE: tests/Support/MethodTestClass.php
  class MethodTestClass (line 10) | class MethodTestClass
    method getValue (line 17) | public function getValue()
    method setValue (line 22) | public function setValue(mixed $value): void

FILE: tests/Support/NonPsrContainer.php
  class NonPsrContainer (line 10) | final class NonPsrContainer implements ContainerInterface
    method get (line 12) | public function get(string $id)
    method has (line 17) | public function has(string $id): bool

FILE: tests/Support/NullCarExtensionProvider.php
  class NullCarExtensionProvider (line 10) | final class NullCarExtensionProvider implements ServiceProviderInterface
    method getDefinitions (line 12) | public function getDefinitions(): array
    method getExtensions (line 18) | public function getExtensions(): array

FILE: tests/Support/NullableConcreteDependency.php
  class NullableConcreteDependency (line 7) | class NullableConcreteDependency
    method __construct (line 9) | public function __construct(?Car $car) {}

FILE: tests/Support/OptionalConcreteDependency.php
  class OptionalConcreteDependency (line 7) | class OptionalConcreteDependency
    method __construct (line 9) | public function __construct(private readonly ?Car $car = null) {}
    method getCar (line 11) | public function getCar(): ?Car

FILE: tests/Support/PropertyTestClass.php
  class PropertyTestClass (line 10) | class PropertyTestClass

FILE: tests/Support/SportCar.php
  class SportCar (line 10) | class SportCar
    method __construct (line 14) | public function __construct(
    method setColor (line 19) | public function setColor(ColorInterface $color): self
    method getColor (line 26) | public function getColor(): ColorInterface
    method getEngine (line 31) | public function getEngine(): EngineInterface
    method getEngineName (line 36) | public function getEngineName(): string
    method getMaxSpeed (line 41) | public function getMaxSpeed(): int

FILE: tests/Support/StaticFactory.php
  class StaticFactory (line 9) | final class StaticFactory
    method create (line 11) | public static function create(): stdClass

FILE: tests/Support/TreeItem.php
  class TreeItem (line 10) | class TreeItem
    method __construct (line 12) | public function __construct(private readonly self $treeItem) {}

FILE: tests/Support/UnionTypeInConstructorFirstTypeInParamResolvable.php
  class UnionTypeInConstructorFirstTypeInParamResolvable (line 7) | final class UnionTypeInConstructorFirstTypeInParamResolvable
    method __construct (line 9) | public function __construct(private readonly EngineMarkOne|EngineInter...

FILE: tests/Support/UnionTypeInConstructorParamNotResolvable.php
  class UnionTypeInConstructorParamNotResolvable (line 7) | final class UnionTypeInConstructorParamNotResolvable
    method __construct (line 9) | public function __construct(private readonly EngineInterface|ColorInte...

FILE: tests/Support/UnionTypeInConstructorSecondParamNotResolvable.php
  class UnionTypeInConstructorSecondParamNotResolvable (line 7) | final class UnionTypeInConstructorSecondParamNotResolvable
    method __construct (line 9) | public function __construct(EngineMarkOne|EngineInterface $engine, str...

FILE: tests/Support/UnionTypeInConstructorSecondTypeInParamResolvable.php
  class UnionTypeInConstructorSecondTypeInParamResolvable (line 7) | final class UnionTypeInConstructorSecondTypeInParamResolvable
    method __construct (line 9) | public function __construct(private readonly EngineInterface|EngineMar...

FILE: tests/Support/VariadicConstructor.php
  class VariadicConstructor (line 7) | final class VariadicConstructor
    method __construct (line 11) | public function __construct(
    method getFirst (line 19) | public function getFirst()
    method getEngine (line 24) | public function getEngine(): EngineInterface
    method getParameters (line 29) | public function getParameters(): array

FILE: tests/Unit/BuildingExceptionTest.php
  class BuildingExceptionTest (line 11) | final class BuildingExceptionTest extends TestCase
    method testMessage (line 13) | public function testMessage(): void
    method testEmptyMessage (line 29) | public function testEmptyMessage(): void
    method testBuildStack (line 36) | public function testBuildStack(): void
    method testCode (line 43) | public function testCode(): void

FILE: tests/Unit/CompositeContainerTest.php
  class CompositeContainerTest (line 22) | final class CompositeContainerTest extends TestCase
    method testGetNonString (line 24) | public function testGetNonString(): void
    method testTagsWithYiiAndNotYiiContainers (line 35) | public function testTagsWithYiiAndNotYiiContainers(): void
    method testNonPsrContainer (line 65) | public function testNonPsrContainer(): void
    method testHasNoString (line 81) | public function testHasNoString(): void
    method testHasTag (line 90) | #[TestWith([true, 'engine'])]
    method testHasTagWithoutYiiContainer (line 105) | public function testHasTagWithoutYiiContainer(): void

FILE: tests/Unit/CompositePsrContainerOverLeagueTest.php
  class CompositePsrContainerOverLeagueTest (line 15) | final class CompositePsrContainerOverLeagueTest extends CompositePsrCont...
    method createContainer (line 17) | public function createContainer(iterable $definitions = []): Container...
    method setupContainer (line 23) | public function setupContainer(ContainerInterface $container, iterable...
    method testNotFoundException (line 32) | public function testNotFoundException(): void

FILE: tests/Unit/CompositePsrContainerOverYiisoftTest.php
  class CompositePsrContainerOverYiisoftTest (line 19) | final class CompositePsrContainerOverYiisoftTest extends CompositePsrCon...
    method createContainer (line 21) | public function createContainer(iterable $definitions = []): Container...
    method testResetterInCompositeContainerWithExternalResetter (line 29) | public function testResetterInCompositeContainerWithExternalResetter()...
    method testNotFoundException (line 109) | public function testNotFoundException(): void

FILE: tests/Unit/CompositePsrContainerTestAbstract.php
  class CompositePsrContainerTestAbstract (line 22) | abstract class CompositePsrContainerTestAbstract extends PsrContainerTes...
    method createCompositeContainer (line 24) | public function createCompositeContainer(ContainerInterface $attachedC...
    method testAttach (line 32) | public function testAttach(): void
    method testDetach (line 46) | public function testDetach(): void
    method testHasDefinition (line 63) | public function testHasDefinition(): void
    method testGetPriority (line 77) | public function testGetPriority(): void
    method testTags (line 103) | public function testTags(): void
    method testDelegateLookup (line 136) | public function testDelegateLookup(): void
    method testDelegateLookupUnionTypes (line 155) | public function testDelegateLookupUnionTypes(): void

FILE: tests/Unit/Container/DependencyFromDelegate/Car.php
  class Car (line 7) | final class Car
    method __construct (line 9) | public function __construct(

FILE: tests/Unit/Container/DependencyFromDelegate/DependencyFromDelegateTest.php
  class DependencyFromDelegateTest (line 20) | final class DependencyFromDelegateTest extends TestCase
    method testAnotherContainer (line 22) | public function testAnotherContainer(): void
    method testNotFoundInDelegate (line 43) | public function testNotFoundInDelegate(): void

FILE: tests/Unit/Container/DependencyFromDelegate/Engine.php
  class Engine (line 7) | final class Engine implements EngineInterface {}

FILE: tests/Unit/Container/DependencyFromDelegate/EngineInterface.php
  type EngineInterface (line 7) | interface EngineInterface {}

FILE: tests/Unit/ContainerTest.php
  class ContainerTest (line 64) | final class ContainerTest extends TestCase
    method testCanCreateWihtoutConfig (line 66) | public function testCanCreateWihtoutConfig(): void
    method testSettingScalars (line 73) | public function testSettingScalars(): void
    method testIntegerKeys (line 86) | public function testIntegerKeys(): void
    method testNullableClassDependency (line 100) | public function testNullableClassDependency(): void
    method testOptionalResolvableClassDependency (line 108) | public function testOptionalResolvableClassDependency(): void
    method testOptionalNotResolvableClassDependency (line 122) | public function testOptionalNotResolvableClassDependency(): void
    method testOptionalCircularClassDependency (line 131) | public function testOptionalCircularClassDependency(): void
    method dataHas (line 144) | public static function dataHas(): array
    method testHas (line 158) | #[DataProvider('dataHas')]
    method dataUnionTypes (line 170) | public static function dataUnionTypes(): array
    method testUnionTypes (line 178) | #[DataProvider('dataUnionTypes')]
    method testClassExistsButIsNotResolvable (line 186) | public function testClassExistsButIsNotResolvable(): void
    method dataClassExistButIsNotResolvableWithUnionTypes (line 197) | public static function dataClassExistButIsNotResolvableWithUnionTypes(...
    method testClassExistButIsNotResolvableWithUnionTypes (line 205) | #[DataProvider('dataClassExistButIsNotResolvableWithUnionTypes')]
    method testWithoutDefinition (line 213) | public function testWithoutDefinition(): void
    method testCircularClassDependencyWithoutDefinition (line 224) | public function testCircularClassDependencyWithoutDefinition(): void
    method testTrivialDefinition (line 231) | public function testTrivialDefinition(): void
    method testCircularClassDependency (line 245) | public function testCircularClassDependency(): void
    method testClassSimple (line 258) | public function testClassSimple(): void
    method testSetAll (line 268) | public function testSetAll(): void
    method testClassConstructor (line 281) | public function testClassConstructor(): void
    method testIntegerIndexedConstructorArguments (line 298) | public function testIntegerIndexedConstructorArguments(): void
    method testExcessiveConstructorParametersIgnored (line 318) | public function testExcessiveConstructorParametersIgnored(): void
    method testVariadicConstructorParameters (line 337) | public function testVariadicConstructorParameters(): void
    method testMixedIndexedConstructorParametersAreNotAllowed (line 367) | public function testMixedIndexedConstructorParametersAreNotAllowed(): ...
    method testClassProperties (line 388) | public function testClassProperties(): void
    method testClassMethods (line 404) | public function testClassMethods(): void
    method testClosureInConstructor (line 420) | public function testClosureInConstructor(): void
    method testDynamicClosureInConstruct (line 438) | public function testDynamicClosureInConstruct(): void
    method testKeepClosureDefinition (line 457) | public function testKeepClosureDefinition(): void
    method testClosureInProperty (line 475) | public function testClosureInProperty(): void
    method testDynamicClosureInProperty (line 492) | public function testDynamicClosureInProperty(): void
    method testClosureInMethodCall (line 511) | public function testClosureInMethodCall(): void
    method testDynamicClosureInMethodCall (line 529) | public function testDynamicClosureInMethodCall(): void
    method testAlias (line 548) | public function testAlias(): void
    method testCircularAlias (line 566) | public function testCircularAlias(): void
    method testUndefinedDependencies (line 580) | public function testUndefinedDependencies(): void
    method testDependencies (line 592) | public function testDependencies(): void
    method testCircularReference (line 606) | public function testCircularReference(): void
    method testFalsePositiveCircularReferenceWithClassID (line 621) | public function testFalsePositiveCircularReferenceWithClassID(): void
    method testFalsePositiveCircularReferenceWithStringID (line 648) | public function testFalsePositiveCircularReferenceWithStringID(): void
    method testCallable (line 675) | public function testCallable(): void
    method testCallableWithInjector (line 688) | public function testCallableWithInjector(): void
    method testCallableWithArgs (line 703) | public function testCallableWithArgs(): void
    method testCallableWithDependencies (line 719) | public function testCallableWithDependencies(): void
    method testObject (line 741) | public function testObject(): void
    method testArrayStaticCall (line 755) | public function testArrayStaticCall(): void
    method testArrayDynamicCall (line 769) | public function testArrayDynamicCall(): void
    method testArrayDynamicCallWithObject (line 783) | public function testArrayDynamicCallWithObject(): void
    method testInvokeable (line 797) | public function testInvokeable(): void
    method testReference (line 810) | public function testReference(): void
    method testReferencesInArrayInDependencies (line 830) | public function testReferencesInArrayInDependencies(): void
    method testReferencesInProperties (line 863) | public function testReferencesInProperties(): void
    method testReferencesInMethodCall (line 883) | public function testReferencesInMethodCall(): void
    method testCallableArrayValueInConstructor (line 902) | public function testCallableArrayValueInConstructor(): void
    method testSameInstance (line 926) | public function testSameInstance(): void
    method testGetByClassIndirectly (line 939) | public function testGetByClassIndirectly(): void
    method testThrowingNotFoundException (line 957) | public function testThrowingNotFoundException(): void
    method testContainerInContainer (line 965) | public function testContainerInContainer(): void
    method testTagsInArrayDefinition (line 977) | public function testTagsInArrayDefinition(): void
    method testTagsInClosureDefinition (line 999) | public function testTagsInClosureDefinition(): void
    method testTagsMultiple (line 1021) | public function testTagsMultiple(): void
    method testTagsEmpty (line 1047) | public function testTagsEmpty(): void
    method testTagsWithExternalDefinition (line 1066) | public function testTagsWithExternalDefinition(): void
    method testTagsWithExternalDefinitionMerge (line 1089) | public function testTagsWithExternalDefinitionMerge(): void
    method testTagsAsArrayInConstructor (line 1117) | public function testTagsAsArrayInConstructor(): void
    method dataResetter (line 1146) | public static function dataResetter(): array
    method testResetter (line 1154) | #[DataProvider('dataResetter')]
    method testResetterInDelegates (line 1195) | public function testResetterInDelegates(): void
    method testNewContainerDefinitionInDelegates (line 1239) | public function testNewContainerDefinitionInDelegates(): void
    method testResetterInDelegatesWithCustomResetter (line 1267) | public function testResetterInDelegatesWithCustomResetter(): void
    method dataResetterInProviderDefinitions (line 1351) | public static function dataResetterInProviderDefinitions(): array
    method testResetterInProviderDefinitions (line 1359) | #[DataProvider('dataResetterInProviderDefinitions')]
    method testResetterInProviderExtensions (line 1405) | public function testResetterInProviderExtensions(): void
    method testNestedResetter (line 1451) | public function testNestedResetter(): void
    method testResetterInCompositeContainer (line 1513) | public function testResetterInCompositeContainer(): void
    method testCircularReferenceExceptionWhileResolvingProviders (line 1593) | public function testCircularReferenceExceptionWhileResolvingProviders(...
    method testDifferentContainerWithProviders (line 1626) | public function testDifferentContainerWithProviders(): void
    method testErrorOnMethodTypo (line 1651) | public function testErrorOnMethodTypo(): void
    method testErrorOnPropertyTypo (line 1668) | public function testErrorOnPropertyTypo(): void
    method testErrorOnDisallowMeta (line 1685) | public function testErrorOnDisallowMeta(): void
    method testDelegateLookup (line 1703) | public function testDelegateLookup(): void
    method testNonClosureDelegate (line 1734) | public function testNonClosureDelegate(): void
    method testNonContainerDelegate (line 1746) | public function testNonContainerDelegate(): void
    method testExtensibleServiceDefinition (line 1760) | public function testExtensibleServiceDefinition(): void
    method testWrongTag (line 1774) | public function testWrongTag(): void
    method testNumberProvider (line 1790) | public function testNumberProvider(): void
    method testNonServiceProviderInterfaceProvider (line 1804) | public function testNonServiceProviderInterfaceProvider(): void
    method testStrictModeDisabled (line 1817) | public function testStrictModeDisabled(): void
    method testStrictModeEnabled (line 1828) | public function testStrictModeEnabled(): void
    method testIntegerKeyInExtensions (line 1839) | public function testIntegerKeyInExtensions(): void
    method testNonCallableExtension (line 1863) | public function testNonCallableExtension(): void
    method testNonArrayReset (line 1887) | public function testNonArrayReset(): void
    method testNonArrayTags (line 1905) | public function testNonArrayTags(): void
    method testNonArrayArguments (line 1923) | public function testNonArrayArguments(): void
    method dataInvalidTags (line 1940) | public static function dataInvalidTags(): array
    method testInvalidTags (line 1958) | #[DataProvider('dataInvalidTags')]
    method dataNotFoundExceptionMessageWithDefinitions (line 1969) | public static function dataNotFoundExceptionMessageWithDefinitions(): ...
    method testNotFoundExceptionMessageWithDefinitions (line 1977) | #[DataProvider('dataNotFoundExceptionMessageWithDefinitions')]
    method testNotFoundExceptionWithNotYiiContainer (line 1996) | public function testNotFoundExceptionWithNotYiiContainer(): void
    method testExceptionOnGetInDelegate (line 2023) | public function testExceptionOnGetInDelegate(): void
    method testExceptionOnHasInDelegate (line 2050) | public function testExceptionOnHasInDelegate(): void
    method testGetStateResetterTwice (line 2077) | public function testGetStateResetterTwice(): void

FILE: tests/Unit/Helpers/DefinitionParserTest.php
  class DefinitionParserTest (line 12) | final class DefinitionParserTest extends TestCase
    method testParseCallableDefinition (line 14) | public function testParseCallableDefinition(): void
    method testParseArrayCallableDefinition (line 26) | public function testParseArrayCallableDefinition(): void
    method testParseArrayDefinition (line 37) | public function testParseArrayDefinition(): void

FILE: tests/Unit/LeaguePsrContainerTest.php
  class LeaguePsrContainerTest (line 13) | final class LeaguePsrContainerTest extends PsrContainerTestAbstract
    method createContainer (line 15) | public function createContainer(array $definitions = []): ContainerInt...
    method setupContainer (line 20) | public function setupContainer(ContainerInterface $container, iterable...

FILE: tests/Unit/NotFoundExceptionTest.php
  class NotFoundExceptionTest (line 10) | final class NotFoundExceptionTest extends TestCase
    method testGetId (line 12) | public function testGetId(): void
    method testMessage (line 26) | public function testMessage(): void
    method testBuildStack (line 33) | public function testBuildStack(): void
    method testCode (line 43) | public function testCode(): void

FILE: tests/Unit/PsrContainerTestAbstract.php
  class PsrContainerTestAbstract (line 20) | abstract class PsrContainerTestAbstract extends TestCase
    method createContainer (line 22) | abstract public function createContainer(array $definitions = []): Con...
    method testCircularClassDependencyWithoutDefinition (line 24) | public function testCircularClassDependencyWithoutDefinition(): void
    method testSimpleDefinition (line 31) | public function testSimpleDefinition(): void
    method testClassSimple (line 40) | public function testClassSimple(): void
    method testSetAll (line 46) | public function testSetAll(): void
    method testObject (line 56) | public function testObject(): void
    method testThrowingNotFoundException (line 65) | public function testThrowingNotFoundException(): void

FILE: tests/Unit/Reference/TagReference/Resolve/A.php
  class A (line 7) | final class A {}

FILE: tests/Unit/Reference/TagReference/Resolve/B.php
  class B (line 7) | final class B {}

FILE: tests/Unit/Reference/TagReference/Resolve/Main.php
  class Main (line 7) | final class Main

FILE: tests/Unit/Reference/TagReference/Resolve/TagReferenceResolveTest.php
  class TagReferenceResolveTest (line 12) | final class TagReferenceResolveTest extends TestCase
    method testBase (line 14) | public function testBase(): void

FILE: tests/Unit/Reference/TagReference/TagReferenceTest.php
  class TagReferenceTest (line 14) | final class TagReferenceTest extends TestCase
    method testConstructorIsPrivate (line 16) | public function testConstructorIsPrivate(): void
    method testConstructor (line 22) | public function testConstructor(): void
    method testAliases (line 30) | public function testAliases(): void
    method testExtractTag (line 37) | public function testExtractTag(): void
    method testExtractWrongTagDelimiter (line 42) | public function testExtractWrongTagDelimiter(): void
    method testExtractWrongTagFormat (line 48) | public function testExtractWrongTagFormat(): void
    method testReference (line 54) | public function testReference(): void
    method testId (line 74) | public function testId(): void

FILE: tests/Unit/ServiceProviderTest.php
  class ServiceProviderTest (line 25) | final class ServiceProviderTest extends TestCase
    method testAddProviderByClassName (line 27) | public function testAddProviderByClassName(): void
    method testAddProviderByInstance (line 33) | public function testAddProviderByInstance(): void
    method testNotExistedExtension (line 39) | public function testNotExistedExtension(): void
    method testContainerInterfaceExtension (line 49) | public function testContainerInterfaceExtension(): void
    method testExtensionOverride (line 59) | public function testExtensionOverride(): void
    method testExtensionReturnedNull (line 80) | public function testExtensionReturnedNull(): void
    method testClassMethodsWithExtensible (line 102) | public function testClassMethodsWithExtensible(): void
    method ensureProviderRegisterExtensions (line 134) | private function ensureProviderRegisterExtensions($provider): void
    method ensureProviderRegisterDefinitions (line 162) | private function ensureProviderRegisterDefinitions($provider): void

FILE: tests/Unit/StateResetterTest.php
  class StateResetterTest (line 14) | final class StateResetterTest extends TestCase
    method testNonStateResetterObject (line 16) | public function testNonStateResetterObject(): void
    method testStateResetterObjectForService (line 29) | public function testStateResetterObjectForService(): void
    method testResetNonObject (line 44) | public function testResetNonObject(): void

FILE: tests/Unit/YiisoftPsrContainerTest.php
  class YiisoftPsrContainerTest (line 14) | final class YiisoftPsrContainerTest extends PsrContainerTestAbstract
    method createContainer (line 16) | public function createContainer(iterable $definitions = []): Container...
Condensed preview — 105 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (249K chars).
[
  {
    "path": ".editorconfig",
    "chars": 336,
    "preview": "# editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\ni"
  },
  {
    "path": ".gitattributes",
    "chars": 836,
    "preview": "# Autodetect text files\n* text=auto eol=lf\n\n# ...Unless the name matches the following overriding patterns\n\n# Definitive"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 5104,
    "preview": "# Yii Contributor Code of Conduct\n\n## Our Pledge\n\nAs contributors and maintainers of this project, and in order to keep "
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 1087,
    "preview": "# Prerequisites\n\n- [Yii goal and values](https://github.com/yiisoft/docs/blob/master/001-yii-values.md)\n- [Namespaces](h"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 187,
    "preview": "| Q             | A\n| ------------- | ---\n| Is bugfix?    | ✔️/❌\n| New feature?  | ✔️/❌\n| Breaks BC?    | ✔️/❌\n| Fixed i"
  },
  {
    "path": ".github/SECURITY.md",
    "chars": 354,
    "preview": "# Security Policy\n\nPlease use the [security issue form](https://www.yiiframework.com/security) to report to us any secur"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 513,
    "preview": "version: 2\nupdates:\n    # Maintain dependencies for GitHub Actions.\n    - package-ecosystem: \"github-actions\"\n      dire"
  },
  {
    "path": ".github/workflows/bc.yml",
    "chars": 650,
    "preview": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n"
  },
  {
    "path": ".github/workflows/bechmark.yml",
    "chars": 576,
    "preview": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 685,
    "preview": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n"
  },
  {
    "path": ".github/workflows/composer-require-checker.yml",
    "chars": 712,
    "preview": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n"
  },
  {
    "path": ".github/workflows/mutation.yml",
    "chars": 670,
    "preview": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n"
  },
  {
    "path": ".github/workflows/rector-cs.yml",
    "chars": 587,
    "preview": "name: Rector + PHP CS Fixer\n\non:\n  pull_request_target:\n    paths:\n      - 'src/**'\n      - 'tests/**'\n      - '.github/"
  },
  {
    "path": ".github/workflows/static.yml",
    "chars": 618,
    "preview": "on:\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - 'README.md'\n      - 'CHANGELOG.md'\n      - '.gitignore'\n"
  },
  {
    "path": ".gitignore",
    "chars": 475,
    "preview": "# phpstorm project files\n.idea\n\n# netbeans project files\nnbproject\n\n# zend studio for eclipse project files\n.buildpath\n."
  },
  {
    "path": ".php-cs-fixer.dist.php",
    "chars": 952,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse PhpCsFixer\\Config;\nuse PhpCsFixer\\Finder;\nuse PhpCsFixer\\Runner\\Parallel\\ParallelCo"
  },
  {
    "path": ".phpstorm.meta.php",
    "chars": 178,
    "preview": "<?php\n// .phpstorm.meta.php\n\nnamespace PHPSTORM_META {\n\n    override(\n        \\Psr\\Container\\ContainerInterface::get(0),"
  },
  {
    "path": ".phpunit-watcher.yml",
    "chars": 200,
    "preview": "watch:\n    directories:\n        - src\n        - tests\n    fileMask: '*.php'\nnotifications:\n    passingTests: false\n    f"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1920,
    "preview": "# Yii Dependency Injection Change Log\n\n## 1.4.2 under development\n\n- Enh #397: Explicitly import functions in \"use\" sect"
  },
  {
    "path": "LICENSE.md",
    "chars": 1524,
    "preview": "Copyright © 2008 by Yii Software (<https://www.yiiframework.com/>)\nAll rights reserved.\n\nRedistribution and use in sourc"
  },
  {
    "path": "README.md",
    "chars": 21315,
    "preview": "<p align=\"center\">\n    <a href=\"https://github.com/yiisoft\" target=\"_blank\">\n        <img src=\"https://yiisoft.github.io"
  },
  {
    "path": "benchmarks.md",
    "chars": 1597,
    "preview": "DI benchmark report\n===================\n\n### suite: 1343bd6191c6668ccf8fb4cf1a51a7b61159c825, date: 2020-04-06, stime: 2"
  },
  {
    "path": "composer.json",
    "chars": 2508,
    "preview": "{\n    \"name\": \"yiisoft/di\",\n    \"type\": \"library\",\n    \"description\": \"Yii DI container\",\n    \"keywords\": [\n        \"di\""
  },
  {
    "path": "docs/internals.md",
    "chars": 8711,
    "preview": "# Internals\n\n## Further reading\n\n- [Martin Fowler's article](https://martinfowler.com/articles/injection.html).\n\n## Benc"
  },
  {
    "path": "infection.json.dist",
    "chars": 246,
    "preview": "{\n    \"source\": {\n        \"directories\": [\n            \"src\"\n        ]\n    },\n    \"logs\": {\n        \"text\": \"php:\\/\\/std"
  },
  {
    "path": "phpbench.json",
    "chars": 287,
    "preview": "{\n    \"runner.bootstrap\": \"vendor/autoload.php\",\n    \"runner.path\": \"tests/Benchmark\",\n    \"runner.retry_threshold\": 3,\n"
  },
  {
    "path": "phpcs.xml.dist",
    "chars": 136,
    "preview": "<?xml version=\"1.0\"?>\n<ruleset>\n    <file>./</file>\n    <exclude-pattern>./vendor/*</exclude-pattern>\n    <rule ref=\"PSR"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 949,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNam"
  },
  {
    "path": "psalm.xml",
    "chars": 600,
    "preview": "<?xml version=\"1.0\"?>\n<psalm\n    errorLevel=\"1\"\n    findUnusedBaselineEntry=\"true\"\n    findUnusedCode=\"false\"\n    ensure"
  },
  {
    "path": "rector.php",
    "chars": 699,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse Rector\\CodeQuality\\Rector\\Class_\\InlineConstructorDefaultToPropertyRector;\nuse Rect"
  },
  {
    "path": "src/BuildingException.php",
    "chars": 1664,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Exception;\nuse Psr\\Container\\ContainerExceptionInterface;\nus"
  },
  {
    "path": "src/CompositeContainer.php",
    "chars": 4601,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse InvalidArgumentException;\nuse Psr\\Container\\ContainerInterfa"
  },
  {
    "path": "src/CompositeNotFoundException.php",
    "chars": 1212,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Exception;\nuse Psr\\Container\\ContainerInterface;\nuse Psr\\Con"
  },
  {
    "path": "src/Container.php",
    "chars": 22266,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Closure;\nuse Psr\\Container\\ContainerExceptionInterface;\nuse "
  },
  {
    "path": "src/ContainerConfig.php",
    "chars": 2796,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\n/**\n * Container configuration.\n */\nfinal class ContainerConfig "
  },
  {
    "path": "src/ContainerConfigInterface.php",
    "chars": 1144,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\n/**\n * Container configuration.\n */\ninterface ContainerConfigInt"
  },
  {
    "path": "src/ExtensibleService.php",
    "chars": 1785,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Definitions\\Co"
  },
  {
    "path": "src/Helpers/DefinitionNormalizer.php",
    "chars": 1285,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Helpers;\n\nuse Yiisoft\\Definitions\\ArrayDefinition;\nuse Yiisoft\\Def"
  },
  {
    "path": "src/Helpers/DefinitionParser.php",
    "chars": 3233,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Helpers;\n\nuse Yiisoft\\Definitions\\ArrayDefinition;\n\nuse function c"
  },
  {
    "path": "src/NotFoundException.php",
    "chars": 2029,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Exception;\nuse Psr\\Container\\NotFoundExceptionInterface;\nuse"
  },
  {
    "path": "src/Reference/TagReference.php",
    "chars": 1051,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Reference;\n\nuse InvalidArgumentException;\nuse Yiisoft\\Definitions\\"
  },
  {
    "path": "src/ServiceProviderInterface.php",
    "chars": 1863,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\n/**\n * Represents a component responsible for class registration"
  },
  {
    "path": "src/StateResetter.php",
    "chars": 2888,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di;\n\nuse Closure;\nuse InvalidArgumentException;\nuse Psr\\Container\\Con"
  },
  {
    "path": "tests/Benchmark/ContainerBench.php",
    "chars": 7029,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Benchmark;\n\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\Befo"
  },
  {
    "path": "tests/Benchmark/ContainerMethodHasBench.php",
    "chars": 1698,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Benchmark;\n\nuse PhpBench\\Benchmark\\Metadata\\Annotations\\Befo"
  },
  {
    "path": "tests/Support/A.php",
    "chars": 138,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nclass A\n{\n    public function __construct(public ?"
  },
  {
    "path": "tests/Support/B.php",
    "chars": 138,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nclass B\n{\n    public function __construct(public ?"
  },
  {
    "path": "tests/Support/Car.php",
    "chars": 774,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * A car\n */\nclass Car\n{\n    public ColorInter"
  },
  {
    "path": "tests/Support/CarExtensionProvider.php",
    "chars": 706,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\"
  },
  {
    "path": "tests/Support/CarFactory.php",
    "chars": 541,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * Produces cars\n */\nclass CarFactory\n{\n    pu"
  },
  {
    "path": "tests/Support/CarProvider.php",
    "chars": 839,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\"
  },
  {
    "path": "tests/Support/ColorInterface.php",
    "chars": 193,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * Interface ColorInterface defines car color\n"
  },
  {
    "path": "tests/Support/ColorPink.php",
    "chars": 273,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * Class ColorPink\n */\nfinal class ColorPink i"
  },
  {
    "path": "tests/Support/ColorRed.php",
    "chars": 271,
    "preview": "<?php\n\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * Class ColorRed\n */\nfinal class ColorRed im"
  },
  {
    "path": "tests/Support/ConstructorTestClass.php",
    "chars": 621,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse function func_get_args;\n\n/**\n * ConstructorTes"
  },
  {
    "path": "tests/Support/ContainerInterfaceExtensionProvider.php",
    "chars": 524,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\"
  },
  {
    "path": "tests/Support/Cycle/Chicken.php",
    "chars": 139,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support\\Cycle;\n\nclass Chicken\n{\n    public function __constr"
  },
  {
    "path": "tests/Support/Cycle/Egg.php",
    "chars": 143,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support\\Cycle;\n\nclass Egg\n{\n    public function __construct("
  },
  {
    "path": "tests/Support/EngineFactory.php",
    "chars": 751,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Exception;\nuse Psr\\Container\\ContainerInterfac"
  },
  {
    "path": "tests/Support/EngineInterface.php",
    "chars": 284,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * EngineInterface defines car engine interfac"
  },
  {
    "path": "tests/Support/EngineMarkOne.php",
    "chars": 536,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * EngineMarkOne\n */\nclass EngineMarkOne imple"
  },
  {
    "path": "tests/Support/EngineMarkTwo.php",
    "chars": 536,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * EngineMarkTwo\n */\nclass EngineMarkTwo imple"
  },
  {
    "path": "tests/Support/EngineStorage.php",
    "chars": 330,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class EngineStorage\n{\n    private readonly a"
  },
  {
    "path": "tests/Support/Garage.php",
    "chars": 259,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * A garage\n */\nfinal class Garage\n{\n    publi"
  },
  {
    "path": "tests/Support/GearBox.php",
    "chars": 181,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * A gear box.\n */\nclass GearBox\n{\n    public "
  },
  {
    "path": "tests/Support/InvokableCarFactory.php",
    "chars": 338,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\n\nclass Invok"
  },
  {
    "path": "tests/Support/MethodTestClass.php",
    "chars": 346,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * MethodTestClass\n */\nclass MethodTestClass\n{"
  },
  {
    "path": "tests/Support/NonPsrContainer.php",
    "chars": 341,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\nuse stdClass"
  },
  {
    "path": "tests/Support/NullCarExtensionProvider.php",
    "chars": 481,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\"
  },
  {
    "path": "tests/Support/NullableConcreteDependency.php",
    "chars": 153,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nclass NullableConcreteDependency\n{\n    public func"
  },
  {
    "path": "tests/Support/OptionalConcreteDependency.php",
    "chars": 252,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nclass OptionalConcreteDependency\n{\n    public func"
  },
  {
    "path": "tests/Support/PropertyTestClass.php",
    "chars": 149,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * PropertyTestClass\n */\nclass PropertyTestCla"
  },
  {
    "path": "tests/Support/SportCar.php",
    "chars": 767,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * A sport car\n */\nclass SportCar\n{\n    public"
  },
  {
    "path": "tests/Support/StaticFactory.php",
    "chars": 204,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nuse stdClass;\n\nfinal class StaticFactory\n{\n    pub"
  },
  {
    "path": "tests/Support/TreeItem.php",
    "chars": 177,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\n/**\n * TreeItem\n */\nclass TreeItem\n{\n    public fu"
  },
  {
    "path": "tests/Support/UnionTypeInConstructorFirstTypeInParamResolvable.php",
    "chars": 226,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class UnionTypeInConstructorFirstTypeInParam"
  },
  {
    "path": "tests/Support/UnionTypeInConstructorParamNotResolvable.php",
    "chars": 218,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class UnionTypeInConstructorParamNotResolvab"
  },
  {
    "path": "tests/Support/UnionTypeInConstructorSecondParamNotResolvable.php",
    "chars": 221,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class UnionTypeInConstructorSecondParamNotRe"
  },
  {
    "path": "tests/Support/UnionTypeInConstructorSecondTypeInParamResolvable.php",
    "chars": 227,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class UnionTypeInConstructorSecondTypeInPara"
  },
  {
    "path": "tests/Support/VariadicConstructor.php",
    "chars": 588,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Support;\n\nfinal class VariadicConstructor\n{\n    private read"
  },
  {
    "path": "tests/Unit/BuildingExceptionTest.php",
    "chars": 1591,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\nuse RuntimeException;"
  },
  {
    "path": "tests/Unit/CompositeContainerTest.php",
    "chars": 3488,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\A"
  },
  {
    "path": "tests/Unit/CompositePsrContainerOverLeagueTest.php",
    "chars": 1742,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse League\\Container\\Container;\nuse Psr\\Container\\Con"
  },
  {
    "path": "tests/Unit/CompositePsrContainerOverYiisoftTest.php",
    "chars": 4140,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\"
  },
  {
    "path": "tests/Unit/CompositePsrContainerTestAbstract.php",
    "chars": 6079,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse Psr\\Container\\ContainerInterface;\nuse Psr\\Contain"
  },
  {
    "path": "tests/Unit/Container/DependencyFromDelegate/Car.php",
    "chars": 211,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Container\\DependencyFromDelegate;\n\nfinal class Car\n{\n  "
  },
  {
    "path": "tests/Unit/Container/DependencyFromDelegate/DependencyFromDelegateTest.php",
    "chars": 2809,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Container\\DependencyFromDelegate;\n\nuse PHPUnit\\Framewor"
  },
  {
    "path": "tests/Unit/Container/DependencyFromDelegate/Engine.php",
    "chars": 149,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Container\\DependencyFromDelegate;\n\nfinal class Engine i"
  },
  {
    "path": "tests/Unit/Container/DependencyFromDelegate/EngineInterface.php",
    "chars": 129,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Container\\DependencyFromDelegate;\n\ninterface EngineInte"
  },
  {
    "path": "tests/Unit/ContainerTest.php",
    "chars": 69953,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse ArrayIterator;\nuse PHPUnit\\Framework\\Attributes\\D"
  },
  {
    "path": "tests/Unit/Helpers/DefinitionParserTest.php",
    "chars": 1707,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Helpers;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Yiisoft\\D"
  },
  {
    "path": "tests/Unit/LeaguePsrContainerTest.php",
    "chars": 684,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse League\\Container\\Container;\nuse Psr\\Container\\Con"
  },
  {
    "path": "tests/Unit/NotFoundExceptionTest.php",
    "chars": 1361,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Yiisoft\\Di\\NotFou"
  },
  {
    "path": "tests/Unit/PsrContainerTestAbstract.php",
    "chars": 2271,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Container\\Con"
  },
  {
    "path": "tests/Unit/Reference/TagReference/Resolve/A.php",
    "chars": 115,
    "preview": "<?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",
    "chars": 115,
    "preview": "<?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",
    "chars": 148,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Reference\\TagReference\\Resolve;\n\nfinal class Main\n{\n   "
  },
  {
    "path": "tests/Unit/Reference/TagReference/Resolve/TagReferenceResolveTest.php",
    "chars": 912,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Reference\\TagReference\\Resolve;\n\nuse PHPUnit\\Framework\\"
  },
  {
    "path": "tests/Unit/Reference/TagReference/TagReferenceTest.php",
    "chars": 2122,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit\\Reference\\TagReference;\n\nuse Error;\nuse InvalidArgument"
  },
  {
    "path": "tests/Unit/ServiceProviderTest.php",
    "chars": 6459,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Container\\Con"
  },
  {
    "path": "tests/Unit/StateResetterTest.php",
    "chars": 1775,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\T"
  },
  {
    "path": "tests/Unit/YiisoftPsrContainerTest.php",
    "chars": 507,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Yiisoft\\Di\\Tests\\Unit;\n\nuse Psr\\Container\\ContainerInterface;\nuse Yiisoft\\Di\\"
  },
  {
    "path": "tools/.gitignore",
    "chars": 27,
    "preview": "/*/vendor\n/*/composer.lock\n"
  },
  {
    "path": "tools/infection/composer.json",
    "chars": 191,
    "preview": "{\n    \"require-dev\": {\n        \"infection/infection\": \"^0.26 || ^0.31.9\"\n    },\n    \"config\": {\n        \"allow-plugins\":"
  },
  {
    "path": "tools/psalm/composer.json",
    "chars": 73,
    "preview": "{\n    \"require-dev\": {\n        \"vimeo/psalm\": \"^5.26.1 || ^6.12\"\n    }\n}\n"
  }
]

About this extraction

This page contains the full source code of the yiisoft/di GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 105 files (227.9 KB), approximately 55.2k tokens, and a symbol index with 387 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!