[
  {
    "path": ".gitattributes",
    "content": "# Normalize line endings\n* text=auto eol=lf\n\n/tests              export-ignore\n/tests-php8         export-ignore\n/demo               export-ignore\n/demo-php8          export-ignore\n/.gitattributes     export-ignore\n/.github            export-ignore\n/vendor-bin         export-ignore\n/.gitignore         export-ignore\n/.scrutinizer.yml   export-ignore\n/.travis.yml        export-ignore\n/codecov.yml        export-ignore\n/phpcs.xml          export-ignore\n/phpmd.xml          export-ignore\n/phpstan.neon       export-ignore\n/phpunit.xml.dist   export-ignore\n/psalm.xml          export-ignore\n/psalm.compiler.xml export-ignore\n\n# Configure diff output for .php and .phar files.\n*.php diff=php\n*.phar -diff\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# CONTRIBUTING\n\n## Code Contributions\n\n## Installation\n\nInstall project dependencies and test tools by running the following commands.\n\n```bash\n$ composer install\n```\n\n## Running tests\n\n```bash\n$ composer test\n```\n```bash\n$ composer coverage // xdebug\n$ composer pcov     // pcov\n```\n\nAdd tests for your new code ensuring that you have 100% code coverage.\nIn rare cases, code may be excluded from test coverage using `@codeCoverageIgnore`.\n\n## Sending a pull request\n\nTo ensure your PHP code changes pass the CI checks, make sure to run all the same checks before submitting a PR.\n\n```bash\n$ composer tests\n```\n\nWhen you make a pull request, the tests will automatically be run again by GH action on multiple php versions.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug Report\nabout: Create a bug report\nlabels: Bug\n---\n\n### Bug Report\n\n<!-- Provide a summary describing the problem you are experiencing. -->\n\n### How to reproduce\n\n<!--- Describe exactly how to reproduce the problem, using a minimal test-case or working code sample. -->\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.md",
    "content": "---\nname: Feature\nabout: Suggest a new feature or enhancement\nlabels: Feature\n---\n\n<!-- Write your suggestion here. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: Question\nabout: Ask a question regarding software usage\nlabels: Support\n---\n\n<!-- Write your question here. -->\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Reporting a vulnerability\n\nIf you have found any issues that might have security implications,\nplease send a report privately to akihito.koriyama@gmail.com\n\nDo not report security reports publicly.\n"
  },
  {
    "path": ".github/workflows/coding-standards.yml",
    "content": "name: Coding Standards\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  cs:\n    uses: ray-di/.github/.github/workflows/coding-standards.yml@v1\n    with:\n      php_version: 8.3\n"
  },
  {
    "path": ".github/workflows/continuous-integration.yml",
    "content": "name: Continuous Integration\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  ci:\n    uses: ray-di/.github/.github/workflows/continuous-integration.yml@v1\n    with:\n      old_stable: '[\"8.2\", \"8.3\", \"8.4\"]'\n      current_stable: 8.5\n      script: demo/run.php\n"
  },
  {
    "path": ".github/workflows/demo.yml",
    "content": "name: Demo\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  demo:\n    name: Demo\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: 8.3\n          tools: cs2pr\n          coverage: none\n\n      - name: Get composer cache directory\n        id: composer-cache\n        run: echo \"dir=$(composer config cache-files-dir)\" >> $GITHUB_OUTPUT\n\n      - name: Cache dependencies\n        uses: actions/cache@v4\n        with:\n          path: ${{ steps.composer-cache.outputs.dir }}\n          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}\n          restore-keys: ${{ runner.os }}-composer-\n\n      - name: Install dependencies\n        run: composer install --no-interaction --no-progress --prefer-dist\n\n      - name: Run Demo\n        run: php demo/run.php\n"
  },
  {
    "path": ".github/workflows/prefer-lowest.yml",
    "content": "name: Prefer Lowest\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  prefer-lowest:\n    name: Prefer Lowest\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: 8.2\n          tools: cs2pr\n          coverage: none\n\n      - name: Get composer cache directory\n        id: composer-cache\n        run: echo \"dir=$(composer config cache-files-dir)\" >> $GITHUB_OUTPUT\n\n      - name: Cache dependencies\n        uses: actions/cache@v4\n        with:\n          path: ${{ steps.composer-cache.outputs.dir }}\n          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}\n          restore-keys: ${{ runner.os }}-composer-\n\n      - name: Install dependencies\n        run: composer update --prefer-lowest --no-interaction --no-progress --prefer-dist\n\n      - name: Run tests\n        run: composer test\n"
  },
  {
    "path": ".github/workflows/static-analysis.yml",
    "content": "name: Static Analysis\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  sa:\n    uses: ray-di/.github/.github/workflows/static-analysis.yml@v1\n    with:\n      php_version: 8.3\n"
  },
  {
    "path": ".github/workflows/update-copyright-years-in-license-file.yml",
    "content": "name: Update copyright year(s) in license file\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 3 1 1 *\"\n\njobs:\n  run:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - uses: FantasticFiasco/action-update-license-year@v2\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/vendor/\n/build/\n/tests/compiler/tmp/car/\n/tests/compiler/tmp/injector_cache/\n/tests/compiler/tmp/logger/\n/tests/compiler/tmp/prod/\n/tests/di/tmp/\n/tests/di/script/grapher.php.txt\n/tests/tmp/\n/tests/compiler/tmp/\n/*.cache.php\n/.phpunit.result.cache\n/composer.lock\n/.phpcs-cache\n/coverage.xml\n/demo/error.log\n/demo/10-cache.php.cache.php\n/demo-php8/error.log\n/demo-php8/10-cache.php.cache.php\n/vendor-bin/**/vendor\n"
  },
  {
    "path": ".scrutinizer.yml",
    "content": "build:\n    image: default-jammy\n    environment:\n        php: 8.4\n    nodes:\n        analysis:\n            tests:\n                override:\n                    - php-scrutinizer-run\n\nfilter:\n    paths: [\"src/*\"]\n"
  },
  {
    "path": ".sonarcloud.properties",
    "content": "# Exclude files from SonarCloud analysis\n# - GitHub workflow files: version tags are best practice\n# - Demo files: example code with intentional duplication for demonstration\nsonar.exclusions=.github/workflows/**,demo/**\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nRay.Di is a dependency injection and AOP (Aspect-Oriented Programming) framework for PHP inspired by Google Guice. It provides annotations-based dependency injection with support for AOP interceptors.\n\n## Core Architecture\n\n### Key Components\n- **AbstractModule**: Base class for defining dependency bindings. Modules are composed using `install()` and can be overridden using `override()`\n- **Injector**: Main entry point that manages the DI container and creates instances. Auto-registers generated proxy classes and handles untargeted bindings\n- **Bind**: Fluent API for creating bindings (`.to()`, `.toProvider()`, `.toInstance()`, `.in()`)\n- **Container**: Internal storage for all bindings and dependencies\n- **Annotations**: Located in `src/di/Di/` - includes `@Inject`, `@Named`, `@Assisted`, etc.\n\n### Directory Structure\n- `src/di/`: Core DI framework code\n- `src-deprecated/`: Legacy code maintained for compatibility  \n- `tests/di/`: Unit tests with extensive fake classes for testing\n- `demo/` and `demo-php8/`: Examples showing framework usage\n- Compiled proxy classes are cached in configurable temp directories\n\n## Development Commands\n\n### Testing\n```bash\ncomposer test              # Run PHPUnit tests\ncomposer coverage          # Generate test coverage with Xdebug\ncomposer pcov              # Generate coverage with PCOV (faster)\n```\n\n### Code Quality\n```bash\ncomposer cs                # Run PHP_CodeSniffer\ncomposer cs-fix            # Auto-fix coding standards\ncomposer sa                # Static analysis (Psalm + PHPStan)\ncomposer clean             # Clear analysis caches\n```\n\n### Build Pipeline\n```bash\ncomposer build             # Full build: cs + sa + pcov + metrics\ncomposer tests             # Quick check: cs + sa + test\n```\n\n### Analysis Tools\n```bash\ncomposer phpmd             # PHP Mess Detector\ncomposer metrics           # Generate code metrics\ncomposer baseline          # Update static analysis baselines\n```\n\n## Testing Strategy\n\n- Tests use extensive fake classes in `tests/di/Fake/` to simulate real-world scenarios\n- Supports both PHP 7.2+ and PHP 8+ with separate test suites\n- Cache files are automatically cleaned between test runs\n- AOP proxy generation is tested with temporary directories\n\n## Framework Patterns\n\n### Module Definition\n```php\nclass MyModule extends AbstractModule\n{\n    protected function configure(): void\n    {\n        $this->bind(Interface::class)->to(Implementation::class);\n        $this->bind(Service::class)->toProvider(ServiceProvider::class);\n    }\n}\n```\n\n### Injection Usage\n```php\n$injector = new Injector(new MyModule());\n$instance = $injector->getInstance(Interface::class);\n```\n\n## Important Notes\n\n- Ray.Di generates proxy classes for AOP which are cached in temp directories\n- The framework supports both constructor and setter injection\n- All bindings are resolved at runtime with automatic proxy weaving for aspects\n- Multi-binding support allows collecting multiple implementations of the same interface"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2012-2026 Akihito Koriyama\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Ray.Di\n\n## A dependency injection framework for PHP\n\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ray-di/Ray.Di/badges/quality-score.png?b=2.x)](https://scrutinizer-ci.com/g/ray-di/Ray.Di/?branch=2.x)\n[![codecov](https://codecov.io/gh/ray-di/Ray.Di/branch/2.x/graph/badge.svg?token=KCQXtu01zc)](https://codecov.io/gh/ray-di/Ray.Di)\n[![Type Coverage](https://shepherd.dev/github/ray-di/Ray.Di/coverage.svg)](https://shepherd.dev/github/ray-di/Ray.Di)\n[![Continuous Integration](https://github.com/ray-di/Ray.Di/actions/workflows/continuous-integration.yml/badge.svg?branch=2.x)](https://github.com/ray-di/Ray.Di/actions/workflows/continuous-integration.yml)\n[![Total Downloads](https://poser.pugx.org/ray/di/downloads)](https://packagist.org/packages/ray/di)\n\n<img src=\"https://ray-di.github.io/images/logo.svg\" width=160  alt=\"logo\">\n\nRay.Di is DI and AOP framework for PHP inspired by [Google Guice](https://github.com/google/guice/wiki).\n\nhttps://ray-di.github.io\n"
  },
  {
    "path": "codecov.yml",
    "content": "codecov:\n  notify:\n    require_ci_to_pass: yes\n\ncoverage:\n  status:\n    project:\n      default:\n        target: 100%\n    patch:\n      default:\n        target: 100%\n"
  },
  {
    "path": "composer-require-checker.json",
    "content": "{\n  \"symbol-whitelist\": [\n    \"null\", \"true\", \"false\",\n    \"static\", \"self\", \"parent\",\n    \"array\", \"string\", \"int\", \"float\", \"bool\", \"iterable\", \"callable\", \"void\", \"object\",\n    \"Attribute\", \"ReflectionAttribute\",\n    \"Doctrine\\\\Common\\\\Cache\\\\CacheProvider\"\n  ]\n}\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"ray/di\",\n    \"description\": \"Guice style dependency injection framework\",\n    \"keywords\": [\"di\", \"aop\"],\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Akihito Koriyama\",\n            \"email\": \"akihito.koriyama@gmail.com\"\n        }\n    ],\n    \"require\": {\n        \"php\": \"^8.2\",\n        \"koriym/null-object\": \"^1.0\",\n        \"ray/aop\": \"^2.19\"\n    },\n    \"require-dev\": {\n        \"ext-pdo\": \"*\",\n        \"bamarni/composer-bin-plugin\": \"^1.4\",\n        \"infection/infection\": \"*\",\n        \"phpunit/phpunit\": \"^9.6.31\"\n    },\n    \"suggest\": {\n        \"ray/compiler\": \"For compiling dependency injection container to improve performance\"\n    },\n    \"config\": {\n        \"sort-packages\": true,\n        \"allow-plugins\": {\n            \"bamarni/composer-bin-plugin\": true,\n            \"infection/extension-installer\": true\n        }\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"Ray\\\\Di\\\\\": [\"src/di\", \"src-deprecated/di\"]\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Ray\\\\Di\\\\\": [\"tests/di\", \"tests/di/Fake/\"]\n        }\n    },\n    \"scripts\": {\n        \"test\": \"phpunit --log-junit=build/junit.xml\",\n        \"tests\": [\"@cs\", \"@sa\", \"@test\"],\n        \"coverage\": [\"php -dzend_extension=xdebug.so -dxdebug.mode=coverage ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage\"],\n        \"pcov\": [\"php -dextension=pcov.so -d pcov.enabled=1 ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage --coverage-xml=build/coverage-xml --coverage-clover=coverage.xml\"],\n        \"mutation\": [\"php -dextension=pcov.so -d pcov.enabled=1 vendor/bin/infection --coverage=build/coverage-xml --threads=2 --min-msi=80 --min-covered-msi=80 --no-progress\"],\n        \"cs\": [\"vendor-bin/tools/vendor/squizlabs/php_codesniffer/bin/phpcs --standard=./phpcs.xml src tests\"],\n        \"cs-fix\": [\"vendor-bin/tools/vendor/squizlabs/php_codesniffer/bin/phpcbf src tests\"],\n        \"clean\": [\"vendor-bin/tools/vendor/phpstan/phpstan/phpstan clear-result-cache\", \"vendor-bin/tools/vendor/vimeo/psalm/psalm --clear-cache\", \"rm -rf tests/tmp/*.php\"],\n        \"sa\": [\"vendor-bin/tools/vendor/vimeo/psalm/psalm -m -c psalm.xml --show-info=false\", \"vendor-bin/tools/vendor/phpstan/phpstan/phpstan analyse -c phpstan.neon --no-progress \"],\n        \"metrics\": [\"@test\", \"vendor-bin/tools/vendor/phpmetrics/phpmetrics/bin/phpmetrics --report-html=build/metrics --exclude=Exception --log-junit=build/junit.xml --junit=build/junit.xml src\"],\n        \"phpmd\": [\"vendor-bin/tools/vendor/phpmd/phpmd/src/bin/phpmd src/di text ./phpmd.xml\"],\n        \"build\": [\"@cs\", \"@sa\", \"@pcov\", \"@metrics\"],\n        \"baseline\": [\"vendor-bin/tools/vendor/phpstan/phpstan/phpstan analyse -c phpstan.neon --generate-baseline\", \"vendor-bin/tools/vendor/vimeo/psalm/psalm --set-baseline=psalm-baseline.xml\"]\n    },\n    \"extra\": {\n        \"bamarni-bin\": {\n            \"bin-links\": true,\n            \"forward-command\": true\n        }\n    }\n}\n"
  },
  {
    "path": "demo/01a-linked-binding.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Injector;\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\n\ninterface FinderInterface\n{\n}\n\nclass Finder implements FinderInterface\n{\n}\n\ninterface MovieListerInterface\n{\n}\n\nclass MovieLister implements MovieListerInterface\n{\n    public function __construct(\n        public FinderInterface $finder\n    ){}\n}\n\nclass FinderModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FinderInterface::class)->to(Finder::class);\n        $this->bind(MovieListerInterface::class)->to(MovieLister::class);\n    }\n}\n\n$injector = new Injector(new FinderModule());\n$movieLister = $injector->getInstance(MovieListerInterface::class);\n/** @var MovieLister $movieLister */\n$works = ($movieLister->finder instanceof Finder);\n\necho($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL;\n"
  },
  {
    "path": "demo/01b-linked-binding-setter-injection.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Di\\Inject;\nuse Ray\\Di\\Injector;\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\n\ninterface FinderInterface\n{\n}\n\nclass Finder implements FinderInterface\n{\n}\n\ninterface MovieListerInterface\n{\n}\n\nclass MovieLister implements MovieListerInterface\n{\n    public FinderInterface $finder;\n\n    #[Inject]\n    public function setFinder(FinderInterface $finder)\n    {\n        $this->finder = $finder;\n    }\n}\n\nclass FinderModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FinderInterface::class)->to(Finder::class);\n        $this->bind(MovieListerInterface::class)->to(MovieLister::class);\n    }\n}\n\n$injector = new Injector(new FinderModule());\n$movieLister = $injector->getInstance(MovieListerInterface::class);\n/** @var MovieLister $movieLister */\n$works = ($movieLister->finder instanceof Finder);\n\necho($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL;\n"
  },
  {
    "path": "demo/02-provider-binding.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Injector;\nuse Ray\\Di\\ProviderInterface;\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\n\ninterface FinderInterface\n{\n}\n\nclass Finder implements FinderInterface\n{\n    public $datetime;\n\n    public function __construct(DateTimeInterface $dateTime)\n    {\n        $this->datetime = $dateTime;\n    }\n}\n\nclass FinderProvider implements ProviderInterface\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function get()\n    {\n        return new Finder(new DateTimeImmutable('now'));\n    }\n}\n\ninterface MovieListerInterface\n{\n}\n\nclass MovieLister implements MovieListerInterface\n{\n    public function __construct(\n        public FinderInterface $finder\n    ){\n    }\n}\n\nclass FinderModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FinderInterface::class)->toProvider(FinderProvider::class);\n        $this->bind(MovieListerInterface::class)->to(MovieLister::class);\n    }\n}\n\n$injector = new Injector(new FinderModule());\n$movieLister = $injector->getInstance(MovieListerInterface::class);\n/** @var MovieLister $movieLister */\n$works = ($movieLister->finder->datetime instanceof DateTimeImmutable);\n\necho($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL;\n"
  },
  {
    "path": "demo/02a-named-by-qualifier.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Di\\Qualifier;\nuse Ray\\Di\\Injector;\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\n\ninterface FinderInterface\n{\n}\n\nclass LegacyFinder implements FinderInterface\n{\n}\n\ninterface MovieListerInterface\n{\n}\n\nclass MovieLister implements MovieListerInterface\n{\n    public FinderInterface $finder;\n\n    public function __construct(#[Legacy] FinderInterface $finder)\n    {\n        $this->finder = $finder;\n    }\n}\n\n#[Attribute(Attribute::TARGET_PARAMETER), Qualifier]\nclass Legacy\n{\n}\n\nclass FinderModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FinderInterface::class)->annotatedWith(Legacy::class)->to(LegacyFinder::class);\n        $this->bind(MovieListerInterface::class)->to(MovieLister::class);\n    }\n}\n\n$injector = new Injector(new FinderModule());\n$movieLister = $injector->getInstance(MovieListerInterface::class);\n/** @var MovieLister $movieLister */\n$works = ($movieLister->finder instanceof LegacyFinder);\n\necho($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL;\n"
  },
  {
    "path": "demo/02b-named-by-named.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Di\\Named;\nuse Ray\\Di\\Injector;\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\n\ninterface FinderInterface\n{\n}\n\nclass LegacyFinder implements FinderInterface\n{\n}\n\nclass ModernFinder implements FinderInterface\n{\n}\n\ninterface MovieListerInterface\n{\n}\n\nclass MovieLister implements MovieListerInterface\n{\n    public function __construct(\n        #[Named('legacy')] public FinderInterface $finder\n    ){}\n}\n\nclass FinderModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FinderInterface::class)->annotatedWith('legacy')->to(LegacyFinder::class);\n        $this->bind(MovieListerInterface::class)->to(MovieLister::class);\n    }\n}\n\n$injector = new Injector(new FinderModule());\n$movieLister = $injector->getInstance(MovieListerInterface::class);\n/** @var MovieLister $movieLister */\n$works = ($movieLister->finder instanceof LegacyFinder);\n\necho($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL;\n"
  },
  {
    "path": "demo/03-injection-point.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\InjectionPointInterface;\nuse Ray\\Di\\Injector;\nuse Ray\\Di\\ProviderInterface;\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\n\ninterface FinderInterface\n{\n}\n\ninterface MovieListerInterface\n{\n}\n\nclass Finder implements FinderInterface\n{\n    private string $className;\n\n    public function __construct(string $className)\n    {\n        $this->className = $className;\n    }\n\n    public function find(): string\n    {\n        return sprintf('search for [%s]', $this->className);\n    }\n}\n\nclass MovieLister implements MovieListerInterface\n{\n    public FinderInterface $finder;\n\n    public function __construct(FinderInterface $finder)\n    {\n        $this->finder = $finder;\n    }\n}\n\nclass FinderProvider implements ProviderInterface\n{\n    public function __construct(\n        public InjectionPointInterface $ip\n    )\n    {}\n\n    /**\n     * {@inheritdoc}\n     */\n    public function get()\n    {\n        $className = $this->ip->getClass()->getName();\n\n        return new Finder($className);\n    }\n}\n\nclass FinderModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FinderInterface::class)->toProvider(FinderProvider::class);\n        $this->bind(MovieListerInterface::class)->to(MovieLister::class);\n    }\n}\n\n$injector = new Injector(new FinderModule());\n$movieLister = $injector->getInstance(MovieListerInterface::class);\n/** @var MovieLister $movieLister */\n$result = $movieLister->finder->find();\n$works = ($result === 'search for [MovieLister]');\n\necho($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL;\n"
  },
  {
    "path": "demo/04-untarget-binding.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Injector;\nuse Ray\\Di\\Scope;\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\n\nclass Finder\n{\n}\n\nclass FinderModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(Finder::class)->in(Scope::SINGLETON);\n    }\n}\n\n$injector = new Injector(new FinderModule());\n$finder1 = $injector->getInstance(Finder::class);\n$finder2 = $injector->getInstance(Finder::class);\n$works = spl_object_hash($finder1) === spl_object_hash($finder2);\n\necho($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL;\n"
  },
  {
    "path": "demo/05a-constructor-binding.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Injector;\n\n//  public PDO::__construct ( string $dsn [, string $username [, string $password [, array $options ]]] )\n\nclass PdoModuleLegacyNaming extends AbstractModule\n{\n    protected function configure()\n    {\n        // 'dsn=pdo_dsn' (still) works\n        $this->bind(PDO::class)->toConstructor(PDO::class, 'dsn=pdo_dsn');\n        $this->bind()->annotatedWith('pdo_dsn')->toInstance('sqlite::memory:');\n    }\n}\n\nclass PdoModule extends AbstractModule\n{\n    protected function configure()\n    {\n        // ['dsn' => 'pdo_dsn'] works (recommended)\n        $this->bind(PDO::class)->toConstructor(PDO::class, ['dsn' => 'pdo_dsn']);\n        $this->bind()->annotatedWith('pdo_dsn')->toInstance('sqlite::memory:');\n    }\n}\n\n$injector = new Injector(new PdoModuleLegacyNaming());\n$pdo = $injector->getInstance(PDO::class);\n$works = $pdo instanceof PDO;\n\n$injector = new Injector(new PdoModule());\n$pdo = $injector->getInstance(PDO::class);\n$works &= $pdo instanceof PDO;\n\necho($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL;\n"
  },
  {
    "path": "demo/05b-constructor-binding-setter-injection.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Demo;\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\InjectionPoints;\nuse Ray\\Di\\Injector;\n\nuse function dirname;\n\nuse const PHP_EOL;\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\n\ninterface FinderInterface\n{\n}\ninterface MovieListerInterface\n{\n}\n\nclass Finder implements FinderInterface\n{\n}\n\nclass MovieLister implements MovieListerInterface\n{\n    public FinderInterface $finder;\n\n    /**\n     * Setter Injection with no Inject annotation\n     */\n    public function setFinder(FinderInterface $finder)\n    {\n        $this->finder = $finder;\n    }\n}\n\nclass ListerModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FinderInterface::class)->to(Finder::class);\n        $this->bind(MovieListerInterface::class)->toConstructor(\n            MovieLister::class,\n            '',\n            (new InjectionPoints())->addMethod('setFinder') // or (new InjectionPoints)->addOptionalMethod('setFinder')\n        );\n    }\n}\n$injector = new Injector(new ListerModule());\n$lister = $injector->getInstance(MovieListerInterface::class);\n/** @var MovieLister $lister */\n$works = ($lister->finder instanceof FinderInterface);\n\necho($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL;\n"
  },
  {
    "path": "demo/07-assisted-injection.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Composer\\Autoload\\ClassLoader;\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Injector;\n\n$loader = require dirname(__DIR__) . '/vendor/autoload.php';\n/** @var ClassLoader $loader */\n$loader->addPsr4('', __DIR__ . '/finder');\n\n$injector = new Injector(new class extends AbstractModule{\n    protected function configure()\n    {\n        $this->bind(FinderInterface::class)->to(Finder::class);\n    }\n});\n$finder = $injector->getInstance(MovieFinder::class);\n/** @var MovieFinder $finder */\n$works = $finder->find('Tokyo Story') === 'searching [Tokyo Story] by [Finder]';\n\necho($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL;\n"
  },
  {
    "path": "demo/10-cache.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Composer\\Autoload\\ClassLoader;\nuse Ray\\Di\\Injector;\n\n$loader = require dirname(__DIR__) . '/vendor/autoload.php';\n/** @var ClassLoader $loader */\n$loader->addPsr4('', __DIR__ . '/finder');\n\n$start = microtime(true);\n$injector = new Injector(new FinderModule());\n$movieLister = $injector->getInstance(MovieListerInterface::class);\nassert($movieLister instanceof MovieLister);\n$time1 = microtime(true) - $start;\n\n// save file cache\nfile_put_contents(__FILE__ . '.cache.php', serialize(new Injector(new FinderModule())));\n\n// cached injector\n$start = microtime(true);\n$injector = unserialize(file_get_contents(__FILE__ . '.cache.php'));\n$movieLister2 = $injector->getInstance(MovieListerInterface::class);\nassert($movieLister2 instanceof MovieLister);\n$time2 = microtime(true) - $start;\n\n$works = $movieLister instanceof MovieListerInterface;\necho $works ? 'It works!' : 'It DOES NOT work!';\necho ' [Injector cache] x' . round($time1 / $time2) . ' times faster.' . PHP_EOL;\n"
  },
  {
    "path": "demo/11-script-injector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Composer\\Autoload\\ClassLoader;\nuse Ray\\Compiler\\DiCompiler;\nuse Ray\\Compiler\\ScriptInjector;\nuse Ray\\Di\\Injector;\n\n$loader = require dirname(__DIR__) . '/vendor/autoload.php';\n/** @var ClassLoader $loader */\n$loader->addPsr4('', __DIR__ . '/finder');\n\n$start = microtime(true);\n$injector = new Injector(new FinderModule());\n$movieLister = $injector->getInstance(MovieListerInterface::class);\nassert($movieLister instanceof MovieLister);\n$time1 = microtime(true) - $start;\n\n// compile\n$tmpDir = __DIR__ . '/tmp';\n$compiler = new DiCompiler(new FinderModule(), $tmpDir);\n$compiler->compile();\n$scriptInjector = new ScriptInjector($tmpDir);\n$movieLister2 = $scriptInjector->getInstance(MovieListerInterface::class);\n\n// script injector\n$start = microtime(true);\n$movieLister2 = $scriptInjector->getInstance(MovieListerInterface::class);\nassert($movieLister2 instanceof MovieLister);\n$time2 = microtime(true) - $start;\n\n$works = $movieLister instanceof MovieListerInterface;\necho $works ? 'It works!' : 'It DOES NOT work!';\necho ' [Script injector] x' . round($time1 / $time2) . ' times faster.' . PHP_EOL;\n"
  },
  {
    "path": "demo/12-dependency-chain-error-message.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Composer\\Autoload\\ClassLoader;\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Exception\\Unbound;\nuse Ray\\Di\\Injector;\n\n$loader = require dirname(__DIR__) . '/vendor/autoload.php';\n/** @var ClassLoader $loader */\n$loader->addPsr4('', __DIR__ . '/chain-error');\n\nclass DeepLinkedClassBindingModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(A::class);\n        $this->bind(B::class);\n        $this->bind(C::class);\n        $this->bind(D::class);\n        // purposefully not bound.\n        // D will require EInterface object to be injected, but\n        // EInterface is not bound and an Unbound exception is thrown.\n    }\n}\n\n$injector = new Injector(new DeepLinkedClassBindingModule());\n// this will fail with an exception as EInterface is not bound\n\ntry {\n    $injector->getInstance(A::class);\n} catch (Unbound $e) {\n    $msg = $e->getMessage();\n    ob_start();\n    // dependency 'B' with name '' used\n    //    dependency 'B' with name '' used in12-dependency-chain-error-message.php:11\n    echo PHP_EOL . '---------' . PHP_EOL;\n    echo $e;\n//    exception 'Ray\\Di\\Exception\\Unbound' with message 'EInterface-'\n//    - dependency 'EInterface' with name '' used in12-dependency-chain-error-message.php:26\n//    - dependency 'D' with name '' used in12-dependency-chain-error-message.php:21\n//    - dependency 'C' with name '' used in12-dependency-chain-error-message.php:16\n//    - dependency 'B' with name '' used in12-dependency-chain-error-message.php:11\n    // ...\n    echo PHP_EOL . '---------' . PHP_EOL;\n    do {\n        echo get_class($e) . ':' . $e->getMessage() . PHP_EOL;\n    } while ($e = $e->getPrevious());\n\n//    Ray\\Di\\Exception\\Unbound:dependency 'B' with name '' used in12-dependency-chain-error-message.php:11\n//    Ray\\Di\\Exception\\Unbound:dependency 'C' with name '' used in12-dependency-chain-error-message.php:16\n//    Ray\\Di\\Exception\\Unbound:dependency 'D' with name '' used in12-dependency-chain-error-message.php:21\n//    Ray\\Di\\Exception\\Unbound:dependency 'EInterface' with name '' used in12-dependency-chain-error-message.php:26\n//    Ray\\Di\\Exception\\Unbound:EInterface-\n}\n\n$log = ob_get_clean();\n$works = strpos($log, \"dependency 'B' with name '' used\");\necho $works ? 'It works!' : 'It DOES NOT work!';\nfile_put_contents(__DIR__ . '/error.log', $log);\n\necho '[error log]: ' . __DIR__ . '/error.log' . PHP_EOL;\n"
  },
  {
    "path": "demo/chain-error/A.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nclass A\n{\n    public function __construct(B $b)\n    {\n    }\n}\n"
  },
  {
    "path": "demo/chain-error/B.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nclass B\n{\n    public function __construct(C $c)\n    {\n    }\n}\n"
  },
  {
    "path": "demo/chain-error/C.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nclass C\n{\n    public function __construct(D $d)\n    {\n    }\n}\n"
  },
  {
    "path": "demo/chain-error/D.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nclass D\n{\n    public function __construct(EInterface $e)\n    {\n    }\n}\n"
  },
  {
    "path": "demo/chain-error/EInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\ninterface EInterface\n{\n}\n"
  },
  {
    "path": "demo/finder/Db.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\Di\\PostConstruct;\n\nclass Db implements DbInterface\n{\n    public function __construct($dsn, $username, $password)\n    {\n    }\n\n    #[PostConstruct]\n    public function init()\n    {\n    }\n}\n"
  },
  {
    "path": "demo/finder/DbFinder.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\Di\\Inject;\n\nclass DbFinder implements FinderInterface\n{\n    public function __construct(DbInterface $db)\n    {\n    }\n\n    #[Inject]\n    public function setDb(DbInterface $db)\n    {\n    }\n\n    #[Inject]\n    public function setSorter(Sorter $sorter, Sorter $sorte2)\n    {\n    }\n}\n"
  },
  {
    "path": "demo/finder/DbInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\ninterface DbInterface\n{\n}\n"
  },
  {
    "path": "demo/finder/Finder.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nclass Finder implements FinderInterface\n{\n}\n"
  },
  {
    "path": "demo/finder/FinderInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\ninterface FinderInterface\n{\n}\n"
  },
  {
    "path": "demo/finder/FinderModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Scope;\n\nclass FinderModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(Sorter::class)->in(Scope::SINGLETON);\n        $this->bind(DbInterface::class)->toConstructor(Db::class, 'dsn=dsn,username=username,password=password');\n        $this->bind()->annotatedWith('dsn')->toInstance('msql:host=localhost;dbname=test');\n        $this->bind()->annotatedWith('username')->toInstance('root');\n        $this->bind()->annotatedWith('password')->toInstance('');\n        $this->bind(FinderInterface::class)->to(DbFinder::class);\n        $this->bind(MovieListerInterface::class)->to(MovieLister::class);\n    }\n}\n"
  },
  {
    "path": "demo/finder/MovieFinder.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\Di\\Assisted;\nuse Ray\\Di\\Di\\Inject;\n\nclass MovieFinder\n{\n    public function find($name, #[Inject] ?FinderInterface $finder = null)\n    {\n        return sprintf('searching [%s] by [%s]', $name, get_class($finder));\n    }\n}\n"
  },
  {
    "path": "demo/finder/MovieLister.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Ray\\Di\\Di\\Inject;\n\nclass MovieLister implements MovieListerInterface\n{\n    public function __construct(FinderInterface $finder)\n    {\n    }\n\n    #[Inject]\n    public function setFinder01(FinderInterface $finder, FinderInterface $finder1, FinderInterface $finder2)\n    {\n    }\n}\n"
  },
  {
    "path": "demo/finder/MovieListerInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\ninterface MovieListerInterface\n{\n}\n"
  },
  {
    "path": "demo/finder/Sorter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nclass Sorter\n{\n}\n"
  },
  {
    "path": "demo/run.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nputenv('TMPDIR=' . __DIR__ . '/tmp');\n\npassthru('php ' . __DIR__ . '/01a-linked-binding.php');\npassthru('php ' . __DIR__ . '/01b-linked-binding-setter-injection.php');\npassthru('php ' . __DIR__ . '/02-provider-binding.php');\npassthru('php ' . __DIR__ . '/02a-named-by-qualifier.php');\npassthru('php ' . __DIR__ . '/02b-named-by-named.php');\npassthru('php ' . __DIR__ . '/03-injection-point.php');\npassthru('php ' . __DIR__ . '/04-untarget-binding.php');\npassthru('php ' . __DIR__ . '/05a-constructor-binding.php');\npassthru('php ' . __DIR__ . '/05b-constructor-binding-setter-injection.php');\npassthru('php ' . __DIR__ . '/07-assisted-injection.php');\npassthru('php ' . __DIR__ . '/10-cache.php');\npassthru('php ' . __DIR__ . '/11-script-injector.php');\npassthru('php ' . __DIR__ . '/12-dependency-chain-error-message.php');\n"
  },
  {
    "path": "demo/tmp/.gitkeep",
    "content": ""
  },
  {
    "path": "phpcs.xml",
    "content": "<?xml version=\"1.0\"?>\n<ruleset\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" name=\"bearcs\"\n    xsi:noNamespaceSchemaLocation=\"./vendor-bin/tools/vendor/squizlabs/php_codesniffer/phpcs.xsd\">\n\n    <arg name=\"basepath\" value=\".\"/>\n    <arg name=\"extensions\" value=\"php\"/>\n    <arg name=\"parallel\" value=\"80\"/>\n    <arg name=\"cache\" value=\".phpcs-cache\"/>\n\n    <!-- Compatibility with PHP 7.2.0 -->\n    <config name=\"php_version\" value=\"70200\"/>\n\n    <!-- Ignore warnings, show progress of the run and show sniff names -->\n    <arg value=\"nps\"/>\n\n    <!-- Directories to be checked -->\n    <file>src</file>\n    <file>tests</file>\n    <exclude-pattern>*/tests/tmp/*</exclude-pattern>\n\n    <!-- PSR12 Coding Standard -->\n    <rule ref=\"PSR12\"/>\n\n    <!-- Doctrine Coding Standard -->\n    <rule ref=\"Doctrine\">\n    <!-- Inapplicable for this project -->\n        <!-- Base -->\n        <exclude name=\"Generic.Formatting.MultipleStatementAlignment.NotSame\"/>\n        <exclude name=\"Squiz.Strings.DoubleQuoteUsage.ContainsVar\"/>\n        <exclude name=\"Squiz.WhiteSpace.LanguageConstructSpacing\"/>\n        <exclude name=\"SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix\"/>\n        <exclude name=\"SlevomatCodingStandard.Classes.SuperfluousExceptionNaming.SuperfluousSuffix\"/>\n        <exclude name=\"SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix\"/>\n        <exclude name=\"SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable\"/>\n        <exclude name=\"SlevomatCodingStandard.Commenting.UselessInheritDocComment.UselessInheritDocComment\"/>\n        <exclude name=\"SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint\"/>\n        <exclude name=\"SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint\"/>\n        <exclude name=\"SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint\"/>\n        <exclude name=\"Squiz.PHP.GlobalKeyword.NotAllowed\"/>\n        <exclude name=\"SlevomatCodingStandard.PHP.RequireExplicitAssertion.RequiredExplicitAssertion\"/>\n        <exclude name=\"SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.InvalidFormat\"/>\n        <exclude name=\"Squiz.Commenting.FunctionComment.InvalidNoReturn\"/>\n        <!-- PHP8 attribute -->\n        <exclude name=\"PSR12.Files.FileHeader.IncorrectOrder\"/>\n        <!-- /Base -->\n        <!-- Option -->\n        <exclude name=\"SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed\"/>\n        <!-- /Option -->\n        <!-- exclude for PHP8 attributes -->\n        <exclude name=\"PEAR.Commenting.InlineComment.WrongStyle\" />\n        <exclude name=\"PSR12.Files.FileHeader.SpacingAfterBlock\" />\n        <!-- Exclude Fake files form Doctrine CS -->\n        <exclude-pattern>*/tests/Fake/*</exclude-pattern>\n    </rule>\n\n    <!-- Additional Rules -->\n    <rule ref=\"Generic.WhiteSpace.LanguageConstructSpacing\"/>\n    <rule ref=\"SlevomatCodingStandard.Namespaces.UnusedUses\">\n        <properties>\n            <property name=\"searchAnnotations\" value=\"true\"/>\n        </properties>\n    </rule>\n    <rule ref=\"SlevomatCodingStandard.Commenting.DocCommentSpacing\">\n        <properties>\n            <property name=\"annotationsGroups\" type=\"array\">\n                <element value=\"@param, @psalm-param, @phpstan-param\"/>\n                <element value=\"@return, @psalm-return, @phpstan-return\"/>\n                <element value=\"@throws\"/>\n            </property>\n        </properties>\n    </rule>\n    <rule ref=\"SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint\">\n        <include-pattern>src/Module/*</include-pattern>\n        <include-pattern>tests/*</include-pattern>\n    </rule>\n    <rule ref=\"SlevomatCodingStandard.Classes.PropertySpacing\">\n        <properties>\n            <property name=\"minLinesCountBeforeWithComment\" value=\"1\"/>\n            <property name=\"maxLinesCountBeforeWithComment\" value=\"1\"/>\n            <property name=\"maxLinesCountBeforeWithoutComment\" value=\"0\"/>\n        </properties>\n    </rule>\n    <exclude-pattern>*/Fake/*</exclude-pattern>\n    <exclude-pattern>*/tmp/*</exclude-pattern>\n</ruleset>\n"
  },
  {
    "path": "phpmd.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ruleset name=\"PHPMD rule set\"\n         xmlns=\"http://pmd.sf.net/ruleset/1.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd\"\n         xsi:noNamespaceSchemaLocation=\"http://pmd.sf.net/ruleset_xml_schema.xsd\">\n    <!--codesize-->\n    <rule ref=\"rulesets/codesize.xml/CyclomaticComplexity\"/>\n    <rule ref=\"rulesets/codesize.xml/NPathComplexity\"/>\n    <rule ref=\"rulesets/codesize.xml/ExcessiveClassComplexity\"/>\n    <rule ref=\"rulesets/codesize.xml/ExcessiveClassLength\"/>\n    <rule ref=\"rulesets/codesize.xml/ExcessiveMethodLength\"/>\n    <rule ref=\"rulesets/codesize.xml/ExcessiveParameterList\"/>\n    <rule ref=\"rulesets/codesize.xml/ExcessivePublicCount\"/>\n    <rule ref=\"rulesets/codesize.xml/TooManyFields\"/>\n    <rule ref=\"rulesets/codesize.xml/TooManyMethods\"/>\n    <!--design-->\n    <rule ref=\"rulesets/design.xml/EvalExpression\"/>\n    <rule ref=\"rulesets/design.xml/ExitExpression\"/>\n    <rule ref=\"rulesets/design.xml/GotoStatement\" />\n    <rule ref=\"rulesets/design.xml/NumberOfChildren\"/>\n    <rule ref=\"rulesets/design.xml/DepthOfInheritance\"/>\n    <!-- <rule ref=\"rulesets/design.xml/CouplingBetweenObjects\" /> -->\n    <!--naming-->\n    <rule ref=\"rulesets/naming.xml/ConstantNamingConventions\"/>\n    <rule ref=\"rulesets/naming.xml/BooleanGetMethodName\"/>\n    <!--unusedcode-->\n    <rule ref=\"rulesets/unusedcode.xml/UnusedFormalParameter\"/>\n    <rule ref=\"rulesets/unusedcode.xml/UnusedLocalVariable\"/>\n    <rule ref=\"rulesets/unusedcode.xml/UnusedPrivateField\"/>\n    <rule ref=\"rulesets/unusedcode.xml/UnusedPrivateMethod\"/>\n    <!--controversial-->\n    <rule ref=\"rulesets/controversial.xml/CamelCaseClassName\"/>\n    <rule ref=\"rulesets/controversial.xml/CamelCasePropertyName\"/>\n    <rule ref=\"rulesets/controversial.xml/CamelCaseMethodName\"/>\n    <!--cleancode-->\n    <rule ref=\"rulesets/cleancode.xml/BooleanArgumentFlag\"/>\n     <rule ref=\"rulesets/cleancode.xml/ElseExpression\" />\n</ruleset>\n"
  },
  {
    "path": "phpstan.neon",
    "content": "parameters:\n  errorFormat: raw\n  level: max\n  paths:\n    - src/di\n    - tests/di\n    - tests/type\n  excludePaths:\n    - tests/tmp/*\n    - tests/di/tmp/*\n    - tests/Di/tmp/*\n    - tests/di/Fake/*\n    - tests/di/Ray_Di_*\n    - tests/di/script/*\n  ignoreErrors:\n    - '#contains generic class ReflectionAttribute but does not specify its types:#'\n    - '#generic class ReflectionAttribute but does not specify its types:#'\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/9.3/phpunit.xsd\"\n        bootstrap=\"tests/bootstrap.php\"\n        convertDeprecationsToExceptions=\"true\"\n>\n  <coverage processUncoveredFiles=\"true\">\n    <include>\n      <directory suffix=\".php\">src/di</directory>\n    </include>\n  </coverage>\n  <testsuites>\n    <testsuite name=\"Ray.Di Test Suite\">\n      <directory>tests/di/</directory>\n      <directory phpVersion=\"8.0.0\" phpVersionOperator=\">=\">tests-php8</directory>\n      <directory>tests/di</directory>\n    </testsuite>\n  </testsuites>\n  <php>\n    <ini name=\"error_reporting\" value=\"-1\"/>\n  </php>\n</phpunit>\n"
  },
  {
    "path": "psalm.xml",
    "content": "<?xml version=\"1.0\"?>\n<psalm\n    errorLevel=\"1\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns=\"https://getpsalm.org/schema/config\"\n    xsi:schemaLocation=\"https://getpsalm.org/schema/config https://psalm.dev/schema/config\"\n    findUnusedCode=\"false\"\n>\n    <projectFiles>\n        <directory name=\"src/di\"/>\n    </projectFiles>\n    <stubs>\n        <file name=\"tests/stub/BindInterface.phpstub\"/>\n    </stubs>\n    <issueHandlers>\n        <MissingOverrideAttribute errorLevel=\"suppress\"/>\n    </issueHandlers>\n</psalm>\n"
  },
  {
    "path": "rector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nuse Rector\\Config\\RectorConfig;\nuse Rector\\Set\\ValueObject\\LevelSetList;\nuse Rector\\Set\\ValueObject\\SetList;\n\nreturn RectorConfig::configure()\n    ->withPaths([\n        __DIR__ . '/src/di',\n    ])\n    ->withSkip([\n        __DIR__ . '/src-deprecated',\n    ])\n    ->withPhpSets(php82: true)\n    ->withPreparedSets(\n        deadCode: true,\n        codeQuality: true,\n        codingStyle: true,\n        typeDeclarations: true,\n        privatization: true,\n        instanceOf: true,\n        earlyReturn: true\n    );\n"
  },
  {
    "path": "src/di/AbstractModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\AbstractMatcher;\nuse Ray\\Aop\\Matcher;\nuse Ray\\Aop\\Pointcut;\nuse Ray\\Aop\\PriorityPointcut;\nuse Stringable;\n\nuse function assert;\nuse function class_exists;\nuse function interface_exists;\n\n/**\n * @psalm-import-type BindableInterface from Types\n * @psalm-import-type PointcutList from Types\n * @psalm-import-type InterceptorClassList from Types\n */\nabstract class AbstractModule implements Stringable\n{\n    /** @var Matcher */\n    protected $matcher;\n\n    /** @var ?AbstractModule */\n    protected $lastModule;\n    private ?Container $container = null;\n\n    public function __construct(\n        ?self $module = null\n    ) {\n        $this->lastModule = $module;\n        $this->activate();\n        if ($module instanceof self && $this->container instanceof Container) {\n            $this->container->merge($module->getContainer());\n        }\n    }\n\n    public function __toString(): string\n    {\n        return (new ModuleString())($this->getContainer(), $this->getContainer()->getPointcuts());\n    }\n\n    /**\n     * Install module\n     */\n    public function install(self $module): void\n    {\n        $this->getContainer()->merge($module->getContainer());\n    }\n\n    /**\n     * Override module\n     */\n    public function override(self $module): void\n    {\n        $module->getContainer()->merge($this->getContainer());\n        $this->container = $module->getContainer();\n    }\n\n    /**\n     * Return activated container\n     */\n    public function getContainer(): Container\n    {\n        if ($this->container === null) {\n            $this->activate();\n        }\n\n        assert($this->container instanceof Container);\n\n        return $this->container;\n    }\n\n    /**\n     * Bind interceptor\n     *\n     * @param InterceptorClassList $interceptors\n     */\n    public function bindInterceptor(AbstractMatcher $classMatcher, AbstractMatcher $methodMatcher, array $interceptors): void\n    {\n        $pointcut = new Pointcut($classMatcher, $methodMatcher, $interceptors);\n        $this->getContainer()->addPointcut($pointcut);\n        foreach ($interceptors as $interceptor) {\n            if (class_exists($interceptor)) {\n                (new Bind($this->getContainer(), $interceptor))->to($interceptor)->in(Scope::SINGLETON);\n\n                return;\n            }\n\n            assert(interface_exists($interceptor));\n            (new Bind($this->getContainer(), $interceptor))->in(Scope::SINGLETON);\n        }\n    }\n\n    /**\n     * Bind interceptor early\n     *\n     * @param InterceptorClassList $interceptors\n     */\n    public function bindPriorityInterceptor(AbstractMatcher $classMatcher, AbstractMatcher $methodMatcher, array $interceptors): void\n    {\n        $pointcut = new PriorityPointcut($classMatcher, $methodMatcher, $interceptors);\n        $this->getContainer()->addPointcut($pointcut);\n        foreach ($interceptors as $interceptor) {\n            (new Bind($this->getContainer(), $interceptor))->to($interceptor)->in(Scope::SINGLETON);\n        }\n    }\n\n    /**\n     * Rename binding name\n     *\n     * @param string $interface       Interface\n     * @param string $newName         New binding name\n     * @param string $sourceName      Original binding name\n     * @param string $targetInterface Original interface\n     */\n    public function rename(string $interface, string $newName, string $sourceName = Name::ANY, string $targetInterface = ''): void\n    {\n        $targetInterface = $targetInterface ?: $interface;\n        if ($this->lastModule instanceof self) {\n            $this->lastModule->getContainer()->move($interface, $sourceName, $targetInterface, $newName);\n        }\n    }\n\n    /**\n     * Configure binding\n     *\n     * @return void\n     *\n     * @noinspection ReturnTypeCanBeDeclaredInspection\n     */\n    abstract protected function configure();\n\n    /**\n     * Bind interface\n     *\n     * @param BindableInterface $interface\n     */\n    protected function bind(string $interface = ''): Bind\n    {\n        return new Bind($this->getContainer(), $interface);\n    }\n\n    /**\n     * Activate bindings\n     */\n    private function activate(): void\n    {\n        $this->container = new Container();\n        $this->matcher = new Matcher();\n        $this->configure();\n    }\n}\n"
  },
  {
    "path": "src/di/AcceptInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n/**\n * This interface defines the accept method, which allows an object to accept a visitor.\n */\ninterface AcceptInterface\n{\n    /**\n     * Accepts a visitor and applies its behavior on the current object.\n     *\n     * @param VisitorInterface $visitor The visitor to accept\n     *\n     * @return mixed|void\n     */\n    public function accept(VisitorInterface $visitor);\n}\n"
  },
  {
    "path": "src/di/AnnotatedClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\ReflectionClass;\nuse Ray\\Di\\Di\\PostConstruct;\nuse ReflectionMethod;\n\nfinal class AnnotatedClass\n{\n    private AnnotatedClassMethods $injectionMethod;\n\n    public function __construct()\n    {\n        $this->injectionMethod = new AnnotatedClassMethods();\n    }\n\n    /**\n     * Return factory instance\n     *\n     * @phpstan-param ReflectionClass<object> $class Target class reflection\n     */\n    public function getNewInstance(ReflectionClass $class): NewInstance\n    {\n        $setterMethods = new SetterMethods([]);\n        $methods = $class->getMethods();\n        foreach ($methods as $method) {\n            if ($method->name === '__construct') {\n                continue;\n            }\n\n            $setterMethods->add($this->injectionMethod->getSetterMethod($method));\n        }\n\n        $name = $this->injectionMethod->getConstructorName($class);\n\n        return new NewInstance($class, $setterMethods, $name);\n    }\n\n    /**\n     * Return @-PostConstruct method reflection\n     *\n     * @phpstan-param ReflectionClass<object> $class\n     */\n    public function getPostConstruct(ReflectionClass $class): ?ReflectionMethod\n    {\n        $methods = $class->getMethods();\n        foreach ($methods as $method) {\n            $annotation = $method->getAnnotation(PostConstruct::class);\n            if ($annotation instanceof PostConstruct) {\n                return $method;\n            }\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/di/AnnotatedClassMethods.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\ReflectionClass;\nuse Ray\\Aop\\ReflectionMethod;\nuse Ray\\Di\\Di\\InjectInterface;\nuse ReflectionAttribute;\n\nfinal class AnnotatedClassMethods\n{\n    /**\n     * @phpstan-param ReflectionClass<object> $class\n     */\n    public function getConstructorName(ReflectionClass $class): Name\n    {\n        $constructor = $class->getConstructor();\n        if (! $constructor instanceof \\ReflectionMethod) {\n            return new Name(Name::ANY);\n        }\n\n        $reflMethod = new \\ReflectionMethod($class->getName(), '__construct');\n        $name = Name::withAttributes($reflMethod);\n        if ($name instanceof Name) {\n            return $name;\n        }\n\n        return new Name(Name::ANY);\n    }\n\n    public function getSetterMethod(ReflectionMethod $method): ?SetterMethod\n    {\n        $inject = $method->getAnnotation(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF);\n        if (! $inject instanceof InjectInterface) {\n            return null;\n        }\n\n        $name = $this->getName($method);\n        $setterMethod = new SetterMethod($method, $name);\n        if ($inject->isOptional()) {\n            $setterMethod->setOptional();\n        }\n\n        return $setterMethod;\n    }\n\n    private function getName(ReflectionMethod $method): Name\n    {\n        $name = Name::withAttributes($method);\n        if ($name instanceof Name) {\n            return $name;\n        }\n\n        return new Name(Name::ANY);\n    }\n}\n"
  },
  {
    "path": "src/di/Annotation/ScriptDir.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Annotation;\n\nuse Attribute;\nuse Ray\\Di\\Di\\Qualifier;\n\n/**\n * Script directory\n *\n * This qualifier should not use in an application code.\n */\n#[Attribute, Qualifier]\nfinal class ScriptDir\n{\n}\n"
  },
  {
    "path": "src/di/Argument.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse ReflectionException;\nuse ReflectionMethod;\nuse ReflectionNamedType;\nuse ReflectionParameter;\nuse Stringable;\n\nuse function assert;\nuse function in_array;\nuse function sprintf;\n\n/**\n * @psalm-import-type DependencyIndex from Types\n */\nfinal class Argument implements AcceptInterface, Stringable\n{\n    public const UNBOUND_TYPE = ['bool', 'int', 'float', 'string', 'array', 'resource', 'callable', 'iterable'];\n\n    /** @var DependencyIndex */\n    private string $index;\n    private bool $isDefaultAvailable;\n\n    /** @var mixed */\n    private $default;\n    private string $meta;\n    private ReflectionParameter $reflection;\n\n    public function __construct(ReflectionParameter $parameter, string $name)\n    {\n        $type = $this->getType($parameter);\n        $isOptional = $parameter->isOptional();\n        $this->isDefaultAvailable = $parameter->isDefaultValueAvailable() || $isOptional;\n        if ($isOptional) {\n            $this->default = null;\n        }\n\n        $this->setDefaultValue($parameter);\n        $this->index = $type . '-' . $name;\n        $this->reflection = $parameter;\n        $this->meta = sprintf(\n            \"'%s-%s' in %s:%d ($%s)\",\n            $type,\n            $name,\n            $this->reflection->getDeclaringFunction()->getFileName(),\n            $this->reflection->getDeclaringFunction()->getStartLine(),\n            $parameter->getName()\n        );\n    }\n\n    /**\n     * Return index\n     *\n     * @return DependencyIndex\n     */\n    public function __toString(): string\n    {\n        return $this->index;\n    }\n\n    /**\n     * Return reflection\n     */\n    public function get(): ReflectionParameter\n    {\n        return $this->reflection;\n    }\n\n    public function isDefaultAvailable(): bool\n    {\n        return $this->isDefaultAvailable;\n    }\n\n    /**\n     * @return mixed\n     */\n    public function getDefaultValue()\n    {\n        return $this->default;\n    }\n\n    public function getMeta(): string\n    {\n        return $this->meta;\n    }\n\n    /**\n     * @return array<mixed>\n     */\n    public function __serialize(): array\n    {\n        $method = $this->reflection->getDeclaringFunction();\n        assert($method instanceof ReflectionMethod);\n        $ref = [\n            $method->class,\n            $method->name,\n            $this->reflection->getName(),\n        ];\n\n        return [\n            $this->index,\n            $this->isDefaultAvailable,\n            $this->default,\n            $this->meta,\n            $ref,\n        ];\n    }\n\n    /**\n     * @param array{0: DependencyIndex, 1: bool, 2: string, 3: string, 4: string, 5: array{0: string, 1: string, 2:string}} $unserialized\n     */\n    public function __unserialize(array $unserialized): void\n    {\n        [\n            $this->index,\n            $this->isDefaultAvailable,\n            $this->default,\n            $this->meta,\n            $ref,\n        ] = $unserialized;\n        $this->reflection = new ReflectionParameter([$ref[0], $ref[1]], $ref[2]);\n    }\n\n    /** @inheritDoc */\n    public function accept(VisitorInterface $visitor): void\n    {\n        $visitor->visitArgument(\n            $this->index,\n            $this->isDefaultAvailable,\n            $this->default,\n            $this->reflection\n        );\n    }\n\n    private function setDefaultValue(ReflectionParameter $parameter): void\n    {\n        if (! $this->isDefaultAvailable) {\n            return;\n        }\n\n        try {\n            $this->default = $parameter->getDefaultValue();\n            // @codeCoverageIgnoreStart\n        } catch (ReflectionException) {\n            $this->default = null;\n            // @codeCoverageIgnoreEnd\n        }\n    }\n\n    /**\n     * @psalm-pure\n     */\n    private function getType(ReflectionParameter $parameter): string\n    {\n        $type = $parameter->getType();\n\n        return $type instanceof ReflectionNamedType && ! in_array($type->getName(), self::UNBOUND_TYPE, true) ? $type->getName() : '';\n    }\n}\n"
  },
  {
    "path": "src/di/Arguments.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Exception\\NoHint;\nuse Ray\\Di\\Exception\\Unbound;\nuse ReflectionMethod;\n\nuse function sprintf;\n\n/**\n * @psalm-import-type ArgumentsList from Types\n * @psalm-import-type MethodArguments from Types\n */\nfinal class Arguments implements AcceptInterface\n{\n    /** @var ArgumentsList */\n    private array $arguments = [];\n\n    public function __construct(ReflectionMethod $method, Name $name)\n    {\n        $parameters = $method->getParameters();\n        foreach ($parameters as $parameter) {\n            $this->arguments[] = new Argument($parameter, $name($parameter));\n        }\n    }\n\n    /**\n     * Return arguments\n     *\n     * @return list<mixed>\n     *\n     * @throws Exception\\Unbound\n     */\n    public function inject(Container $container): array\n    {\n        $parameters = [];\n        foreach ($this->arguments as $parameter) {\n            /** @psalm-suppress MixedAssignment */\n            $parameters[] = $this->getParameter($container, $parameter);\n        }\n\n        return $parameters;\n    }\n\n    public function accept(VisitorInterface $visitor): void\n    {\n        $visitor->visitArguments($this->arguments);\n    }\n\n    /**\n     * @return mixed\n     *\n     * @throws Unbound\n     */\n    private function getParameter(Container $container, Argument $argument)\n    {\n        $this->bindInjectionPoint($container, $argument);\n        try {\n            return $container->getDependency((string) $argument);\n        } catch (Unbound $unbound) {\n            if ($argument->isDefaultAvailable()) {\n                return $argument->getDefaultValue();\n            }\n\n            if ($unbound instanceof NoHint) {\n                throw new NoHint($this->getNoHintMsg($argument), 0, $unbound);\n            }\n\n            throw new Unbound($argument->getMeta(), 0, $unbound);\n        }\n    }\n\n    private function bindInjectionPoint(Container $container, Argument $argument): void\n    {\n        $isSelf = (string) $argument === InjectionPointInterface::class . '-' . Name::ANY;\n        if ($isSelf) {\n            return;\n        }\n\n        (new Bind($container, InjectionPointInterface::class))->toInstance(new InjectionPoint($argument->get()));\n    }\n\n    private function getNoHintMsg(Argument $argument): string\n    {\n        $ref = $argument->get();\n        $func = $ref->getDeclaringFunction();\n        $fileName = $func->getFileName();\n\n        return sprintf(\n            '$%s (%s:%d)',\n            $ref->getName(),\n            $fileName !== false ? $fileName : 'unknown file',\n            $func->getStartLine()\n        );\n    }\n}\n"
  },
  {
    "path": "src/di/AspectBind.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\Bind as AopBind;\nuse Ray\\Aop\\MethodInterceptor;\n\nuse function assert;\n\n/**\n * @psalm-import-type MethodInterceptorBindings from Types\n */\nfinal class AspectBind implements AcceptInterface\n{\n    public function __construct(private AopBind $bind)\n    {\n    }\n\n    /**\n     * Instantiate interceptors\n     *\n     * @return MethodInterceptorBindings\n     */\n    public function inject(Container $container): array\n    {\n        $bindings = $this->bind->getBindings();\n        $instantiatedBindings = [];\n        foreach ($bindings as $methodName => $interceptorClassNames) {\n            $interceptors = [];\n            foreach ($interceptorClassNames as $interceptorClassName) {\n                /** @var class-string $interceptorClassName */\n                $interceptor = $container->getInstance($interceptorClassName);\n                assert($interceptor instanceof MethodInterceptor);\n                $interceptors[] = $interceptor;\n            }\n\n            $instantiatedBindings[$methodName] = $interceptors;\n        }\n\n        return $instantiatedBindings;\n    }\n\n    /** @inheritDoc */\n    public function accept(VisitorInterface $visitor): void\n    {\n        $visitor->visitAspectBind($this->bind);\n    }\n}\n"
  },
  {
    "path": "src/di/AssistedInjectInterceptor.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\nuse Ray\\Di\\Di\\Assisted;\nuse Ray\\Di\\Di\\Inject;\nuse Ray\\Di\\Di\\InjectInterface;\nuse Ray\\Di\\Di\\Named;\nuse ReflectionAttribute;\nuse ReflectionNamedType;\nuse ReflectionParameter;\n\nuse function assert;\nuse function call_user_func_array;\nuse function in_array;\nuse function is_callable;\n\n/**\n * @psalm-import-type NamedArguments from Types\n * @psalm-import-type InjectableValue from Types\n */\n\n/**\n * Assisted injection interceptor for #[Inject] attributed parameter\n *\n * @psalm-import-type NamedArguments from Types\n */\nfinal class AssistedInjectInterceptor implements MethodInterceptor\n{\n    public function __construct(private InjectorInterface $injector, private MethodInvocationProvider $methodInvocationProvider)\n    {\n    }\n\n    /**\n     * @return mixed\n     */\n    public function invoke(MethodInvocation $invocation)\n    {\n        $this->methodInvocationProvider->set($invocation);\n        $params = $invocation->getMethod()->getParameters();\n        $namedArguments = $this->getNamedArguments($invocation);\n        foreach ($params as $param) {\n            /** @var list<ReflectionAttribute> $inject */\n            $inject = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF); // @phpstan-ignore-line\n            /** @var list<ReflectionAttribute> $assisted */\n            $assisted = $param->getAttributes(Assisted::class);\n            if (isset($assisted[0]) || isset($inject[0])) {\n                /** @psalm-suppress MixedAssignment */\n                $namedArguments[$param->getName()] = $this->getDependency($param);\n            }\n        }\n\n        $callable = [$invocation->getThis(), $invocation->getMethod()->getName()];\n        assert(is_callable($callable));\n\n        return call_user_func_array($callable, $namedArguments);\n    }\n\n    /**\n     * @param MethodInvocation<object> $invocation\n     *\n     * @return array<string, mixed>\n     */\n    private function getNamedArguments(MethodInvocation $invocation): array\n    {\n        $args = $invocation->getArguments();\n        $params = $invocation->getMethod()->getParameters();\n        $namedParams = [];\n        foreach ($params as $param) {\n            $pos = $param->getPosition();\n            if (isset($args[$pos])) {\n                /** @psalm-suppress MixedAssignment */\n                $namedParams[$param->getName()] = $args[$pos];\n            }\n        }\n\n        return $namedParams;\n    }\n\n    /**\n     * @return mixed\n     */\n    private function getDependency(ReflectionParameter $param)\n    {\n        $named = (string) $this->getName($param);\n        $type = $param->getType();\n        assert($type instanceof ReflectionNamedType || $type === null);\n        $typeName = $type instanceof ReflectionNamedType ? $type->getName() : '';\n        $interface = in_array($typeName, Argument::UNBOUND_TYPE) ? '' : $typeName;\n\n        /** @var class-string $interface */\n        return $this->injector->getInstance($interface, $named);\n    }\n\n    private function getName(ReflectionParameter $param): ?string\n    {\n        /** @var list<ReflectionAttribute> $nameds */\n        $nameds = $param->getAttributes(Named::class);\n        if (isset($nameds[0])) {\n            $named = $nameds[0]->newInstance();\n            assert($named instanceof Named);\n\n            return $named->value;\n        }\n\n        if ($param->getAttributes(Inject::class)) {\n            return null;\n        }\n\n        return $this->getCustomInject($param);\n    }\n\n    /**\n     * @return ?class-string\n     */\n    private function getCustomInject(ReflectionParameter $param): ?string\n    {\n        /** @var list<ReflectionAttribute> $injects */\n        $injects = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF);\n        if (! $injects) {\n            return null;\n        }\n\n        $inject = $injects[0]->newInstance();\n        assert($inject instanceof InjectInterface);\n\n        return $inject::class;\n    }\n}\n"
  },
  {
    "path": "src/di/AssistedInjectModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Matcher\\AssistedInjectMatcher;\n\n/**\n * Assisted module for php8 attributes\n */\nfinal class AssistedInjectModule extends AbstractModule\n{\n    protected function configure(): void\n    {\n        $this->bindInterceptor(\n            $this->matcher->any(),\n            (new AssistedInjectMatcher()),\n            [AssistedInjectInterceptor::class]\n        );\n    }\n}\n"
  },
  {
    "path": "src/di/AssistedModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInvocation;\n\n/**\n * Module for assisted injection using PHP 8 attributes\n *\n * This module enables assisted injection by delegating to AssistedInjectModule\n * and provides necessary bindings for method invocation context.\n * Use #[Assisted] attribute on method parameters to enable automatic injection.\n */\nfinal class AssistedModule extends AbstractModule\n{\n    protected function configure(): void\n    {\n        $this->install(new AssistedInjectModule());\n        $this->bind(MethodInvocation::class)->toProvider(MethodInvocationProvider::class)->in(Scope::SINGLETON);\n        $this->bind(MethodInvocationProvider::class)->in(Scope::SINGLETON);\n    }\n}\n"
  },
  {
    "path": "src/di/Bind.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\ReflectionClass;\nuse ReflectionException;\nuse ReflectionMethod;\nuse Stringable;\n\nuse function assert;\nuse function class_exists;\nuse function interface_exists;\n\n/**\n * @psalm-import-type BindableInterface from Types\n * @psalm-import-type BindingName from Types\n * @psalm-import-type ParameterNameMapping from Types\n */\nfinal class Bind implements Stringable\n{\n    private string $name = Name::ANY;\n    private DependencyInterface $bound;\n    private readonly BindValidator $validate;\n\n    /** @var ?Untarget */\n    private $untarget;\n\n    /**\n     * @param Container         $container dependency container\n     * @param BindableInterface $interface interface or concrete class name\n     * @phpstan-param class-string<MethodInterceptor>|string $interface\n     */\n    public function __construct(\n        private readonly Container $container,\n        private readonly string $interface\n    ) {\n        $this->validate = new BindValidator();\n        $bindUntarget = class_exists($this->interface) && ! (new \\ReflectionClass($this->interface))->isAbstract() && ! $this->isRegistered($this->interface);\n        $this->bound = new NullDependency();\n        if ($bindUntarget) {\n            /** @var class-string $interface */\n            $interface = $this->interface;\n            $this->untarget = new Untarget($interface);\n\n            return;\n        }\n\n        $this->validate->constructor($this->interface);\n    }\n\n    public function __destruct()\n    {\n        if ($this->untarget) {\n            ($this->untarget)($this->container, $this);\n            $this->untarget = null;\n        }\n    }\n\n    /**\n     * @return non-empty-string\n     */\n    public function __toString(): string\n    {\n        return $this->interface . '-' . $this->name;\n    }\n\n    /**\n     * Set dependency name\n     *\n     * @param BindingName $name\n     */\n    public function annotatedWith(string $name): self\n    {\n        $this->name = $name;\n\n        return $this;\n    }\n\n    /**\n     * Bind to class\n     *\n     * @param class-string $class\n     */\n    public function to(string $class): self\n    {\n        $this->untarget = null;\n        $refClass = $this->validate->to($this->interface, $class);\n        $this->bound = (new DependencyFactory())->newAnnotatedDependency($refClass);\n        $this->container->add($this);\n\n        return $this;\n    }\n\n    /**\n     * Bind to constructor\n     *\n     * @param class-string<T>             $class class name\n     * @param ParameterNameMapping|string $name  \"varName=bindName,...\" or [$varName => $bindName, $varName => $bindName...]\n     *\n     * @throws ReflectionException\n     *\n     * @template T of object\n     */\n    public function toConstructor(string $class, string|array $name, ?InjectionPoints $injectionPoints = null, ?string $postConstruct = null): self\n    {\n        $this->untarget = null;\n        $postConstructRef = $postConstruct !== null ? new ReflectionMethod($class, $postConstruct) : null;\n        /** @var ReflectionClass<object> $reflection */\n        $reflection = new ReflectionClass($class);\n        $this->bound = (new DependencyFactory())->newToConstructor($reflection, $name, $injectionPoints, $postConstructRef);\n        $this->container->add($this);\n\n        return $this;\n    }\n\n    /**\n     * Bind to provider\n     *\n     * @phpstan-param class-string $provider\n     */\n    public function toProvider(string $provider, string $context = ''): self\n    {\n        $this->untarget = null;\n        $refClass = $this->validate->toProvider($provider);\n        $this->bound = (new DependencyFactory())->newProvider($refClass, $context);\n        $this->container->add($this);\n\n        return $this;\n    }\n\n    /**\n     * Bind to instance\n     *\n     * @param mixed $instance\n     */\n    public function toInstance($instance): self\n    {\n        $this->untarget = null;\n        $this->bound = new Instance($instance);\n        $this->container->add($this);\n\n        return $this;\n    }\n\n    /**\n     * Bind to NullObject\n     */\n    public function toNull(): self\n    {\n        $this->untarget = null;\n        assert(interface_exists($this->interface));\n        $this->bound = new NullObjectDependency($this->interface);\n        $this->container->add($this);\n\n        return $this;\n    }\n\n    /**\n     * Set scope\n     */\n    public function in(string $scope): self\n    {\n        if ($this->bound instanceof Dependency || $this->bound instanceof DependencyProvider || $this->bound instanceof NullDependency) {\n            $this->bound->setScope($scope);\n        }\n\n        if ($this->untarget) {\n            $this->untarget->setScope($scope);\n        }\n\n        return $this;\n    }\n\n    public function getBound(): DependencyInterface\n    {\n        return $this->bound;\n    }\n\n    public function setBound(DependencyInterface $bound): void\n    {\n        $this->bound = $bound;\n    }\n\n    private function isRegistered(string $interface): bool\n    {\n        return isset($this->container->getContainer()[$interface . '-' . Name::ANY]);\n    }\n}\n"
  },
  {
    "path": "src/di/BindValidator.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\NullInterceptor;\nuse Ray\\Aop\\ReflectionClass;\nuse Ray\\Di\\Exception\\InvalidProvider;\nuse Ray\\Di\\Exception\\InvalidType;\nuse Ray\\Di\\Exception\\NotFound;\n\nuse function class_exists;\nuse function interface_exists;\nuse function sprintf;\n\nfinal class BindValidator\n{\n    public function constructor(string $interface): void\n    {\n        if ($interface && ! interface_exists($interface) && ! class_exists($interface)) {\n            throw new NotFound($interface);\n        }\n    }\n\n    /**\n     * To validator\n     *\n     * @param class-string<T> $class\n     *\n     * @return ReflectionClass<T>\n     *\n     * @template T of object\n     */\n    public function to(string $interface, string $class): ReflectionClass\n    {\n        if (! class_exists($class)) {\n            throw new NotFound($class);\n        }\n\n        if (! $this->isNullInterceptorBinding($class, $interface) && interface_exists($interface) && ! (new ReflectionClass($class))->implementsInterface($interface)) {\n            throw new InvalidType(sprintf('[%s] is no implemented [%s] interface', $class, $interface));\n        }\n\n        return new ReflectionClass($class);\n    }\n\n    /**\n     * toProvider validator\n     *\n     * @phpstan-param class-string $provider\n     *\n     * @psalm-return ReflectionClass\n     * @phpstan-return ReflectionClass<object>\n     *\n     * @throws NotFound\n     */\n    public function toProvider(string $provider): ReflectionClass\n    {\n        if (! class_exists($provider)) {\n            /** @psalm-suppress MixedArgument -- should be string */\n            throw new NotFound($provider);\n        }\n\n        $reflectioClass = new ReflectionClass($provider);\n        if (! $reflectioClass->implementsInterface(ProviderInterface::class)) {\n            throw new InvalidProvider($provider);\n        }\n\n        return $reflectioClass;\n    }\n\n    private function isNullInterceptorBinding(string $class, string $interface): bool\n    {\n        return $class === NullInterceptor::class && interface_exists($interface) && (new ReflectionClass($interface))->implementsInterface(MethodInterceptor::class);\n    }\n}\n"
  },
  {
    "path": "src/di/BuiltinModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\MultiBinding\\MultiBindingModule;\n\nfinal class BuiltinModule\n{\n    public function __invoke(AbstractModule $module): AbstractModule\n    {\n        $module->install(new AssistedModule());\n        $module->install(new ProviderSetModule());\n        $module->install(new MultiBindingModule());\n\n        return $module;\n    }\n}\n"
  },
  {
    "path": "src/di/CompileNullObject.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n/**\n * Convert NullObjectDependency to Dependency\n */\nfinal class CompileNullObject\n{\n    public function __invoke(Container $container, string $scriptDir): void\n    {\n        $container->map(static function (DependencyInterface $dependency) use ($scriptDir): Dependency|DependencyInterface {\n            if ($dependency instanceof NullObjectDependency) {\n                return $dependency->toNull($scriptDir);\n            }\n\n            return $dependency;\n        });\n    }\n}\n"
  },
  {
    "path": "src/di/Container.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse BadMethodCallException;\nuse Ray\\Aop\\Compiler;\nuse Ray\\Aop\\CompilerInterface;\nuse Ray\\Aop\\Pointcut;\nuse Ray\\Di\\Exception\\NoHint;\nuse Ray\\Di\\Exception\\Unbound;\nuse Ray\\Di\\Exception\\Untargeted;\nuse Ray\\Di\\MultiBinding\\MultiBindings;\nuse ReflectionClass;\n\nuse function array_merge;\nuse function class_exists;\nuse function explode;\nuse function ksort;\nuse function sprintf;\n\n/**\n * @psalm-import-type DependencyContainer from Types\n * @psalm-import-type PointcutList from Types\n * @psalm-import-type DependencyIndex from Types\n * @psalm-import-type MethodArguments from Types\n * @psalm-import-type InjectableValue from Types\n */\nfinal class Container implements InjectorInterface\n{\n    /** @var MultiBindings */\n    public $multiBindings;\n\n    /** @var DependencyContainer */\n    private array $container = [];\n\n    /** @var array<int, Pointcut> */\n    private array $pointcuts = [];\n\n    public function __construct()\n    {\n        $this->multiBindings = new MultiBindings();\n    }\n\n    /**\n     * @return list<string>\n     */\n    public function __sleep()\n    {\n        return ['container', 'pointcuts', 'multiBindings'];\n    }\n\n    /**\n     * Add binding to a container\n     */\n    public function add(Bind $bind): void\n    {\n        $dependency = $bind->getBound();\n        $dependency->register($this->container, $bind);\n    }\n\n    /**\n     * Add Pointcut to container\n     */\n    public function addPointcut(Pointcut $pointcut): void\n    {\n        $this->pointcuts[] = $pointcut;\n    }\n\n    /**\n     * {@inheritDoc}\n     *\n     * @param ''|class-string<T> $interface\n     * @param string             $name\n     *\n     * @return ($interface is '' ? mixed : T)\n     *\n     * @template T of object\n     */\n    public function getInstance($interface, $name = Name::ANY)\n    {\n        /** @psalm-suppress MixedReturnStatement */\n        return $this->getDependency($interface . '-' . $name);\n    }\n\n    /**\n     * Return dependency injected instance\n     *\n     * @param MethodArguments $params\n     *\n     * @return mixed\n     *\n     * @throws Unbound\n     */\n    public function getInstanceWithArgs(string $interface, array $params)\n    {\n        $index = $interface . '-';\n        if (! isset($this->container[$index])) {\n            throw $this->unbound($index);\n        }\n\n        $dependency = $this->container[$index];\n        if (! $dependency instanceof Dependency) {\n            throw new BadMethodCallException($interface);\n        }\n\n        return $dependency->injectWithArgs($this, $params);\n    }\n\n    /**\n     * Return dependency injected instance\n     *\n     * @param DependencyIndex $index\n     *\n     * @return mixed\n     *\n     * @throws Unbound\n     */\n    public function getDependency(string $index)\n    {\n        if (! isset($this->container[$index])) {\n            throw $this->unbound($index);\n        }\n\n        return $this->container[$index]->inject($this);\n    }\n\n    /**\n     * Rename existing dependency interface + name\n     */\n    public function move(string $sourceInterface, string $sourceName, string $targetInterface, string $targetName): void\n    {\n        $sourceIndex = $sourceInterface . '-' . $sourceName;\n        if (! isset($this->container[$sourceIndex])) {\n            throw $this->unbound($sourceIndex);\n        }\n\n        $targetIndex = $targetInterface . '-' . $targetName;\n        $this->container[$targetIndex] = $this->container[$sourceIndex];\n        unset($this->container[$sourceIndex]);\n    }\n\n    /**\n     * Return Unbound exception\n     *\n     * @param DependencyIndex $index {interface}-{bind name}\n     */\n    public function unbound(string $index): Untargeted|Unbound\n    {\n        [$class, $name] = explode('-', $index);\n        if (class_exists($class) && ! (new ReflectionClass($class))->isAbstract()) {\n            return new Untargeted($class);\n        }\n\n        if ($class === '' && $name === '') {\n            return new NoHint();\n        }\n\n        return new Unbound(sprintf(\"'%s-%s'\", $class, $name));\n    }\n\n    /**\n     * Return container\n     *\n     * @return array<non-empty-string, DependencyInterface>\n     * @psalm-return DependencyContainer\n     */\n    public function getContainer(): array\n    {\n        return $this->container;\n    }\n\n    /**\n     * Return pointcuts\n     *\n     * @return array<int, Pointcut>\n     * @psalm-return PointcutList\n     */\n    public function getPointcuts(): array\n    {\n        return $this->pointcuts;\n    }\n\n    /**\n     * Merge container\n     */\n    public function merge(self $container): void\n    {\n        $this->multiBindings->merge($container->multiBindings);\n        $this->container += $container->getContainer();\n        $this->pointcuts = array_merge($this->pointcuts, $container->getPointcuts());\n    }\n\n    /**\n     * Weave aspects to all dependency in container\n     */\n    public function weaveAspects(CompilerInterface $compiler): void\n    {\n        if ($this->pointcuts === []) {\n            return;\n        }\n\n        foreach ($this->container as $dependency) {\n            if ($dependency instanceof Dependency) {\n                $dependency->weaveAspects($compiler, $this->pointcuts);\n            }\n        }\n    }\n\n    /**\n     * Weave aspect to single dependency\n     */\n    public function weaveAspect(Compiler $compiler, Dependency $dependency): self\n    {\n        $dependency->weaveAspects($compiler, $this->pointcuts);\n\n        return $this;\n    }\n\n    /**\n     * @param callable(DependencyInterface, string): DependencyInterface $f\n     */\n    public function map(callable $f): void\n    {\n        foreach ($this->container as $key => &$index) {\n            $index = $f($index, $key);\n        }\n    }\n\n    public function sort(): void\n    {\n        ksort($this->container);\n    }\n}\n"
  },
  {
    "path": "src/di/ContainerFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\Compiler;\n\nuse function array_shift;\n\n/**\n * @psalm-import-type ModuleList from Types\n * @psalm-import-type ScriptDir from Types\n */\nfinal class ContainerFactory\n{\n    /**\n     * @param AbstractModule|ModuleList|null $module   Module(s)\n     * @param ScriptDir                      $classDir\n     */\n    public function __invoke($module, string $classDir): Container\n    {\n        $oneModule = $this->getModule($module);\n        // install built-in module\n        $appModule = (new BuiltinModule())($oneModule);\n        $container = $appModule->getContainer();\n        // Compile null objects\n        (new CompileNullObject())($container, $classDir);\n        // Compile aspects\n        /** @psalm-suppress InvalidArgument */\n        $container->weaveAspects(new Compiler($classDir));\n\n        return $container;\n    }\n\n    /**\n     * @param AbstractModule|ModuleList|null $module Module(s)\n     */\n    private function getModule($module): AbstractModule\n    {\n        if ($module instanceof AbstractModule) {\n            return $module;\n        }\n\n        if ($module === null) {\n            return new NullModule();\n        }\n\n        $modules = $module;\n        $oneModule = array_shift($modules);\n        foreach ($modules as $module) {\n            $oneModule->install($module);\n        }\n\n        return $oneModule;\n    }\n}\n"
  },
  {
    "path": "src/di/Dependency.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\Bind as AopBind;\nuse Ray\\Aop\\CompilerInterface;\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\WeavedInterface;\nuse ReflectionClass;\nuse ReflectionMethod;\n\nuse function assert;\nuse function method_exists;\nuse function sprintf;\n\n/**\n * @psalm-import-type MethodArguments from Types\n * @psalm-import-type PointcutList from Types\n */\nfinal class Dependency implements DependencyInterface, AcceptInterface\n{\n    /** @var NewInstance */\n    private $newInstance;\n\n    /** @var ?string */\n    private $postConstruct;\n    private bool $isSingleton = false;\n\n    /** @var ?mixed */\n    private $instance;\n\n    public function __construct(NewInstance $newInstance, ?ReflectionMethod $postConstruct = null)\n    {\n        $this->newInstance = $newInstance;\n        $this->postConstruct = $postConstruct->name ?? null;\n    }\n\n    /**\n     * @return array<string>\n     */\n    public function __sleep()\n    {\n        return ['newInstance', 'postConstruct', 'isSingleton'];\n    }\n\n    public function __toString(): string\n    {\n        return sprintf(\n            '(dependency) %s',\n            (string) $this->newInstance\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function register(array &$container, Bind $bind): void\n    {\n        $container[(string) $bind] = $bind->getBound();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function inject(Container $container)\n    {\n        // singleton ?\n        if ($this->isSingleton === true && $this->instance !== null) {\n            return $this->instance;\n        }\n\n        // create dependency injected instance\n        $this->instance = ($this->newInstance)($container);\n\n        // @PostConstruct\n        if ($this->postConstruct !== null) {\n            assert(method_exists($this->instance, $this->postConstruct));\n            $this->instance->{$this->postConstruct}();\n        }\n\n        return $this->instance;\n    }\n\n    /**\n     * @param MethodArguments $params\n     *\n     * @return mixed\n     */\n    public function injectWithArgs(Container $container, array $params)\n    {\n        // singleton ?\n        if ($this->isSingleton === true && $this->instance !== null) {\n            return $this->instance;\n        }\n\n        // create dependency injected instance\n        $this->instance = $this->newInstance->newInstanceArgs($container, $params);\n\n        // @PostConstruct\n        if ($this->postConstruct !== null) {\n            assert(method_exists($this->instance, $this->postConstruct));\n            $this->instance->{$this->postConstruct}();\n        }\n\n        return $this->instance;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function setScope($scope): void\n    {\n        if ($scope === Scope::SINGLETON) {\n            $this->isSingleton = true;\n        }\n    }\n\n    /**\n     * @param PointcutList $pointcuts\n     */\n    public function weaveAspects(CompilerInterface $compiler, array $pointcuts): void\n    {\n        $class = (string) $this->newInstance;\n        if ((new ReflectionClass($class))->isFinal()) {\n            return;\n        }\n\n        $isInterceptor = (new ReflectionClass($class))->implementsInterface(MethodInterceptor::class);\n        $isWeaved = (new ReflectionClass($class))->implementsInterface(WeavedInterface::class);\n        if ($isInterceptor || $isWeaved) {\n            return;\n        }\n\n        $bind = new AopBind();\n        $className = (string) $this->newInstance;\n        $bind->bind($className, $pointcuts);\n        if (! $bind->getBindings()) {\n            return;\n        }\n\n        $class = $compiler->compile($className, $bind);\n        $this->newInstance->weaveAspects($class, $bind);\n    }\n\n    /** @inheritDoc */\n    public function accept(VisitorInterface $visitor)\n    {\n        return $visitor->visitDependency(\n            $this->newInstance,\n            $this->postConstruct,\n            $this->isSingleton\n        );\n    }\n\n    public function isSingleton(): bool\n    {\n        return $this->isSingleton;\n    }\n}\n"
  },
  {
    "path": "src/di/DependencyFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\ReflectionClass;\nuse ReflectionMethod;\n\nuse function assert;\nuse function class_exists;\n\nfinal class DependencyFactory\n{\n    /**\n     * Create dependency object\n     *\n     * @param ReflectionClass<object> $class\n     */\n    public function newAnnotatedDependency(ReflectionClass $class): Dependency\n    {\n        $annotateClass = new AnnotatedClass();\n        $newInstance = $annotateClass->getNewInstance($class);\n        $postConstruct = $annotateClass->getPostConstruct($class);\n\n        return new Dependency($newInstance, $postConstruct);\n    }\n\n    /**\n     * Create Provider binding\n     *\n     * @param ReflectionClass<object> $provider\n     */\n    public function newProvider(ReflectionClass $provider, string $context): DependencyProvider\n    {\n        $dependency = $this->newAnnotatedDependency($provider);\n\n        return new DependencyProvider($dependency, $context);\n    }\n\n    /**\n     * Create ToConstructor binding\n     *\n     * @param ReflectionClass<object>      $class\n     * @param string|array<string, string> $name\n     */\n    public function newToConstructor(\n        ReflectionClass $class,\n        string|array $name,\n        ?InjectionPoints $injectionPoints = null,\n        ?ReflectionMethod $postConstruct = null\n    ): Dependency {\n        assert(class_exists($class->name));\n        $setterMethods = $injectionPoints instanceof InjectionPoints ? $injectionPoints($class->name) : new SetterMethods([]);\n        $newInstance = new NewInstance($class, $setterMethods, new Name($name));\n\n        return new Dependency($newInstance, $postConstruct);\n    }\n}\n"
  },
  {
    "path": "src/di/DependencyInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n/**\n * @psalm-import-type DependencyContainer from Types\n * @psalm-import-type ScopeType from Types\n * @psalm-import-type InjectableValue from Types\n */\ninterface DependencyInterface\n{\n    /**\n     * @return string\n     */\n    public function __toString();\n\n    /**\n     * Inject dependencies into dependent objects\n     *\n     * @return mixed\n     */\n    public function inject(Container $container);\n\n    /**\n     * Register dependency to container\n     *\n     * @param DependencyContainer $container\n     *\n     * @return void\n     *\n     * @param-out DependencyContainer $container\n     */\n    public function register(array &$container, Bind $bind);\n\n    /**\n     * Set scope\n     *\n     * @param string $scope\n     *\n     * @return void\n     */\n    public function setScope($scope);\n}\n"
  },
  {
    "path": "src/di/DependencyProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse function assert;\nuse function sprintf;\n\nfinal class DependencyProvider implements DependencyInterface, AcceptInterface\n{\n    private bool $isSingleton = false;\n\n    /** @var ?mixed */\n    private $instance;\n\n    public function __construct(\n        /**\n         * Provider dependency\n         */\n        private readonly Dependency $dependency,\n        public string $context\n    ) {\n    }\n\n    /**\n     * @return list<string>\n     */\n    public function __sleep()\n    {\n        return ['context', 'dependency', 'isSingleton'];\n    }\n\n    public function __toString(): string\n    {\n        return sprintf(\n            '(provider) %s',\n            (string) $this->dependency\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function register(array &$container, Bind $bind): void\n    {\n        $container[(string) $bind] = $bind->getBound();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function inject(Container $container)\n    {\n        if ($this->isSingleton && $this->instance !== null) {\n            return $this->instance;\n        }\n\n        $provider = $this->dependency->inject($container);\n        assert($provider instanceof ProviderInterface);\n        if ($provider instanceof SetContextInterface) {\n            $this->setContext($provider);\n        }\n\n        $this->instance = $provider->get();\n\n        return $this->instance;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function setScope($scope): void\n    {\n        if ($scope === Scope::SINGLETON) {\n            $this->isSingleton = true;\n        }\n    }\n\n    public function setContext(SetContextInterface $provider): void\n    {\n        $provider->setContext($this->context);\n    }\n\n    public function isSingleton(): bool\n    {\n        return $this->isSingleton;\n    }\n\n    /** @inheritDoc */\n    public function accept(VisitorInterface $visitor)\n    {\n        return $visitor->visitProvider(\n            $this->dependency,\n            $this->context,\n            $this->isSingleton\n        );\n    }\n}\n"
  },
  {
    "path": "src/di/Di/Assisted.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Di;\n\nuse Attribute;\n\n/**\n * Marks a parameter for assisted injection\n *\n * When applied to a method parameter, this indicates that the parameter\n * should be automatically injected by the DI container during method invocation.\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nfinal class Assisted\n{\n}\n"
  },
  {
    "path": "src/di/Di/Inject.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Di;\n\nuse Attribute;\n\n/**\n * Annotates your class methods into which the Injector should inject values\n *\n * @psalm-immutable\n */\n#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER)]\nfinal class Inject implements InjectInterface\n{\n    /**\n     * @SuppressWarnings(\"PHPMD.BooleanArgumentFlag\")\n     */\n    public function __construct(\n        /**\n         * If true, and the appropriate binding is not found, the Injector will skip injection of this method or field rather than produce an error.\n         */\n        public bool $optional = false\n    ) {\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function isOptional(): bool\n    {\n        return $this->optional;\n    }\n}\n"
  },
  {
    "path": "src/di/Di/InjectInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Di;\n\ninterface InjectInterface\n{\n    /**\n     * Whether or not to use optional injection\n     *\n     * @return bool\n     */\n    public function isOptional();\n}\n"
  },
  {
    "path": "src/di/Di/Named.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Di;\n\nuse Attribute;\n\n/**\n * Annotates named things\n *\n * @psalm-immutable\n */\n#[Attribute(Attribute::TARGET_PARAMETER | Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)]\nfinal class Named\n{\n    public function __construct(public string $value)\n    {\n    }\n}\n"
  },
  {
    "path": "src/di/Di/PostConstruct.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Di;\n\nuse Attribute;\n\n/**\n * PostConstruct\n *\n * The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to\n * perform any initialization. This method MUST be invoked before the class is put into service. The method annotated\n * with PostConstruct MUST be invoked even if the class does not request any resources to be injected. Only one method\n * can be annotated with this annotation. The method on which the PostConstruct annotation is applied MUST fulfill\n * all of the following criteria\n *\n *  - The method MUST NOT have any parameters.\n *  - The return type of the method MUST be void.\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nfinal class PostConstruct\n{\n}\n"
  },
  {
    "path": "src/di/Di/Qualifier.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Di;\n\nuse Attribute;\n\n/**\n * Identifies qualifier annotations\n */\n#[Attribute(Attribute::TARGET_CLASS)]\nfinal class Qualifier\n{\n}\n"
  },
  {
    "path": "src/di/Di/Set.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Di;\n\nuse Attribute;\n\n/**\n * @template T of object\n * @psalm-immutable\n */\n#[Attribute(Attribute::TARGET_PARAMETER | Attribute::TARGET_PROPERTY)]\nfinal class Set\n{\n    /**\n     * @param ''|class-string<T> $interface\n     */\n    public function __construct(public string $interface, public string $name = '')\n    {\n    }\n}\n"
  },
  {
    "path": "src/di/Exception/DirectoryNotWritable.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\nuse RuntimeException;\n\nfinal class DirectoryNotWritable extends RuntimeException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/di/Exception/ExceptionInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\ninterface ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/di/Exception/InvalidContext.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\nuse InvalidArgumentException;\n\nfinal class InvalidContext extends InvalidArgumentException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/di/Exception/InvalidProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\nuse InvalidArgumentException;\n\nfinal class InvalidProvider extends InvalidArgumentException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/di/Exception/InvalidToConstructorNameParameter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\nuse InvalidArgumentException;\n\n/**\n * @see https://github.com/ray-di/Ray.Di#constructor-bindings\n */\nfinal class InvalidToConstructorNameParameter extends InvalidArgumentException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/di/Exception/InvalidType.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\nuse InvalidArgumentException;\n\nfinal class InvalidType extends InvalidArgumentException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/di/Exception/MethodInvocationNotAvailable.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\nfinal class MethodInvocationNotAvailable extends Unbound\n{\n}\n"
  },
  {
    "path": "src/di/Exception/NoHint.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\n/**\n * Exception thrown when a parameter has no type hint and no binding name\n *\n * Message format: ${var} (file:line)\n *\n * This occurs when Ray.Di cannot identify what to inject because\n * the parameter has neither a class/interface type hint nor a #[Named] or #[Qualifier] attribute.\n */\nfinal class NoHint extends Unbound\n{\n}\n"
  },
  {
    "path": "src/di/Exception/NotFound.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\nuse LogicException;\n\nfinal class NotFound extends LogicException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/di/Exception/SetNotFound.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\nuse LogicException;\n\nfinal class SetNotFound extends LogicException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/di/Exception/Unbound.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\nuse Exception;\nuse LogicException;\n\nuse function array_pop;\nuse function array_reverse;\nuse function implode;\nuse function sprintf;\n\n/**\n * Exception thrown when a binding is not found\n *\n * Message format: '{type}-{name}' in file:line ($var)\n *\n * Examples:\n * - \"'FooInterface-' in file.php:10 ($foo)\" - FooInterface with no name\n * - \"'FooInterface-db' in file.php:10 ($foo)\" - FooInterface with name 'db'\n * - \"'-db' in file.php:10 ($foo)\" - no type with name 'db'\n */\nclass Unbound extends LogicException implements ExceptionInterface\n{\n    public function __toString(): string\n    {\n        $messages = [sprintf(\"- %s\\n\", $this->getMessage())];\n        $e = $this->getPrevious();\n        if (! $e instanceof Exception) {\n            return $this->getMainMessage($this);\n        }\n\n        if ($e instanceof self) {\n            return $this->buildMessage($e, $messages) . \"\\n\" . $e->getTraceAsString();\n        }\n\n        return parent::__toString();\n    }\n\n    /**\n     * @param array<int, string> $msg\n     */\n    private function buildMessage(self $e, array $msg): string\n    {\n        $lastE = $e;\n        while ($e instanceof self) {\n            $msg[] = sprintf(\"- %s\\n\", $e->getMessage());\n            $lastE = $e;\n            $e = $e->getPrevious();\n        }\n\n        array_pop($msg);\n        $msg = array_reverse($msg);\n\n        return $this->getMainMessage($lastE) . implode('', $msg);\n    }\n\n    /**\n     * @psalm-pure\n     */\n    private function getMainMessage(self $e): string\n    {\n        return sprintf(\n            \"exception '%s' with message '%s'\\n\",\n            $e::class,\n            $e->getMessage()\n        );\n    }\n}\n"
  },
  {
    "path": "src/di/Exception/Untargeted.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Exception;\n\nfinal class Untargeted extends Unbound\n{\n}\n"
  },
  {
    "path": "src/di/Exception.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nfinal class Exception extends \\Exception\n{\n}\n"
  },
  {
    "path": "src/di/Grapher.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\Compiler;\n\nuse function file_exists;\nuse function spl_autoload_register;\nuse function sprintf;\nuse function str_replace;\n\n/**\n * @psalm-import-type MethodArguments from Types\n * @psalm-import-type ScriptDir from Types\n */\nfinal class Grapher\n{\n    private Container $container;\n\n    /**\n     * @param AbstractModule $module   Binding module\n     * @param ScriptDir      $classDir Class directory\n     */\n    public function __construct(AbstractModule $module, private string $classDir)\n    {\n        $module->install(new AssistedModule());\n        $this->container = $module->getContainer();\n        /** @psalm-suppress InvalidArgument */\n        $this->container->weaveAspects(new Compiler($this->classDir));\n\n        // builtin injection\n        (new Bind($this->container, InjectorInterface::class))->toInstance(new Injector($module));\n    }\n\n    /**\n     * Wakeup\n     */\n    public function __wakeup()\n    {\n        spl_autoload_register(\n            function (string $class): void {\n                $file = sprintf('%s/%s.php', $this->classDir, str_replace('\\\\', '_', $class));\n                if (file_exists($file)) {\n                    include $file; //@codeCoverageIgnore\n                }\n            }\n        );\n    }\n\n    /**\n     * Build an object graph with give constructor parameters\n     *\n     * @param string      $class  class name\n     * @param list<mixed> $params construct parameters (MethodArguments)\n     *\n     * @return mixed\n     */\n    public function newInstanceArgs(string $class, array $params)\n    {\n        return $this->container->getInstanceWithArgs($class, $params);\n    }\n}\n"
  },
  {
    "path": "src/di/InjectableInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface InjectableInterface\n{\n}\n"
  },
  {
    "path": "src/di/InjectionPoint.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\ReflectionClass;\nuse Ray\\Aop\\ReflectionMethod;\nuse Ray\\Di\\Di\\Qualifier;\nuse ReflectionParameter;\n\nuse function assert;\nuse function class_exists;\n\nfinal class InjectionPoint implements InjectionPointInterface\n{\n    private string $pClass;\n\n    /** @var string */\n    private $pFunction;\n\n    /** @var string */\n    private $pName;\n\n    public function __construct(private ReflectionParameter $parameter)\n    {\n        $this->pFunction = $this->parameter->getDeclaringFunction()->name;\n        $class = $this->parameter->getDeclaringClass();\n        $this->pClass = $class instanceof ReflectionClass ? $class->name : '';\n        $this->pName = $this->parameter->name;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getParameter(): ReflectionParameter\n    {\n        return $this->parameter;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getMethod(): ReflectionMethod\n    {\n        $this->parameter = $this->getParameter();\n        $class = $this->parameter->getDeclaringClass();\n        $method = $this->parameter->getDeclaringFunction()->getShortName();\n        assert($class instanceof \\ReflectionClass);\n        assert(class_exists($class->getName()));\n\n        return new ReflectionMethod($class->getName(), $method);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getClass(): ReflectionClass\n    {\n        $this->parameter = $this->getParameter();\n        $class = $this->parameter->getDeclaringClass();\n        assert($class instanceof \\ReflectionClass);\n\n        return new ReflectionClass($class->getName());\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function getQualifiers(): array\n    {\n        $qualifiers = [];\n        $annotations = $this->getMethod()->getAnnotations();\n        foreach ($annotations as $annotation) {\n            $maybeQualifier = (new ReflectionClass($annotation))->getAnnotation(Qualifier::class);\n            if ($maybeQualifier instanceof Qualifier) {\n                $qualifiers[] = $annotation;\n            }\n        }\n\n        return $qualifiers;\n    }\n\n    /**\n     * @return array<string>\n     */\n    public function __serialize(): array\n    {\n        return [$this->pClass, $this->pFunction, $this->pName];\n    }\n\n    /**\n     * @param array<string> $array\n     */\n    public function __unserialize(array $array): void\n    {\n        [$this->pClass, $this->pFunction, $this->pName] = $array;\n    }\n}\n"
  },
  {
    "path": "src/di/InjectionPointInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\ReflectionClass;\nuse Ray\\Aop\\ReflectionMethod;\nuse ReflectionParameter;\n\n/**\n * @psalm-import-type QualifierList from Types\n */\ninterface InjectionPointInterface\n{\n    /**\n     * Return parameter reflection\n     */\n    public function getParameter(): ReflectionParameter;\n\n    /**\n     * Return method reflection\n     */\n    public function getMethod(): ReflectionMethod;\n\n    /**\n     * Return class reflection\n     *\n     * @psalm-return ReflectionClass\n     * @phpstan-return ReflectionClass<object>\n     */\n    public function getClass(): ReflectionClass;\n\n    /**\n     * Return Qualifier annotations\n     *\n     * @return QualifierList\n     */\n    public function getQualifiers(): array;\n}\n"
  },
  {
    "path": "src/di/InjectionPoints.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse ReflectionException;\nuse ReflectionMethod;\n\n/**\n * @psalm-import-type InjectionPointDefinition from Types\n * @psalm-import-type InjectionPointsList from Types\n */\nfinal class InjectionPoints\n{\n    /**\n     * Injection points\n     *\n     * @var InjectionPointsList\n     */\n    private array $points = [];\n\n    /**\n     * @param class-string $class\n     *\n     * @throws ReflectionException\n     */\n    public function __invoke(string $class): SetterMethods\n    {\n        $points = [];\n        foreach ($this->points as $point) {\n            $points[] = $this->getSetterMethod($class, $point);\n        }\n\n        return new SetterMethods($points);\n    }\n\n    public function addMethod(string $method, string $name = Name::ANY): self\n    {\n        $this->points[] = [$method, $name, false];\n\n        return $this;\n    }\n\n    public function addOptionalMethod(string $method, string $name = Name::ANY): self\n    {\n        $this->points[] = [$method, $name, true];\n\n        return $this;\n    }\n\n    /**\n     * @param class-string             $class\n     * @param InjectionPointDefinition $point\n     *\n     * @throws ReflectionException\n     */\n    private function getSetterMethod(string $class, array $point): SetterMethod\n    {\n        $setterMethod = new SetterMethod(new ReflectionMethod($class, $point[0]), new Name($point[1]));\n        if ($point[2]) {\n            $setterMethod->setOptional();\n        }\n\n        return $setterMethod;\n    }\n}\n"
  },
  {
    "path": "src/di/Injector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\Compiler;\nuse Ray\\Di\\Exception\\DirectoryNotWritable;\nuse Ray\\Di\\Exception\\Untargeted;\n\nuse function assert;\nuse function file_exists;\nuse function is_dir;\nuse function is_writable;\nuse function spl_autoload_register;\nuse function sprintf;\nuse function str_replace;\nuse function sys_get_temp_dir;\n\n/**\n * @psalm-import-type BindableInterface from Types\n * @psalm-import-type ModuleList from Types\n * @psalm-import-type ScriptDir from Types\n */\nfinal class Injector implements InjectorInterface\n{\n    /** @var ScriptDir */\n    private readonly string $classDir;\n    private readonly Container $container;\n\n    /**\n     * @param AbstractModule|ModuleList|null $module Module(s)\n     * @param string                         $tmpDir Temp directory for generated class\n     */\n    public function __construct($module = null, string $tmpDir = '')\n    {\n        /** @var ScriptDir $classDir */\n        $classDir = is_dir($tmpDir) ? $tmpDir : sys_get_temp_dir();\n        if (! is_writable($classDir)) {\n            throw new DirectoryNotWritable($classDir); // @codeCoverageIgnore\n        }\n\n        $this->classDir = $classDir;\n        $this->container = (new ContainerFactory())($module, $this->classDir);\n        // Bind injector (built-in bindings)\n        (new Bind($this->container, InjectorInterface::class))->toInstance($this);\n        $this->container->sort();\n    }\n\n    /**\n     * Wakeup\n     */\n    public function __wakeup()\n    {\n        spl_autoload_register(\n            function (string $class): void {\n                $file = sprintf('%s/%s.php', $this->classDir, str_replace('\\\\', '_', $class));\n                if (file_exists($file)) {\n                    include $file; //@codeCoverageIgnore\n                }\n            }\n        );\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function getInstance($interface, $name = Name::ANY)\n    {\n        try {\n            /** @psalm-suppress MixedAssignment */\n            $instance = $this->container->getInstance($interface, $name);\n        } catch (Untargeted) {\n            /** @psalm-var class-string $interface */\n            $this->bind($interface);\n            /** @psalm-suppress MixedAssignment */\n            $instance = $this->getInstance($interface, $name);\n        }\n\n        /** @psalm-suppress MixedReturnStatement */\n        return $instance;\n    }\n\n    /**\n     * @param BindableInterface $class\n     */\n    private function bind(string $class): void\n    {\n        new Bind($this->container, $class);\n        $bound = $this->container->getContainer()[$class . '-' . Name::ANY];\n        assert($bound instanceof Dependency);\n        /** @psalm-suppress InvalidArgument */\n        $this->container->weaveAspect(new Compiler($this->classDir), $bound)->getInstance($class);\n    }\n}\n"
  },
  {
    "path": "src/di/InjectorInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n/**\n * Builds the graphs of objects that make up your application\n *\n * The injector tracks the dependencies for each type and uses bindings to inject them.\n * This is the core of Ray.Di, although you rarely interact with it directly.\n * This \"behind-the-scenes\" operation is what distinguishes dependency injection from its cousin, the service locator pattern.\n */\ninterface InjectorInterface\n{\n    /**\n     * Return object graph\n     *\n     * @param ''|class-string<T> $interface\n     * @param string             $name\n     *\n     * @return ($interface is '' ? mixed : T)\n     *\n     * @template T of object\n     */\n    public function getInstance($interface, $name = Name::ANY);\n}\n"
  },
  {
    "path": "src/di/Instance.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse function gettype;\nuse function is_object;\nuse function is_scalar;\nuse function sprintf;\n\nfinal class Instance implements DependencyInterface, AcceptInterface\n{\n    /**\n     * @param mixed $value\n     */\n    public function __construct(public $value)\n    {\n    }\n\n    public function __toString(): string\n    {\n        if (is_scalar($this->value)) {\n            return sprintf(\n                '(%s) %s',\n                gettype($this->value),\n                (string) $this->value\n            );\n        }\n\n        if (is_object($this->value)) {\n            return '(object) ' . $this->value::class;\n        }\n\n        return '(' . gettype($this->value) . ')';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function register(array &$container, Bind $bind): void\n    {\n        $index = (string) $bind;\n        $container[$index] = $bind->getBound();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function inject(Container $container)\n    {\n        return $this->value;\n    }\n\n    /**\n     * {@inheritdoc}\n     *\n     * @codeCoverageIgnore\n     */\n    public function setScope($scope): void\n    {\n    }\n\n    /** @inheritDoc */\n    public function accept(VisitorInterface $visitor)\n    {\n        return $visitor->visitInstance($this->value);\n    }\n}\n"
  },
  {
    "path": "src/di/Matcher/AssistedInjectMatcher.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Matcher;\n\nuse LogicException;\nuse Ray\\Aop\\AbstractMatcher;\nuse Ray\\Di\\Di\\Assisted;\nuse Ray\\Di\\Di\\InjectInterface;\nuse ReflectionAttribute;\nuse ReflectionClass;\nuse ReflectionMethod;\n\nfinal class AssistedInjectMatcher extends AbstractMatcher\n{\n    /**\n     * {@inheritdoc}\n     *\n     * @codeCoverageIgnore\n     */\n    public function matchesClass(ReflectionClass $class, array $arguments): bool\n    {\n        throw new LogicException('Should not used in class matcher');\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function matchesMethod(ReflectionMethod $method, array $arguments): bool\n    {\n        $params = $method->getParameters();\n        foreach ($params as $param) {\n            /** @var list<ReflectionAttribute> $attributes */\n            $attributes = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF);\n            if (isset($attributes[0])) {\n                return true;\n            }\n\n            /** @var list<ReflectionAttribute> $assisted */\n            $assisted = $param->getAttributes(Assisted::class);\n            if (isset($assisted[0])) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/di/MethodInvocationProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInvocation;\nuse Ray\\Di\\Exception\\MethodInvocationNotAvailable;\n\n/**\n * @implements ProviderInterface<MethodInvocation>\n */\nfinal class MethodInvocationProvider implements ProviderInterface\n{\n    /** @var ?MethodInvocation<object> */\n    private ?MethodInvocation $invocation = null;\n\n    /**\n     * @param MethodInvocation<object> $invocation\n     */\n    public function set(MethodInvocation $invocation): void\n    {\n        $this->invocation = $invocation;\n    }\n\n    /**\n     * @return MethodInvocation<object>\n     */\n    public function get(): MethodInvocation\n    {\n        if ($this->invocation === null) {\n            throw new MethodInvocationNotAvailable();\n        }\n\n        return $this->invocation;\n    }\n}\n"
  },
  {
    "path": "src/di/ModuleString.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse function assert;\nuse function implode;\nuse function serialize;\nuse function sort;\nuse function sprintf;\nuse function unserialize;\n\nuse const PHP_EOL;\n\n/**\n * @psalm-import-type PointcutList from Types\n */\nfinal class ModuleString\n{\n    /**\n     * @param PointcutList $pointcuts\n     */\n    public function __invoke(Container $container, array $pointcuts): string\n    {\n        $log = [];\n        /** @psalm-suppress MixedAssignment */\n        $container = unserialize(serialize($container), ['allowed_classes' => true]);\n        assert($container instanceof Container);\n        $spy = new SpyCompiler();\n        foreach ($container->getContainer() as $dependencyIndex => $dependency) {\n            if ($dependency instanceof Dependency) {\n                $dependency->weaveAspects($spy, $pointcuts);\n            }\n\n            $log[] = sprintf(\n                '%s => %s',\n                $dependencyIndex,\n                (string) $dependency\n            );\n        }\n\n        sort($log);\n\n        return implode(PHP_EOL, $log);\n    }\n}\n"
  },
  {
    "path": "src/di/MultiBinder.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\MultiBinding\\LazyInstance;\nuse Ray\\Di\\MultiBinding\\LazyInterface;\nuse Ray\\Di\\MultiBinding\\LazyProvider;\nuse Ray\\Di\\MultiBinding\\LazyTo;\nuse Ray\\Di\\MultiBinding\\MultiBindings;\n\n/**\n * @psalm-import-type BindableInterface from Types\n * @psalm-import-type LazyBindingList from Types\n */\nfinal class MultiBinder\n{\n    private readonly Container $container;\n\n    /** @var MultiBindings */\n    private $multiBindings;\n    private ?string $key = null;\n\n    private function __construct(AbstractModule $module, private readonly string $interface)\n    {\n        $this->container = $module->getContainer();\n        $this->multiBindings = $this->container->multiBindings;\n        $this->container->add(\n            (new Bind($this->container, MultiBindings::class))->toInstance($this->multiBindings)\n        );\n    }\n\n    public static function newInstance(AbstractModule $module, string $interface): self\n    {\n        return new self($module, $interface);\n    }\n\n    public function addBinding(?string $key = null): self\n    {\n        $this->key = $key;\n\n        return $this;\n    }\n\n    public function setBinding(?string $key = null): self\n    {\n        $this->container->multiBindings->exchangeArray([]);\n        $this->key = $key;\n\n        return $this;\n    }\n\n    /**\n     * @param class-string $class\n     */\n    public function to(string $class): void\n    {\n        $this->bind(new LazyTo($class), $this->key);\n    }\n\n    /**\n     * @param class-string<ProviderInterface<T>> $provider\n     *\n     * @template T of mixed\n     */\n    public function toProvider(string $provider): void\n    {\n        $this->bind(new LazyProvider($provider), $this->key);\n    }\n\n    /**\n     * @param mixed $instance\n     */\n    public function toInstance($instance): void\n    {\n        $this->bind(new LazyInstance($instance), $this->key);\n    }\n\n    private function bind(LazyInterface $lazy, ?string $key): void\n    {\n        $bindings = [];\n        if ($this->multiBindings->offsetExists($this->interface)) {\n            $bindings = $this->multiBindings->offsetGet($this->interface);\n        }\n\n        if ($key === null) {\n            $bindings[] = $lazy;\n            $this->multiBindings->offsetSet($this->interface, $bindings);\n\n            return;\n        }\n\n        $bindings[$key] = $lazy;\n        $this->multiBindings->offsetSet($this->interface, $bindings);\n    }\n}\n"
  },
  {
    "path": "src/di/MultiBinding/LazyInstance.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\MultiBinding;\n\nuse Ray\\Di\\InjectorInterface;\n\n/**\n * @template T of mixed\n * @psalm-immutable\n */\nfinal class LazyInstance implements LazyInterface\n{\n    /**\n     * @param T $instance\n     */\n    public function __construct(private $instance)\n    {\n    }\n\n    /**\n     * @return T\n     */\n    public function __invoke(InjectorInterface $injector)\n    {\n        unset($injector);\n\n        return $this->instance;\n    }\n}\n"
  },
  {
    "path": "src/di/MultiBinding/LazyInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\MultiBinding;\n\nuse Ray\\Di\\InjectorInterface;\n\ninterface LazyInterface\n{\n    /**\n     * @return mixed\n     */\n    public function __invoke(InjectorInterface $injector);\n}\n"
  },
  {
    "path": "src/di/MultiBinding/LazyProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\MultiBinding;\n\nuse Ray\\Di\\InjectorInterface;\nuse Ray\\Di\\ProviderInterface;\n\n/**\n * @template T of ProviderInterface\n */\nfinal class LazyProvider implements LazyInterface\n{\n    /**\n     * @param class-string<T> $class\n     */\n    public function __construct(private string $class)\n    {\n    }\n\n    /**\n     * @return mixed\n     */\n    public function __invoke(InjectorInterface $injector)\n    {\n        $provider = $injector->getInstance($this->class);\n\n        return $provider->get();\n    }\n}\n"
  },
  {
    "path": "src/di/MultiBinding/LazyTo.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\MultiBinding;\n\nuse Ray\\Di\\InjectorInterface;\n\n/**\n * @template T of object\n */\nfinal class LazyTo implements LazyInterface\n{\n    /**\n     * @param class-string<T> $class\n     */\n    public function __construct(private string $class)\n    {\n    }\n\n    /**\n     * @return T\n     */\n    public function __invoke(InjectorInterface $injector)\n    {\n        return $injector->getInstance($this->class);\n    }\n}\n"
  },
  {
    "path": "src/di/MultiBinding/Map.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\MultiBinding;\n\nuse ArrayAccess;\nuse Countable;\nuse Generator;\nuse Iterator;\nuse IteratorAggregate;\nuse LogicException;\nuse Ray\\Di\\InjectorInterface;\nuse ReturnTypeWillChange;\n\nuse function array_key_exists;\nuse function count;\n\n/**\n * @template T\n * @implements ArrayAccess<array-key, T>\n * @implements IteratorAggregate<string, mixed>\n */\nfinal class Map implements IteratorAggregate, ArrayAccess, Countable\n{\n    /**\n     * @param array<array-key, LazyInterface> $lazies\n     */\n    public function __construct(private array $lazies, private readonly InjectorInterface $injector)\n    {\n    }\n\n    /**\n     * @param array-key $offset\n     *\n     * @codeCoverageIgnore\n     */\n    #[ReturnTypeWillChange]\n    public function offsetExists($offset): bool\n    {\n        return array_key_exists($offset, $this->lazies);\n    }\n\n    /**\n     * @param array-key $offset\n     *\n     * @return T\n     *\n     * @codeCoverageIgnore\n     */\n    #[ReturnTypeWillChange]\n    public function offsetGet($offset)\n    {\n        /** @var T $instance */\n        $instance = ($this->lazies[$offset])($this->injector);\n\n        return $instance;\n    }\n\n    /**\n     * @param array-key $offset\n     * @param mixed     $value\n     *\n     * @return never\n     *\n     * @codeCoverageIgnore\n     */\n    #[ReturnTypeWillChange]\n    public function offsetSet($offset, $value): void\n    {\n        unset($offset, $value);\n\n        throw new LogicException();\n    }\n\n    /**\n     * @param array-key $offset\n     *\n     * @return never\n     *\n     * @codeCoverageIgnore\n     */\n    #[ReturnTypeWillChange]\n    public function offsetUnset($offset): void\n    {\n        unset($offset);\n\n        throw new LogicException();\n    }\n\n    /** @return Generator<array-key, T, void, void> */\n    public function getIterator(): Iterator\n    {\n        foreach ($this->lazies as $key => $lazy) {\n            /** @var T $object */\n            $object = ($lazy)($this->injector);\n\n            yield $key => $object;\n        }\n    }\n\n    public function count(): int\n    {\n        return count($this->lazies);\n    }\n}\n"
  },
  {
    "path": "src/di/MultiBinding/MapProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\MultiBinding;\n\nuse Ray\\Di\\Di\\Set;\nuse Ray\\Di\\Exception\\SetNotFound;\nuse Ray\\Di\\InjectionPointInterface;\nuse Ray\\Di\\InjectorInterface;\nuse Ray\\Di\\ProviderInterface;\n\n/**\n * @implements ProviderInterface<Map>\n */\nfinal class MapProvider implements ProviderInterface\n{\n    public function __construct(private readonly InjectionPointInterface $ip, private MultiBindings $multiBindings, private readonly InjectorInterface $injector)\n    {\n    }\n\n    /**\n     * @return Map<mixed>\n     */\n    public function get(): Map\n    {\n        $param = $this->ip->getParameter();\n        $setAttribute = $param->getAttributes(Set::class);\n        if (! isset($setAttribute[0])) {\n            throw new SetNotFound((string) $param);\n        }\n\n        /** @var Set<object> $set */\n        $set = $setAttribute[0]->newInstance();\n\n        /** @var array<string, LazyTo<object>> $lazies */\n        $lazies = $this->multiBindings[$set->interface];\n\n        return new Map($lazies, $this->injector);\n    }\n}\n"
  },
  {
    "path": "src/di/MultiBinding/MultiBindingModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\MultiBinding;\n\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Types;\n\n/**\n * @psalm-import-type BindableInterface from Types\n */\nfinal class MultiBindingModule extends AbstractModule\n{\n    protected function configure(): void\n    {\n        $this->bind(MultiBindings::class);\n        $this->bind(Map::class)->toProvider(MapProvider::class);\n    }\n}\n"
  },
  {
    "path": "src/di/MultiBinding/MultiBindings.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\MultiBinding;\n\nuse ArrayObject;\nuse Ray\\Di\\Types;\n\nuse function array_merge_recursive;\n\n/**\n * @psalm-import-type LazyBindingList from Types\n * @extends ArrayObject<string, LazyBindingList>\n */\nfinal class MultiBindings extends ArrayObject\n{\n    public function merge(self $multiBindings): void\n    {\n        $this->exchangeArray(\n            array_merge_recursive($this->getArrayCopy(), $multiBindings->getArrayCopy())\n        );\n    }\n}\n"
  },
  {
    "path": "src/di/Name.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Named;\nuse Ray\\Di\\Di\\Qualifier;\nuse ReflectionAttribute;\nuse ReflectionClass;\nuse ReflectionException;\nuse ReflectionMethod;\nuse ReflectionParameter;\n\nuse function class_exists;\nuse function is_string;\nuse function preg_match;\n\n/**\n * @psalm-import-type ParameterNameMapping from Types\n */\nfinal class Name\n{\n    /**\n     * 'Unnamed' name\n     */\n    public const ANY = '';\n\n    private string $name = '';\n\n    /**\n     * Named database\n     *\n     * format: array<varName, NamedName>\n     *\n     * @var ParameterNameMapping\n     */\n    private array $names = [];\n\n    /**\n     * @param string|ParameterNameMapping $name\n     */\n    public function __construct(string|array $name)\n    {\n        if (is_string($name)) {\n            $this->setName($name);\n\n            return;\n        }\n\n        $this->names = $name;\n    }\n\n    /**\n     * Create instance from PHP8 attributes\n     *\n     * psalm does not know ReflectionAttribute?? PHPStan produces no type error here.\n     */\n    public static function withAttributes(ReflectionMethod $method): ?self\n    {\n        $params = $method->getParameters();\n        $names = [];\n        foreach ($params as $param) {\n            /** @var array<ReflectionAttribute> $attributes */\n            $attributes = $param->getAttributes();\n            if ($attributes) {\n                $name = self::getName($attributes);\n                $names[$param->name] = $name;\n            }\n        }\n\n        // Backward compatibility: Get parameter qualifiers from method-level attribute\n        if ($names === []) {\n            /** @psalm-suppress DeprecatedClass */\n            $names = BcParameterQualifier::getNames($method);\n        }\n\n        if ($names !== []) {\n            return new self($names);\n        }\n\n        return null;\n    }\n\n    /**\n     * @param non-empty-array<ReflectionAttribute> $attributes\n     *\n     * @throws ReflectionException\n     *\n     * @psalm-suppress MixedAssignment\n     * @psalm-suppress MixedArgument\n     */\n    private static function getName(array $attributes): string\n    {\n        $refAttribute = $attributes[0];\n        $attribute = $refAttribute->newInstance();\n        if ($attribute instanceof Named) {\n            return $attribute->value;\n        }\n\n        $isQualifier = (bool) (new ReflectionClass($attribute))->getAttributes(Qualifier::class);\n        if ($isQualifier) {\n            return $attribute::class;\n        }\n\n        return '';\n    }\n\n    public function __invoke(ReflectionParameter $parameter): string\n    {\n        // single variable named binding\n        if ($this->name) {\n            return $this->name;\n        }\n\n        $parameterName = $parameter->name;\n\n        // multiple variable named binding\n        return $this->names[$parameterName] ?? $this->names[self::ANY] ?? self::ANY;\n    }\n\n    private function setName(string $name): void\n    {\n        // annotation\n        if (class_exists($name, false)) {\n            $this->name = $name;\n\n            return;\n        }\n\n        // single name\n        // @Named(name)\n        if ($name === self::ANY || preg_match('/^\\w+$/', $name)) {\n            $this->name = $name;\n\n            return;\n        }\n\n        // name list (backward compatibility)\n        // @Named(varName1=name1, varName2=name2) or toConstructor string format\n        /** @psalm-suppress DeprecatedClass */\n        $this->names = BcStringParser::parse($name);\n    }\n}\n"
  },
  {
    "path": "src/di/NewInstance.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\Bind as AopBind;\nuse Ray\\Aop\\WeavedInterface;\nuse ReflectionClass;\nuse ReflectionException;\nuse Stringable;\n\nuse function assert;\n\n/**\n * @psalm-import-type MethodArguments from Types\n */\nfinal class NewInstance implements Stringable\n{\n    /** @var class-string */\n    private string $class;\n\n    /** @var SetterMethods */\n    private $setterMethods;\n    private ?Arguments $arguments = null;\n    private ?AspectBind $bind = null;\n\n    /**\n     * @phpstan-param ReflectionClass<object> $class\n     */\n    public function __construct(\n        ReflectionClass $class,\n        SetterMethods $setterMethods,\n        ?Name $constructorName = null\n    ) {\n        $constructorName = $constructorName ?: new Name(Name::ANY);\n        $this->class = $class->getName();\n        $constructor = $class->getConstructor();\n        if ($constructor) {\n            $this->arguments = new Arguments($constructor, $constructorName);\n        }\n\n        $this->setterMethods = $setterMethods;\n    }\n\n    /**\n     * @throws ReflectionException\n     */\n    public function __invoke(Container $container): object\n    {\n        $reflection = new ReflectionClass($this->class);\n        /** @psalm-suppress MixedMethodCall */\n        $instance = $this->arguments instanceof Arguments ? $reflection->newInstanceArgs($this->arguments->inject($container)) : new $this->class();\n\n        return $this->postNewInstance($container, $instance);\n    }\n\n    /**\n     * @return class-string\n     */\n    public function __toString(): string\n    {\n        return $this->class;\n    }\n\n    /**\n     * @param MethodArguments $params\n     *\n     * @throws ReflectionException\n     */\n    public function newInstanceArgs(Container $container, array $params): object\n    {\n        $instance = (new ReflectionClass($this->class))->newInstanceArgs($params);\n\n        return $this->postNewInstance($container, $instance);\n    }\n\n    /**\n     * @param class-string $class\n     */\n    public function weaveAspects(string $class, AopBind $bind): void\n    {\n        $this->class = $class;\n        $this->bind = new AspectBind($bind);\n    }\n\n    public function accept(VisitorInterface $visitor): void\n    {\n        $visitor->visitNewInstance(\n            $this->class,\n            $this->setterMethods,\n            $this->arguments,\n            $this->bind\n        );\n    }\n\n    private function postNewInstance(Container $container, object $instance): object\n    {\n        $this->enableAop($instance, $container);\n\n        // setter injection\n        ($this->setterMethods)($instance, $container);\n\n        return $instance;\n    }\n\n    public function enableAop(object $instance, Container $container): void\n    {\n        if (! $this->bind instanceof AspectBind) {\n            return;\n        }\n\n        assert($instance instanceof WeavedInterface);\n\n        $instance->_setBindings($this->bind->inject($container));  // Ray.Aop ^2.18\n    }\n}\n"
  },
  {
    "path": "src/di/NullDependency.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n/**\n * @codeCoverageIgnore\n */\nfinal class NullDependency implements DependencyInterface\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function __toString(): string\n    {\n        return '';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function inject(Container $container): void\n    {\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function register(array &$container, Bind $bind): void\n    {\n        $container[(string) $bind] = $bind->getBound();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function setScope($scope): void\n    {\n    }\n}\n"
  },
  {
    "path": "src/di/NullModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nfinal class NullModule extends AbstractModule\n{\n    protected function configure(): void\n    {\n    }\n}\n"
  },
  {
    "path": "src/di/NullObjectDependency.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Koriym\\NullObject\\NullObject;\nuse ReflectionClass;\n\nuse function assert;\nuse function is_dir;\n\n/**\n * @codeCoverageIgnore\n */\nfinal class NullObjectDependency implements DependencyInterface\n{\n    /**\n     * @param class-string $interface\n     */\n    public function __construct(private string $interface)\n    {\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function __toString(): string\n    {\n        return '';\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function inject(Container $container): null\n    {\n        return null;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function register(array &$container, Bind $bind): void\n    {\n        $container[(string) $bind] = $bind->getBound();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function setScope($scope): void\n    {\n    }\n\n    public function toNull(string $scriptDir): Dependency\n    {\n        assert(is_dir($scriptDir));\n        $nullObject = new NullObject();\n        $class = $nullObject->save($this->interface, $scriptDir);\n\n        return new Dependency(new NewInstance(new ReflectionClass($class), new SetterMethods([])));\n    }\n}\n"
  },
  {
    "path": "src/di/ProviderInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n/**\n * @template T of mixed\n */\ninterface ProviderInterface\n{\n    /**\n     * @return T\n     */\n    public function get();\n}\n"
  },
  {
    "path": "src/di/ProviderProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Set;\n\n/**\n * @implements ProviderInterface<mixed>\n * @template T of object\n */\nfinal class ProviderProvider implements ProviderInterface\n{\n    /** @param Set<T> $set */\n    public function __construct(private InjectorInterface $injector, private Set $set)\n    {\n    }\n\n    /** @return mixed */\n    public function get()\n    {\n        return $this->injector->getInstance($this->set->interface, $this->set->name);\n    }\n}\n"
  },
  {
    "path": "src/di/ProviderSetModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nfinal class ProviderSetModule extends AbstractModule\n{\n    protected function configure(): void\n    {\n        $this->bind(ProviderInterface::class)->toProvider(ProviderSetProvider::class);\n    }\n}\n"
  },
  {
    "path": "src/di/ProviderSetProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Set;\nuse Ray\\Di\\Exception\\SetNotFound;\n\n/**\n * @implements ProviderInterface<mixed>\n * @template T of object\n */\nfinal class ProviderSetProvider implements ProviderInterface\n{\n    public function __construct(private InjectionPointInterface $ip, private InjectorInterface $injector)\n    {\n    }\n\n    /** @phpstan-return ProviderProvider<object> */\n    public function get(): ProviderProvider\n    {\n        $param = $this->ip->getParameter();\n        $setAttribute = $param->getAttributes(Set::class);\n        if (! isset($setAttribute[0])) {\n            throw new SetNotFound((string) $this->ip->getParameter());\n        }\n\n        $set = $setAttribute[0]->newInstance();\n\n        return new ProviderProvider($this->injector, $set);\n    }\n}\n"
  },
  {
    "path": "src/di/Scope.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nfinal class Scope\n{\n    /**\n     * Singleton scope\n     */\n    public const SINGLETON = 'Singleton';\n\n    /**\n     * Prototype scope\n     */\n    public const PROTOTYPE = 'Prototype';\n}\n"
  },
  {
    "path": "src/di/SetContextInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface SetContextInterface\n{\n    /**\n     * Set provider context\n     *\n     * @param string $context\n     *\n     * @return void\n     */\n    public function setContext($context);\n}\n"
  },
  {
    "path": "src/di/SetterMethod.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Exception;\nuse LogicException;\nuse Ray\\Di\\Exception\\Unbound;\nuse ReflectionMethod;\n\nuse function call_user_func_array;\nuse function is_callable;\n\nfinal class SetterMethod implements AcceptInterface\n{\n    private readonly string $method;\n    private readonly Arguments $arguments;\n\n    /**\n     * Is optional binding ?\n     */\n    private bool $isOptional = false;\n\n    public function __construct(ReflectionMethod $method, Name $name)\n    {\n        $this->method = $method->name;\n        $this->arguments = new Arguments($method, $name);\n    }\n\n    /**\n     * @param object $instance\n     *\n     * @throws Unbound\n     * @throws Exception\n     */\n    public function __invoke($instance, Container $container): void\n    {\n        try {\n            $parameters = $this->arguments->inject($container);\n        } catch (Unbound $unbound) {\n            if ($this->isOptional) {\n                return;\n            }\n\n            throw $unbound;\n        }\n\n        $callable = [$instance, $this->method];\n        if (! is_callable($callable)) {\n            throw new LogicException(); // @codeCoverageIgnore\n        }\n\n        call_user_func_array($callable, $parameters);\n    }\n\n    public function setOptional(): void\n    {\n        $this->isOptional = true;\n    }\n\n    /** @inheritDoc */\n    public function accept(VisitorInterface $visitor): void\n    {\n        try {\n            $visitor->visitSetterMethod($this->method, $this->arguments);\n        } catch (Unbound $unbound) {\n            if ($this->isOptional) {\n                // Return when no dependency given and @Inject(optional=true) annotated to setter method.\n                return;\n            }\n\n            // Throw exception when no dependency given and @Inject(optional=false) annotated to setter method.\n            throw $unbound;\n        }\n    }\n}\n"
  },
  {
    "path": "src/di/SetterMethods.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Exception;\n\n/**\n * @psalm-import-type SetterMethodsList from Types\n */\nfinal class SetterMethods implements AcceptInterface\n{\n    /**\n     * @param SetterMethodsList $setterMethods\n     */\n    public function __construct(private array $setterMethods)\n    {\n    }\n\n    /**\n     * @throws Exception\n     */\n    public function __invoke(object $instance, Container $container): void\n    {\n        foreach ($this->setterMethods as $setterMethod) {\n            ($setterMethod)($instance, $container);\n        }\n    }\n\n    public function add(?SetterMethod $setterMethod = null): void\n    {\n        if (! $setterMethod instanceof SetterMethod) {\n            return;\n        }\n\n        $this->setterMethods[] = $setterMethod;\n    }\n\n    /** @inheritDoc */\n    public function accept(VisitorInterface $visitor): void\n    {\n        $visitor->visitSetterMethods($this->setterMethods);\n    }\n}\n"
  },
  {
    "path": "src/di/SpyCompiler.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse LogicException;\nuse Ray\\Aop\\BindInterface;\nuse Ray\\Aop\\CompilerInterface;\n\nuse function array_keys;\nuse function implode;\nuse function method_exists;\nuse function sprintf;\n\n/**\n * @codeCoverageIgnore\n */\nfinal class SpyCompiler implements CompilerInterface\n{\n    /**\n     * {@inheritDoc}\n     *\n     * @phpstan-return never\n     *\n     * @psalm-suppress InvalidReturnType\n     * @template T of object\n     */\n    public function newInstance(string $class, array $args, BindInterface $bind)\n    {\n        // never called\n        throw new LogicException('SpyCompiler::newInstance() should never be called');\n    }\n\n    /**\n     * Return \"logging\" class name\n     *\n     * Dummy classes are used for logging and don't really exist.\n     * So the code breaks the QA rules as shown below.\n     * NOTE: psalm-suppress is acceptable here for dummy/logging infrastructure\n     *\n     * @psalm-suppress MoreSpecificReturnType\n     * @psalm-suppress LessSpecificReturnStatement\n     */\n    public function compile(string $class, BindInterface $bind): string\n    {\n        if ($this->hasNoBinding($class, $bind)) {\n            return $class;\n        }\n\n        return $class . $this->getInterceptors($bind); // @phpstan-ignore-line\n    }\n\n    /**\n     * @param class-string $class\n     */\n    private function hasNoBinding(string $class, BindInterface $bind): bool\n    {\n        $hasMethod = $this->hasBoundMethod($class, $bind);\n\n        return ! $bind->getBindings() && ! $hasMethod;\n    }\n\n    /**\n     * @param class-string $class\n     */\n    private function hasBoundMethod(string $class, BindInterface $bind): bool\n    {\n        $bindingMethods = array_keys($bind->getBindings());\n        $hasMethod = false;\n        foreach ($bindingMethods as $bindingMethod) {\n            if (method_exists($class, $bindingMethod)) {\n                $hasMethod = true;\n            }\n        }\n\n        return $hasMethod;\n    }\n\n    private function getInterceptors(BindInterface $bind): string\n    {\n        $bindings = $bind->getBindings();\n        if (! $bindings) {\n            return ''; // @codeCoverageIgnore\n        }\n\n        $log = ' (aop)';\n        foreach ($bindings as $method => $interceptors) {\n            /**\n             * @phpstan-var array<string> $interceptors\n             * @psalm-ignore-var\n             */\n            $log .= sprintf(\n                ' +%s(%s)',\n                $method,\n                implode(', ', $interceptors)\n            );\n        }\n\n        return $log;\n    }\n}\n"
  },
  {
    "path": "src/di/Types.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\Pointcut;\n\n/**\n * Ray.Di Domain Types for Psalm\n *\n * This file contains Psalm type definitions for the Ray.Di dependency injection framework.\n * These types enhance static analysis and provide better IDE support.\n *\n * Container and Registry Types\n *\n * @psalm-type DependencyContainer = array<non-empty-string, DependencyInterface>\n * @psalm-type DependencyIndex = non-empty-string\n * @psalm-type PointcutList array<int, Pointcut>\n * @psalm-type BindingName = non-empty-string\n * @psalm-type BindableInterface = class-string|''\n * @psalm-type ConstructorNameMapping = array<non-empty-string, non-empty-string>\n * @psalm-type ParameterNameMapping = array<string, string>\n * @psalm-type NamedParameterString = non-empty-string\n * @psalm-type ScriptDir = non-empty-string\n *\n * Enhanced Injection and Argument Types\n * @psalm-type InjectableValue object|scalar|array<array-key, (object|scalar|null)>|null\n * @psalm-type InjectionPointDefinition = array{0: string, 1: string, 2: bool}\n * @psalm-type InjectionPointsList = list<InjectionPointDefinition>\n * @psalm-type MethodArguments = list<mixed>\n * @psalm-type ArgumentSerializationData = array{0: DependencyIndex, 1: bool, 2: string, 3: string, 4: string, 5: array{0: string, 1: string, 2: string}}\n * @psalm-type UnboundTypeList = list<'bool'|'int'|'float'|'string'|'array'|'resource'|'callable'|'iterable'|'object'|'mixed'>\n * @psalm-type QualifierList = array<object>\n *\n * Scope and Lifecycle Types\n * @psalm-type ScopeType = Scope::SINGLETON|Scope::PROTOTYPE\n * @psalm-type ProviderContext = string\n *\n * MultiBinding Types\n * @psalm-type MultiBindingMap = array<string, non-empty-array<array-key, MultiBinding\\LazyInterface>>\n * @psalm-type LazyBindingList = non-empty-array<array-key, MultiBinding\\LazyInterface>\n *\n * AOP and Aspect Types\n * @psalm-type MethodInterceptorBindings array<non-empty-string, list<MethodInterceptor>>\n * @psalm-type InterceptorClassList array<class-string<MethodInterceptor>>\n * @psalm-type VisitorResult = object|array<array-key, mixed>|null\n *\n * Reflection and Metadata Types\n * @psalm-type ReflectionMethodReference = array{0: string, 1: string, 2: string}\n * @psalm-type DependencyMeta = string\n *\n * Exception and Error Types\n * @psalm-type DiException = Exception\\Unbound|Exception\\Untargeted|Exception\\NotFound|Exception\\InvalidProvider|Exception\\InvalidType\n *\n * Annotation Types\n * @psalm-type AnnotationType = Di\\Named|Di\\Inject|Di\\Qualifier|Di\\PostConstruct|Di\\Assisted|Di\\Set<object>\n * @psalm-type DependencyImplementation = Dependency|DependencyProvider|Instance|NullDependency|NullObjectDependency\n * @psalm-type LazyImplementation = MultiBinding\\LazyInstance<mixed>|MultiBinding\\LazyProvider<ProviderInterface<mixed>>|MultiBinding\\LazyTo<object>\n *\n * Core Component Types\n * @psalm-type SetterMethodsList = array<SetterMethod>\n * @psalm-type ArgumentsList = array<Argument>\n *\n * Domain-Specific Array Types\n * @psalm-type ModuleList = non-empty-array<AbstractModule>\n * @psalm-type NamedArguments = array<string, InjectableValue>\n */\nfinal class Types\n{\n}\n"
  },
  {
    "path": "src/di/Untarget.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\ReflectionClass;\n\nfinal class Untarget\n{\n    /**\n     * @phpstan-var ReflectionClass<object>\n     * @psalm-var ReflectionClass\n     */\n    private readonly ReflectionClass $class;\n    private string $scope = Scope::PROTOTYPE;\n\n    /**\n     * @param class-string $class\n     */\n    public function __construct(string $class)\n    {\n        $this->class = new ReflectionClass($class);\n    }\n\n    /**\n     * Bind untargeted binding\n     */\n    public function __invoke(Container $container, Bind $bind): void\n    {\n        $bound = (new DependencyFactory())->newAnnotatedDependency($this->class);\n        $bound->setScope($this->scope);\n\n        $bind->setBound($bound);\n        $container->add($bind);\n    }\n\n    public function setScope(string $scope): void\n    {\n        $this->scope = $scope;\n    }\n}\n"
  },
  {
    "path": "src/di/VisitorInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\Bind as AopBind;\nuse ReflectionParameter;\n\n/**\n * Visits a Dependency\n *\n * @psalm-import-type SetterMethodsList from Types\n * @psalm-import-type ArgumentsList from Types\n */\ninterface VisitorInterface\n{\n    /**\n     * Visits a Dependency\n     *\n     * @return mixed|void\n     */\n    public function visitDependency(NewInstance $newInstance, ?string $postConstruct, bool $isSingleton);\n\n    /**\n     * Visits a Provider\n     *\n     * @return mixed|void\n     */\n    public function visitProvider(Dependency $dependency, string $context, bool $isSingleton);\n\n    /**\n     * Visits an Instance\n     *\n     * @param mixed $value\n     *\n     * @return mixed|void\n     */\n    public function visitInstance($value);\n\n    /**\n     * Visits an AspectBind\n     *\n     * @return mixed|void\n     */\n    public function visitAspectBind(AopBind $aopBind);\n\n    /**\n     * Visits a New Instance\n     *\n     * @return mixed|void\n     */\n    public function visitNewInstance(\n        string $class,\n        SetterMethods $setterMethods,\n        ?Arguments $arguments,\n        ?AspectBind $bind\n    );\n\n    /**\n     * Visits Setter Methods\n     *\n     * @param SetterMethodsList $setterMethods\n     *\n     * @return mixed|void\n     */\n    public function visitSetterMethods(array $setterMethods);\n\n    /**\n     * Visits a Setter Method\n     *\n     * @return mixed|void\n     */\n    public function visitSetterMethod(string $method, Arguments $arguments);\n\n    /**\n     * Visits Arguments\n     *\n     * @param ArgumentsList $arguments\n     *\n     * @return mixed|void\n     */\n    public function visitArguments(array $arguments);\n\n    /**\n     * Visits an Argument\n     *\n     * @param mixed $defaultValue\n     *\n     * @return mixed|void\n     */\n    public function visitArgument(\n        string $index,\n        bool $isDefaultAvailable,\n        $defaultValue,\n        ReflectionParameter $parameter\n    );\n}\n"
  },
  {
    "path": "src-deprecated/di/BcParameterQualifier.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\InjectInterface;\nuse Ray\\Di\\Di\\Qualifier;\nuse ReflectionAttribute;\nuse ReflectionClass;\nuse ReflectionMethod;\n\nuse function count;\n\n/**\n * Backward compatible parameter qualifier for single-parameter methods\n *\n * Automatically applies method-level Qualifier attributes to parameters when:\n * - Method has exactly one parameter\n * - Parameter has no explicit qualifier\n * - For constructors: Method has a Qualifier attribute (InjectInterface is implicit)\n * - For setters: Method has an attribute implementing both InjectInterface and Qualifier\n *\n * This behavior is deprecated for the following reasons:\n * - Violates Single Responsibility Principle (one attribute serving dual purposes)\n * - Creates fragility when refactoring (adding parameters changes behavior)\n * - Reduces code clarity (implicit rather than explicit)\n *\n * @deprecated Use explicit separation: #[Inject] at method level, Qualifier at parameter level\n *\n * Recommended migration:\n *   OLD (implicit):\n *     #[FakeLogDbInject]\n *     public function setDb(ExtendedPdoInterface $pdo) { }\n *\n *   NEW (explicit):\n *     #[Inject]\n *     public function setDb(#[FakeLogDb] ExtendedPdoInterface $pdo) { }\n *\n * @internal\n */\nfinal class BcParameterQualifier\n{\n    /**\n     * Get parameter qualifier names from method-level attribute if applicable\n     *\n     * Returns parameter name mapping if:\n     * 1. Method has exactly one parameter\n     * 2. Parameter has no qualifier attribute\n     * 3. For setters: Method has an attribute implementing both InjectInterface and Qualifier\n     * 4. For constructors: Method has a Qualifier attribute (InjectInterface is implicit)\n     *\n     * @param ReflectionMethod $method The method to analyze\n     *\n     * @return array<string, string> Parameter name to qualifier mapping (empty if not applicable)\n     */\n    public static function getNames(ReflectionMethod $method): array\n    {\n        $params = $method->getParameters();\n\n        // Only for single-parameter methods\n        if (count($params) !== 1) {\n            return [];\n        }\n\n        $qualifier = self::getQualifier($method);\n        if ($qualifier === '') {\n            return [];\n        }\n\n        return [$params[0]->name => $qualifier];\n    }\n\n    /**\n     * Get parameter qualifier from method-level attribute if applicable\n     *\n     * Returns the qualifier name if:\n     * 1. Method has exactly one parameter\n     * 2. Parameter has no qualifier attribute\n     * 3. For setters: Method has an attribute implementing both InjectInterface and Qualifier\n     * 4. For constructors: Method has a Qualifier attribute (InjectInterface is implicit)\n     *\n     * @param ReflectionMethod $method The method to analyze\n     *\n     * @return string The qualifier class name, or empty string if not applicable\n     */\n    private static function getQualifier(ReflectionMethod $method): string\n    {\n        $params = $method->getParameters();\n\n        // Only for single-parameter methods\n        if (count($params) !== 1) {\n            return '';\n        }\n\n        $param = $params[0];\n\n        // Check if parameter already has a qualifier\n        if (self::hasParameterQualifier($param->getAttributes())) {\n            return '';\n        }\n\n        $isConstructor = $method->name === '__construct';\n\n        // Check method-level attributes for Qualifier\n        $methodAttributes = $method->getAttributes();\n        foreach ($methodAttributes as $attr) {\n            $instance = $attr->newInstance();\n            $attrClass = new ReflectionClass($attr->getName());\n            $qualifierAttr = $attrClass->getAttributes(Qualifier::class);\n\n            // Skip if not a Qualifier\n            if ($qualifierAttr === []) {\n                continue;\n            }\n\n            // For constructors: Qualifier alone is sufficient (InjectInterface is implicit)\n            if ($isConstructor) {\n                return $attr->getName();\n            }\n\n            // For setters: Must also implement InjectInterface\n            if ($instance instanceof InjectInterface) {\n                return $attr->getName();\n            }\n        }\n\n        return '';\n    }\n\n    /**\n     * Check if parameter already has a qualifier attribute\n     *\n     * @param array<ReflectionAttribute> $attributes\n     */\n    private static function hasParameterQualifier(array $attributes): bool\n    {\n        foreach ($attributes as $attr) {\n            $attrClass = new ReflectionClass($attr->getName());\n\n            // Check for Qualifier marker\n            if ($attrClass->getAttributes(Qualifier::class) !== []) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "src-deprecated/di/BcStringParser.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse function explode;\nuse function substr;\nuse function trim;\n\n/**\n * Backward compatible string format parser for toConstructor() method\n *\n * Parses \"key=value,key=value\" string format for backward compatibility.\n * This format is deprecated. Use parameter-level attributes or array format instead.\n *\n * @deprecated Use parameter-level attributes (#[Named('name')]) or array format (['param' => 'name'])\n *\n * @internal\n */\nfinal class BcStringParser\n{\n    /**\n     * Parse \"key=value,key=value\" format\n     *\n     * @return array<string, string>\n     *\n     * @psalm-pure\n     */\n    public static function parse(string $name): array\n    {\n        $names = [];\n        $keyValues = explode(',', $name);\n        foreach ($keyValues as $keyValue) {\n            $exploded = explode('=', $keyValue, 2);\n            if (isset($exploded[1])) {\n                [$key, $value] = $exploded;\n                if (isset($key[0]) && $key[0] === '$') {\n                    $key = substr($key, 1);\n                }\n\n                $trimmedKey = trim($key);\n                if ($trimmedKey === '') {\n                    continue;\n                }\n\n                $names[$trimmedKey] = trim($value);\n            }\n        }\n\n        return $names;\n    }\n}\n"
  },
  {
    "path": "src-deprecated/di/EmptyModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n/**\n * @deprecated User NullModule instead\n */\nclass EmptyModule extends AbstractModule\n{\n    /**\n     * {@inheritdoc}\n     */\n    protected function configure() : void\n    {\n    }\n}\n"
  },
  {
    "path": "src-deprecated/di/NullCache.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Doctrine\\Common\\Cache\\CacheProvider;\n\n/**\n * @psalm-suppress DeprecatedInterface\n * @deprecated\n */\nfinal class NullCache extends CacheProvider\n{\n    /**\n     * {@inheritDoc}\n     */\n    protected function doFetch($id)\n    {\n        return false;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected function doContains($id)\n    {\n        return false;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected function doSave($id, $data, $lifeTime = 0)\n    {\n        return true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected function doDelete($id)\n    {\n        return true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected function doFlush()\n    {\n        return true;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected function doGetStats()\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src-deprecated/di/Provider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n/**\n * @deprecated User ProviderInterface instead\n */\ninterface Provider extends ProviderInterface\n{\n}\n"
  },
  {
    "path": "tests/bootstrap.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nputenv('TMPDIR=' . __DIR__ . '/tmp');\n\nrequire_once dirname(__DIR__) . '/vendor/autoload.php';\n\n$deleteFiles = static function (string $path) use (&$deleteFiles): void {\n    foreach (array_filter((array) glob($path . '/*')) as $file) {\n        is_dir($file) ? $deleteFiles($file) : unlink($file);\n        @rmdir($file);\n    }\n};\n\n$deleteFiles(__DIR__ . '/tmp');\n$deleteFiles(__DIR__ . '/compiler/tmp');\n"
  },
  {
    "path": "tests/di/AnnotatedClassTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse LogicException;\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Aop\\ReflectionClass;\n\nclass AnnotatedClassTest extends TestCase\n{\n    /** @var AnnotatedClass */\n    protected $annotatedClass;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n        $this->annotatedClass = new AnnotatedClass();\n    }\n\n    public function testInvoke(): void\n    {\n        $newInstance = $this->annotatedClass->getNewInstance(new ReflectionClass(FakeCar::class)); // @phpstan-ignore-line\n        $this->assertInstanceOf(NewInstance::class, $newInstance);\n        $container = new Container();\n        (new Bind($container, FakeTyreInterface::class))->to(FakeTyre::class);\n        (new Bind($container, FakeEngineInterface::class))->to(FakeEngine::class);\n        (new Bind($container, FakeHandleInterface::class))->toProvider(FakeHandleProvider::class);\n        (new Bind($container, FakeMirrorInterface::class))->annotatedWith('right')->to(FakeMirrorRight::class);\n        (new Bind($container, FakeMirrorInterface::class))->annotatedWith('left')->to(FakeMirrorRight::class);\n        (new Bind($container, FakeGearStickInterface::class))->annotatedWith(FakeGearStickInject::class)->toProvider(FakeGearStickProvider::class);\n        $car = $newInstance($container);\n        if (! $car instanceof FakeCar) {\n            throw new LogicException();\n        }\n\n        $this->assertInstanceOf(FakeCar::class, $car);\n        $this->assertInstanceOf(FakeTyre::class, $car->frontTyre);\n        $this->assertInstanceOf(FakeTyre::class, $car->rearTyre);\n        $this->assertInstanceOf(FakeLeatherGearStick::class, $car->gearStick);\n        $this->assertNull($car->hardtop);\n    }\n\n    /**\n     * @phpstan-param class-string $class\n     *\n     * @dataProvider classProvider\n     */\n    public function testAnnotatedByAnnotation(string $class): void\n    {\n        $newInstance = $this->annotatedClass->getNewInstance(new ReflectionClass($class));\n        $container = new Container();\n        (new Bind($container, FakeMirrorInterface::class))->annotatedWith(FakeLeft::class)->to(FakeMirrorLeft::class);\n        (new Bind($container, FakeMirrorInterface::class))->annotatedWith(FakeRight::class)->to(FakeMirrorRight::class);\n        $handleBar = $newInstance($container);\n        if (! $handleBar instanceof FakeHandleBar && ! $handleBar instanceof FakeHandleBarQualifier) {\n            throw new LogicException();\n        }\n\n        $this->assertInstanceOf(FakeMirrorLeft::class, $handleBar->leftMirror);\n        $this->assertInstanceOf(FakeMirrorRight::class, $handleBar->rightMirror);\n    }\n\n    /**\n     * @return array<array<class-string>>\n     */\n    public function classProvider(): array\n    {\n        return [\n            [FakeHandleBar::class],\n            [FakeHandleBarQualifier::class],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/di/ArgumentTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse ReflectionMethod;\nuse ReflectionParameter;\n\nuse function assert;\nuse function serialize;\nuse function unserialize;\n\nclass ArgumentTest extends TestCase\n{\n    /** @var Argument */\n    protected $argument;\n\n    protected function setUp(): void\n    {\n        $this->argument = new Argument(new ReflectionParameter([FakeCar::class, '__construct'], 'engine'), Name::ANY);\n    }\n\n    public function testToString(): void\n    {\n        $this->assertSame('Ray\\Di\\FakeEngineInterface-' . Name::ANY, (string) $this->argument);\n    }\n\n    public function testToStringScalar(): void\n    {\n        $argument = new Argument(new ReflectionParameter([FakeInternalTypes::class, 'stringId'], 'id'), Name::ANY);\n        $this->assertSame('-' . Name::ANY, (string) $argument);\n    }\n\n    public function testSerializable(): void\n    {\n        $argument = unserialize(serialize(new Argument(new ReflectionParameter([FakeInternalTypes::class, 'stringId'], 'id'), Name::ANY)));\n        assert($argument instanceof Argument);\n        $class = $argument->get()->getDeclaringFunction();\n        $this->assertInstanceOf(ReflectionMethod::class, $class);\n    }\n}\n"
  },
  {
    "path": "tests/di/ArgumentsTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse ReflectionMethod;\nuse ReflectionParameter;\n\nuse function spl_object_hash;\n\nclass ArgumentsTest extends TestCase\n{\n    /** @var Arguments */\n    protected $arguments;\n\n    protected function setUp(): void\n    {\n        $this->arguments = new Arguments(new ReflectionMethod(FakeCar::class, 'setTires'), new Name(Name::ANY));\n    }\n\n    public function testInject(): void\n    {\n        $container = (new FakeCarModule())->getContainer();\n        $parameters = $this->arguments->inject($container);\n        $this->assertInstanceOf(FakeTyre::class, $parameters[0]);\n        $this->assertInstanceOf(FakeTyre::class, $parameters[1]);\n        $param0 = $parameters[0];\n        $this->assertNotSame(spl_object_hash($param0), $parameters[1]);\n    }\n\n    public function testParameterDefaultValue(): void\n    {\n        $defaultValue = (new ReflectionParameter([FakeHandleProvider::class, '__construct'], 'logo'))->getDefaultValue();\n        $emptyContainer = new Container();\n        $parameters = new Arguments(new ReflectionMethod(FakeHandleProvider::class, '__construct'), new Name(Name::ANY));\n        $parametersValue = $parameters->inject($emptyContainer);\n        $this->assertSame($defaultValue, $parametersValue[0]);\n    }\n}\n"
  },
  {
    "path": "tests/di/AssistedTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\Exception\\MethodInvocationNotAvailable;\n\nclass AssistedTest extends TestCase\n{\n    /** @var InjectorInterface */\n    private $injector;\n\n    protected function setUp(): void\n    {\n        $this->injector = new Injector(new FakeToBindModule());\n    }\n\n    public function testAssisted(): void\n    {\n        $consumer = $this->injector->getInstance(FakeAssistedConsumer::class);\n        $assistedDependency = $consumer->assistOne('a', 'b');\n        $expected = FakeRobot::class;\n        $this->assertInstanceOf($expected, $assistedDependency);\n    }\n\n    public function testAssistedWithName(): void\n    {\n        $this->injector = new Injector(new FakeInstanceBindModule());\n        $consumer = $this->injector->getInstance(FakeAssistedConsumer::class);\n        $assistedDependency = $consumer->assistWithName('a7');\n        $expected = 1;\n        $this->assertSame($expected, $assistedDependency);\n    }\n\n    public function testAssistedAnyWithName(): void\n    {\n        $injector = new Injector(new FakeToBindModule(new FakeInstanceBindModule()));\n        $consumer = $injector->getInstance(FakeAssistedConsumer::class);\n        [$assistedDependency1, $assistedDependency2] = $consumer->assistAny();\n        $expected1 = 1;\n        $this->assertSame($expected1, $assistedDependency1);\n        $this->assertInstanceOf(FakeRobot::class, $assistedDependency2);\n    }\n\n    public function testAssistedMethodInvocation(): void\n    {\n        $assistedConsumer = (new Injector(new FakeAssistedDbModule()))->getInstance(FakeAssistedParamsConsumer::class);\n        [$id, $db] = $assistedConsumer->getUser(1);\n        /** @var FakeAbstractDb $db */\n        $this->assertSame(1, $id);\n        $this->assertSame(1, $db->dbId);\n    }\n\n    public function testAssistedMethodInvocationNotAvailable(): void\n    {\n        $this->expectException(MethodInvocationNotAvailable::class);\n        $assistedDbProvider = (new Injector(new FakeAssistedDbModule()))->getInstance(FakeAssistedDbProvider::class);\n        $assistedDbProvider->get();\n    }\n\n    public function testAssistedCustomInject(): void\n    {\n        $assistedConsumer = (new Injector(new FakeAssistedDbModule()))->getInstance(FakeAssistedParamsConsumer::class);\n        [$id] = $assistedConsumer->getUser(1);\n        $this->assertSame(1, $id);\n    }\n}\n"
  },
  {
    "path": "tests/di/BcParameterQualifierIntegrationTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass BcParameterQualifierIntegrationTest extends TestCase\n{\n    public function testBcParameterQualifierWorks(): void\n    {\n        $injector = new Injector(new FakeBcParameterQualifierModule());\n        /** @psalm-suppress DeprecatedClass */\n        $instance = $injector->getInstance(FakeClassWithBcParameterQualifier::class);\n\n        // The setGearStick method uses #[FakeInjectOne] at method level only\n        // BcParameterQualifier should apply it to the parameter\n        $this->assertInstanceOf(FakeLeatherGearStick::class, $instance->gearStick);\n    }\n}\n"
  },
  {
    "path": "tests/di/BcParameterQualifierTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\Annotation\\FakeInjectOne;\nuse Ray\\Di\\Annotation\\FakeQualifierOnly;\nuse ReflectionMethod;\n\nclass BcParameterQualifierTest extends TestCase\n{\n    public function testGetNamesFromMethodLevelAttribute(): void\n    {\n        $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setSingleParam');\n        /** @psalm-suppress DeprecatedClass */\n        $names = BcParameterQualifier::getNames($method);\n\n        $this->assertSame(['param' => FakeInjectOne::class], $names);\n    }\n\n    public function testNoNamesForMultipleParameters(): void\n    {\n        $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setMultipleParams');\n        /** @psalm-suppress DeprecatedClass */\n        $names = BcParameterQualifier::getNames($method);\n\n        $this->assertSame([], $names);\n    }\n\n    public function testNoNamesWhenParameterHasQualifier(): void\n    {\n        $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setSingleParamWithQualifier');\n        /** @psalm-suppress DeprecatedClass */\n        $names = BcParameterQualifier::getNames($method);\n\n        $this->assertSame([], $names);\n    }\n\n    public function testNoNamesForNonQualifierAttribute(): void\n    {\n        $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setSingleParamWithInjectOnly');\n        /** @psalm-suppress DeprecatedClass */\n        $names = BcParameterQualifier::getNames($method);\n\n        $this->assertSame([], $names);\n    }\n\n    public function testNoNamesForMethodWithoutInjectInterface(): void\n    {\n        $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setSingleParamNoInject');\n        /** @psalm-suppress DeprecatedClass */\n        $names = BcParameterQualifier::getNames($method);\n\n        $this->assertSame([], $names);\n    }\n\n    public function testNamesForTargetMethodOnly(): void\n    {\n        // FakeGearStickInject has TARGET_METHOD only\n        // BC parameter qualifier now supports TARGET_METHOD-only attributes for backward compatibility\n        $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setSingleParamMethodOnly');\n        /** @psalm-suppress DeprecatedClass */\n        $names = BcParameterQualifier::getNames($method);\n\n        $this->assertSame(['param' => FakeGearStickInject::class], $names);\n    }\n\n    public function testConstructorWithQualifierOnly(): void\n    {\n        // Constructor with Qualifier-only attribute (no InjectInterface)\n        // BC parameter qualifier should apply for constructors (InjectInterface is implicit)\n        $method = new ReflectionMethod(FakeBcConstructorQualifierClass::class, '__construct');\n        /** @psalm-suppress DeprecatedClass */\n        $names = BcParameterQualifier::getNames($method);\n\n        $this->assertSame(['param' => FakeQualifierOnly::class], $names);\n    }\n}\n"
  },
  {
    "path": "tests/di/BcStringParserTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass BcStringParserTest extends TestCase\n{\n    public function testParseStringFormat(): void\n    {\n        $result = BcStringParser::parse('engine=engine_name,var=var_name');\n        $expected = ['engine' => 'engine_name', 'var' => 'var_name'];\n        $this->assertSame($expected, $result);\n    }\n\n    public function testParseStringFormatWithSpaces(): void\n    {\n        $result = BcStringParser::parse('engine=engine_name, var=var_name');\n        $expected = ['engine' => 'engine_name', 'var' => 'var_name'];\n        $this->assertSame($expected, $result);\n    }\n\n    public function testParseStringFormatWithDollarPrefix(): void\n    {\n        $result = BcStringParser::parse('$engine=engine_name,$var=var_name');\n        $expected = ['engine' => 'engine_name', 'var' => 'var_name'];\n        $this->assertSame($expected, $result);\n    }\n}\n"
  },
  {
    "path": "tests/di/BindTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\Exception\\InvalidProvider;\nuse Ray\\Di\\Exception\\InvalidType;\nuse Ray\\Di\\Exception\\NotFound;\n\nuse function assert;\nuse function property_exists;\nuse function spl_object_hash;\n\nclass BindTest extends TestCase\n{\n    /** @var Bind */\n    private $bind;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n        $this->bind = new Bind(new Container(), FakeTyreInterface::class);\n    }\n\n    public function testGetBound(): void\n    {\n        $this->bind->to(FakeTyre::class);\n        $bound = $this->bind->getBound();\n        $this->assertInstanceOf(Dependency::class, $bound);\n    }\n\n    public function testToString(): void\n    {\n        $this->assertSame('Ray\\Di\\FakeTyreInterface-' . Name::ANY, (string) $this->bind);\n    }\n\n    public function testInvalidToTest(): void\n    {\n        $this->expectException(NotFound::class);\n        $this->bind->to('invalid-class'); // @phpstan-ignore-line\n    }\n\n    public function testInvalidToProviderTest(): void\n    {\n        $this->expectException(NotFound::class);\n        $this->bind->toProvider('invalid-class'); // @phpstan-ignore-line\n    }\n\n    public function testInValidInterfaceBinding(): void\n    {\n        $this->expectException(NotFound::class);\n        new Bind(new Container(), 'invalid-interface');\n    }\n\n    public function testUntargetedBind(): void\n    {\n        $container = new Container();\n        $bind = new Bind($container, FakeEngine::class);\n        unset($bind);\n        $container = $container->getContainer();\n        $this->assertArrayHasKey(FakeEngine::class . '-' . Name::ANY, $container);\n    }\n\n    public function testUntargetedBindSingleton(): void\n    {\n        $container = new Container();\n        $bind = (new Bind($container, FakeEngine::class))->in(Scope::SINGLETON);\n        unset($bind);\n        $dependency1 = $container->getInstance(FakeEngine::class, Name::ANY);\n        $dependency2 = $container->getInstance(FakeEngine::class, Name::ANY);\n        $this->assertSame(spl_object_hash($dependency1), spl_object_hash($dependency2));\n    }\n\n    /**\n     * @return array<int, array<int, array<string, string>>>\n     */\n    public function nameProvider(): array\n    {\n        return [\n            [['tmpDir' => 'tmp_dir', 'leg' => 'left']],\n        ];\n    }\n\n    /**\n     * @param array<string, string>|string $name\n     *\n     * @dataProvider nameProvider\n     */\n    public function testToConstructor($name): void\n    {\n        $container = new Container();\n        $container->add((new Bind($container, ''))->annotatedWith('tmp_dir')->toInstance('/tmp'));\n        $container->add((new Bind($container, FakeLegInterface::class))->annotatedWith('left')->to(FakeLeftLeg::class));\n        $container->add((new Bind($container, FakeRobotInterface::class))->toConstructor(FakeToConstructorRobot::class, $name));\n        $instance = $container->getInstance(FakeRobotInterface::class, Name::ANY);\n        assert($instance instanceof FakeToConstructorRobot);\n        $this->assertInstanceOf(FakeLeftLeg::class, $instance->leg);\n        $this->assertSame('/tmp', $instance->tmpDir);\n    }\n\n    public function testToConstructorWithMethodInjection(): void\n    {\n        $container = new Container();\n        $container->add((new Bind($container, ''))->annotatedWith('tmp_dir')->toInstance('/tmp'));\n        $container->add((new Bind($container, FakeLegInterface::class))->annotatedWith('left')->to(FakeLeftLeg::class));\n        $container->add((new Bind($container, FakeEngineInterface::class))->to(FakeEngine::class));\n        $container->add(\n            (new Bind($container, FakeRobotInterface::class))->toConstructor(\n                FakeToConstructorRobot::class,\n                ['tmpDir' => 'tmp_dir', 'leg' => 'left'],\n                (new InjectionPoints())->addMethod('setEngine')\n            )\n        );\n        $instance = $container->getInstance(FakeRobotInterface::class, Name::ANY);\n        assert($instance instanceof FakeToConstructorRobot);\n        $this->assertInstanceOf(FakeEngine::class, $instance->engine);\n    }\n\n    public function testToValidation(): void\n    {\n        $this->expectException(InvalidType::class);\n        (new Bind(new Container(), FakeHandleInterface::class))->to(FakeEngine::class);\n    }\n\n    public function testToProvider(): void\n    {\n        $this->expectException(InvalidProvider::class);\n        (new Bind(new Container(), FakeHandleInterface::class))->toProvider(FakeEngine::class);\n    }\n\n    public function testBindProviderAsProvider(): void\n    {\n        $container = new Container();\n        (new Bind($container, ProviderInterface::class))->annotatedWith('handle')->to(FakeHandleProvider::class);\n        $instance = $container->getInstance(ProviderInterface::class, 'handle');\n        $this->assertInstanceOf(FakeHandleProvider::class, $instance);\n    }\n\n    public function testBindProviderAsProviderInSingleton(): void\n    {\n        $container = new Container();\n        (new Bind($container, ProviderInterface::class))->annotatedWith('handle')->to(FakeHandleProvider::class)->in(Scope::SINGLETON);\n        $instance1 = $container->getInstance(ProviderInterface::class, 'handle');\n        $instance2 = $container->getInstance(ProviderInterface::class, 'handle');\n        $this->assertSame(spl_object_hash($instance1), spl_object_hash($instance2));\n    }\n\n    public function testProviderContext(): void\n    {\n        $container = new Container();\n        (new Bind($container, ProviderInterface::class))->toProvider(FakeContextualProvider::class, 'context_string');\n        $instance = $container->getInstance(ProviderInterface::class, Name::ANY);\n        assert(property_exists($instance, 'context'));\n        $this->assertSame('context_string', $instance->context);\n    }\n}\n"
  },
  {
    "path": "tests/di/ContainerTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse BadMethodCallException;\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Aop\\Compiler;\nuse Ray\\Aop\\Matcher;\nuse Ray\\Aop\\Pointcut;\nuse Ray\\Di\\Exception\\Unbound;\nuse Throwable;\n\nuse function assert;\nuse function get_class;\nuse function sys_get_temp_dir;\n\nclass ContainerTest extends TestCase\n{\n    /** @var Container */\n    private $container;\n\n    /** @var FakeEngine */\n    private $engine;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n        $this->container = new Container();\n        $this->engine = new FakeEngine();\n        (new Bind($this->container, FakeEngineInterface::class))->toInstance($this->engine);\n    }\n\n    public function testGetDependency(): void\n    {\n        $dependencyIndex = FakeEngineInterface::class . '-' . Name::ANY;\n        $instance = $this->container->getDependency($dependencyIndex);\n        $this->assertInstanceOf(FakeEngine::class, $instance);\n        $this->assertSame($this->engine, $instance);\n    }\n\n    public function testClassGetDependency(): void\n    {\n        (new Bind($this->container, FakeEngine::class))->toInstance($this->engine);\n        $dependencyIndex = FakeEngine::class . '-' . Name::ANY;\n        $instance = $this->container->getDependency($dependencyIndex);\n        $this->assertInstanceOf(FakeEngine::class, $instance);\n        $this->assertSame($this->engine, $instance);\n    }\n\n    public function testProviderGetDependency(): void\n    {\n        (new Bind($this->container, FakeEngine::class))->toProvider(FakeEngineProvider::class);\n        $dependencyIndex = FakeEngine::class . '-' . Name::ANY;\n        $instance = $this->container->getDependency($dependencyIndex);\n        $this->assertInstanceOf(FakeEngine::class, $instance);\n    }\n\n    public function testGetInstance(): void\n    {\n        $instance = $this->container->getInstance(FakeEngineInterface::class, Name::ANY);\n        $this->assertInstanceOf(FakeEngine::class, $instance);\n        $this->assertSame($this->engine, $instance);\n    }\n\n    public function testClassGetInstance(): void\n    {\n        (new Bind($this->container, FakeEngine::class))->toInstance($this->engine);\n        $instance = $this->container->getInstance(FakeEngine::class, Name::ANY);\n        $this->assertInstanceOf(FakeEngine::class, $instance);\n        $this->assertSame($this->engine, $instance);\n    }\n\n    public function testProviderGetInstance(): void\n    {\n        (new Bind($this->container, FakeEngine::class))->toProvider(FakeEngineProvider::class);\n        $instance = $this->container->getInstance(FakeEngine::class, Name::ANY);\n        $this->assertInstanceOf(FakeEngine::class, $instance);\n    }\n\n    public function testGetContainer(): void\n    {\n        $array = $this->container->getContainer();\n        $dependencyIndex = FakeEngineInterface::class . '-' . Name::ANY;\n        $this->assertArrayHasKey($dependencyIndex, $array);\n    }\n\n    public function testClassGetContainer(): void\n    {\n        (new Bind($this->container, FakeEngine::class))->toInstance($this->engine);\n        $array = $this->container->getContainer();\n        $dependencyIndex = FakeEngine::class . '-' . Name::ANY;\n        $this->assertArrayHasKey($dependencyIndex, $array);\n    }\n\n    public function testMerge(): void\n    {\n        $extraContainer = new Container();\n        $bind = (new Bind($this->container, FakeRobotInterface::class))->to(FakeRobot::class);\n        $this->container->add($bind);\n        $this->container->merge($extraContainer);\n        $array = $this->container->getContainer();\n        $this->assertArrayHasKey(FakeEngineInterface::class . '-' . Name::ANY, $array);\n        $this->assertArrayHasKey(FakeRobotInterface::class . '-' . Name::ANY, $array);\n    }\n\n    public function testMergePointcuts(): void\n    {\n        $extraContainer = new Container();\n        $pointcut1 = new Pointcut((new Matcher())->any(), (new Matcher())->any(), [FakeDoubleInterceptor::class]);\n        $pointcut2 = new Pointcut((new Matcher())->any(), (new Matcher())->any(), [FakeDoubleInterceptor::class]);\n        $this->container->addPointcut($pointcut1);\n        $extraContainer->addPointcut($pointcut2);\n        $this->container->merge($extraContainer);\n        $array = [];\n        foreach ($this->container->getPointcuts() as $pointcut) {\n            $array[] = $pointcut->interceptors[0];\n        }\n\n        $this->assertContains(FakeDoubleInterceptor::class, $array);\n    }\n\n    public function testMove(): void\n    {\n        $newName = 'new';\n        $this->container->move(FakeEngineInterface::class, Name::ANY, FakeEngineInterface::class, $newName);\n        $dependencyIndex = FakeEngineInterface::class . '-' . $newName;\n        $instance = $this->container->getDependency($dependencyIndex);\n        $this->assertInstanceOf(FakeEngine::class, $instance);\n    }\n\n    public function testMoveUnbound(): void\n    {\n        $this->expectException(Unbound::class);\n        $this->container->move(FakeEngineInterface::class, 'invalid', FakeEngineInterface::class, 'new');\n    }\n\n    public function testAbstractClassUnbound(): void\n    {\n        try {\n            $this->container->getInstance('_INVALID_INTERFACE_', Name::ANY); // @phpstan-ignore-line\n        } catch (Throwable $e) {\n            $this->assertSame(Unbound::class, get_class($e));\n        }\n    }\n\n    public function testAnnotateConstant(): void\n    {\n        $container = new Container();\n        //FakeConstantInterface\n        (new Bind($container, ''))->annotatedWith(FakeConstant::class)->toInstance('kuma');\n        (new Bind($container, FakeConstantConsumer::class));\n        $instance = $container->getInstance(FakeConstantConsumer::class, Name::ANY);\n        $this->assertSame('kuma', $instance->constantByConstruct);\n        $this->assertSame('kuma', $instance->constantBySetter);\n        $this->assertSame('kuma', $instance->setterConstantWithoutVarName);\n        $this->assertSame('default_construct', $instance->defaultByConstruct);\n        $this->assertSame('default_setter', $instance->defaultBySetter);\n    }\n\n    public function testBadMethodCall(): void\n    {\n        $this->expectException(BadMethodCallException::class);\n        $container = new Container();\n        //FakeConstantInterface\n        (new Bind($container, FakeEngineInterface::class))->toInstance(new FakeEngine());\n        $container->getInstanceWithArgs(FakeEngineInterface::class, []);\n    }\n\n    /**\n     * @covers \\Ray\\Di\\Container::getInstanceWithArgs\n     */\n    public function testUnbound(): void\n    {\n        $this->expectException(Unbound::class);\n        (new Container())->getInstanceWithArgs(FakeEngineInterface::class, []);\n    }\n\n    public function testWeaveAspectsWithEmptyPointcuts(): void\n    {\n        $container = new Container();\n        (new Bind($container, FakeEngine::class));\n\n        // Should work fine even when no pointcuts are defined\n        $tmpDir = sys_get_temp_dir();\n        assert($tmpDir !== '');\n        $container->weaveAspects(new Compiler($tmpDir));\n\n        $instance = $container->getInstance(FakeEngine::class);\n        $this->assertInstanceOf(FakeEngine::class, $instance);\n    }\n}\n"
  },
  {
    "path": "tests/di/ContextualProviderTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass ContextualProviderTest extends TestCase\n{\n    public function testContextualProviderInjection(): void\n    {\n        $robot = (new Injector(new FakeContextualModule('main')))->getInstance(FakeRobotInterface::class);\n        /** @var FakeContextualRobot $robot */\n        $this->assertSame($robot->context, 'main');\n    }\n}\n"
  },
  {
    "path": "tests/di/DependencyTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Aop\\Compiler;\nuse Ray\\Aop\\Matcher;\nuse Ray\\Aop\\Pointcut;\nuse Ray\\Aop\\WeavedInterface;\nuse ReflectionClass;\nuse ReflectionMethod;\n\nuse function assert;\nuse function is_object;\nuse function property_exists;\nuse function spl_object_hash;\n\nclass DependencyTest extends TestCase\n{\n    /** @var Dependency */\n    private $dependency;\n\n    protected function setUp(): void\n    {\n        /** @var ReflectionClass<object> $class */\n        $class = new ReflectionClass(FakeCar::class);\n        $setters = [];\n        $name = new Name(Name::ANY);\n        $setters[] = new SetterMethod(new ReflectionMethod(FakeCar::class, 'setTires'), $name);\n        $setters[] = new SetterMethod(new ReflectionMethod(FakeCar::class, 'setHardtop'), $name);\n        $setterMethods = new SetterMethods($setters);\n        $newInstance = new NewInstance($class, $setterMethods);\n        $this->dependency = new Dependency($newInstance, new ReflectionMethod(FakeCar::class, 'postConstruct'));\n    }\n\n    /**\n     * @return Container[][]\n     * @psalm-return array{0: array{0: Container}}\n     */\n    public function containerProvider(): array\n    {\n        $container = new Container();\n        (new Bind($container, FakeTyreInterface::class))->to(FakeTyre::class);\n        (new Bind($container, FakeEngineInterface::class))->to(FakeEngine::class);\n        (new Bind($container, FakeHardtopInterface::class))->to(FakeHardtop::class);\n\n        return [[$container]];\n    }\n\n    /**\n     * @dataProvider containerProvider\n     */\n    public function testInject(Container $container): void\n    {\n        $car = $this->dependency->inject($container);\n        /** @var FakeCar $car */\n        $this->assertInstanceOf(FakeCar::class, $car);\n    }\n\n    /**\n     * @dataProvider containerProvider\n     */\n    public function testSetterInjection(Container $container): void\n    {\n        $car = $this->dependency->inject($container);\n        /** @var FakeCar $car */\n        $this->assertInstanceOf(FakeCar::class, $car);\n        $this->assertInstanceOf(FakeTyre::class, $car->frontTyre);\n    }\n\n    /**\n     * @dataProvider containerProvider\n     */\n    public function testPostConstruct(Container $container): void\n    {\n        $car = $this->dependency->inject($container);\n        /** @var FakeCar $car */\n        $this->assertTrue($car->isConstructed);\n    }\n\n    /**\n     * @dataProvider containerProvider\n     */\n    public function testPrototype(Container $container): void\n    {\n        $this->dependency->setScope(Scope::PROTOTYPE);\n        $car1 = $this->dependency->inject($container);\n        $car2 = $this->dependency->inject($container);\n        assert(is_object($car1) && is_object($car2));\n        $this->assertNotSame(spl_object_hash($car1), spl_object_hash($car2));\n    }\n\n    /**\n     * @dataProvider containerProvider\n     */\n    public function testSingleton(Container $container): void\n    {\n        $this->dependency->setScope(Scope::SINGLETON);\n        $car1 = $this->dependency->inject($container);\n        $car2 = $this->dependency->inject($container);\n        assert(is_object($car1) && is_object($car2));\n        $this->assertSame(spl_object_hash($car1), spl_object_hash($car2));\n    }\n\n    public function testInjectInterceptor(): void\n    {\n        /** @var ReflectionClass<object> $class */\n        $class = new ReflectionClass(FakeAop::class);\n        $dependency = new Dependency(new NewInstance($class, new SetterMethods([])));\n        $pointcut = new Pointcut((new Matcher())->any(), (new Matcher())->any(), [FakeDoubleInterceptor::class]);\n        $dependency->weaveAspects(new Compiler(__DIR__ . '/tmp'), [$pointcut]);\n        $container = new Container();\n        $container->add((new Bind($container, FakeDoubleInterceptor::class))->to(FakeDoubleInterceptor::class));\n        $instance = $dependency->inject($container);\n        assert(is_object($instance));\n        $isWeave = (new ReflectionClass($instance))->implementsInterface(WeavedInterface::class);\n        $this->assertTrue($isWeave);\n        assert(property_exists($instance, 'bindings'));\n        $this->assertArrayHasKey('returnSame', (array) $instance->bindings);\n    }\n\n    /**\n     * @dataProvider containerProvider\n     * @covers \\Ray\\Di\\Dependency::injectWithArgs\n     */\n    public function testInjectWithArgsPostConstruct(Container $container): void\n    {\n        $car = $this->dependency->injectWithArgs($container, [new FakeEngine()]);\n        $this->assertInstanceOf(FakeCar::class, $car);\n    }\n\n    /**\n     * @dataProvider containerProvider\n     * @covers \\Ray\\Di\\Dependency::injectWithArgs\n     */\n    public function testInjectWithArgsSingleton(Container $container): void\n    {\n        $this->dependency->setScope(Scope::SINGLETON);\n        $this->dependency->injectWithArgs($container, [new FakeEngine()]);\n        $car = $this->dependency->injectWithArgs($container, [new FakeEngine()]);\n        $this->assertInstanceOf(FakeCar::class, $car);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/Annotation/FakeInjectOne.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Annotation;\n\nuse Attribute;\nuse Ray\\Di\\Di\\InjectInterface;\nuse Ray\\Di\\Di\\Qualifier;\n\n#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER), Qualifier]\nfinal class FakeInjectOne implements InjectInterface\n{\n    public function isOptional()\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/Annotation/FakeLeft.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Annotation;\n\nuse Attribute;\nuse Ray\\Di\\Di\\Qualifier;\n\n#[Attribute, Qualifier]\nfinal class FakeLeft\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/Annotation/FakeNotQualifer.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Annotation;\n\nuse Attribute;\nuse Ray\\Di\\Di\\Qualifier;\n\n#[Attribute]\nfinal class FakeNotQualifer\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/Annotation/FakeQualifierOnly.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Annotation;\n\nuse Attribute;\nuse Ray\\Di\\Di\\Qualifier;\n\n/**\n * Qualifier-only attribute for testing (no InjectInterface)\n */\n#[Attribute(Attribute::TARGET_METHOD), Qualifier]\nfinal class FakeQualifierOnly\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/Annotation/FakeRight.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\Annotation;\n\nuse Attribute;\nuse Ray\\Di\\Di\\Qualifier;\n\n#[Attribute, Qualifier]\nfinal class FakeRight\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAbstractClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nabstract class FakeAbstractClass\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAbstractDb.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAbstractDb\n{\n    public $dbId;\n\n    public function __construct($id)\n    {\n        $this->dbId = $id;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Attribute;\n\n#[Attribute(Attribute::TARGET_CLASS)]\nclass FakeAnnoClass\n{\n    public static $order = [];\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoInterceptor1.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\nclass FakeAnnoInterceptor1 implements MethodInterceptor\n{\n    public function invoke(MethodInvocation $invocation)\n    {\n        FakeAnnoClass::$order[] = self::class;\n\n        return $invocation->proceed();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoInterceptor2.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\nclass FakeAnnoInterceptor2 implements MethodInterceptor\n{\n    public function invoke(MethodInvocation $invocation)\n    {\n        FakeAnnoClass::$order[] = self::class;\n\n        return $invocation->proceed();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoInterceptor3.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\nclass FakeAnnoInterceptor3 implements MethodInterceptor\n{\n    public function invoke(MethodInvocation $invocation)\n    {\n        FakeAnnoClass::$order[] = self::class;\n\n        return $invocation->proceed();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoInterceptor4.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\nclass FakeAnnoInterceptor4 implements MethodInterceptor\n{\n    public function invoke(MethodInvocation $invocation)\n    {\n        FakeAnnoClass::$order[] = self::class;\n\n        return $invocation->proceed();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoInterceptor5.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\nclass FakeAnnoInterceptor5 implements MethodInterceptor\n{\n    public function invoke(MethodInvocation $invocation)\n    {\n        FakeAnnoClass::$order[] = self::class;\n\n        return $invocation->proceed();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoInterceptorInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\ninterface FakeAnnoInterceptorInterface extends MethodInterceptor\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoMethod1.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Attribute;\n\n#[Attribute(Attribute::TARGET_METHOD)]\nclass FakeAnnoMethod1\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoMethod2.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Attribute;\n\n#[Attribute(Attribute::TARGET_METHOD)]\nclass FakeAnnoMethod2\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoMethod3.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Attribute;\n\n#[Attribute(Attribute::TARGET_METHOD)]\nclass FakeAnnoMethod3\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAnnoModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bindInterceptor(\n            $this->matcher->any(),\n            $this->matcher->annotatedWith(FakeAnnoMethod1::class),\n            [FakeAnnoInterceptor1::class]\n        );\n        $this->bindInterceptor(\n            $this->matcher->any(),\n            $this->matcher->annotatedWith(FakeAnnoMethod2::class),\n            [FakeAnnoInterceptor2::class]\n        );\n        $this->bindInterceptor(\n            $this->matcher->any(),\n            $this->matcher->annotatedWith(FakeAnnoMethod3::class),\n            [FakeAnnoInterceptor3::class]\n        );\n        $this->bindPriorityInterceptor(\n            $this->matcher->annotatedWith(FakeAnnoClass::class),\n            $this->matcher->any(),\n            [FakeAnnoInterceptor4::class]\n        );\n        $this->bindInterceptor(\n            $this->matcher->annotatedWith(FakeAnnoClass::class),\n            $this->matcher->any(),\n            [FakeAnnoInterceptor5::class]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAnnoOrderClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n#[FakeAnnoClass]\nclass FakeAnnoOrderClass\n{\n    #[FakeAnnoMethod1]\n    #[FakeAnnoMethod2]\n    #[FakeAnnoMethod3]\n    public function get()\n    {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAop.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAop implements FakeAopInterface\n{\n    public function returnSame($a)\n    {\n        return $a;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAopDoublyInstallModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAopDoublyInstallModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->install(new FakeAopInterceptorModule());\n        $this->install(new FakeAopInterceptorModule());\n        $this->install(new FakeAopInterfaceModule());\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAopGrapher.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAopGrapher implements FakeAopInterface\n{\n    public $a;\n\n    public function __construct($a)\n    {\n        $this->a = $a;\n    }\n\n    public function returnSame($a)\n    {\n        return $a;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAopGrapherModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAopGrapherModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeAopInterface::class)->to(FakeAopGrapher::class);\n        $this->bindInterceptor(\n            $this->matcher->any(),\n            $this->matcher->any(),\n            [FakeDoubleInterceptor::class]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAopInstallModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAopInstallModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->install(new FakeAopInterceptorModule());\n        $this->install(new FakeAopInterfaceModule());\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAopInterceptorModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAopInterceptorModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bindInterceptor(\n            $this->matcher->any(),\n            $this->matcher->any(),\n            [FakeDoubleInterceptor::class]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAopInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeAopInterface\n{\n    public function returnSame($a);\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAopInterfaceModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAopInterfaceModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeAopInterface::class)->to(FakeAop::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAopModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAopModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeAopInterface::class)->to(FakeAop::class);\n        $this->bindInterceptor(\n            $this->matcher->any(),\n            $this->matcher->any(),\n            [FakeDoubleInterceptor::class]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAssistedConsumer.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Assisted;\nuse Ray\\Di\\Di\\Named;\n\nclass FakeAssistedConsumer\n{\n    /**\n     * @return FakeRobotInterface|null\n     */\n    public function assistOne($a, $b, #[Assisted] ?FakeRobotInterface $robot = null)\n    {\n        unset($a, $b);\n\n        return $robot;\n    }\n\n    public function assistWithName($a, #[Assisted] #[Named('one')] $var1 = null)\n    {\n        unset($a);\n\n        return $var1;\n    }\n\n    /**\n     * @return (FakeRobotInterface|mixed|null)[]\n     * @psalm-return array{0: mixed, 1: FakeRobotInterface|null}\n     */\n    public function assistAny(#[Assisted] #[Named('one')] $var2 = null, #[Assisted] ?FakeRobotInterface $robot = null)\n    {\n        return [$var2, $robot];\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAssistedDb.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAssistedDb extends FakeAbstractDb\n{\n    public $dbId;\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAssistedDbModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAssistedDbModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeAbstractDb::class)->toProvider(FakeAssistedDbProvider::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAssistedDbProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeAssistedDbProvider implements ProviderInterface\n{\n    /** @var MethodInvocationProvider */\n    private $invocationProvider;\n\n    public function __construct(MethodInvocationProvider $invocationProvider)\n    {\n        $this->invocationProvider = $invocationProvider;\n    }\n\n    public function get()\n    {\n        $parameters = $this->invocationProvider->get()->getArguments()->getArrayCopy();\n        [$id] = $parameters;\n\n        return new FakeAssistedDb($id);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAssistedInjectConsumer.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Annotation\\FakeInjectOne;\nuse Ray\\Di\\Di\\Assisted;\nuse Ray\\Di\\Di\\Inject;\nuse Ray\\Di\\Di\\Named;\n\nclass FakeAssistedInjectConsumer\n{\n    public function assistOne($a, $b, #[Assisted] ?FakeRobotInterface $robot = null): ?FakeRobotInterface\n    {\n        unset($a, $b);\n\n        return $robot;\n    }\n\n    public function assistWithName($a, #[Assisted, Named('one')] $var1 = null)\n    {\n        unset($a);\n\n        return $var1;\n    }\n\n    /**\n     * @return (FakeRobotInterface|mixed|null)[]\n     * @psalm-return array{0: mixed, 1: FakeRobotInterface|null}\n     */\n    public function assistAny(#[Assisted, Named('one')] $var2 = null, #[Inject] ?FakeRobotInterface $robot = null)\n    {\n        return [$var2, $robot];\n    }\n\n    public function assistCustomeAssistedInject(#[FakeInjectOne] int $one = 0): int\n    {\n        return $one;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAssistedInjectDb.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Assisted;\nuse Ray\\Di\\Di\\Inject;\n\nclass FakeAssistedInjectDb\n{\n    /**\n     * @return array [int, FakeAbstractDb]\n     */\n    public function getUser($id, #[Inject] ?FakeAbstractDb $db = null)\n    {\n        return [$id, $db];\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeAssistedParamsConsumer.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Assisted;\n\nclass FakeAssistedParamsConsumer\n{\n    /**\n     * @return array [int, FakeAbstractDb]\n     */\n    public function getUser($id, #[Assisted] ?FakeAbstractDb $db = null)\n    {\n        return [$id, $db];\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeBcConstructorQualifierClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Annotation\\FakeQualifierOnly;\n\nclass FakeBcConstructorQualifierClass\n{\n    /**\n     * Constructor with method-level Qualifier-only attribute (no InjectInterface)\n     * This should trigger BC parameter qualifier for constructors\n     */\n    #[FakeQualifierOnly]\n    public function __construct(public $param)\n    {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeBcParameterQualifierClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Annotation\\FakeInjectOne;\nuse Ray\\Di\\Di\\Inject;\n\nclass FakeBcParameterQualifierClass\n{\n    public $singleParam;\n    public $multipleParams1;\n    public $multipleParams2;\n    public $singleParamWithQualifier;\n    public $singleParamWithInjectOnly;\n    public $singleParamNoInject;\n\n    /**\n     * Single parameter with method-level InjectInterface+Qualifier\n     * This should trigger BC parameter qualifier\n     */\n    #[FakeInjectOne]\n    public function setSingleParam(FakeGearStickInterface $param): void\n    {\n        $this->singleParam = $param;\n    }\n\n    /**\n     * Multiple parameters - should NOT infer\n     */\n    #[FakeGearStickInject('test')]\n    public function setMultipleParams(FakeGearStickInterface $param1, FakeTyreInterface $param2): void\n    {\n        $this->multipleParams1 = $param1;\n        $this->multipleParams2 = $param2;\n    }\n\n    /**\n     * Single parameter but already has parameter-level qualifier - should NOT infer\n     */\n    #[FakeGearStickInject('test')]\n    public function setSingleParamWithQualifier(#[FakeInjectOne] FakeGearStickInterface $param): void\n    {\n        $this->singleParamWithQualifier = $param;\n    }\n\n    /**\n     * Method attribute only implements InjectInterface, not Qualifier - should NOT infer\n     */\n    #[Inject]\n    public function setSingleParamWithInjectOnly(FakeGearStickInterface $param): void\n    {\n        $this->singleParamWithInjectOnly = $param;\n    }\n\n    /**\n     * No InjectInterface at all - should NOT infer\n     */\n    public function setSingleParamNoInject(FakeGearStickInterface $param): void\n    {\n        $this->singleParamNoInject = $param;\n    }\n\n    /**\n     * Method-level attribute with TARGET_METHOD only\n     * BC parameter qualifier now supports this for backward compatibility\n     */\n    #[FakeGearStickInject('test')]\n    public function setSingleParamMethodOnly(FakeGearStickInterface $param): void\n    {\n        $this->singleParam = $param;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeBcParameterQualifierModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Annotation\\FakeInjectOne;\n\nclass FakeBcParameterQualifierModule extends AbstractModule\n{\n    protected function configure(): void\n    {\n        // Bind with FakeInjectOne qualifier\n        $this->bind(FakeGearStickInterface::class)\n            ->annotatedWith(FakeInjectOne::class)\n            ->to(FakeLeatherGearStick::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeBuiltin.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeBuiltin\n{\n    public $injector;\n\n    public function __construct(InjectorInterface $Injector)\n    {\n        $this->injector = $Injector;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeCar.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Inject;\nuse Ray\\Di\\Di\\Named;\nuse Ray\\Di\\Di\\PostConstruct;\n\nclass FakeCar implements FakeCarInterface\n{\n    public $engine;\n    public $hardtop;\n    public $frontTyre;\n    public $rearTyre;\n    public $isConstructed = false;\n    public $rightMirror;\n    public $leftMirror;\n    public $spareMirror;\n\n    /** @var FakeHandleInterface */\n    public $handle;\n    public $gearStick;\n\n    /**\n     * Inject annotation at constructor is just for human, not mandatory.\n     */\n    public function __construct(FakeEngineInterface $engine)\n    {\n        $this->engine = $engine;\n    }\n\n    #[Inject]\n    public function setTires(FakeTyreInterface $frontTyre, FakeTyreInterface $rearTyre): void\n    {\n        $this->frontTyre = $frontTyre;\n        $this->rearTyre = $rearTyre;\n    }\n\n    #[Inject(optional: true)]\n    public function setHardtop(FakeHardtopInterface $hardtop): void\n    {\n        $this->hardtop = $hardtop;\n    }\n\n    #[Inject]\n    public function setMirrors(#[Named('right')] FakeMirrorInterface $rightMirror, #[Named('left')] FakeMirrorInterface $leftMirror): void\n    {\n        $this->rightMirror = $rightMirror;\n        $this->leftMirror = $leftMirror;\n    }\n\n    #[Inject]\n    public function setSpareMirror(#[Named('right')] FakeMirrorInterface $rightMirror): void\n    {\n        $this->spareMirror = $rightMirror;\n    }\n\n    #[Inject]\n    public function setHandle(FakeHandleInterface $handle): void\n    {\n        $this->handle = $handle;\n    }\n\n    #[FakeGearStickInject('leather')]\n    public function setGearStick(FakeGearStickInterface $stick): void\n    {\n        $this->gearStick = $stick;\n    }\n\n    #[PostConstruct]\n    public function postConstruct(): void\n    {\n        $isEngineInstalled = $this->engine instanceof FakeEngine;\n        $isTyreInstalled = $this->frontTyre instanceof FakeTyre;\n        if ($isEngineInstalled && $isTyreInstalled) {\n            $this->isConstructed = true;\n        }\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeCarEngine.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeCarEngine extends FakeEngine\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeCarEngineModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeCarEngineModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeEngine::class)->to(FakeCarEngine::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeCarInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeCarInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeCarModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\NullInterceptor;\n\nclass FakeCarModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeCarInterface::class)->to(FakeCar::class); // dependent\n        $this->bind(FakeEngineInterface::class)->to(FakeEngine::class); // constructor\n        $this->bind(FakeHardtopInterface::class)->to(FakeHardtop::class); // optional setter\n        $this->bind(FakeTyreInterface::class)->to(FakeTyre::class); // setter\n        $this->bind(FakeMirrorInterface::class)->annotatedWith('right')->to(FakeMirrorRight::class)->in(Scope::SINGLETON); // named binding\n        $this->bind(FakeMirrorInterface::class)->annotatedWith('left')->to(FakeMirrorLeft::class)->in(Scope::SINGLETON); // named binding\n        $this->bind('')->annotatedWith('logo')->toInstance('momo');\n        $this->bind(FakeHandleInterface::class)->toProvider(FakeHandleProvider::class);\n        $this->bind(FakeGearStickInterface::class)->annotatedWith(FakeGearStickInject::class)->to(FakeLeatherGearStick::class);\n        $this->bindInterceptor(\n            $this->matcher->any(),\n            $this->matcher->any('start'),\n            [NullInterceptor::class]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeClassInstanceBindModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeClassInstanceBindModule extends AbstractModule\n{\n    private $object;\n\n    public function __construct($object)\n    {\n        $this->object = $object;\n        parent::__construct();\n    }\n\n    protected function configure()\n    {\n        $this->bind(FakeEngine::class)->toInstance($this->object);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeClassWithBcParameterQualifier.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Annotation\\FakeInjectOne;\n\nclass FakeClassWithBcParameterQualifier\n{\n    public $gearStick;\n\n    /**\n     * Method-level #[FakeInjectOne] without parameter-level qualifier\n     * BcParameterQualifier should apply FakeInjectOne to the parameter\n     */\n    #[FakeInjectOne]\n    public function setGearStick(FakeGearStickInterface $gearStick): void\n    {\n        $this->gearStick = $gearStick;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeConcreteClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeConcreteClass\n{\n    public function __construct(FakeAbstractClass $class)\n    {\n        unset($class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeConstant.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Attribute;\nuse Ray\\Di\\Di\\Qualifier;\n\n#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER)]\n#[Qualifier]\nfinal class FakeConstant\n{\n    public function __construct(\n        public $value = null\n    ) {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeConstantConsumer.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Inject;\n\nclass FakeConstantConsumer\n{\n    public $constantByConstruct;\n    public $constantBySetter;\n    public $defaultByConstruct;\n    public $defaultBySetter;\n    public $setterConstantWithoutVarName;\n\n    public function __construct(#[FakeConstant('constant')] $constant, $default = 'default_construct')\n    {\n        $this->constantByConstruct = $constant;\n        $this->defaultByConstruct = $default;\n    }\n\n    #[Inject]\n    public function setFakeConstant(#[FakeConstant('constant')] $constant, $default = 'default_setter'): void\n    {\n        $this->constantBySetter = $constant;\n        $this->defaultBySetter = $default;\n    }\n\n    #[Inject]\n    public function setFakeConstantWithoutVarName(#[FakeConstant] $constant): void\n    {\n        $this->setterConstantWithoutVarName = $constant;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeConstantInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeConstantInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeConstantModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeConstantModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind()->annotatedWith(FakeConstant::class)->toInstance('kuma');\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeContextualModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeContextualModule extends AbstractModule\n{\n    private $context;\n\n    public function __construct($context)\n    {\n        $this->context = $context;\n        parent::__construct();\n    }\n\n    protected function configure()\n    {\n        $this->bind(FakeRobotInterface::class)->toProvider(FakeContextualProvider::class, $this->context);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeContextualProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeContextualProvider implements ProviderInterface, SetContextInterface\n{\n    private $context;\n\n    /**\n     * {@inheritdoc}\n     */\n    public function setContext($context)\n    {\n        $this->context = $context;\n    }\n\n    public function get()\n    {\n        return new FakeContextualRobot($this->context);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeContextualRobot.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeContextualRobot implements FakeRobotInterface\n{\n    public $context;\n\n    public function __construct($context)\n    {\n        $this->context = $context;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeDoubleInterceptor.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\nclass FakeDoubleInterceptor implements MethodInterceptor, FakeDoubleInterceptorInterface\n{\n    public function invoke(MethodInvocation $invocation)\n    {\n        $result = $invocation->proceed();\n\n        return $result * 2;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeDoubleInterceptorInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\ninterface FakeDoubleInterceptorInterface extends MethodInterceptor\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeEngine.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeEngine implements FakeEngineInterface\n{\n    public function foo()\n    {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeEngine2.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeEngine2 implements FakeEngineInterface\n{\n    public function foo()\n    {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeEngine3.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeEngine3 implements FakeEngineInterface\n{\n    public function foo()\n    {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeEngineInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeEngineInterface\n{\n    public function foo();\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeEngineProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeEngineProvider implements ProviderInterface\n{\n    public function get()\n    {\n        return new FakeEngine();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeEngineToProviderModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeEngineToProviderModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeEngine::class)->toProvider(FakeEngineProvider::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeFormerBindingHasPriorityModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeFormerBindingHasPriorityModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->install(new FakeInstanceBindModule());\n        $this->install(new FakeInstanceBindModuleOneTo3());\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeGearStickInject.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Attribute;\nuse Ray\\Di\\Di\\InjectInterface;\nuse Ray\\Di\\Di\\Qualifier;\n\n#[Attribute(Attribute::TARGET_METHOD)]\n#[Qualifier]\nclass FakeGearStickInject implements InjectInterface\n{\n    public $value;\n\n    public function isOptional()\n    {\n        return true;\n    }\n\n    public function __construct($value)\n    {\n        $this->value = $value['value'] ?? $value;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeGearStickInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeGearStickInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeGearStickProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse InvalidArgumentException;\n\nclass FakeGearStickProvider implements ProviderInterface\n{\n    /** @var InjectionPointInterface */\n    private $ip;\n\n    public function __construct(InjectionPointInterface $ip)\n    {\n        $this->ip = $ip;\n    }\n\n    public function get()\n    {\n        $qualifiers = $this->ip->getQualifiers();\n        $type = null;\n\n        foreach ($qualifiers as $qualifier) {\n            if ($qualifier instanceof FakeGearStickInject) {\n                $type = $qualifier->value;\n            }\n        }\n\n        if ($type !== 'leather') {\n            throw new InvalidArgumentException('Invalid Gear Stick Type');\n        }\n\n        return new FakeLeatherGearStick();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeHandle.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeHandle implements FakeHandleInterface\n{\n    public $logo;\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeHandleBar.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Inject;\n\nclass FakeHandleBar\n{\n    public $rightMirror;\n    public $leftMirror;\n\n    #[Inject]\n    public function setMirrors(#[FakeRight] FakeMirrorInterface $rightMirror): void\n    {\n        $this->rightMirror = $rightMirror;\n    }\n\n    #[Inject]\n    public function setLeftMirror(#[FakeLeft] FakeMirrorInterface $leftMirror): void\n    {\n        $this->leftMirror = $leftMirror;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeHandleBarQualifier.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeHandleBarQualifier\n{\n    public function __construct(\n        #[FakeRight('rightMirror')]\n        public FakeMirrorInterface $rightMirror,\n        #[FakeLeft('leftMirror')]\n        public FakeMirrorInterface $leftMirror\n    ) {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeHandleInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeHandleInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeHandleProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Inject;\nuse Ray\\Di\\Di\\Named;\n\nclass FakeHandleProvider implements ProviderInterface\n{\n    private $logo;\n\n    #[Inject]\n    public function __construct(#[Named('logo')] $logo = 'nardi')\n    {\n        $this->logo = $logo;\n    }\n\n    public function get()\n    {\n        $handle = new FakeHandle();\n        $handle->logo = $this->logo;\n\n        return $handle;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeHardtop.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeHardtop implements FakeHardtopInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeHardtopInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeHardtopInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeInjectionPoint.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Named;\nuse ReflectionParameter;\n\nclass FakeInjectionPoint implements ProviderInterface\n{\n    public $ip;\n\n    public function __construct(#[Named('aa')] ReflectionParameter $ip)\n    {\n        $this->ip = $ip;\n    }\n\n    public function get()\n    {\n        if ($this->ip->getName()) {\n            return $this->ip;\n        }\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeInstallModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeInstallModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->install(new FakeInstanceBindModule());\n        $this->install(new FakeInstanceBindModule2());\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeInstanceBindModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Annotation\\FakeInjectOne;\n\nclass FakeInstanceBindModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind('')->annotatedWith('one')->toInstance(1);\n        $this->bind('')->annotatedWith(FakeInjectOne::class)->toInstance(1);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeInstanceBindModule2.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeInstanceBindModule2 extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind('')->annotatedWith('two')->toInstance(2);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeInstanceBindModuleOneTo3.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeInstanceBindModuleOneTo3 extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind('')->annotatedWith('one')->toInstance(3);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeInternalTypeModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse stdClass;\n\nuse function fopen;\n\nclass FakeInternalTypeModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind('')->annotatedWith('type-bool')->toInstance(false);\n        $this->bind('')->annotatedWith('type-int')->toInstance(1);\n        $this->bind('')->annotatedWith('type-string')->toInstance('1');\n        $this->bind('')->annotatedWith('type-array')->toInstance([1]);\n        $this->bind('')->annotatedWith('type-callable')->toInstance(static function () {\n        });\n        $this->bind('')->annotatedWith('type-object')->toInstance(new stdClass());\n        $this->bind('')->annotatedWith('type-resource')->toInstance(fopen('data://,', 'w'));\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeInternalTypes.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Named;\n\nclass FakeInternalTypes\n{\n    public $bool;\n    public $int;\n    public $string;\n    public $array;\n    public $callable;\n\n    public function __construct(\n        #[Named('type-bool')] bool $bool,\n        #[Named('type-int')] int $int,\n        #[Named('type-string')] string $string,\n        #[Named('type-array')] array $array,\n        #[Named('type-callable')] callable $callable\n    ) {\n        $this->bool = $bool;\n        $this->int = $int;\n        $this->string = $string;\n        $this->array = $array;\n        $this->callable = $callable;\n    }\n\n    public function stringId(string $id): void\n    {\n        unset($id);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeLeatherGearStick.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeLeatherGearStick implements FakeGearStickInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeLeft.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Attribute;\nuse Ray\\Di\\Di\\Qualifier;\n\n#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER)]\n#[Qualifier]\nclass FakeLeft\n{\n    public function __construct(\n        public $value = null\n    ) {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeLeftLeg.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeLeftLeg implements FakeLegInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeLegInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeLegInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeLogStringModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse stdClass;\n\nclass FakeLogStringModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind()->annotatedWith('null')->toInstance(null);\n        $this->bind()->annotatedWith('bool')->toInstance(true);\n        $this->bind()->annotatedWith('int')->toInstance(1);\n        $this->bind()->annotatedWith('string')->toInstance('1');\n        $this->bind()->annotatedWith('array')->toInstance([1]);\n        $this->bind()->annotatedWith('object')->toInstance(new stdClass());\n        $this->bind(FakeAopInterface::class)->to(FakeAop::class);\n        $this->bind(FakeRobotInterface::class)->toProvider(FakeRobotProvider::class)->in(Scope::SINGLETON);\n        $this->bindInterceptor(\n            $this->matcher->any(),\n            $this->matcher->startsWith('returnSame'),\n            [FakeDoubleInterceptor::class]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeMirrorInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeMirrorInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeMirrorLeft.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeMirrorLeft implements FakeMirrorInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeMirrorRight.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeMirrorRight implements FakeMirrorInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeModuleInModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeModuleInModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->install(new FakeInstanceBindModule2(new FakeInstanceBindModule()));\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeModuleInModuleOverride.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeModuleInModuleOverride extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->install(new FakeInstanceBindModuleOneTo3(new FakeInstanceBindModule()));\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeMultiBindingAnnotation.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n\nuse Ray\\Di\\Di\\Set;\nuse Ray\\Di\\MultiBinding\\Map;\n\nfinal class FakeMultiBindingAnnotation\n{\n    /** @var Map<FakeEngineInterface> */\n    public $engines;\n\n    /** @var Map<FakeRobotInterface> */\n    public $robots;\n\n    public function __construct(\n        #[Set(FakeEngineInterface::class)]\n        Map $engines,\n        #[Set(FakeRobotInterface::class)]\n        Map $robots\n    ){\n        $this->engines = $engines;\n        $this->robots = $robots;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeMultiBindingConsumer.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n\nuse Ray\\Di\\Di\\Set;\nuse Ray\\Di\\MultiBinding\\Map;\n\nfinal class FakeMultiBindingConsumer\n{\n    /**\n     * @param Map<FakeEngineInterface> $engines\n     * @param Map<FakeRobotInterface> $robots\n     */\n    public function __construct(\n        #[Set(FakeEngineInterface::class)] public Map $engines,\n        #[Set(FakeRobotInterface::class)] public Map $robots\n    ){}\n\n    public function testValid(): void\n    {\n        $f = $this->engines['one'];\n        // cause no error in psalm\n        $f->foo();\n    }\n\n    /**\n     * Test generic\n     *\n     * This tests generic expression of Map<FakeEngineInterface>\n     * Not called from any place. Created to confirm \"@psalm-suppress\" works with psalm\n     *\n     * @psalm-suppress UndefinedInterfaceMethod\n     */\n    public function testInvalid(): void\n    {\n        $f = $this->engines['one'];\n        // cause error in psalm\n        $f->warnUndefinedInterfaceMethod();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeOnionInterceptor2.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\nclass FakeOnionInterceptor2 implements MethodInterceptor\n{\n    public function invoke(MethodInvocation $invocation)\n    {\n        return $invocation->proceed();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeOnionInterceptor3.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\nclass FakeOnionInterceptor3 implements MethodInterceptor\n{\n    public function invoke(MethodInvocation $invocation)\n    {\n        return $invocation->proceed();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeOnionInterceptor4.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\MethodInterceptor;\nuse Ray\\Aop\\MethodInvocation;\n\nclass FakeOnionInterceptor4 implements MethodInterceptor\n{\n    public function invoke(MethodInvocation $invocation)\n    {\n        return $invocation->proceed();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeOpenCarModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeOpenCarModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeCarInterface::class)->to(FakeCar::class);\n        $this->bind(FakeEngineInterface::class)->to(FakeEngine::class);\n        // No hardtop, Go open !\n        // $this->bind(FakeHardtopInterface::class)->to(FakeHardtop::class);\n        $this->bind(FakeTyreInterface::class)->to(FakeTyre::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeOverrideInstallModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeOverrideInstallModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->install(new FakeInstanceBindModule());\n        $this->override(new FakeInstanceBindModuleOneTo3());\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakePdoModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PDO;\n\nclass FakePdoModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(PDO::class)->in(Scope::SINGLETON);\n        $this->bind(PDO::class)->toConstructor(PDO::class, ['dsn' => 'pdo_dsn']);\n        $this->bind()->annotatedWith('pdo_dsn')->toInstance('sqlite::memory:');\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakePhp8Car.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Annotation\\FakeInjectOne;\nuse Ray\\Di\\Annotation\\FakeNotQualifer;\nuse Ray\\Di\\Di\\Inject;\nuse Ray\\Di\\Di\\Named;\nuse Ray\\Di\\Di\\PostConstruct;\nuse Ray\\Di\\Annotation\\FakeLeft;\nuse Ray\\Di\\Annotation\\FakeRight;\n\nclass FakePhp8Car implements FakeCarInterface\n{\n    public $engine;\n    public $hardtop;\n    public $frontTyre;\n    public $rearTyre;\n    public $isConstructed = false;\n    public $rightMirror;\n    public $constructerInjectedRightMirror;\n    public $leftMirror;\n    public $qualfiedRightMirror;\n    public $qualfiedLeftMirror;\n    public $one;\n\n    /** @var FakeHandleInterface */public $handle;\n    public $gearStick;\n\n    public function __construct(FakeEngineInterface $engine, #[Named('right')] FakeMirrorInterface $rightMirror)\n    {\n        $this->engine = $engine;\n        $this->constructerInjectedRightMirror = $rightMirror;\n    }\n\n    #[Inject]\n    public function setTires(FakeTyreInterface $frontTyre, FakeTyreInterface $rearTyre): void\n    {\n        $this->frontTyre = $frontTyre;\n        $this->rearTyre = $rearTyre;\n    }\n\n    #[Inject(optional: true)]\n    public function setHardtop(FakeHardtopInterface $hardtop): void\n    {\n        $this->hardtop = $hardtop;\n    }\n\n    #[Inject]\n    public function setMirrors(#[Named('right')] FakeMirrorInterface $rightMirror, #[Named('left')] FakeMirrorInterface $leftMirror): void\n    {\n        $this->rightMirror = $rightMirror;\n        $this->leftMirror = $leftMirror;\n    }\n\n    #[Inject]\n    public function setQualiferMirrors(#[FakeRight] FakeMirrorInterface $rightMirror, #[FakeLeft] FakeMirrorInterface $leftMirror): void\n    {\n        $this->qualfiedRightMirror = $rightMirror;\n        $this->qualfiedLeftMirror = $leftMirror;\n    }\n\n    #[Inject]\n    public function notQualifer(#[FakeNotQualifer] ?FakeMirrorInterface $rightMirror =  null): void\n    {\n    }\n\n    /**\n     * Test provider attribute\n     *\n     * @see FakeHandleProvider\n     */\n    #[Inject]\n    public function setHandle(FakeHandleInterface $handle){\n        $this->handle = $handle;\n    }\n\n    /**\n     * Custom inject annotation\n     */\n    #[FakeGearStickInject('leather')]\n    public function setGearStick(FakeGearStickInterface $stick): void\n    {\n        $this->gearStick = $stick;\n    }\n\n    #[PostConstruct]\n    public function postConstruct(): void\n    {\n        $isEngineInstalled = $this->engine instanceof FakeEngine;\n        $isTyreInstalled = $this->frontTyre instanceof FakeTyre;\n        if ($isEngineInstalled && $isTyreInstalled) {\n            $this->isConstructed = true;\n        }\n    }\n\n    #[Inject]\n    public function setOne(#[FakeInjectOne] int $one)\n    {\n        $this->one = $one;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakePhp8CarModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Annotation\\FakeInjectOne;\nuse Ray\\Di\\Annotation\\FakeLeft;\nuse Ray\\Di\\Annotation\\FakeRight;\n\nclass FakePhp8CarModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeCarInterface::class)->to(FakePhp8Car::class); // dependent\n        $this->bind(FakeEngineInterface::class)->to(FakeEngine::class); // constructor\n        $this->bind(FakeTyreInterface::class)->to(FakeTyre::class); // setter\n        $this->bind(FakeMirrorInterface::class)->annotatedWith('right')->to(FakeMirrorRight::class)->in(Scope::SINGLETON); // named binding\n        $this->bind(FakeMirrorInterface::class)->annotatedWith('left')->to(FakeMirrorLeft::class)->in(Scope::SINGLETON); // named binding\n        $this->bind(FakeMirrorInterface::class)->annotatedWith(FakeLeft::class)->to(FakeMirrorLeft::class); // named binding\n        $this->bind(FakeMirrorInterface::class)->annotatedWith(FakeRight::class)->to(FakeMirrorRight::class); // named binding\n        $this->bind('')->annotatedWith('logo')->toInstance('momo');\n        $this->bind(FakeHandleInterface::class)->toProvider(FakeHandleProvider::class);\n        $this->bind(FakeGearStickInterface::class)->annotatedWith(FakeGearStickInject::class)->toProvider(FakeGearStickProvider::class);\n        $this->bind()->annotatedWith(FakeInjectOne::class)->toInstance(1);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakePhp8HandleProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Inject;\nuse Ray\\Di\\Di\\Named;\n\nclass FakePhp8HandleProvider implements ProviderInterface\n{\n    private $logo;\n\n    public function __construct(#[Named('logo')] $logo = 'nardi')\n    {\n        $this->logo = $logo;\n    }\n\n    public function get()\n    {\n        $handle = new FakeHandle();\n        $handle->logo = $this->logo;\n\n        return $handle;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakePriorityModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakePriorityModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bindInterceptor(\n            $this->matcher->any(),\n            $this->matcher->any(),\n            [FakeDoubleInterceptor::class]\n        );\n\n        $this->bindPriorityInterceptor(\n            $this->matcher->any(),\n            $this->matcher->any(),\n            [FakeDoubleInterceptor::class]\n        );\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakePropConstruct.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Named;\n\nclass FakePropConstruct\n{\n    public function __construct(\n        #[Named('abc')] public readonly string $abc)\n    {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeRenameModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeRenameModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->rename(FakeRobotInterface::class, 'original');\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeRight.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Attribute;\nuse Ray\\Di\\Di\\Qualifier;\n\n#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER)]\n#[Qualifier]\nclass FakeRight\n{\n    public function __construct(\n        public $value = null\n    ) {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeRightLeg.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeRightLeg implements FakeLegInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeRobot.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeRobot implements FakeRobotInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeRobotInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeRobotInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeRobotProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeRobotProvider implements ProviderInterface\n{\n    public function get()\n    {\n        return new FakeRobot();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeRobotTeam.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeRobotTeam\n{\n    public $robot1;\n    public $robot2;\n\n    public function __construct(FakeRobot $robot1, FakeRobot $robot2)\n    {\n        $this->robot1 = $robot1;\n        $this->robot2 = $robot2;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeSet.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Di\\Di\\Inject;\nuse Ray\\Di\\Di\\Set;\n\nfinal class FakeSet\n{\n    /**\n     * @param Provider<FakeEngineInterface> $provider\n     */\n    public function __construct(#[Set(FakeEngineInterface::class)] public ProviderInterface $provider)\n    {\n        $this->provider = $provider;\n    }\n\n    public function warn(): void\n    {\n        // valid method\n        $this->provider->get()->foo();\n\n        // invalid method (but phpstan does not detect the error)\n        /** @psalm-suppress UndefinedInterfaceMethod */\n        $this->provider->get()->bar();\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeSetNotFoundWithMap.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n\nuse Ray\\Di\\Di\\Set;\nuse Ray\\Di\\MultiBinding\\Map;\n\nfinal class FakeSetNotFoundWithMap\n{\n    /**\n     * @var Map<FakeEngineInterface>\n     */\n    public $engines;\n\n    /**\n     * This property should Set annotated for setProviderButNotSetFound method.\n     * SetNotFound exception will be thrown.\n     */\n    public $engineProvider;\n\n    public function __construct(\n        Map $engines\n    ){\n        $this->engines = $engines;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeSetNotFoundWithProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n\nuse Ray\\Di\\Di\\Set;\nuse Ray\\Di\\MultiBinding\\Map;\n\nfinal class FakeSetNotFoundWithProvider\n{\n    /**\n     * This property should Set annotated for setProviderButNotSetFound method.\n     * SetNotFound exception will be thrown.\n     */\n    public $engineProvider;\n\n    public function __construct(\n        ProviderInterface $engineProvider\n    ){\n        $this->$engineProvider = $engineProvider;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeToBindInvalidClassModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeToBindInvalidClassModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind('\\Ray\\Di\\Mock\\Dependency\\RobotInterface')->to('InvalidClassXXX');\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeToBindModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeToBindModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeRobotInterface::class)->to(FakeRobot::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeToBindSingletonModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeToBindSingletonModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeRobotInterface::class)->to(FakeRobot::class)->in(Scope::SINGLETON);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeToConstructorRobot.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeToConstructorRobot implements FakeRobotInterface\n{\n    public $leg;\n    public $tmpDir;\n    public $engine;\n\n    public function __construct(FakeLegInterface $leg, $tmpDir)\n    {\n        $this->leg = $leg;\n        $this->tmpDir = $tmpDir;\n    }\n\n    public function setEngine(FakeEngineInterface $engine): void\n    {\n        $this->engine = $engine;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeToProviderBindModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeToProviderBindModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeRobotInterface::class)->toProvider(FakeRobotProvider::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeToProviderSingletonBindModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeToProviderSingletonBindModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeRobotInterface::class)->toProvider(FakeRobotProvider::class)->in(Scope::SINGLETON);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeTyre.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeTyre implements FakeTyreInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeTyreInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\ninterface FakeTyreInterface\n{\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeUnNamedClass.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeUnNamedClass\n{\n    public function __construct(string $value)\n    {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeUnNamedModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeUnNamedModule extends AbstractModule\n{\n    protected function configure(): void\n    {\n        $this->bind(FakeUnNamedClass::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeUntarget.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * This file is part of the _package_ package\n */\n\nnamespace Ray\\Di;\n\nclass FakeUntarget\n{\n    public $child;\n\n    public function __construct(FakeUntargetChild $child)\n    {\n        $this->child = $child;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeUntargetChild.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * This file is part of the _package_ package\n */\n\nnamespace Ray\\Di;\n\nclass FakeUntargetChild\n{\n    public $val;\n\n    public function __construct($val)\n    {\n        $this->val = $val;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeUntargetModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * This file is part of the _package_ package\n */\n\nnamespace Ray\\Di;\n\nclass FakeUntargetModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeUntargetChild::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeUntargetToIntanceModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * This file is part of the _package_ package\n */\n\nnamespace Ray\\Di;\n\nclass FakeUntargetToIntanceModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $instance = new FakeUntarget(new FakeUntargetChild(1));\n        $this->bind(FakeUntarget::class)->toInstance($instance);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeWalkRobot.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\n#[FakeConstant('class_constant_val')]\nclass FakeWalkRobot\n{\n    /** @var FakeLegInterface */\n    public $leftLeg;\n\n    /** @var FakeLegInterface */\n    public $rightLeg;\n\n    #[FakeConstant(10)]\n    #[FakeAnnoMethod1]\n    public function __construct(FakeLegInterface $rightLeg, FakeLegInterface $leftLeg)\n    {\n        $this->rightLeg = $rightLeg;\n        $this->leftLeg = $leftLeg;\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeWalkRobotLegProvider.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse InvalidArgumentException;\n\nclass FakeWalkRobotLegProvider implements ProviderInterface\n{\n    /** @var InjectionPointInterface */\n    private $ip;\n\n    public function __construct(InjectionPointInterface $ip)\n    {\n        $this->ip = $ip;\n    }\n\n    public function get()\n    {\n        $varName = $this->ip->getParameter()->getName();\n        if ($varName === 'leftLeg') {\n            return new FakeLeftLeg();\n        }\n\n        if ($varName === 'rightLeg') {\n            return new FakeRightLeg();\n        }\n\n        throw new InvalidArgumentException('Unexpected var name for LegInterface: ' . $varName);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakeWalkRobotModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakeWalkRobotModule extends AbstractModule\n{\n    protected function configure()\n    {\n        $this->bind(FakeLegInterface::class)->toProvider(FakeWalkRobotLegProvider::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/FakelNoConstructorCallModule.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nclass FakelNoConstructorCallModule extends AbstractModule\n{\n    public function __construct()\n    {\n    }\n\n    public function configure()\n    {\n    }\n}\n"
  },
  {
    "path": "tests/di/Fake/NullVisitor.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Aop\\Bind;\nuse ReflectionParameter;\n\nfinal class NullVisitor implements VisitorInterface\n{\n    /** @inheritDoc */\n    public function visitDependency(\n        NewInstance $newInstance,\n        ?string $postConstruct,\n        bool $isSingleton\n    ) {\n        $newInstance->accept($this);\n\n        return true;\n    }\n\n    /** @inheritDoc */\n    public function visitProvider(\n        Dependency $dependency,\n        string $context,\n        bool $isSingleton\n    ) {\n        return true;\n    }\n\n    /** @inheritDoc */\n    public function visitInstance($value)\n    {\n        return $value;\n    }\n\n    /** @inheritDoc */\n    public function visitAspectBind(Bind $aopBind)\n    {\n        return $aopBind;\n    }\n\n    /** @inheritDoc */\n    public function visitNewInstance(\n        string $class,\n        SetterMethods $setterMethods,\n        ?Arguments $arguments,\n        ?AspectBind $bind\n    ) {\n        $setterMethods->accept($this);\n        if ($arguments) {\n            $arguments->accept($this);\n        }\n        if ($bind instanceof AspectBind) {\n            $bind->accept($this);\n        }\n    }\n\n    /** @inheritDoc */\n    public function visitSetterMethods(\n        array $setterMethods\n    ) {\n        foreach ($setterMethods as $setterMethod) {\n            $setterMethod->accept($this);\n        }\n    }\n\n    /** @inheritDoc */\n    public function visitSetterMethod(string $method, Arguments $arguments)\n    {\n        $arguments->accept($this);\n    }\n\n    /** @inheritDoc */\n    public function visitArguments(array $arguments)\n    {\n        foreach ($arguments as $argument) {\n            $argument->accept($this);\n        }\n    }\n\n    /** @inheritDoc */\n    public function visitArgument(\n        string $index,\n        bool $isDefaultAvailable,\n        $defaultValue,\n        ReflectionParameter $parameter\n    ): void {\n    }\n}\n"
  },
  {
    "path": "tests/di/GrapherTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse LogicException;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\nuse function file_get_contents;\nuse function is_string;\nuse function passthru;\nuse function unserialize;\n\nclass GrapherTest extends TestCase\n{\n    public function testNew(): void\n    {\n        $grapher = new Grapher(new FakeInstanceBindModule(), __DIR__ . '/tmp');\n        $this->assertInstanceOf(Grapher::class, $grapher);\n    }\n\n    public function testGetInstanceWithArgs(): void\n    {\n        $grapher = new Grapher(new FakeUntargetModule(), __DIR__ . '/tmp');\n        $instance = $grapher->newInstanceArgs(FakeUntargetChild::class, ['1']);\n        $this->assertInstanceOf(FakeUntargetChild::class, $instance);\n        $this->assertSame('1', $instance->val);\n    }\n\n    public function testAopClassAutoloader(): void\n    {\n        passthru('php ' . __DIR__ . '/script/grapher.php');\n        $cacheFile = __DIR__ . '/script/grapher.php.txt';\n        $cache = file_get_contents($cacheFile);\n        if (! is_string($cache)) {\n            throw new LogicException();\n        }\n\n        $grapher = unserialize($cache);\n        assert($grapher instanceof Grapher);\n        $this->assertInstanceOf(Grapher::class, $grapher);\n\n        $instance = $grapher->newInstanceArgs(FakeAopInterface::class, ['a']);\n        /** @var FakeAopGrapher $instance */\n        $result = $instance->returnSame(2);\n        $this->assertSame(4, $result);\n        $this->assertSame('a', $instance->a);\n    }\n}\n"
  },
  {
    "path": "tests/di/InjectionPointTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse ReflectionParameter;\n\nclass InjectionPointTest extends TestCase\n{\n    /** @var InjectionPointInterface */\n    private $ip;\n\n    /** @var ReflectionParameter */\n    private $parameter;\n\n    protected function setUp(): void\n    {\n        $this->parameter = new ReflectionParameter([FakeWalkRobot::class, '__construct'], 'rightLeg');\n        $this->ip = new InjectionPoint($this->parameter);\n    }\n\n    public function testGetParameter(): void\n    {\n        $actual = $this->ip->getParameter();\n        $this->assertSame($this->parameter, $actual);\n    }\n\n    public function testGetMethod(): void\n    {\n        $actual = $this->ip->getMethod();\n        $this->assertSame((string) $this->parameter->getDeclaringFunction(), (string) $actual);\n    }\n\n    public function testGetClass(): void\n    {\n        $actual = $this->ip->getClass();\n        $this->assertSame((string) $this->parameter->getDeclaringClass(), (string) $actual);\n    }\n\n    public function testGetQualifiers(): void\n    {\n        $annotations = $this->ip->getQualifiers();\n        $this->assertCount(1, $annotations);\n        $this->assertInstanceOf(FakeConstant::class, $annotations[0]);\n    }\n}\n"
  },
  {
    "path": "tests/di/InjectionPointsTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\n\nuse function get_class;\n\nclass InjectionPointsTest extends TestCase\n{\n    /** @var InjectionPoints */\n    protected $injectionPoints;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n        $this->injectionPoints = (new InjectionPoints())->addMethod('setTires')->addOptionalMethod('setHardtop');\n    }\n\n    public function testNew(): void\n    {\n        $this->assertInstanceOf(InjectionPoints::class, $this->injectionPoints);\n    }\n\n    public function testInvoke(): SetterMethods\n    {\n        $car = new FakeCar(new FakeEngine());\n        $setterMethods = $this->injectionPoints->__invoke(get_class($car));\n        $this->assertInstanceOf(SetterMethods::class, $setterMethods);\n\n        return $setterMethods;\n    }\n\n    /**\n     * @depends testInvoke\n     */\n    public function testSetterMethod(SetterMethods $setterMethod): void\n    {\n        $car = new FakeCar(new FakeEngine());\n        $container = (new FakeCarModule())->getContainer();\n        $setterMethod($car, $container);\n        $this->assertInstanceOf(FakeTyre::class, $car->frontTyre);\n        $this->assertInstanceOf(FakeTyre::class, $car->rearTyre);\n        $this->assertInstanceOf(FakeHardtop::class, $car->hardtop);\n    }\n\n    /**\n     * @depends testInvoke\n     */\n    public function testSetterMethodOptional(SetterMethods $setterMethod): void\n    {\n        $car = new FakeCar(new FakeEngine());\n        // no hardtop installed with this module\n        $container = (new FakeOpenCarModule())->getContainer();\n        $setterMethod($car, $container);\n        $this->assertInstanceOf(FakeTyre::class, $car->frontTyre);\n        $this->assertInstanceOf(FakeTyre::class, $car->rearTyre);\n        $this->assertNull($car->hardtop);\n    }\n}\n"
  },
  {
    "path": "tests/di/InjectorTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse LogicException;\nuse PDO;\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Aop\\NullInterceptor;\nuse Ray\\Di\\Exception\\Unbound;\n\nuse function assert;\nuse function defined;\nuse function file_get_contents;\nuse function is_string;\nuse function passthru;\nuse function serialize;\nuse function spl_object_hash;\nuse function unlink;\nuse function unserialize;\n\nclass InjectorTest extends TestCase\n{\n    public function testNew(): void\n    {\n        $injector = new Injector(new FakeInstanceBindModule());\n        $this->assertInstanceOf(Injector::class, $injector);\n    }\n\n    public function testGetToInstance(): void\n    {\n        $injector = new Injector(new FakeInstanceBindModule());\n        $instance = $injector->getInstance('', 'one');\n        $this->assertSame(1, $instance);\n    }\n\n    public function testToInstance(): void\n    {\n        $engine = new FakeEngine();\n        $injector = new Injector(new FakeClassInstanceBindModule($engine));\n        $this->assertSame($engine, $injector->getInstance(FakeEngine::class));\n    }\n\n    public function testUnbound(): void\n    {\n        $this->expectException(Unbound::class);\n        $injector = new Injector(new FakeInstanceBindModule());\n        $injector->getInstance('', 'invalid-binding-xxx');\n    }\n\n    public function testInstall(): void\n    {\n        $injector = new Injector(new FakeInstallModule());\n        $instance = $injector->getInstance('', 'one');\n        $this->assertSame(1, $instance);\n        $instance = $injector->getInstance('', 'two');\n        $this->assertSame(2, $instance);\n    }\n\n    public function testFormerBindingHasPriority(): void\n    {\n        $injector = new Injector(new FakeFormerBindingHasPriorityModule());\n        $instance = $injector->getInstance('', 'one');\n        $this->assertSame(1, $instance);\n    }\n\n    public function testLatterBindingHasPriorityWithThisParameter(): void\n    {\n        $injector = new Injector(new FakeOverrideInstallModule());\n        $instance = $injector->getInstance('', 'one');\n        $this->assertSame(3, $instance);\n    }\n\n    public function testModuleInModule(): void\n    {\n        $injector = new Injector(new FakeModuleInModule());\n        $instance = $injector->getInstance('', 'one');\n        $this->assertSame(1, $instance);\n        $instance = $injector->getInstance('', 'two');\n        $this->assertSame(2, $instance);\n    }\n\n    public function testModuleInModuleOverride(): void\n    {\n        $injector = new Injector(new FakeModuleInModuleOverride());\n        $instance = $injector->getInstance('', 'one');\n        $this->assertSame(3, $instance);\n    }\n\n    public function testToBinding(): void\n    {\n        $injector = new Injector(new FakeToBindModule());\n        $instance = $injector->getInstance(FakeRobotInterface::class);\n        $this->assertInstanceOf(FakeRobot::class, $instance);\n    }\n\n    public function testClassToClassBinding(): void\n    {\n        $injector = new Injector(new FakeCarEngineModule());\n        $instance = $injector->getInstance(FakeEngine::class);\n        $this->assertInstanceOf(FakeCarEngine::class, $instance);\n    }\n\n    public function testToBindingPrototype(): void\n    {\n        $injector = new Injector(new FakeToBindModule());\n        $instance1 = $injector->getInstance(FakeRobotInterface::class);\n        $instance2 = $injector->getInstance(FakeRobotInterface::class);\n        $this->assertNotSame(spl_object_hash($instance1), spl_object_hash($instance2));\n    }\n\n    public function testToBindingSingleton(): void\n    {\n        $injector = new Injector(new FakeToBindSingletonModule());\n        $instance1 = $injector->getInstance(FakeRobotInterface::class);\n        $instance2 = $injector->getInstance(FakeRobotInterface::class);\n        $this->assertSame(spl_object_hash($instance1), spl_object_hash($instance2));\n    }\n\n    public function testToProviderBinding(): void\n    {\n        $injector = new Injector(new FakeToProviderBindModule());\n        $instance1 = $injector->getInstance(FakeRobotInterface::class);\n        $instance2 = $injector->getInstance(FakeRobotInterface::class);\n        $this->assertNotSame(spl_object_hash($instance1), spl_object_hash($instance2));\n    }\n\n    public function testClassToProviderBinding(): void\n    {\n        $injector = new Injector(new FakeEngineToProviderModule());\n        $instance = $injector->getInstance(FakeEngine::class);\n        $this->assertInstanceOf(FakeEngine::class, $instance);\n    }\n\n    public function testToProviderBindingSingleton(): void\n    {\n        $injector = new Injector(new FakeToProviderSingletonBindModule());\n        $instance1 = $injector->getInstance(FakeRobotInterface::class);\n        $instance2 = $injector->getInstance(FakeRobotInterface::class);\n        $this->assertSame(spl_object_hash($instance1), spl_object_hash($instance2));\n    }\n\n    public function testGetConcreteClass(): void\n    {\n        $injector = new Injector();\n        $robot = $injector->getInstance(FakeRobot::class);\n        $this->assertInstanceOf(FakeRobot::class, $robot);\n    }\n\n    public function testGetConcreteHavingDependency(): void\n    {\n        $injector = new Injector(new class extends AbstractModule{\n            protected function configure()\n            {\n                $this->bind(FakeRobot::class);\n            }\n        });\n        $team = $injector->getInstance(FakeRobotTeam::class);\n        $this->assertInstanceOf(FakeRobotTeam::class, $team);\n        $this->assertInstanceOf(FakeRobot::class, $team->robot1);\n        $this->assertInstanceOf(FakeRobot::class, $team->robot2);\n    }\n\n    public function testGetConcreteClassWithModule(): void\n    {\n        $injector = new Injector(new FakeCarModule());\n        $car = $injector->getInstance(FakeCar::class);\n        $this->assertInstanceOf(FakeCar::class, $car);\n    }\n\n    public function testAnnotationBasedInjection(): Injector\n    {\n        $injector = new Injector(new FakeCarModule());\n        $car = $injector->getInstance(FakeCarInterface::class);\n        /** @var FakeCar $car */\n        $this->assertInstanceOf(FakeCar::class, $car);\n        $this->assertInstanceOf(FakeTyre::class, $car->frontTyre);\n        $this->assertInstanceOf(FakeTyre::class, $car->rearTyre);\n        $this->assertInstanceOf(FakeHardtop::class, $car->hardtop);\n        $this->assertInstanceOf(FakeMirrorInterface::class, $car->rightMirror);\n        $this->assertInstanceOf(FakeMirrorInterface::class, $car->spareMirror);\n        $this->assertSame(spl_object_hash($car->rightMirror), spl_object_hash($car->spareMirror));\n        $this->assertInstanceOf(FakeHandle::class, $car->handle);\n        $this->assertSame($car->handle->logo, 'momo');\n\n        return $injector;\n    }\n\n    /**\n     * @depends testAnnotationBasedInjection\n     */\n    public function testSerialize(Injector $injector): void\n    {\n        $extractedInjector = unserialize(serialize($injector));\n        assert($extractedInjector instanceof InjectorInterface);\n        $car = $extractedInjector->getInstance(FakeCarInterface::class);\n        $this->assertInstanceOf(FakeCar::class, $car);\n    }\n\n    public function testAop(): void\n    {\n        $injector = new Injector(new FakeAopModule());\n        $instance = $injector->getInstance(FakeAopInterface::class);\n        /** @var FakeAop $instance */\n        $result = $instance->returnSame(2);\n        $this->assertSame(4, $result);\n    }\n\n    public function testIntefaceBindingAop(): void\n    {\n        $module = new class extends AbstractModule{\n            protected function configure()\n            {\n                $this->bind(FakeAopInterface::class)->to(FakeAop::class);\n                $this->bind(FakeDoubleInterceptorInterface::class)->to(FakeDoubleInterceptor::class);\n                $this->bindInterceptor(\n                    $this->matcher->any(),\n                    $this->matcher->any(),\n                    [FakeDoubleInterceptorInterface::class]\n                );\n            }\n        };\n        $injector = new Injector($module);\n        $instance = $injector->getInstance(FakeAopInterface::class);\n        /** @var FakeAop $instance */\n        $result = $instance->returnSame(2);\n        $this->assertSame(4, $result);\n    }\n\n    public function testBuiltinBinding(): void\n    {\n        $instance = (new Injector())->getInstance(FakeBuiltin::class);\n        $this->assertInstanceOf(Injector::class, $instance->injector);\n    }\n\n    public function testSerializeBuiltinBinding(): void\n    {\n        $injector = unserialize(serialize(new Injector()));\n        assert($injector instanceof InjectorInterface);\n        $instance = $injector->getInstance(FakeBuiltin::class);\n        $this->assertInstanceOf(Injector::class, $instance->injector);\n    }\n\n    public function testAopBoundInDifferentModule(): void\n    {\n        $injector = new Injector(new FakeAopInstallModule());\n        $instance = $injector->getInstance(FakeAopInterface::class);\n        /** @var FakeAop $instance */\n        $result = $instance->returnSame(2);\n        $this->assertSame(4, $result);\n    }\n\n    public function testAopBoundInDifferentModuleAfterAnotherBinding(): void\n    {\n        $injector = new Injector(new FakeAopInstallModule(new FakeAopModule()));\n        $instance = $injector->getInstance(FakeAopInterface::class);\n        /** @var FakeAop $instance */\n        $result = $instance->returnSame(2);\n        $this->assertSame(8, $result);\n    }\n\n    public function testAopBoundDoublyInDifferentModule(): void\n    {\n        $injector = new Injector(new FakeAopDoublyInstallModule());\n        $instance = $injector->getInstance(FakeAopInterface::class);\n        /** @var FakeAop $instance */\n        $result = $instance->returnSame(2);\n        $this->assertSame(8, $result);\n    }\n\n    public function testAopClassAutoloader(): void\n    {\n        passthru('php ' . __DIR__ . '/script/aop.php');\n        $cacheFile = __DIR__ . '/script/aop.php.cache.txt';\n        $cache = file_get_contents($cacheFile);\n        if (! is_string($cache)) {\n            throw new LogicException();\n        }\n\n        $injector = unserialize($cache);\n        if (! $injector instanceof Injector) {\n            throw new LogicException();\n        }\n\n        $instance = $injector->getInstance(FakeAopInterface::class);\n        /** @var FakeAop $instance */\n        $result = $instance->returnSame(2);\n        $this->assertSame(4, $result);\n        unlink($cacheFile);\n    }\n\n    public function testAopOnDemandByUnboundConcreteClass(): void\n    {\n        $injector = new Injector(new FakeAopInterceptorModule());\n        $instance = $injector->getInstance(FakeAop::class);\n        $result = $instance->returnSame(2);\n        $this->assertSame(4, $result);\n    }\n\n    public function testBindOrder(): void\n    {\n        $injector = new Injector(new FakeAnnoModule());\n        $instance = $injector->getInstance(FakeAnnoOrderClass::class);\n        $instance->get();\n        $expect = [FakeAnnoInterceptor4::class, FakeAnnoInterceptor1::class, FakeAnnoInterceptor2::class, FakeAnnoInterceptor3::class, FakeAnnoInterceptor5::class];\n        $this->assertSame($expect, FakeAnnoClass::$order);\n    }\n\n    public function testAnnotateConstant(): void\n    {\n        $instance = (new Injector(new FakeConstantModule()))->getInstance(FakeConstantConsumer::class);\n        $this->assertSame('default_construct', $instance->defaultByConstruct);\n    }\n\n    public function testContextualDependencyInjection(): void\n    {\n        $injector = new Injector(new FakeWalkRobotModule());\n        $robot = $injector->getInstance(FakeWalkRobot::class);\n        $this->assertInstanceOf(FakeLeftLeg::class, $robot->leftLeg);\n        $this->assertInstanceOf(FakeRightLeg::class, $robot->rightLeg);\n    }\n\n    public function testNewAbstract(): void\n    {\n        $this->expectException(Unbound::class);\n        (new Injector())->getInstance(FakeConcreteClass::class);\n    }\n\n    /** @requires OS ^Linux|^Darwin */\n    public function testIsOptionalValue(): void\n    {\n        if (! defined('HHVM_VERSION')) {\n            $pdo = (new Injector(new FakePdoModule()))->getInstance(PDO::class);\n            $this->assertInstanceOf(PDO::class, $pdo);\n        }\n    }\n\n    public function testInternalTypes(): void\n    {\n        $injector = new Injector(new FakeInternalTypeModule());\n        $types = $injector->getInstance(FakeInternalTypes::class);\n        $this->assertInstanceOf(FakeInternalTypes::class, $types);\n    }\n\n    /** @requires OS ^Linux|^Darwin */\n    public function testToConstructor(): void\n    {\n        $module = new class extends AbstractModule {\n            protected function configure()\n            {\n                $this->bind(PDO::class)->toConstructor(\n                    PDO::class,\n                    ['dsn' => 'pdo_dsn']\n                )->in(Scope::SINGLETON);\n                $this->bind()->annotatedWith('pdo_dsn')->toInstance('sqlite::memory:');\n            }\n        };\n        $injector = new Injector($module);\n        $pdo = $injector->getInstance(PDO::class);\n        $this->assertInstanceOf(PDO::class, $pdo);\n    }\n\n    public function testNullObject(): void\n    {\n        $injector = (new Injector(new class extends AbstractModule {\n            protected function configure()\n            {\n                $this->bind(FakeTyreInterface::class)->toNull();\n            }\n        }));\n        $nullObject = $injector->getInstance(FakeTyreInterface::class);\n        $this->assertInstanceOf(FakeTyreInterface::class, $nullObject);\n    }\n\n    public function testBindInterfeceInterceptor(): void\n    {\n        $injector = (new Injector(new class extends AbstractModule {\n            protected function configure()\n            {\n                $this->bind(FakeAop::class);\n                $this->bind(FakeDoubleInterceptorInterface::class)->to(FakeDoubleInterceptor::class);\n                $this->bindInterceptor(\n                    $this->matcher->any(),\n                    $this->matcher->any(),\n                    [FakeDoubleInterceptorInterface::class]\n                );\n            }\n        }));\n        $instance = $injector->getInstance(FakeAop::class);\n        $result = $instance->returnSame(2);\n        $this->assertSame(4, $result);\n    }\n\n    public function testBindInterfeceNullInterceptor(): void\n    {\n        $injector = (new Injector(new class extends AbstractModule {\n            protected function configure()\n            {\n                $this->bind(FakeAop::class);\n                $this->bind(FakeDoubleInterceptorInterface::class)->to(NullInterceptor::class);\n                $this->bindInterceptor(\n                    $this->matcher->any(),\n                    $this->matcher->any(),\n                    [FakeDoubleInterceptorInterface::class]\n                );\n            }\n        }));\n        $instance = $injector->getInstance(FakeAop::class);\n        $result = $instance->returnSame(2);\n        $this->assertSame(2, $result);\n        $this->assertInstanceOf(NullInterceptor::class, $instance->bindings['returnSame'][0]); // @phpstan-ignore-line\n    }\n\n    public function testModuleArray(): void\n    {\n        $modules = [\n            new class extends AbstractModule\n            {\n                protected function configure()\n                {\n                    $this->bind()->annotatedWith('var')->toInstance('a');\n                    $this->bind()->annotatedWith('first')->toInstance('1');\n                }\n            },\n            new class extends AbstractModule\n            {\n                protected function configure()\n                {\n                    $this->bind()->annotatedWith('var')->toInstance('b');\n                    $this->bind()->annotatedWith('second')->toInstance('2');\n                }\n            },\n        ];\n        $injector = new Injector($modules);\n        $this->assertSame('a', $injector->getInstance('', 'var'));\n        $this->assertSame('2', $injector->getInstance('', 'second'));\n        $this->assertSame('1', $injector->getInstance('', 'first'));\n    }\n\n    public function testSingleArray(): void\n    {\n        $modules = [\n            new class extends AbstractModule\n            {\n                protected function configure()\n                {\n                    $this->bind()->annotatedWith('var')->toInstance('a');\n                    $this->bind()->annotatedWith('first')->toInstance('1');\n                }\n            },\n        ];\n        $injector = new Injector($modules);\n        $this->assertSame('a', $injector->getInstance('', 'var'));\n    }\n\n    /**\n     * @requires PHP 8.0\n     */\n    public function testProviderInjectWithSet(): void\n    {\n        $injector = new Injector(new class extends AbstractModule{\n            protected function configure()\n            {\n                $this->bind(FakeEngineInterface::class)->to(FakeEngine::class);\n            }\n        });\n        $fakeSet = $injector->getInstance(FakeSet::class);\n        $this->assertInstanceOf(ProviderInterface::class, $fakeSet->provider);\n        $this->assertInstanceOf(FakeEngine::class, $fakeSet->provider->get());\n    }\n}\n"
  },
  {
    "path": "tests/di/ModuleMergerTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass ModuleMergerTest extends TestCase\n{\n    public function testInvoke(): void\n    {\n        $modules = [\n            new class extends AbstractModule\n            {\n                protected function configure()\n                {\n                    $this->bind()->annotatedWith('var')->toInstance('a');\n                    $this->bind()->annotatedWith('first')->toInstance('1');\n                }\n            },\n            new class extends AbstractModule\n            {\n                protected function configure()\n                {\n                    $this->bind()->annotatedWith('var')->toInstance('b');\n                    $this->bind()->annotatedWith('second')->toInstance('2');\n                }\n            },\n        ];\n        $injector = new Injector($modules);\n        $this->assertSame('a', $injector->getInstance('', 'var'));\n        $this->assertSame('2', $injector->getInstance('', 'second'));\n        $this->assertSame('1', $injector->getInstance('', 'first'));\n    }\n}\n"
  },
  {
    "path": "tests/di/ModuleTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\Exception\\NotFound;\n\nuse function str_replace;\n\nclass ModuleTest extends TestCase\n{\n    public function testNew(): void\n    {\n        $module = new FakeInstanceBindModule();\n        $this->assertInstanceOf(AbstractModule::class, $module);\n    }\n\n    public function testInstall(): void\n    {\n        $module = new FakeInstallModule();\n        $this->assertInstanceOf(AbstractModule::class, $module);\n    }\n\n    public function testToInvalidClass(): void\n    {\n        $this->expectException(NotFound::class);\n        new FakeToBindInvalidClassModule();\n    }\n\n    public function testRename(): void\n    {\n        $module = new FakeRenameModule(new FakeToBindModule());\n        $instance = $module->getContainer()->getInstance(FakeRobotInterface::class, 'original');\n        $this->assertInstanceOf(FakeRobotInterface::class, $instance);\n    }\n\n    public function testConstructorCallModule(): void\n    {\n        $module = new FakelNoConstructorCallModule();\n        $container = $module->getContainer();\n        $this->assertInstanceOf(Container::class, $container);\n    }\n\n    public function testActivate(): void\n    {\n        $module = new FakeInstanceBindModule();\n        $this->assertInstanceOf(Container::class, $module->getContainer());\n    }\n\n    public function testtoString(): void\n    {\n        $string = (string) new FakeLogStringModule();\n        $normalize = static function (string $str): string {\n            return str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $str);\n        };\n        $this->assertSame($normalize('-array => (array)\n-bool => (boolean) 1\n-int => (integer) 1\n-null => (NULL)\n-object => (object) stdClass\n-string => (string) 1\nRay\\Di\\FakeAopInterface- => (dependency) Ray\\Di\\FakeAop (aop) +returnSame(Ray\\Di\\FakeDoubleInterceptor)\nRay\\Di\\FakeDoubleInterceptor- => (dependency) Ray\\Di\\FakeDoubleInterceptor\nRay\\Di\\FakeRobotInterface- => (provider) (dependency) Ray\\Di\\FakeRobotProvider'), $normalize($string));\n    }\n}\n"
  },
  {
    "path": "tests/di/MultiBinding/MultiBinderTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\MultiBinding;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\FakeEngine;\nuse Ray\\Di\\FakeEngine2;\nuse Ray\\Di\\FakeEngineInterface;\nuse Ray\\Di\\MultiBinder;\nuse Ray\\Di\\NullModule;\n\n/**\n * @requires PHP 8.0\n */\nclass MultiBinderTest extends TestCase\n{\n    public function testAdd(): void\n    {\n        $module = new NullModule();\n        $binder = MultiBinder::newInstance($module, FakeEngineInterface::class);\n        $binder->addBinding('one')->to(FakeEngine::class);\n        $binder->addBinding('two')->to(FakeEngine2::class);\n        /** @var MultiBindings $multiBindings */\n        $multiBindings = $module->getContainer()->getInstance(MultiBindings::class);\n        $this->assertArrayHasKey('one', (array) $multiBindings[FakeEngineInterface::class]);\n        $this->assertArrayHasKey('two', (array) $multiBindings[FakeEngineInterface::class]);\n    }\n\n    public function testSet(): void\n    {\n        $module = new NullModule();\n        $binder = MultiBinder::newInstance($module, FakeEngineInterface::class);\n        $binder->addBinding('one')->to(FakeEngine::class);\n        $binder->addBinding('two')->to(FakeEngine2::class);\n        $binder->setBinding('one')->to(FakeEngine::class);\n        /** @var MultiBindings $multiBindings */\n        $multiBindings = $module->getContainer()->getInstance(MultiBindings::class);\n        $this->assertArrayHasKey('one', (array) $multiBindings[FakeEngineInterface::class]);\n        $this->assertArrayNotHasKey('two', (array) $multiBindings[FakeEngineInterface::class]);\n    }\n}\n"
  },
  {
    "path": "tests/di/MultiBinding/MultiBindingModuleTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di\\MultiBinding;\n\nuse ArrayAccess;\nuse LogicException;\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\AbstractModule;\nuse Ray\\Di\\Exception\\SetNotFound;\nuse Ray\\Di\\FakeEngine;\nuse Ray\\Di\\FakeEngine2;\nuse Ray\\Di\\FakeEngine3;\nuse Ray\\Di\\FakeEngineInterface;\nuse Ray\\Di\\FakeMultiBindingAnnotation;\nuse Ray\\Di\\FakeMultiBindingConsumer;\nuse Ray\\Di\\FakeRobot;\nuse Ray\\Di\\FakeRobotInterface;\nuse Ray\\Di\\FakeRobotProvider;\nuse Ray\\Di\\FakeSetNotFoundWithMap;\nuse Ray\\Di\\FakeSetNotFoundWithProvider;\nuse Ray\\Di\\Injector;\nuse Ray\\Di\\MultiBinder;\nuse Ray\\Di\\NullModule;\n\nuse function count;\n\n/**\n * @requires PHP 8.0\n */\nclass MultiBindingModuleTest extends TestCase\n{\n    /** @var AbstractModule */\n    private $module;\n\n    protected function setUp(): void\n    {\n        $this->module = new class extends AbstractModule {\n            protected function configure(): void\n            {\n                $engineBinder = MultiBinder::newInstance($this, FakeEngineInterface::class);\n                $engineBinder->addBinding('one')->to(FakeEngine::class);\n                $engineBinder->addBinding('two')->to(FakeEngine2::class);\n                $engineBinder->addBinding()->to(FakeEngine3::class);\n                $robotBinder = MultiBinder::newInstance($this, FakeRobotInterface::class);\n                $robotBinder->addBinding('to')->to(FakeRobot::class);\n                $robotBinder->addBinding('provider')->toProvider(FakeRobotProvider::class);\n                $robotBinder->addBinding('instance')->toInstance(new FakeRobot());\n            }\n        };\n    }\n\n    /**\n     * @return Map<FakeEngineInterface>\n     */\n    public function testInjectMap(): Map\n    {\n        $injector = new Injector($this->module);\n        $consumer = $injector->getInstance(FakeMultiBindingConsumer::class);\n        $this->assertInstanceOf(Map::class, $consumer->engines);\n\n        return $consumer->engines;\n    }\n\n    /**\n     * @param Map<object> $map\n     *\n     * @depends testInjectMap\n     */\n    public function testMapInstance(Map $map): void\n    {\n        $this->assertInstanceOf(FakeEngine::class, $map['one']);\n        $this->assertInstanceOf(FakeEngine2::class, $map['two']);\n    }\n\n    /**\n     * @param Map<object> $map\n     *\n     * @depends testInjectMap\n     */\n    public function testMapIteration(Map $map): void\n    {\n        $this->assertContainsOnlyInstancesOf(FakeEngineInterface::class, $map);\n\n        $this->assertSame(3, count($map));\n    }\n\n    /**\n     * @param Map<object> $map\n     *\n     * @depends testInjectMap\n     */\n    public function testIsSet(Map $map): void\n    {\n        $this->assertTrue(isset($map['one']));\n        $this->assertTrue(isset($map['two']));\n    }\n\n    /**\n     * @param Map<object> $map\n     *\n     * @depends testInjectMap\n     */\n    public function testOffsetSet(Map $map): void\n    {\n        $this->expectException(LogicException::class);\n        $map['one'] = 1;\n    }\n\n    /**\n     * @param Map<object> $map\n     *\n     * @depends testInjectMap\n     */\n    public function testOffsetUnset(Map $map): void\n    {\n        $this->expectException(LogicException::class);\n        unset($map['one']);\n    }\n\n    public function testAnotherBinder(): void\n    {\n        $injector = new Injector($this->module);\n        $consumer = $injector->getInstance(FakeMultiBindingConsumer::class);\n        $this->assertInstanceOf(Map::class, $consumer->robots);\n        $this->assertContainsOnlyInstancesOf(FakeRobot::class, $consumer->robots);\n        $this->assertSame(3, count($consumer->robots));\n    }\n\n    public function testMultipileModule(): void\n    {\n        $module = new NullModule();\n        $binder = MultiBinder::newInstance($module, FakeEngineInterface::class);\n        $binder->addBinding('one')->to(FakeEngine::class);\n        $binder->addBinding('two')->to(FakeEngine2::class);\n        $module->install(new class extends AbstractModule {\n            protected function configure()\n            {\n                $binder = MultiBinder::newInstance($this, FakeEngineInterface::class);\n                $binder->addBinding('three')->to(FakeEngine::class);\n                $binder->addBinding('four')->to(FakeEngine::class);\n            }\n        });\n        /** @var ArrayAccess<string, object> $multiBindings */\n        $multiBindings = $module->getContainer()->getInstance(MultiBindings::class);\n        $this->assertArrayHasKey('one', (array) $multiBindings[FakeEngineInterface::class]);\n        $this->assertArrayHasKey('two', (array) $multiBindings[FakeEngineInterface::class]);\n        $this->assertArrayHasKey('three', (array) $multiBindings[FakeEngineInterface::class]);\n        $this->assertArrayHasKey('four', (array) $multiBindings[FakeEngineInterface::class]);\n    }\n\n    public function testAnnotation(): void\n    {\n        $injector = new Injector($this->module);\n        $fake = $injector->getInstance(FakeMultiBindingAnnotation::class);\n        $this->assertContainsOnlyInstancesOf(FakeEngineInterface::class, $fake->engines);\n        $this->assertSame(3, count($fake->engines));\n        $this->assertContainsOnlyInstancesOf(FakeRobotInterface::class, $fake->robots);\n        $this->assertSame(3, count($fake->robots));\n    }\n\n    public function testSetNotFoundInMap(): void\n    {\n        $this->expectException(SetNotFound::class);\n        $injector = new Injector($this->module);\n        $injector->getInstance(FakeSetNotFoundWithMap::class);\n    }\n\n    public function testSetNotFoundInProvider(): void\n    {\n        $this->expectException(SetNotFound::class);\n        $injector = new Injector();\n        $injector->getInstance(FakeSetNotFoundWithProvider::class);\n    }\n}\n"
  },
  {
    "path": "tests/di/NameTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse ReflectionParameter;\n\nclass NameTest extends TestCase\n{\n    public function testUnName(): void\n    {\n        $name = new Name(Name::ANY);\n        $parameter = new ReflectionParameter([FakeCar::class, '__construct'], 'engine');\n        $boundName = $name($parameter);\n        $this->assertSame(Name::ANY, $boundName);\n    }\n\n    public function testSingleName(): void\n    {\n        $name = new Name('turbo');\n        $parameter = new ReflectionParameter([FakeCar::class, '__construct'], 'engine');\n        $boundName = $name($parameter);\n        $this->assertSame('turbo', $boundName);\n    }\n\n    public function testSetName(): void\n    {\n        $name = new Name(FakeMirrorRight::class);\n        $parameter = new ReflectionParameter([FakeHandleBar::class, 'setMirrors'], 'rightMirror');\n        $boundName = $name($parameter);\n        $expected = FakeMirrorRight::class;\n        $this->assertSame($expected, $boundName);\n    }\n\n    /**\n     * @dataProvider keyPairStringProvider\n     */\n    public function testKeyValuePairName(string $keyPairValueString): void\n    {\n        $name = new Name($keyPairValueString);\n        $parameter = new ReflectionParameter([FakeCar::class, '__construct'], 'engine');\n        $boundName = $name($parameter);\n        $this->assertSame('engine_name', $boundName);\n    }\n\n    /**\n     * @return string[][]\n     * @psalm-return array{0: array{0: string}, 1: array{0: string}, 2: array{0: string}, 3: array{0: string}}\n     */\n    public function keyPairStringProvider(): array\n    {\n        return [\n            ['engine=engine_name,var=var_name'],\n            ['engine=engine_name, var=var_name'],\n            ['var=var_name,engine=engine_name'],\n            ['var=var_name, engine=engine_name'],\n        ];\n    }\n\n    public function testKeyValuePairButNotFound(): void\n    {\n        $name = new Name('foo=bar');\n        $parameter = new ReflectionParameter([FakeCar::class, '__construct'], 'engine');\n        $boundName = $name($parameter);\n        $this->assertSame(Name::ANY, $boundName);\n    }\n\n    public function testKeyValuePairWithDollarPrefix(): void\n    {\n        $name = new Name('$engine=engine_name,$var=var_name');\n        $parameter = new ReflectionParameter([FakeCar::class, '__construct'], 'engine');\n        $boundName = $name($parameter);\n        $this->assertSame('engine_name', $boundName);\n    }\n}\n"
  },
  {
    "path": "tests/di/NewInstanceTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse ReflectionClass;\nuse ReflectionMethod;\n\nuse function assert;\n\nclass NewInstanceTest extends TestCase\n{\n    /** @var NewInstance */\n    protected $newInstance;\n\n    protected function setUp(): void\n    {\n        /** @var ReflectionClass<object> $class */\n        $class = new ReflectionClass(FakeCar::class);\n        $setters = [];\n        $name = new Name(Name::ANY);\n        $setters[] = new SetterMethod(new ReflectionMethod(FakeCar::class, 'setTires'), $name);\n        $setters[] = new SetterMethod(new ReflectionMethod(FakeCar::class, 'setHardtop'), $name);\n        $setterMethods = new SetterMethods($setters);\n        $this->newInstance = new NewInstance($class, $setterMethods);\n    }\n\n    public function testInvoke(): void\n    {\n        $container = new Container();\n        (new Bind($container, FakeTyreInterface::class))->to(FakeTyre::class);\n        (new Bind($container, FakeEngineInterface::class))->to(FakeEngine::class);\n        (new Bind($container, FakeHardtopInterface::class))->to(FakeHardtop::class);\n        $car = $this->newInstance->__invoke($container);\n        assert($car instanceof FakeCar);\n        $this->assertInstanceOf(FakeCar::class, $car);\n        $this->assertInstanceOf(FakeTyre::class, $car->frontTyre);\n        $this->assertInstanceOf(FakeTyre::class, $car->rearTyre);\n        $this->assertInstanceOf(FakeHardtop::class, $car->hardtop);\n    }\n}\n"
  },
  {
    "path": "tests/di/NoHintTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\Exception\\NoHint;\nuse Ray\\Di\\Exception\\Unbound;\n\nclass NoHintTest extends TestCase\n{\n    public function testNoHintIsUnbound(): void\n    {\n        $e = new NoHint();\n        $this->assertInstanceOf(Unbound::class, $e);\n    }\n\n    public function testNoHintThrownForNoTypeNoName(): void\n    {\n        $this->expectException(NoHint::class);\n\n        $injector = new Injector(new FakeUnNamedModule());\n        $injector->getInstance(FakeUnNamedClass::class);\n    }\n\n    public function testNoHintMessageFormat(): void\n    {\n        $injector = new Injector(new FakeUnNamedModule());\n\n        try {\n            $injector->getInstance(FakeUnNamedClass::class);\n            $this->fail('NoHint exception should be thrown');\n        } catch (NoHint $e) {\n            // Message format: ${var} (file:line)\n            $this->assertMatchesRegularExpression(\n                '/^\\$\\w+ \\(.+:\\d+\\)$/',\n                $e->getMessage(),\n            );\n            $this->assertStringContainsString('$value', $e->getMessage());\n            $this->assertStringContainsString('FakeUnNamedClass.php', $e->getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "tests/di/NullModuleTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass NullModuleTest extends TestCase\n{\n    public function testEmpty(): void\n    {\n        $module = new NullModule();\n        $container = $module->getContainer();\n        $this->assertSame([], $container->getContainer());\n    }\n}\n"
  },
  {
    "path": "tests/di/ProviderProviderTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\Di\\Set;\n\nclass ProviderProviderTest extends TestCase\n{\n    public function testGet(): void\n    {\n        $injector = new Injector(\n            new class extends AbstractModule {\n                protected function configure()\n                {\n                    $this->bind(FakeEngineInterface::class)->toInstance(new FakeEngine());\n                }\n            }\n        );\n        $set = new Set(FakeEngineInterface::class);\n        $provider = new ProviderProvider($injector, $set);\n        $instance = $provider->get();\n        $this->assertInstanceOf(FakeEngine::class, $instance);\n    }\n}\n"
  },
  {
    "path": "tests/di/SetterMethodTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\Exception\\Unbound;\nuse ReflectionMethod;\nuse ReflectionParameter;\nuse stdClass;\n\nuse function spl_object_hash;\n\nclass SetterMethodTest extends TestCase\n{\n    /** @var SetterMethods */\n    protected $setterMethods;\n\n    protected function setUp(): void\n    {\n        $method = new ReflectionMethod(FakeCar::class, 'setTires');\n        $setterMethod = new SetterMethod($method, new Name(Name::ANY));\n        $this->setterMethods = new SetterMethods([$setterMethod]);\n    }\n\n    public function testInvoke(): void\n    {\n        $container = new Container();\n        (new Bind($container, FakeTyreInterface::class))->to(FakeTyre::class);\n        $car = new FakeCar(new FakeEngine());\n        // setter injection\n        $this->setterMethods->__invoke($car, $container);\n        $this->assertInstanceOf(FakeTyre::class, $car->frontTyre);\n        $this->assertInstanceOf(FakeTyre::class, $car->rearTyre);\n        $this->assertNotSame(spl_object_hash($car->frontTyre), spl_object_hash($car->rearTyre));\n    }\n\n    public function testUnbound(): void\n    {\n        $this->expectException(Unbound::class);\n        $container = new Container();\n        $car = new FakeCar(new FakeEngine());\n        $this->setterMethods->__invoke($car, $container);\n    }\n\n    public function testAcceptWithUnboundException(): void\n    {\n        $this->expectException(Unbound::class);\n        $method = new ReflectionMethod(FakeCar::class, 'setTires');\n        $setterMethod = new SetterMethod($method, new Name(Name::ANY));\n\n        $visitor = new class implements VisitorInterface\n        {\n            public function visitSetterMethod(string $method, Arguments $arguments): void\n            {\n                throw new Unbound(FakeTyreInterface::class);\n            }\n\n            public function visitDependency(NewInstance $newInstance, ?string $postConstruct, bool $isSingleton): void\n            {\n            }\n\n            public function visitProvider(Dependency $dependency, string $context, bool $isSingleton): string\n            {\n                return '';\n            }\n\n            /** @param mixed $value */\n            public function visitInstance($value): string\n            {\n                return '';\n            }\n\n            public function visitAspectBind(\\Ray\\Aop\\Bind $aopBind): void\n            {\n            }\n\n            public function visitNewInstance(string $class, SetterMethods $setterMethods, ?Arguments $arguments, ?AspectBind $bind): void\n            {\n            }\n\n            /** @param array<SetterMethod> $setterMethods */\n            public function visitSetterMethods(array $setterMethods): void\n            {\n            }\n\n            /** @param array<Argument> $arguments */\n            public function visitArguments(array $arguments): void\n            {\n            }\n\n            /** @param mixed $defaultValue */\n            public function visitArgument(string $index, bool $isDefaultAvailable, $defaultValue, ReflectionParameter $parameter): void\n            {\n            }\n        };\n\n        $setterMethod->accept($visitor);\n    }\n\n    public function testAcceptWithUnboundExceptionOptional(): void\n    {\n        $method = new ReflectionMethod(FakeCar::class, 'setTires');\n        $setterMethod = new SetterMethod($method, new Name(Name::ANY));\n        $setterMethod->setOptional();\n\n        $state = (object) ['exceptionThrown' => false];\n        $visitor = new class ($state) implements VisitorInterface\n        {\n            public function __construct(private stdClass $state)\n            {\n            }\n\n            public function visitSetterMethod(string $method, Arguments $arguments): void\n            {\n                $this->state->exceptionThrown = true;\n\n                throw new Unbound(FakeTyreInterface::class);\n            }\n\n            public function visitDependency(NewInstance $newInstance, ?string $postConstruct, bool $isSingleton): void\n            {\n            }\n\n            public function visitProvider(Dependency $dependency, string $context, bool $isSingleton): string\n            {\n                return '';\n            }\n\n            /** @param mixed $value */\n            public function visitInstance($value): string\n            {\n                return '';\n            }\n\n            public function visitAspectBind(\\Ray\\Aop\\Bind $aopBind): void\n            {\n            }\n\n            public function visitNewInstance(string $class, SetterMethods $setterMethods, ?Arguments $arguments, ?AspectBind $bind): void\n            {\n            }\n\n            /** @param array<SetterMethod> $setterMethods */\n            public function visitSetterMethods(array $setterMethods): void\n            {\n            }\n\n            /** @param array<Argument> $arguments */\n            public function visitArguments(array $arguments): void\n            {\n            }\n\n            /** @param mixed $defaultValue */\n            public function visitArgument(string $index, bool $isDefaultAvailable, $defaultValue, ReflectionParameter $parameter): void\n            {\n            }\n        };\n\n        $setterMethod->accept($visitor);\n        // Verify that exception was thrown but caught due to optional binding\n        /** @phpstan-ignore-next-line */\n        $this->assertTrue($state->exceptionThrown, 'Unbound exception should have been thrown');\n    }\n}\n"
  },
  {
    "path": "tests/di/SetterMethodsTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse ReflectionMethod;\n\nclass SetterMethodsTest extends TestCase\n{\n    /** @var SetterMethod */\n    protected $setterMethod;\n\n    protected function setUp(): void\n    {\n        $method = new ReflectionMethod(FakeCar::class, 'setTires');\n        $this->setterMethod = new SetterMethod($method, new Name(Name::ANY));\n    }\n\n    public function testInvoke(): void\n    {\n        $car = new FakeCar(new FakeEngine());\n        $container = (new FakeCarModule())->getContainer();\n        $this->setterMethod->__invoke($car, $container);\n        $this->assertInstanceOf(FakeTyre::class, $car->frontTyre);\n        $this->assertInstanceOf(FakeTyre::class, $car->rearTyre);\n    }\n}\n"
  },
  {
    "path": "tests/di/SpyCompilerTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Aop\\Bind as AopBind;\n\nclass SpyCompilerTest extends TestCase\n{\n    public function testNoBindng(): void\n    {\n        $spy = new SpyCompiler();\n        $class = $spy->compile(FakeHandle::class, new AopBind());\n        $this->assertSame(FakeHandle::class, $class);\n    }\n}\n"
  },
  {
    "path": "tests/di/UnboundTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse LogicException;\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\Exception\\Unbound;\n\nclass UnboundTest extends TestCase\n{\n    public function testGetBound(): void\n    {\n        $previous = new Unbound('dep1-');\n        $e = new Unbound('dep2-', 0, $previous);\n        $string = (string) $e;\n        $this->assertStringContainsString('Ray\\\\Di\\\\Exception\\\\Unbound', $string);\n        $this->assertStringContainsString('dep1-', $string);\n        $this->assertStringContainsString('dep2-', $string);\n    }\n\n    public function testNoPrevious(): void\n    {\n        $e = new Unbound('dep0-', 0);\n        $string = (string) $e;\n        $this->assertStringContainsString('Ray\\\\Di\\\\Exception\\\\Unbound', $string);\n    }\n\n    public function testNonUnboundPrevious(): void\n    {\n        $string = (string) new Unbound('', 0, new LogicException());\n        $expected = 'LogicException';\n        $this->assertStringContainsString($expected, $string);\n    }\n}\n"
  },
  {
    "path": "tests/di/VisitorTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Aop\\Bind as AopBind;\nuse ReflectionParameter;\n\nuse function assert;\nuse function implode;\nuse function method_exists;\nuse function sprintf;\n\nclass VisitorTest extends TestCase\n{\n    /** @var NullVisitor */\n    private $visitor;\n\n    /** @var Dependency */\n    private $dependency;\n\n    /** @var DependencyProvider */\n    private $dependencyProvider;\n\n    /** @var Container  */\n    private $container;\n\n    public function setUp(): void\n    {\n        $this->visitor = new NullVisitor();\n        $this->container = (new ContainerFactory())(new FakeCarModule(), __DIR__ . '/tmp');\n        $container = $this->container->getContainer();\n        $dependency = $container['Ray\\Di\\FakeCarInterface-'];\n        assert($dependency instanceof Dependency);\n        $this->dependency = $dependency;\n        $dependencyProvider = $container['Ray\\Di\\FakeHandleInterface-'];\n        assert($dependencyProvider instanceof DependencyProvider);\n        $this->dependencyProvider = $dependencyProvider;\n    }\n\n    public function testNullVisit(): void\n    {\n        $maybeTrue = $this->dependency->accept($this->visitor);\n        $this->assertTrue($maybeTrue);\n    }\n\n    public function testCollectVisit(): void\n    {\n        $collector = new class () {\n            /** @var array<string> */\n            public $args = [];\n\n            /** @var array<string> */\n            public $methods = [];\n\n            /** @var string */\n            public $newInstance;\n\n            /** @var AopBind */\n            public $bind;\n\n            public function pushArg(string $arg, bool $isSingleton): void\n            {\n                $type = $isSingleton ? 'singleton.' : 'prototype.';\n                $this->args[] = $type . $arg;\n            }\n\n            public function pushMethod(string $method): void\n            {\n                $this->methods[] = sprintf('%s(%s)', $method, implode(',', $this->args));\n                $this->args = [];\n            }\n\n            public function pushNewInstance(string $class): void\n            {\n                $this->newInstance = sprintf('%s(%s)', $class, implode(',', $this->args));\n                $this->args = [];\n            }\n\n            public function pushAopBind(AopBind $bind): void\n            {\n                $this->bind = $bind;\n            }\n        };\n\n        $visitor = new class ($collector, $this->container) implements VisitorInterface\n        {\n            /** @var object */\n            private $collector;\n\n            /** @var Container */\n            private $container;\n\n            public function __construct(object $collector, Container $container)\n            {\n                $this->collector = $collector;\n                $this->container = $container;\n            }\n\n            public function visitDependency(NewInstance $newInstance, ?string $postConstruct, bool $isSingleton)\n            {\n                $newInstance->accept($this);\n            }\n\n            public function visitProvider(Dependency $dependency, string $context, bool $isSingleton): string\n            {\n                return 'visitProvider';\n            }\n\n            /** @inheritDoc */\n            public function visitInstance($value): string\n            {\n                return 'visitInstance';\n            }\n\n            public function visitAspectBind(AopBind $aopBind)\n            {\n                assert(method_exists($this->collector, 'pushAopBind'));\n                $this->collector->pushAopBind($aopBind);\n            }\n\n            public function visitNewInstance(string $class, SetterMethods $setterMethods, ?Arguments $arguments, ?AspectBind $bind)\n            {\n                if ($arguments) {\n                    $arguments->accept($this);\n                }\n\n                $setterMethods->accept($this);\n                assert(method_exists($this->collector, 'pushNewInstance'));\n                $this->collector->pushNewInstance($class);\n                if ($bind instanceof AspectBind) {\n                    $bind->accept($this);\n                }\n            }\n\n            /** @inheritDoc */\n            public function visitSetterMethods(array $setterMethods)\n            {\n                foreach ($setterMethods as $setterMethod) {\n                    $setterMethod->accept($this);\n                }\n            }\n\n            /** @inheritDoc */\n            public function visitSetterMethod(string $method, Arguments $arguments)\n            {\n                assert(method_exists($this->collector, 'pushMethod'));\n                $this->collector->pushMethod($method);\n                $arguments->accept($this);\n            }\n\n            /** @inheritDoc */\n            public function visitArguments(array $arguments)\n            {\n                foreach ($arguments as $argument) {\n                    $argument->accept($this);\n                }\n            }\n\n            /** @inheritDoc */\n            public function visitArgument(string $index, bool $isDefaultAvailable, $defaultValue, ReflectionParameter $parameter)\n            {\n                $container = $this->container->getContainer();\n                $dependency = $container[$index];\n                assert($dependency instanceof Dependency || $dependency instanceof DependencyProvider);\n                $isSingleton = $dependency->isSingleton();\n                assert(method_exists($this->collector, 'pushArg'));\n                $this->collector->pushArg($index, $isSingleton);\n            }\n        };\n\n        $this->dependency->accept($visitor);\n        $this->assertStringContainsString('Ray\\Di\\FakeCar', $collector->newInstance);\n        $this->assertStringContainsString('(prototype.Ray\\Di\\FakeGearStickInterface-Ray\\Di\\FakeGearStickInject)', $collector->newInstance);\n        $this->assertSame('setTires(prototype.Ray\\Di\\FakeEngineInterface-)', $collector->methods[0]);\n        $this->assertSame('setHardtop(prototype.Ray\\Di\\FakeTyreInterface-,prototype.Ray\\Di\\FakeTyreInterface-)', $collector->methods[1]);\n    }\n\n    public function testVisitDependencyProvider(): void\n    {\n        $result = $this->dependencyProvider->accept($this->visitor);\n        $this->assertTrue($result);\n    }\n\n    public function testVisitInsntace(): void\n    {\n        $instance = new Instance('1');\n        $this->assertSame('1', $instance->accept($this->visitor));\n    }\n}\n"
  },
  {
    "path": "tests/di/script/aop.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse function dirname;\nuse function file_put_contents;\nuse function serialize;\n\n$appRoot = dirname(__DIR__, 3);\nrequire $appRoot . '/vendor/autoload.php';\n\n$injector = new Injector(new FakeAopModule(), $appRoot . '/tests/tmp');\nfile_put_contents(__FILE__ . '.cache.txt', serialize($injector));\n"
  },
  {
    "path": "tests/di/script/bench.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse Ray\\Compiler\\DiCompiler;\nuse Ray\\Compiler\\ScriptInjector;\n\nuse function assert;\nuse function microtime;\nuse function printf;\nuse function range;\nuse function serialize;\nuse function unserialize;\n\nrequire __DIR__ . '/bootstrap.php';\n$n = 20;\n\n$injector = new Injector(new FakeCarModule());\n$serialize = serialize($injector);\n\n$timer = microtime(true);\nforeach (range(1, $n) as $i) {\n    $injector = new Injector(new FakeCarModule());\n    $injector->getInstance(FakeCarInterface::class);\n}\n\n$timer1 = microtime(true) - $timer;\n\n$timer = microtime(true);\n$injector = unserialize($serialize);\nassert($injector instanceof InjectorInterface);\nforeach (range(1, $n) as $i) {\n    $injector->getInstance(FakeCarInterface::class);\n}\n\n$timer2 = microtime(true) - $timer;\n\n$compiler = new DiCompiler(new FakeCarModule(), __DIR__ . '/tmp');\n$compiler->compile();\n$timer = microtime(true);\n$injector = new ScriptInjector(__DIR__ . '/tmp');\nforeach (range(1, $n) as $i) {\n    $injector->getInstance(FakeCarInterface::class);\n}\n\n$timer3 = microtime(true) - $timer;\n\n// Microsecond per inject\nprintf(\"micro second per inject (MPI):%f speed:x%d\\n\", $timer2 / $n, $timer1 / $timer2);\nprintf(\"micro second per inject (MPI):%f speed:x%d\\n\", $timer3 / $n, $timer1 / $timer3);\n"
  },
  {
    "path": "tests/di/script/grapher.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse function dirname;\nuse function file_put_contents;\nuse function serialize;\n\n$appRoot = dirname(__DIR__, 3);\nrequire $appRoot . '/vendor/autoload.php';\n\n$grapher = new Grapher(new FakeAopGrapherModule(), $appRoot . '/tests/tmp');\nfile_put_contents(__FILE__ . '.txt', serialize($grapher));\n"
  },
  {
    "path": "tests/stub/BindInterface.phpstub",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Aop;\n\ninterface BindInterface\n{\n    /**\n     * Bind pointcuts\n     *\n     * @param class-string $class     class name\n     * @param Pointcut[]   $pointcuts Pointcut array\n     */\n    public function bind(string $class, array $pointcuts): self;\n\n    /**\n     * Bind interceptors to method\n     *\n     * @param MethodInterceptor[] $interceptors\n     */\n    public function bindInterceptors(string $method, array $interceptors): self;\n\n    /**\n     * Return bindings data\n     *\n     * [$methodNameA => [$interceptorA, ...][]\n     *\n     * @return array<string, array<class-string<MethodInterceptor>>>\n     */\n    public function getBindings();\n\n    /**\n     * Return hash\n     *\n     * @param string $salt\n     *\n     * @return string\n     */\n    public function toString($salt);\n}\n"
  },
  {
    "path": "tests/type/InjectorInterfaceTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse DateTime;\nuse DateTimeInterface;\n\nfinal class InjectorInterfaceTest\n{\n    private DateTimeInterface $a; // @phpstan-ignore-line\n    private DateTime $b; // @phpstan-ignore-line\n    private mixed $m; // @phpstan-ignore-line\n\n    public function __construct()\n    {\n        $injector = new Injector(new class extends AbstractModule {\n            protected function configure()\n            {\n            }\n        });\n        $injector->getInstance(DateTimeInterface::class);\n        $this->a = $injector->getInstance(DateTimeInterface::class);\n        /** @psalm-suppress PropertyTypeCoercion */\n        $this->b = $injector->getInstance(DateTimeInterface::class); // @phpstan-ignore-line\n        /** @psalm-suppress */\n        $this->m = $injector->getInstance('a'); // @phpstan-ignore-line\n    }\n}\n"
  },
  {
    "path": "tests-php8/AssistedInjectTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Ray\\Di\\Exception\\MethodInvocationNotAvailable;\n\nclass AssistedInjectTest extends TestCase\n{\n    /** @var InjectorInterface */\n    private $injector;\n\n    protected function setUp(): void\n    {\n        $this->injector = new Injector(new FakeToBindModule(), __DIR__ . '/tmp');\n    }\n\n    public function testAssisted(): void\n    {\n        $consumer = $this->injector->getInstance(FakeAssistedInjectConsumer::class);\n        /** @var FakeAssistedConsumer $consumer */\n        $assistedDependency = $consumer->assistOne('a', 'b');\n        $expecetd = FakeRobot::class;\n        $this->assertInstanceOf($expecetd, $assistedDependency);\n    }\n\n    public function testAssistedWithName(): void\n    {\n        $this->injector = new Injector(new FakeInstanceBindModule());\n        $consumer = $this->injector->getInstance(FakeAssistedInjectConsumer::class);\n        /** @var FakeAssistedConsumer $consumer */\n        $assistedDependency = $consumer->assistWithName('a7');\n        $expecetd = 1;\n        $this->assertSame($expecetd, $assistedDependency);\n    }\n\n    public function testAssistedAnyWithName(): void\n    {\n        $injector = new Injector(new FakeToBindModule(new FakeInstanceBindModule()));\n        $consumer = $injector->getInstance(FakeAssistedInjectConsumer::class);\n        /** @var FakeAssistedConsumer $consumer */\n        [$assistedDependency1, $assistedDependency2] = $consumer->assistAny();\n        $expected1 = 1;\n        $this->assertSame($expected1, $assistedDependency1);\n        $this->assertInstanceOf(FakeRobot::class, $assistedDependency2);\n    }\n\n    public function testAssistedMethodInvocation(): void\n    {\n        $assistedConsumer = (new Injector(new FakeAssistedDbModule(), __DIR__ . '/tmp'))->getInstance(FakeAssistedInjectDb::class);\n        /** @var FakeAssistedParamsConsumer $assistedConsumer */\n        [$id, $db] = $assistedConsumer->getUser(1);\n        /** @var FakeAbstractDb $db */\n        $this->assertSame(1, $id);\n        $this->assertSame(1, $db->dbId);\n    }\n\n    public function testAssistedCustomeInject(): void\n    {\n        $injector = new Injector(new FakeInstanceBindModule());\n\n        $assistedConsumer = $injector->getInstance(FakeAssistedInjectConsumer::class);\n        /** @var FakeAssistedInjectConsumer $assistedConsumer */\n        $i = $assistedConsumer->assistCustomeAssistedInject();\n        $this->assertSame(1, $i);\n    }\n\n    /**\n     * @requires PHP 8.1\n     */\n    public function testConstructorPropertyPromotion(): void\n    {\n        $injector = new Injector(\n            new class extends AbstractModule\n            {\n                protected function configure()\n                {\n                    $this->bind()->annotatedWith('abc')->toInstance('abc');\n                }\n            }\n        );\n        $fake = $injector->getInstance(FakePropConstruct::class);\n        $this->assertSame('abc', $fake->abc);\n    }\n}\n"
  },
  {
    "path": "tests-php8/DualReaderTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Ray\\Di;\n\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\n\nclass DualReaderTest extends TestCase\n{\n    public function testPhp8Attribute(): FakePhp8Car\n    {\n        $injector = new Injector(new FakePhp8CarModule());\n        $car = $injector->getInstance(FakePhp8Car::class);\n        $this->assertInstanceOf(FakePhp8Car::class, $car);\n\n        return $car;\n    }\n\n    /**\n     * @depends testPhp8Attribute\n     */\n    public function testNamedParameterInMethod(FakePhp8Car $car): void\n    {\n        $this->assertInstanceOf(FakeMirrorRight::class, $car->rightMirror);\n        $this->assertInstanceOf(FakeMirrorRight::class, $car->qualfiedRightMirror);\n        $this->assertInstanceOf(FakeMirrorLeft::class, $car->leftMirror);\n        $this->assertInstanceOf(FakeMirrorLeft::class, $car->qualfiedLeftMirror);\n    }\n\n    /**\n     * @depends testPhp8Attribute\n     */\n    public function testNamedParameterInConstructor(FakePhp8Car $car): void\n    {\n        $this->assertInstanceOf(FakeMirrorRight::class, $car->constructerInjectedRightMirror);\n    }\n\n    /**\n     * @depends testPhp8Attribute\n     */\n    public function testPostConstruct(FakePhp8Car $car): void\n    {\n        $this->assertTrue($car->isConstructed);\n    }\n\n    /**\n     * @depends testPhp8Attribute\n     */\n    public function testCunstomInjectAnnotation(FakePhp8Car $car): void\n    {\n        $this->assertInstanceOf(FakeGearStickInterface::class, $car->gearStick);\n    }\n\n    /**\n     * @depends testPhp8Attribute\n     */\n    public function testProviderAttribute(FakePhp8Car $car): void\n    {\n        assert($car->handle instanceof FakeHandle);\n        $this->assertSame('momo', $car->handle->logo);\n    }\n\n    /**\n     * @depends testPhp8Attribute\n     */\n    public function testCumstomInject(FakePhp8Car $car): void\n    {\n        $this->assertSame(1, $car->one);\n    }\n}\n"
  },
  {
    "path": "vendor-bin/tools/composer.json",
    "content": "{\n    \"require-dev\": {\n        \"doctrine/coding-standard\": \"^9.0\",\n        \"phpmd/phpmd\": \"^2.9\",\n        \"phpmetrics/phpmetrics\": \"^2.7 || v3.0.0rc8\",\n        \"phpstan/phpstan\": \"^2.0\",\n        \"squizlabs/php_codesniffer\": \"^3.5\",\n        \"vimeo/psalm\": \"^6.8\",\n        \"phpstan/phpstan-phpunit\": \"^2.0\"\n    },\n    \"config\": {\n        \"allow-plugins\": {\n            \"dealerdirect/phpcodesniffer-composer-installer\": true\n        }\n    }\n}\n"
  }
]