Repository: ray-di/Ray.Di Branch: 2.x Commit: 13700a23ca7d Files: 311 Total size: 279.0 KB Directory structure: gitextract_o85d1j_u/ ├── .gitattributes ├── .github/ │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature.md │ │ └── question.md │ ├── SECURITY.md │ └── workflows/ │ ├── coding-standards.yml │ ├── continuous-integration.yml │ ├── demo.yml │ ├── prefer-lowest.yml │ ├── static-analysis.yml │ └── update-copyright-years-in-license-file.yml ├── .gitignore ├── .scrutinizer.yml ├── .sonarcloud.properties ├── CLAUDE.md ├── LICENSE ├── README.md ├── codecov.yml ├── composer-require-checker.json ├── composer.json ├── demo/ │ ├── 01a-linked-binding.php │ ├── 01b-linked-binding-setter-injection.php │ ├── 02-provider-binding.php │ ├── 02a-named-by-qualifier.php │ ├── 02b-named-by-named.php │ ├── 03-injection-point.php │ ├── 04-untarget-binding.php │ ├── 05a-constructor-binding.php │ ├── 05b-constructor-binding-setter-injection.php │ ├── 07-assisted-injection.php │ ├── 10-cache.php │ ├── 11-script-injector.php │ ├── 12-dependency-chain-error-message.php │ ├── chain-error/ │ │ ├── A.php │ │ ├── B.php │ │ ├── C.php │ │ ├── D.php │ │ └── EInterface.php │ ├── finder/ │ │ ├── Db.php │ │ ├── DbFinder.php │ │ ├── DbInterface.php │ │ ├── Finder.php │ │ ├── FinderInterface.php │ │ ├── FinderModule.php │ │ ├── MovieFinder.php │ │ ├── MovieLister.php │ │ ├── MovieListerInterface.php │ │ └── Sorter.php │ ├── run.php │ └── tmp/ │ └── .gitkeep ├── phpcs.xml ├── phpmd.xml ├── phpstan.neon ├── phpunit.xml.dist ├── psalm.xml ├── rector.php ├── src/ │ └── di/ │ ├── AbstractModule.php │ ├── AcceptInterface.php │ ├── AnnotatedClass.php │ ├── AnnotatedClassMethods.php │ ├── Annotation/ │ │ └── ScriptDir.php │ ├── Argument.php │ ├── Arguments.php │ ├── AspectBind.php │ ├── AssistedInjectInterceptor.php │ ├── AssistedInjectModule.php │ ├── AssistedModule.php │ ├── Bind.php │ ├── BindValidator.php │ ├── BuiltinModule.php │ ├── CompileNullObject.php │ ├── Container.php │ ├── ContainerFactory.php │ ├── Dependency.php │ ├── DependencyFactory.php │ ├── DependencyInterface.php │ ├── DependencyProvider.php │ ├── Di/ │ │ ├── Assisted.php │ │ ├── Inject.php │ │ ├── InjectInterface.php │ │ ├── Named.php │ │ ├── PostConstruct.php │ │ ├── Qualifier.php │ │ └── Set.php │ ├── Exception/ │ │ ├── DirectoryNotWritable.php │ │ ├── ExceptionInterface.php │ │ ├── InvalidContext.php │ │ ├── InvalidProvider.php │ │ ├── InvalidToConstructorNameParameter.php │ │ ├── InvalidType.php │ │ ├── MethodInvocationNotAvailable.php │ │ ├── NoHint.php │ │ ├── NotFound.php │ │ ├── SetNotFound.php │ │ ├── Unbound.php │ │ └── Untargeted.php │ ├── Exception.php │ ├── Grapher.php │ ├── InjectableInterface.php │ ├── InjectionPoint.php │ ├── InjectionPointInterface.php │ ├── InjectionPoints.php │ ├── Injector.php │ ├── InjectorInterface.php │ ├── Instance.php │ ├── Matcher/ │ │ └── AssistedInjectMatcher.php │ ├── MethodInvocationProvider.php │ ├── ModuleString.php │ ├── MultiBinder.php │ ├── MultiBinding/ │ │ ├── LazyInstance.php │ │ ├── LazyInterface.php │ │ ├── LazyProvider.php │ │ ├── LazyTo.php │ │ ├── Map.php │ │ ├── MapProvider.php │ │ ├── MultiBindingModule.php │ │ └── MultiBindings.php │ ├── Name.php │ ├── NewInstance.php │ ├── NullDependency.php │ ├── NullModule.php │ ├── NullObjectDependency.php │ ├── ProviderInterface.php │ ├── ProviderProvider.php │ ├── ProviderSetModule.php │ ├── ProviderSetProvider.php │ ├── Scope.php │ ├── SetContextInterface.php │ ├── SetterMethod.php │ ├── SetterMethods.php │ ├── SpyCompiler.php │ ├── Types.php │ ├── Untarget.php │ └── VisitorInterface.php ├── src-deprecated/ │ └── di/ │ ├── BcParameterQualifier.php │ ├── BcStringParser.php │ ├── EmptyModule.php │ ├── NullCache.php │ └── Provider.php ├── tests/ │ ├── bootstrap.php │ ├── di/ │ │ ├── AnnotatedClassTest.php │ │ ├── ArgumentTest.php │ │ ├── ArgumentsTest.php │ │ ├── AssistedTest.php │ │ ├── BcParameterQualifierIntegrationTest.php │ │ ├── BcParameterQualifierTest.php │ │ ├── BcStringParserTest.php │ │ ├── BindTest.php │ │ ├── ContainerTest.php │ │ ├── ContextualProviderTest.php │ │ ├── DependencyTest.php │ │ ├── Fake/ │ │ │ ├── Annotation/ │ │ │ │ ├── FakeInjectOne.php │ │ │ │ ├── FakeLeft.php │ │ │ │ ├── FakeNotQualifer.php │ │ │ │ ├── FakeQualifierOnly.php │ │ │ │ └── FakeRight.php │ │ │ ├── FakeAbstractClass.php │ │ │ ├── FakeAbstractDb.php │ │ │ ├── FakeAnnoClass.php │ │ │ ├── FakeAnnoInterceptor1.php │ │ │ ├── FakeAnnoInterceptor2.php │ │ │ ├── FakeAnnoInterceptor3.php │ │ │ ├── FakeAnnoInterceptor4.php │ │ │ ├── FakeAnnoInterceptor5.php │ │ │ ├── FakeAnnoInterceptorInterface.php │ │ │ ├── FakeAnnoMethod1.php │ │ │ ├── FakeAnnoMethod2.php │ │ │ ├── FakeAnnoMethod3.php │ │ │ ├── FakeAnnoModule.php │ │ │ ├── FakeAnnoOrderClass.php │ │ │ ├── FakeAop.php │ │ │ ├── FakeAopDoublyInstallModule.php │ │ │ ├── FakeAopGrapher.php │ │ │ ├── FakeAopGrapherModule.php │ │ │ ├── FakeAopInstallModule.php │ │ │ ├── FakeAopInterceptorModule.php │ │ │ ├── FakeAopInterface.php │ │ │ ├── FakeAopInterfaceModule.php │ │ │ ├── FakeAopModule.php │ │ │ ├── FakeAssistedConsumer.php │ │ │ ├── FakeAssistedDb.php │ │ │ ├── FakeAssistedDbModule.php │ │ │ ├── FakeAssistedDbProvider.php │ │ │ ├── FakeAssistedInjectConsumer.php │ │ │ ├── FakeAssistedInjectDb.php │ │ │ ├── FakeAssistedParamsConsumer.php │ │ │ ├── FakeBcConstructorQualifierClass.php │ │ │ ├── FakeBcParameterQualifierClass.php │ │ │ ├── FakeBcParameterQualifierModule.php │ │ │ ├── FakeBuiltin.php │ │ │ ├── FakeCar.php │ │ │ ├── FakeCarEngine.php │ │ │ ├── FakeCarEngineModule.php │ │ │ ├── FakeCarInterface.php │ │ │ ├── FakeCarModule.php │ │ │ ├── FakeClassInstanceBindModule.php │ │ │ ├── FakeClassWithBcParameterQualifier.php │ │ │ ├── FakeConcreteClass.php │ │ │ ├── FakeConstant.php │ │ │ ├── FakeConstantConsumer.php │ │ │ ├── FakeConstantInterface.php │ │ │ ├── FakeConstantModule.php │ │ │ ├── FakeContextualModule.php │ │ │ ├── FakeContextualProvider.php │ │ │ ├── FakeContextualRobot.php │ │ │ ├── FakeDoubleInterceptor.php │ │ │ ├── FakeDoubleInterceptorInterface.php │ │ │ ├── FakeEngine.php │ │ │ ├── FakeEngine2.php │ │ │ ├── FakeEngine3.php │ │ │ ├── FakeEngineInterface.php │ │ │ ├── FakeEngineProvider.php │ │ │ ├── FakeEngineToProviderModule.php │ │ │ ├── FakeFormerBindingHasPriorityModule.php │ │ │ ├── FakeGearStickInject.php │ │ │ ├── FakeGearStickInterface.php │ │ │ ├── FakeGearStickProvider.php │ │ │ ├── FakeHandle.php │ │ │ ├── FakeHandleBar.php │ │ │ ├── FakeHandleBarQualifier.php │ │ │ ├── FakeHandleInterface.php │ │ │ ├── FakeHandleProvider.php │ │ │ ├── FakeHardtop.php │ │ │ ├── FakeHardtopInterface.php │ │ │ ├── FakeInjectionPoint.php │ │ │ ├── FakeInstallModule.php │ │ │ ├── FakeInstanceBindModule.php │ │ │ ├── FakeInstanceBindModule2.php │ │ │ ├── FakeInstanceBindModuleOneTo3.php │ │ │ ├── FakeInternalTypeModule.php │ │ │ ├── FakeInternalTypes.php │ │ │ ├── FakeLeatherGearStick.php │ │ │ ├── FakeLeft.php │ │ │ ├── FakeLeftLeg.php │ │ │ ├── FakeLegInterface.php │ │ │ ├── FakeLogStringModule.php │ │ │ ├── FakeMirrorInterface.php │ │ │ ├── FakeMirrorLeft.php │ │ │ ├── FakeMirrorRight.php │ │ │ ├── FakeModuleInModule.php │ │ │ ├── FakeModuleInModuleOverride.php │ │ │ ├── FakeMultiBindingAnnotation.php │ │ │ ├── FakeMultiBindingConsumer.php │ │ │ ├── FakeOnionInterceptor2.php │ │ │ ├── FakeOnionInterceptor3.php │ │ │ ├── FakeOnionInterceptor4.php │ │ │ ├── FakeOpenCarModule.php │ │ │ ├── FakeOverrideInstallModule.php │ │ │ ├── FakePdoModule.php │ │ │ ├── FakePhp8Car.php │ │ │ ├── FakePhp8CarModule.php │ │ │ ├── FakePhp8HandleProvider.php │ │ │ ├── FakePriorityModule.php │ │ │ ├── FakePropConstruct.php │ │ │ ├── FakeRenameModule.php │ │ │ ├── FakeRight.php │ │ │ ├── FakeRightLeg.php │ │ │ ├── FakeRobot.php │ │ │ ├── FakeRobotInterface.php │ │ │ ├── FakeRobotProvider.php │ │ │ ├── FakeRobotTeam.php │ │ │ ├── FakeSet.php │ │ │ ├── FakeSetNotFoundWithMap.php │ │ │ ├── FakeSetNotFoundWithProvider.php │ │ │ ├── FakeToBindInvalidClassModule.php │ │ │ ├── FakeToBindModule.php │ │ │ ├── FakeToBindSingletonModule.php │ │ │ ├── FakeToConstructorRobot.php │ │ │ ├── FakeToProviderBindModule.php │ │ │ ├── FakeToProviderSingletonBindModule.php │ │ │ ├── FakeTyre.php │ │ │ ├── FakeTyreInterface.php │ │ │ ├── FakeUnNamedClass.php │ │ │ ├── FakeUnNamedModule.php │ │ │ ├── FakeUntarget.php │ │ │ ├── FakeUntargetChild.php │ │ │ ├── FakeUntargetModule.php │ │ │ ├── FakeUntargetToIntanceModule.php │ │ │ ├── FakeWalkRobot.php │ │ │ ├── FakeWalkRobotLegProvider.php │ │ │ ├── FakeWalkRobotModule.php │ │ │ ├── FakelNoConstructorCallModule.php │ │ │ └── NullVisitor.php │ │ ├── GrapherTest.php │ │ ├── InjectionPointTest.php │ │ ├── InjectionPointsTest.php │ │ ├── InjectorTest.php │ │ ├── ModuleMergerTest.php │ │ ├── ModuleTest.php │ │ ├── MultiBinding/ │ │ │ ├── MultiBinderTest.php │ │ │ └── MultiBindingModuleTest.php │ │ ├── NameTest.php │ │ ├── NewInstanceTest.php │ │ ├── NoHintTest.php │ │ ├── NullModuleTest.php │ │ ├── ProviderProviderTest.php │ │ ├── SetterMethodTest.php │ │ ├── SetterMethodsTest.php │ │ ├── SpyCompilerTest.php │ │ ├── UnboundTest.php │ │ ├── VisitorTest.php │ │ └── script/ │ │ ├── aop.php │ │ ├── bench.php │ │ └── grapher.php │ ├── script/ │ │ └── aop.php.cache │ ├── stub/ │ │ └── BindInterface.phpstub │ └── type/ │ └── InjectorInterfaceTest.php ├── tests-php8/ │ ├── AssistedInjectTest.php │ └── DualReaderTest.php └── vendor-bin/ └── tools/ └── composer.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Normalize line endings * text=auto eol=lf /tests export-ignore /tests-php8 export-ignore /demo export-ignore /demo-php8 export-ignore /.gitattributes export-ignore /.github export-ignore /vendor-bin export-ignore /.gitignore export-ignore /.scrutinizer.yml export-ignore /.travis.yml export-ignore /codecov.yml export-ignore /phpcs.xml export-ignore /phpmd.xml export-ignore /phpstan.neon export-ignore /phpunit.xml.dist export-ignore /psalm.xml export-ignore /psalm.compiler.xml export-ignore # Configure diff output for .php and .phar files. *.php diff=php *.phar -diff ================================================ FILE: .github/CONTRIBUTING.md ================================================ # CONTRIBUTING ## Code Contributions ## Installation Install project dependencies and test tools by running the following commands. ```bash $ composer install ``` ## Running tests ```bash $ composer test ``` ```bash $ composer coverage // xdebug $ composer pcov // pcov ``` Add tests for your new code ensuring that you have 100% code coverage. In rare cases, code may be excluded from test coverage using `@codeCoverageIgnore`. ## Sending a pull request To ensure your PHP code changes pass the CI checks, make sure to run all the same checks before submitting a PR. ```bash $ composer tests ``` When you make a pull request, the tests will automatically be run again by GH action on multiple php versions. ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug Report about: Create a bug report labels: Bug --- ### Bug Report ### How to reproduce ================================================ FILE: .github/ISSUE_TEMPLATE/feature.md ================================================ --- name: Feature about: Suggest a new feature or enhancement labels: Feature --- ================================================ FILE: .github/ISSUE_TEMPLATE/question.md ================================================ --- name: Question about: Ask a question regarding software usage labels: Support --- ================================================ FILE: .github/SECURITY.md ================================================ # Reporting a vulnerability If you have found any issues that might have security implications, please send a report privately to akihito.koriyama@gmail.com Do not report security reports publicly. ================================================ FILE: .github/workflows/coding-standards.yml ================================================ name: Coding Standards on: push: pull_request: workflow_dispatch: jobs: cs: uses: ray-di/.github/.github/workflows/coding-standards.yml@v1 with: php_version: 8.3 ================================================ FILE: .github/workflows/continuous-integration.yml ================================================ name: Continuous Integration on: push: pull_request: workflow_dispatch: jobs: ci: uses: ray-di/.github/.github/workflows/continuous-integration.yml@v1 with: old_stable: '["8.2", "8.3", "8.4"]' current_stable: 8.5 script: demo/run.php ================================================ FILE: .github/workflows/demo.yml ================================================ name: Demo on: push: pull_request: workflow_dispatch: jobs: demo: name: Demo runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 8.3 tools: cs2pr coverage: none - name: Get composer cache directory id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --no-interaction --no-progress --prefer-dist - name: Run Demo run: php demo/run.php ================================================ FILE: .github/workflows/prefer-lowest.yml ================================================ name: Prefer Lowest on: push: pull_request: workflow_dispatch: jobs: prefer-lowest: name: Prefer Lowest runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 8.2 tools: cs2pr coverage: none - name: Get composer cache directory id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer update --prefer-lowest --no-interaction --no-progress --prefer-dist - name: Run tests run: composer test ================================================ FILE: .github/workflows/static-analysis.yml ================================================ name: Static Analysis on: push: pull_request: workflow_dispatch: jobs: sa: uses: ray-di/.github/.github/workflows/static-analysis.yml@v1 with: php_version: 8.3 ================================================ FILE: .github/workflows/update-copyright-years-in-license-file.yml ================================================ name: Update copyright year(s) in license file on: workflow_dispatch: schedule: - cron: "0 3 1 1 *" jobs: run: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: FantasticFiasco/action-update-license-year@v2 with: token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ /vendor/ /build/ /tests/compiler/tmp/car/ /tests/compiler/tmp/injector_cache/ /tests/compiler/tmp/logger/ /tests/compiler/tmp/prod/ /tests/di/tmp/ /tests/di/script/grapher.php.txt /tests/tmp/ /tests/compiler/tmp/ /*.cache.php /.phpunit.result.cache /composer.lock /.phpcs-cache /coverage.xml /demo/error.log /demo/10-cache.php.cache.php /demo-php8/error.log /demo-php8/10-cache.php.cache.php /vendor-bin/**/vendor ================================================ FILE: .scrutinizer.yml ================================================ build: image: default-jammy environment: php: 8.4 nodes: analysis: tests: override: - php-scrutinizer-run filter: paths: ["src/*"] ================================================ FILE: .sonarcloud.properties ================================================ # Exclude files from SonarCloud analysis # - GitHub workflow files: version tags are best practice # - Demo files: example code with intentional duplication for demonstration sonar.exclusions=.github/workflows/**,demo/** ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Ray.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. ## Core Architecture ### Key Components - **AbstractModule**: Base class for defining dependency bindings. Modules are composed using `install()` and can be overridden using `override()` - **Injector**: Main entry point that manages the DI container and creates instances. Auto-registers generated proxy classes and handles untargeted bindings - **Bind**: Fluent API for creating bindings (`.to()`, `.toProvider()`, `.toInstance()`, `.in()`) - **Container**: Internal storage for all bindings and dependencies - **Annotations**: Located in `src/di/Di/` - includes `@Inject`, `@Named`, `@Assisted`, etc. ### Directory Structure - `src/di/`: Core DI framework code - `src-deprecated/`: Legacy code maintained for compatibility - `tests/di/`: Unit tests with extensive fake classes for testing - `demo/` and `demo-php8/`: Examples showing framework usage - Compiled proxy classes are cached in configurable temp directories ## Development Commands ### Testing ```bash composer test # Run PHPUnit tests composer coverage # Generate test coverage with Xdebug composer pcov # Generate coverage with PCOV (faster) ``` ### Code Quality ```bash composer cs # Run PHP_CodeSniffer composer cs-fix # Auto-fix coding standards composer sa # Static analysis (Psalm + PHPStan) composer clean # Clear analysis caches ``` ### Build Pipeline ```bash composer build # Full build: cs + sa + pcov + metrics composer tests # Quick check: cs + sa + test ``` ### Analysis Tools ```bash composer phpmd # PHP Mess Detector composer metrics # Generate code metrics composer baseline # Update static analysis baselines ``` ## Testing Strategy - Tests use extensive fake classes in `tests/di/Fake/` to simulate real-world scenarios - Supports both PHP 7.2+ and PHP 8+ with separate test suites - Cache files are automatically cleaned between test runs - AOP proxy generation is tested with temporary directories ## Framework Patterns ### Module Definition ```php class MyModule extends AbstractModule { protected function configure(): void { $this->bind(Interface::class)->to(Implementation::class); $this->bind(Service::class)->toProvider(ServiceProvider::class); } } ``` ### Injection Usage ```php $injector = new Injector(new MyModule()); $instance = $injector->getInstance(Interface::class); ``` ## Important Notes - Ray.Di generates proxy classes for AOP which are cached in temp directories - The framework supports both constructor and setter injection - All bindings are resolved at runtime with automatic proxy weaving for aspects - Multi-binding support allows collecting multiple implementations of the same interface ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2012-2026 Akihito Koriyama Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Ray.Di ## A dependency injection framework for PHP [![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) [![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) [![Type Coverage](https://shepherd.dev/github/ray-di/Ray.Di/coverage.svg)](https://shepherd.dev/github/ray-di/Ray.Di) [![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) [![Total Downloads](https://poser.pugx.org/ray/di/downloads)](https://packagist.org/packages/ray/di) logo Ray.Di is DI and AOP framework for PHP inspired by [Google Guice](https://github.com/google/guice/wiki). https://ray-di.github.io ================================================ FILE: codecov.yml ================================================ codecov: notify: require_ci_to_pass: yes coverage: status: project: default: target: 100% patch: default: target: 100% ================================================ FILE: composer-require-checker.json ================================================ { "symbol-whitelist": [ "null", "true", "false", "static", "self", "parent", "array", "string", "int", "float", "bool", "iterable", "callable", "void", "object", "Attribute", "ReflectionAttribute", "Doctrine\\Common\\Cache\\CacheProvider" ] } ================================================ FILE: composer.json ================================================ { "name": "ray/di", "description": "Guice style dependency injection framework", "keywords": ["di", "aop"], "license": "MIT", "authors": [ { "name": "Akihito Koriyama", "email": "akihito.koriyama@gmail.com" } ], "require": { "php": "^8.2", "koriym/null-object": "^1.0", "ray/aop": "^2.19" }, "require-dev": { "ext-pdo": "*", "bamarni/composer-bin-plugin": "^1.4", "infection/infection": "*", "phpunit/phpunit": "^9.6.31" }, "suggest": { "ray/compiler": "For compiling dependency injection container to improve performance" }, "config": { "sort-packages": true, "allow-plugins": { "bamarni/composer-bin-plugin": true, "infection/extension-installer": true } }, "autoload": { "psr-4": { "Ray\\Di\\": ["src/di", "src-deprecated/di"] } }, "autoload-dev": { "psr-4": { "Ray\\Di\\": ["tests/di", "tests/di/Fake/"] } }, "scripts": { "test": "phpunit --log-junit=build/junit.xml", "tests": ["@cs", "@sa", "@test"], "coverage": ["php -dzend_extension=xdebug.so -dxdebug.mode=coverage ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage"], "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"], "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"], "cs": ["vendor-bin/tools/vendor/squizlabs/php_codesniffer/bin/phpcs --standard=./phpcs.xml src tests"], "cs-fix": ["vendor-bin/tools/vendor/squizlabs/php_codesniffer/bin/phpcbf src tests"], "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"], "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 "], "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"], "phpmd": ["vendor-bin/tools/vendor/phpmd/phpmd/src/bin/phpmd src/di text ./phpmd.xml"], "build": ["@cs", "@sa", "@pcov", "@metrics"], "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"] }, "extra": { "bamarni-bin": { "bin-links": true, "forward-command": true } } } ================================================ FILE: demo/01a-linked-binding.php ================================================ bind(FinderInterface::class)->to(Finder::class); $this->bind(MovieListerInterface::class)->to(MovieLister::class); } } $injector = new Injector(new FinderModule()); $movieLister = $injector->getInstance(MovieListerInterface::class); /** @var MovieLister $movieLister */ $works = ($movieLister->finder instanceof Finder); echo($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL; ================================================ FILE: demo/01b-linked-binding-setter-injection.php ================================================ finder = $finder; } } class FinderModule extends AbstractModule { protected function configure() { $this->bind(FinderInterface::class)->to(Finder::class); $this->bind(MovieListerInterface::class)->to(MovieLister::class); } } $injector = new Injector(new FinderModule()); $movieLister = $injector->getInstance(MovieListerInterface::class); /** @var MovieLister $movieLister */ $works = ($movieLister->finder instanceof Finder); echo($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL; ================================================ FILE: demo/02-provider-binding.php ================================================ datetime = $dateTime; } } class FinderProvider implements ProviderInterface { /** * {@inheritdoc} */ public function get() { return new Finder(new DateTimeImmutable('now')); } } interface MovieListerInterface { } class MovieLister implements MovieListerInterface { public function __construct( public FinderInterface $finder ){ } } class FinderModule extends AbstractModule { protected function configure() { $this->bind(FinderInterface::class)->toProvider(FinderProvider::class); $this->bind(MovieListerInterface::class)->to(MovieLister::class); } } $injector = new Injector(new FinderModule()); $movieLister = $injector->getInstance(MovieListerInterface::class); /** @var MovieLister $movieLister */ $works = ($movieLister->finder->datetime instanceof DateTimeImmutable); echo($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL; ================================================ FILE: demo/02a-named-by-qualifier.php ================================================ finder = $finder; } } #[Attribute(Attribute::TARGET_PARAMETER), Qualifier] class Legacy { } class FinderModule extends AbstractModule { protected function configure() { $this->bind(FinderInterface::class)->annotatedWith(Legacy::class)->to(LegacyFinder::class); $this->bind(MovieListerInterface::class)->to(MovieLister::class); } } $injector = new Injector(new FinderModule()); $movieLister = $injector->getInstance(MovieListerInterface::class); /** @var MovieLister $movieLister */ $works = ($movieLister->finder instanceof LegacyFinder); echo($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL; ================================================ FILE: demo/02b-named-by-named.php ================================================ bind(FinderInterface::class)->annotatedWith('legacy')->to(LegacyFinder::class); $this->bind(MovieListerInterface::class)->to(MovieLister::class); } } $injector = new Injector(new FinderModule()); $movieLister = $injector->getInstance(MovieListerInterface::class); /** @var MovieLister $movieLister */ $works = ($movieLister->finder instanceof LegacyFinder); echo($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL; ================================================ FILE: demo/03-injection-point.php ================================================ className = $className; } public function find(): string { return sprintf('search for [%s]', $this->className); } } class MovieLister implements MovieListerInterface { public FinderInterface $finder; public function __construct(FinderInterface $finder) { $this->finder = $finder; } } class FinderProvider implements ProviderInterface { public function __construct( public InjectionPointInterface $ip ) {} /** * {@inheritdoc} */ public function get() { $className = $this->ip->getClass()->getName(); return new Finder($className); } } class FinderModule extends AbstractModule { protected function configure() { $this->bind(FinderInterface::class)->toProvider(FinderProvider::class); $this->bind(MovieListerInterface::class)->to(MovieLister::class); } } $injector = new Injector(new FinderModule()); $movieLister = $injector->getInstance(MovieListerInterface::class); /** @var MovieLister $movieLister */ $result = $movieLister->finder->find(); $works = ($result === 'search for [MovieLister]'); echo($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL; ================================================ FILE: demo/04-untarget-binding.php ================================================ bind(Finder::class)->in(Scope::SINGLETON); } } $injector = new Injector(new FinderModule()); $finder1 = $injector->getInstance(Finder::class); $finder2 = $injector->getInstance(Finder::class); $works = spl_object_hash($finder1) === spl_object_hash($finder2); echo($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL; ================================================ FILE: demo/05a-constructor-binding.php ================================================ bind(PDO::class)->toConstructor(PDO::class, 'dsn=pdo_dsn'); $this->bind()->annotatedWith('pdo_dsn')->toInstance('sqlite::memory:'); } } class PdoModule extends AbstractModule { protected function configure() { // ['dsn' => 'pdo_dsn'] works (recommended) $this->bind(PDO::class)->toConstructor(PDO::class, ['dsn' => 'pdo_dsn']); $this->bind()->annotatedWith('pdo_dsn')->toInstance('sqlite::memory:'); } } $injector = new Injector(new PdoModuleLegacyNaming()); $pdo = $injector->getInstance(PDO::class); $works = $pdo instanceof PDO; $injector = new Injector(new PdoModule()); $pdo = $injector->getInstance(PDO::class); $works &= $pdo instanceof PDO; echo($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL; ================================================ FILE: demo/05b-constructor-binding-setter-injection.php ================================================ finder = $finder; } } class ListerModule extends AbstractModule { protected function configure() { $this->bind(FinderInterface::class)->to(Finder::class); $this->bind(MovieListerInterface::class)->toConstructor( MovieLister::class, '', (new InjectionPoints())->addMethod('setFinder') // or (new InjectionPoints)->addOptionalMethod('setFinder') ); } } $injector = new Injector(new ListerModule()); $lister = $injector->getInstance(MovieListerInterface::class); /** @var MovieLister $lister */ $works = ($lister->finder instanceof FinderInterface); echo($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL; ================================================ FILE: demo/07-assisted-injection.php ================================================ addPsr4('', __DIR__ . '/finder'); $injector = new Injector(new class extends AbstractModule{ protected function configure() { $this->bind(FinderInterface::class)->to(Finder::class); } }); $finder = $injector->getInstance(MovieFinder::class); /** @var MovieFinder $finder */ $works = $finder->find('Tokyo Story') === 'searching [Tokyo Story] by [Finder]'; echo($works ? 'It works!' : 'It DOES NOT work!') . PHP_EOL; ================================================ FILE: demo/10-cache.php ================================================ addPsr4('', __DIR__ . '/finder'); $start = microtime(true); $injector = new Injector(new FinderModule()); $movieLister = $injector->getInstance(MovieListerInterface::class); assert($movieLister instanceof MovieLister); $time1 = microtime(true) - $start; // save file cache file_put_contents(__FILE__ . '.cache.php', serialize(new Injector(new FinderModule()))); // cached injector $start = microtime(true); $injector = unserialize(file_get_contents(__FILE__ . '.cache.php')); $movieLister2 = $injector->getInstance(MovieListerInterface::class); assert($movieLister2 instanceof MovieLister); $time2 = microtime(true) - $start; $works = $movieLister instanceof MovieListerInterface; echo $works ? 'It works!' : 'It DOES NOT work!'; echo ' [Injector cache] x' . round($time1 / $time2) . ' times faster.' . PHP_EOL; ================================================ FILE: demo/11-script-injector.php ================================================ addPsr4('', __DIR__ . '/finder'); $start = microtime(true); $injector = new Injector(new FinderModule()); $movieLister = $injector->getInstance(MovieListerInterface::class); assert($movieLister instanceof MovieLister); $time1 = microtime(true) - $start; // compile $tmpDir = __DIR__ . '/tmp'; $compiler = new DiCompiler(new FinderModule(), $tmpDir); $compiler->compile(); $scriptInjector = new ScriptInjector($tmpDir); $movieLister2 = $scriptInjector->getInstance(MovieListerInterface::class); // script injector $start = microtime(true); $movieLister2 = $scriptInjector->getInstance(MovieListerInterface::class); assert($movieLister2 instanceof MovieLister); $time2 = microtime(true) - $start; $works = $movieLister instanceof MovieListerInterface; echo $works ? 'It works!' : 'It DOES NOT work!'; echo ' [Script injector] x' . round($time1 / $time2) . ' times faster.' . PHP_EOL; ================================================ FILE: demo/12-dependency-chain-error-message.php ================================================ addPsr4('', __DIR__ . '/chain-error'); class DeepLinkedClassBindingModule extends AbstractModule { protected function configure() { $this->bind(A::class); $this->bind(B::class); $this->bind(C::class); $this->bind(D::class); // purposefully not bound. // D will require EInterface object to be injected, but // EInterface is not bound and an Unbound exception is thrown. } } $injector = new Injector(new DeepLinkedClassBindingModule()); // this will fail with an exception as EInterface is not bound try { $injector->getInstance(A::class); } catch (Unbound $e) { $msg = $e->getMessage(); ob_start(); // dependency 'B' with name '' used // dependency 'B' with name '' used in12-dependency-chain-error-message.php:11 echo PHP_EOL . '---------' . PHP_EOL; echo $e; // exception 'Ray\Di\Exception\Unbound' with message 'EInterface-' // - dependency 'EInterface' with name '' used in12-dependency-chain-error-message.php:26 // - dependency 'D' with name '' used in12-dependency-chain-error-message.php:21 // - dependency 'C' with name '' used in12-dependency-chain-error-message.php:16 // - dependency 'B' with name '' used in12-dependency-chain-error-message.php:11 // ... echo PHP_EOL . '---------' . PHP_EOL; do { echo get_class($e) . ':' . $e->getMessage() . PHP_EOL; } while ($e = $e->getPrevious()); // Ray\Di\Exception\Unbound:dependency 'B' with name '' used in12-dependency-chain-error-message.php:11 // Ray\Di\Exception\Unbound:dependency 'C' with name '' used in12-dependency-chain-error-message.php:16 // Ray\Di\Exception\Unbound:dependency 'D' with name '' used in12-dependency-chain-error-message.php:21 // Ray\Di\Exception\Unbound:dependency 'EInterface' with name '' used in12-dependency-chain-error-message.php:26 // Ray\Di\Exception\Unbound:EInterface- } $log = ob_get_clean(); $works = strpos($log, "dependency 'B' with name '' used"); echo $works ? 'It works!' : 'It DOES NOT work!'; file_put_contents(__DIR__ . '/error.log', $log); echo '[error log]: ' . __DIR__ . '/error.log' . PHP_EOL; ================================================ FILE: demo/chain-error/A.php ================================================ bind(Sorter::class)->in(Scope::SINGLETON); $this->bind(DbInterface::class)->toConstructor(Db::class, 'dsn=dsn,username=username,password=password'); $this->bind()->annotatedWith('dsn')->toInstance('msql:host=localhost;dbname=test'); $this->bind()->annotatedWith('username')->toInstance('root'); $this->bind()->annotatedWith('password')->toInstance(''); $this->bind(FinderInterface::class)->to(DbFinder::class); $this->bind(MovieListerInterface::class)->to(MovieLister::class); } } ================================================ FILE: demo/finder/MovieFinder.php ================================================ src tests */tests/tmp/* */tests/Fake/* src/Module/* tests/* */Fake/* */tmp/* ================================================ FILE: phpmd.xml ================================================ ================================================ FILE: phpstan.neon ================================================ parameters: errorFormat: raw level: max paths: - src/di - tests/di - tests/type excludePaths: - tests/tmp/* - tests/di/tmp/* - tests/Di/tmp/* - tests/di/Fake/* - tests/di/Ray_Di_* - tests/di/script/* ignoreErrors: - '#contains generic class ReflectionAttribute but does not specify its types:#' - '#generic class ReflectionAttribute but does not specify its types:#' ================================================ FILE: phpunit.xml.dist ================================================ src/di tests/di/ tests-php8 tests/di ================================================ FILE: psalm.xml ================================================ ================================================ FILE: rector.php ================================================ withPaths([ __DIR__ . '/src/di', ]) ->withSkip([ __DIR__ . '/src-deprecated', ]) ->withPhpSets(php82: true) ->withPreparedSets( deadCode: true, codeQuality: true, codingStyle: true, typeDeclarations: true, privatization: true, instanceOf: true, earlyReturn: true ); ================================================ FILE: src/di/AbstractModule.php ================================================ lastModule = $module; $this->activate(); if ($module instanceof self && $this->container instanceof Container) { $this->container->merge($module->getContainer()); } } public function __toString(): string { return (new ModuleString())($this->getContainer(), $this->getContainer()->getPointcuts()); } /** * Install module */ public function install(self $module): void { $this->getContainer()->merge($module->getContainer()); } /** * Override module */ public function override(self $module): void { $module->getContainer()->merge($this->getContainer()); $this->container = $module->getContainer(); } /** * Return activated container */ public function getContainer(): Container { if ($this->container === null) { $this->activate(); } assert($this->container instanceof Container); return $this->container; } /** * Bind interceptor * * @param InterceptorClassList $interceptors */ public function bindInterceptor(AbstractMatcher $classMatcher, AbstractMatcher $methodMatcher, array $interceptors): void { $pointcut = new Pointcut($classMatcher, $methodMatcher, $interceptors); $this->getContainer()->addPointcut($pointcut); foreach ($interceptors as $interceptor) { if (class_exists($interceptor)) { (new Bind($this->getContainer(), $interceptor))->to($interceptor)->in(Scope::SINGLETON); return; } assert(interface_exists($interceptor)); (new Bind($this->getContainer(), $interceptor))->in(Scope::SINGLETON); } } /** * Bind interceptor early * * @param InterceptorClassList $interceptors */ public function bindPriorityInterceptor(AbstractMatcher $classMatcher, AbstractMatcher $methodMatcher, array $interceptors): void { $pointcut = new PriorityPointcut($classMatcher, $methodMatcher, $interceptors); $this->getContainer()->addPointcut($pointcut); foreach ($interceptors as $interceptor) { (new Bind($this->getContainer(), $interceptor))->to($interceptor)->in(Scope::SINGLETON); } } /** * Rename binding name * * @param string $interface Interface * @param string $newName New binding name * @param string $sourceName Original binding name * @param string $targetInterface Original interface */ public function rename(string $interface, string $newName, string $sourceName = Name::ANY, string $targetInterface = ''): void { $targetInterface = $targetInterface ?: $interface; if ($this->lastModule instanceof self) { $this->lastModule->getContainer()->move($interface, $sourceName, $targetInterface, $newName); } } /** * Configure binding * * @return void * * @noinspection ReturnTypeCanBeDeclaredInspection */ abstract protected function configure(); /** * Bind interface * * @param BindableInterface $interface */ protected function bind(string $interface = ''): Bind { return new Bind($this->getContainer(), $interface); } /** * Activate bindings */ private function activate(): void { $this->container = new Container(); $this->matcher = new Matcher(); $this->configure(); } } ================================================ FILE: src/di/AcceptInterface.php ================================================ injectionMethod = new AnnotatedClassMethods(); } /** * Return factory instance * * @phpstan-param ReflectionClass $class Target class reflection */ public function getNewInstance(ReflectionClass $class): NewInstance { $setterMethods = new SetterMethods([]); $methods = $class->getMethods(); foreach ($methods as $method) { if ($method->name === '__construct') { continue; } $setterMethods->add($this->injectionMethod->getSetterMethod($method)); } $name = $this->injectionMethod->getConstructorName($class); return new NewInstance($class, $setterMethods, $name); } /** * Return @-PostConstruct method reflection * * @phpstan-param ReflectionClass $class */ public function getPostConstruct(ReflectionClass $class): ?ReflectionMethod { $methods = $class->getMethods(); foreach ($methods as $method) { $annotation = $method->getAnnotation(PostConstruct::class); if ($annotation instanceof PostConstruct) { return $method; } } return null; } } ================================================ FILE: src/di/AnnotatedClassMethods.php ================================================ $class */ public function getConstructorName(ReflectionClass $class): Name { $constructor = $class->getConstructor(); if (! $constructor instanceof \ReflectionMethod) { return new Name(Name::ANY); } $reflMethod = new \ReflectionMethod($class->getName(), '__construct'); $name = Name::withAttributes($reflMethod); if ($name instanceof Name) { return $name; } return new Name(Name::ANY); } public function getSetterMethod(ReflectionMethod $method): ?SetterMethod { $inject = $method->getAnnotation(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF); if (! $inject instanceof InjectInterface) { return null; } $name = $this->getName($method); $setterMethod = new SetterMethod($method, $name); if ($inject->isOptional()) { $setterMethod->setOptional(); } return $setterMethod; } private function getName(ReflectionMethod $method): Name { $name = Name::withAttributes($method); if ($name instanceof Name) { return $name; } return new Name(Name::ANY); } } ================================================ FILE: src/di/Annotation/ScriptDir.php ================================================ getType($parameter); $isOptional = $parameter->isOptional(); $this->isDefaultAvailable = $parameter->isDefaultValueAvailable() || $isOptional; if ($isOptional) { $this->default = null; } $this->setDefaultValue($parameter); $this->index = $type . '-' . $name; $this->reflection = $parameter; $this->meta = sprintf( "'%s-%s' in %s:%d ($%s)", $type, $name, $this->reflection->getDeclaringFunction()->getFileName(), $this->reflection->getDeclaringFunction()->getStartLine(), $parameter->getName() ); } /** * Return index * * @return DependencyIndex */ public function __toString(): string { return $this->index; } /** * Return reflection */ public function get(): ReflectionParameter { return $this->reflection; } public function isDefaultAvailable(): bool { return $this->isDefaultAvailable; } /** * @return mixed */ public function getDefaultValue() { return $this->default; } public function getMeta(): string { return $this->meta; } /** * @return array */ public function __serialize(): array { $method = $this->reflection->getDeclaringFunction(); assert($method instanceof ReflectionMethod); $ref = [ $method->class, $method->name, $this->reflection->getName(), ]; return [ $this->index, $this->isDefaultAvailable, $this->default, $this->meta, $ref, ]; } /** * @param array{0: DependencyIndex, 1: bool, 2: string, 3: string, 4: string, 5: array{0: string, 1: string, 2:string}} $unserialized */ public function __unserialize(array $unserialized): void { [ $this->index, $this->isDefaultAvailable, $this->default, $this->meta, $ref, ] = $unserialized; $this->reflection = new ReflectionParameter([$ref[0], $ref[1]], $ref[2]); } /** @inheritDoc */ public function accept(VisitorInterface $visitor): void { $visitor->visitArgument( $this->index, $this->isDefaultAvailable, $this->default, $this->reflection ); } private function setDefaultValue(ReflectionParameter $parameter): void { if (! $this->isDefaultAvailable) { return; } try { $this->default = $parameter->getDefaultValue(); // @codeCoverageIgnoreStart } catch (ReflectionException) { $this->default = null; // @codeCoverageIgnoreEnd } } /** * @psalm-pure */ private function getType(ReflectionParameter $parameter): string { $type = $parameter->getType(); return $type instanceof ReflectionNamedType && ! in_array($type->getName(), self::UNBOUND_TYPE, true) ? $type->getName() : ''; } } ================================================ FILE: src/di/Arguments.php ================================================ getParameters(); foreach ($parameters as $parameter) { $this->arguments[] = new Argument($parameter, $name($parameter)); } } /** * Return arguments * * @return list * * @throws Exception\Unbound */ public function inject(Container $container): array { $parameters = []; foreach ($this->arguments as $parameter) { /** @psalm-suppress MixedAssignment */ $parameters[] = $this->getParameter($container, $parameter); } return $parameters; } public function accept(VisitorInterface $visitor): void { $visitor->visitArguments($this->arguments); } /** * @return mixed * * @throws Unbound */ private function getParameter(Container $container, Argument $argument) { $this->bindInjectionPoint($container, $argument); try { return $container->getDependency((string) $argument); } catch (Unbound $unbound) { if ($argument->isDefaultAvailable()) { return $argument->getDefaultValue(); } if ($unbound instanceof NoHint) { throw new NoHint($this->getNoHintMsg($argument), 0, $unbound); } throw new Unbound($argument->getMeta(), 0, $unbound); } } private function bindInjectionPoint(Container $container, Argument $argument): void { $isSelf = (string) $argument === InjectionPointInterface::class . '-' . Name::ANY; if ($isSelf) { return; } (new Bind($container, InjectionPointInterface::class))->toInstance(new InjectionPoint($argument->get())); } private function getNoHintMsg(Argument $argument): string { $ref = $argument->get(); $func = $ref->getDeclaringFunction(); $fileName = $func->getFileName(); return sprintf( '$%s (%s:%d)', $ref->getName(), $fileName !== false ? $fileName : 'unknown file', $func->getStartLine() ); } } ================================================ FILE: src/di/AspectBind.php ================================================ bind->getBindings(); $instantiatedBindings = []; foreach ($bindings as $methodName => $interceptorClassNames) { $interceptors = []; foreach ($interceptorClassNames as $interceptorClassName) { /** @var class-string $interceptorClassName */ $interceptor = $container->getInstance($interceptorClassName); assert($interceptor instanceof MethodInterceptor); $interceptors[] = $interceptor; } $instantiatedBindings[$methodName] = $interceptors; } return $instantiatedBindings; } /** @inheritDoc */ public function accept(VisitorInterface $visitor): void { $visitor->visitAspectBind($this->bind); } } ================================================ FILE: src/di/AssistedInjectInterceptor.php ================================================ methodInvocationProvider->set($invocation); $params = $invocation->getMethod()->getParameters(); $namedArguments = $this->getNamedArguments($invocation); foreach ($params as $param) { /** @var list $inject */ $inject = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF); // @phpstan-ignore-line /** @var list $assisted */ $assisted = $param->getAttributes(Assisted::class); if (isset($assisted[0]) || isset($inject[0])) { /** @psalm-suppress MixedAssignment */ $namedArguments[$param->getName()] = $this->getDependency($param); } } $callable = [$invocation->getThis(), $invocation->getMethod()->getName()]; assert(is_callable($callable)); return call_user_func_array($callable, $namedArguments); } /** * @param MethodInvocation $invocation * * @return array */ private function getNamedArguments(MethodInvocation $invocation): array { $args = $invocation->getArguments(); $params = $invocation->getMethod()->getParameters(); $namedParams = []; foreach ($params as $param) { $pos = $param->getPosition(); if (isset($args[$pos])) { /** @psalm-suppress MixedAssignment */ $namedParams[$param->getName()] = $args[$pos]; } } return $namedParams; } /** * @return mixed */ private function getDependency(ReflectionParameter $param) { $named = (string) $this->getName($param); $type = $param->getType(); assert($type instanceof ReflectionNamedType || $type === null); $typeName = $type instanceof ReflectionNamedType ? $type->getName() : ''; $interface = in_array($typeName, Argument::UNBOUND_TYPE) ? '' : $typeName; /** @var class-string $interface */ return $this->injector->getInstance($interface, $named); } private function getName(ReflectionParameter $param): ?string { /** @var list $nameds */ $nameds = $param->getAttributes(Named::class); if (isset($nameds[0])) { $named = $nameds[0]->newInstance(); assert($named instanceof Named); return $named->value; } if ($param->getAttributes(Inject::class)) { return null; } return $this->getCustomInject($param); } /** * @return ?class-string */ private function getCustomInject(ReflectionParameter $param): ?string { /** @var list $injects */ $injects = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF); if (! $injects) { return null; } $inject = $injects[0]->newInstance(); assert($inject instanceof InjectInterface); return $inject::class; } } ================================================ FILE: src/di/AssistedInjectModule.php ================================================ bindInterceptor( $this->matcher->any(), (new AssistedInjectMatcher()), [AssistedInjectInterceptor::class] ); } } ================================================ FILE: src/di/AssistedModule.php ================================================ install(new AssistedInjectModule()); $this->bind(MethodInvocation::class)->toProvider(MethodInvocationProvider::class)->in(Scope::SINGLETON); $this->bind(MethodInvocationProvider::class)->in(Scope::SINGLETON); } } ================================================ FILE: src/di/Bind.php ================================================ |string $interface */ public function __construct( private readonly Container $container, private readonly string $interface ) { $this->validate = new BindValidator(); $bindUntarget = class_exists($this->interface) && ! (new \ReflectionClass($this->interface))->isAbstract() && ! $this->isRegistered($this->interface); $this->bound = new NullDependency(); if ($bindUntarget) { /** @var class-string $interface */ $interface = $this->interface; $this->untarget = new Untarget($interface); return; } $this->validate->constructor($this->interface); } public function __destruct() { if ($this->untarget) { ($this->untarget)($this->container, $this); $this->untarget = null; } } /** * @return non-empty-string */ public function __toString(): string { return $this->interface . '-' . $this->name; } /** * Set dependency name * * @param BindingName $name */ public function annotatedWith(string $name): self { $this->name = $name; return $this; } /** * Bind to class * * @param class-string $class */ public function to(string $class): self { $this->untarget = null; $refClass = $this->validate->to($this->interface, $class); $this->bound = (new DependencyFactory())->newAnnotatedDependency($refClass); $this->container->add($this); return $this; } /** * Bind to constructor * * @param class-string $class class name * @param ParameterNameMapping|string $name "varName=bindName,..." or [$varName => $bindName, $varName => $bindName...] * * @throws ReflectionException * * @template T of object */ public function toConstructor(string $class, string|array $name, ?InjectionPoints $injectionPoints = null, ?string $postConstruct = null): self { $this->untarget = null; $postConstructRef = $postConstruct !== null ? new ReflectionMethod($class, $postConstruct) : null; /** @var ReflectionClass $reflection */ $reflection = new ReflectionClass($class); $this->bound = (new DependencyFactory())->newToConstructor($reflection, $name, $injectionPoints, $postConstructRef); $this->container->add($this); return $this; } /** * Bind to provider * * @phpstan-param class-string $provider */ public function toProvider(string $provider, string $context = ''): self { $this->untarget = null; $refClass = $this->validate->toProvider($provider); $this->bound = (new DependencyFactory())->newProvider($refClass, $context); $this->container->add($this); return $this; } /** * Bind to instance * * @param mixed $instance */ public function toInstance($instance): self { $this->untarget = null; $this->bound = new Instance($instance); $this->container->add($this); return $this; } /** * Bind to NullObject */ public function toNull(): self { $this->untarget = null; assert(interface_exists($this->interface)); $this->bound = new NullObjectDependency($this->interface); $this->container->add($this); return $this; } /** * Set scope */ public function in(string $scope): self { if ($this->bound instanceof Dependency || $this->bound instanceof DependencyProvider || $this->bound instanceof NullDependency) { $this->bound->setScope($scope); } if ($this->untarget) { $this->untarget->setScope($scope); } return $this; } public function getBound(): DependencyInterface { return $this->bound; } public function setBound(DependencyInterface $bound): void { $this->bound = $bound; } private function isRegistered(string $interface): bool { return isset($this->container->getContainer()[$interface . '-' . Name::ANY]); } } ================================================ FILE: src/di/BindValidator.php ================================================ $class * * @return ReflectionClass * * @template T of object */ public function to(string $interface, string $class): ReflectionClass { if (! class_exists($class)) { throw new NotFound($class); } if (! $this->isNullInterceptorBinding($class, $interface) && interface_exists($interface) && ! (new ReflectionClass($class))->implementsInterface($interface)) { throw new InvalidType(sprintf('[%s] is no implemented [%s] interface', $class, $interface)); } return new ReflectionClass($class); } /** * toProvider validator * * @phpstan-param class-string $provider * * @psalm-return ReflectionClass * @phpstan-return ReflectionClass * * @throws NotFound */ public function toProvider(string $provider): ReflectionClass { if (! class_exists($provider)) { /** @psalm-suppress MixedArgument -- should be string */ throw new NotFound($provider); } $reflectioClass = new ReflectionClass($provider); if (! $reflectioClass->implementsInterface(ProviderInterface::class)) { throw new InvalidProvider($provider); } return $reflectioClass; } private function isNullInterceptorBinding(string $class, string $interface): bool { return $class === NullInterceptor::class && interface_exists($interface) && (new ReflectionClass($interface))->implementsInterface(MethodInterceptor::class); } } ================================================ FILE: src/di/BuiltinModule.php ================================================ install(new AssistedModule()); $module->install(new ProviderSetModule()); $module->install(new MultiBindingModule()); return $module; } } ================================================ FILE: src/di/CompileNullObject.php ================================================ map(static function (DependencyInterface $dependency) use ($scriptDir): Dependency|DependencyInterface { if ($dependency instanceof NullObjectDependency) { return $dependency->toNull($scriptDir); } return $dependency; }); } } ================================================ FILE: src/di/Container.php ================================================ */ private array $pointcuts = []; public function __construct() { $this->multiBindings = new MultiBindings(); } /** * @return list */ public function __sleep() { return ['container', 'pointcuts', 'multiBindings']; } /** * Add binding to a container */ public function add(Bind $bind): void { $dependency = $bind->getBound(); $dependency->register($this->container, $bind); } /** * Add Pointcut to container */ public function addPointcut(Pointcut $pointcut): void { $this->pointcuts[] = $pointcut; } /** * {@inheritDoc} * * @param ''|class-string $interface * @param string $name * * @return ($interface is '' ? mixed : T) * * @template T of object */ public function getInstance($interface, $name = Name::ANY) { /** @psalm-suppress MixedReturnStatement */ return $this->getDependency($interface . '-' . $name); } /** * Return dependency injected instance * * @param MethodArguments $params * * @return mixed * * @throws Unbound */ public function getInstanceWithArgs(string $interface, array $params) { $index = $interface . '-'; if (! isset($this->container[$index])) { throw $this->unbound($index); } $dependency = $this->container[$index]; if (! $dependency instanceof Dependency) { throw new BadMethodCallException($interface); } return $dependency->injectWithArgs($this, $params); } /** * Return dependency injected instance * * @param DependencyIndex $index * * @return mixed * * @throws Unbound */ public function getDependency(string $index) { if (! isset($this->container[$index])) { throw $this->unbound($index); } return $this->container[$index]->inject($this); } /** * Rename existing dependency interface + name */ public function move(string $sourceInterface, string $sourceName, string $targetInterface, string $targetName): void { $sourceIndex = $sourceInterface . '-' . $sourceName; if (! isset($this->container[$sourceIndex])) { throw $this->unbound($sourceIndex); } $targetIndex = $targetInterface . '-' . $targetName; $this->container[$targetIndex] = $this->container[$sourceIndex]; unset($this->container[$sourceIndex]); } /** * Return Unbound exception * * @param DependencyIndex $index {interface}-{bind name} */ public function unbound(string $index): Untargeted|Unbound { [$class, $name] = explode('-', $index); if (class_exists($class) && ! (new ReflectionClass($class))->isAbstract()) { return new Untargeted($class); } if ($class === '' && $name === '') { return new NoHint(); } return new Unbound(sprintf("'%s-%s'", $class, $name)); } /** * Return container * * @return array * @psalm-return DependencyContainer */ public function getContainer(): array { return $this->container; } /** * Return pointcuts * * @return array * @psalm-return PointcutList */ public function getPointcuts(): array { return $this->pointcuts; } /** * Merge container */ public function merge(self $container): void { $this->multiBindings->merge($container->multiBindings); $this->container += $container->getContainer(); $this->pointcuts = array_merge($this->pointcuts, $container->getPointcuts()); } /** * Weave aspects to all dependency in container */ public function weaveAspects(CompilerInterface $compiler): void { if ($this->pointcuts === []) { return; } foreach ($this->container as $dependency) { if ($dependency instanceof Dependency) { $dependency->weaveAspects($compiler, $this->pointcuts); } } } /** * Weave aspect to single dependency */ public function weaveAspect(Compiler $compiler, Dependency $dependency): self { $dependency->weaveAspects($compiler, $this->pointcuts); return $this; } /** * @param callable(DependencyInterface, string): DependencyInterface $f */ public function map(callable $f): void { foreach ($this->container as $key => &$index) { $index = $f($index, $key); } } public function sort(): void { ksort($this->container); } } ================================================ FILE: src/di/ContainerFactory.php ================================================ getModule($module); // install built-in module $appModule = (new BuiltinModule())($oneModule); $container = $appModule->getContainer(); // Compile null objects (new CompileNullObject())($container, $classDir); // Compile aspects /** @psalm-suppress InvalidArgument */ $container->weaveAspects(new Compiler($classDir)); return $container; } /** * @param AbstractModule|ModuleList|null $module Module(s) */ private function getModule($module): AbstractModule { if ($module instanceof AbstractModule) { return $module; } if ($module === null) { return new NullModule(); } $modules = $module; $oneModule = array_shift($modules); foreach ($modules as $module) { $oneModule->install($module); } return $oneModule; } } ================================================ FILE: src/di/Dependency.php ================================================ newInstance = $newInstance; $this->postConstruct = $postConstruct->name ?? null; } /** * @return array */ public function __sleep() { return ['newInstance', 'postConstruct', 'isSingleton']; } public function __toString(): string { return sprintf( '(dependency) %s', (string) $this->newInstance ); } /** * {@inheritdoc} */ public function register(array &$container, Bind $bind): void { $container[(string) $bind] = $bind->getBound(); } /** * {@inheritdoc} */ public function inject(Container $container) { // singleton ? if ($this->isSingleton === true && $this->instance !== null) { return $this->instance; } // create dependency injected instance $this->instance = ($this->newInstance)($container); // @PostConstruct if ($this->postConstruct !== null) { assert(method_exists($this->instance, $this->postConstruct)); $this->instance->{$this->postConstruct}(); } return $this->instance; } /** * @param MethodArguments $params * * @return mixed */ public function injectWithArgs(Container $container, array $params) { // singleton ? if ($this->isSingleton === true && $this->instance !== null) { return $this->instance; } // create dependency injected instance $this->instance = $this->newInstance->newInstanceArgs($container, $params); // @PostConstruct if ($this->postConstruct !== null) { assert(method_exists($this->instance, $this->postConstruct)); $this->instance->{$this->postConstruct}(); } return $this->instance; } /** * {@inheritdoc} */ public function setScope($scope): void { if ($scope === Scope::SINGLETON) { $this->isSingleton = true; } } /** * @param PointcutList $pointcuts */ public function weaveAspects(CompilerInterface $compiler, array $pointcuts): void { $class = (string) $this->newInstance; if ((new ReflectionClass($class))->isFinal()) { return; } $isInterceptor = (new ReflectionClass($class))->implementsInterface(MethodInterceptor::class); $isWeaved = (new ReflectionClass($class))->implementsInterface(WeavedInterface::class); if ($isInterceptor || $isWeaved) { return; } $bind = new AopBind(); $className = (string) $this->newInstance; $bind->bind($className, $pointcuts); if (! $bind->getBindings()) { return; } $class = $compiler->compile($className, $bind); $this->newInstance->weaveAspects($class, $bind); } /** @inheritDoc */ public function accept(VisitorInterface $visitor) { return $visitor->visitDependency( $this->newInstance, $this->postConstruct, $this->isSingleton ); } public function isSingleton(): bool { return $this->isSingleton; } } ================================================ FILE: src/di/DependencyFactory.php ================================================ $class */ public function newAnnotatedDependency(ReflectionClass $class): Dependency { $annotateClass = new AnnotatedClass(); $newInstance = $annotateClass->getNewInstance($class); $postConstruct = $annotateClass->getPostConstruct($class); return new Dependency($newInstance, $postConstruct); } /** * Create Provider binding * * @param ReflectionClass $provider */ public function newProvider(ReflectionClass $provider, string $context): DependencyProvider { $dependency = $this->newAnnotatedDependency($provider); return new DependencyProvider($dependency, $context); } /** * Create ToConstructor binding * * @param ReflectionClass $class * @param string|array $name */ public function newToConstructor( ReflectionClass $class, string|array $name, ?InjectionPoints $injectionPoints = null, ?ReflectionMethod $postConstruct = null ): Dependency { assert(class_exists($class->name)); $setterMethods = $injectionPoints instanceof InjectionPoints ? $injectionPoints($class->name) : new SetterMethods([]); $newInstance = new NewInstance($class, $setterMethods, new Name($name)); return new Dependency($newInstance, $postConstruct); } } ================================================ FILE: src/di/DependencyInterface.php ================================================ */ public function __sleep() { return ['context', 'dependency', 'isSingleton']; } public function __toString(): string { return sprintf( '(provider) %s', (string) $this->dependency ); } /** * {@inheritdoc} */ public function register(array &$container, Bind $bind): void { $container[(string) $bind] = $bind->getBound(); } /** * {@inheritdoc} */ public function inject(Container $container) { if ($this->isSingleton && $this->instance !== null) { return $this->instance; } $provider = $this->dependency->inject($container); assert($provider instanceof ProviderInterface); if ($provider instanceof SetContextInterface) { $this->setContext($provider); } $this->instance = $provider->get(); return $this->instance; } /** * {@inheritdoc} */ public function setScope($scope): void { if ($scope === Scope::SINGLETON) { $this->isSingleton = true; } } public function setContext(SetContextInterface $provider): void { $provider->setContext($this->context); } public function isSingleton(): bool { return $this->isSingleton; } /** @inheritDoc */ public function accept(VisitorInterface $visitor) { return $visitor->visitProvider( $this->dependency, $this->context, $this->isSingleton ); } } ================================================ FILE: src/di/Di/Assisted.php ================================================ optional; } } ================================================ FILE: src/di/Di/InjectInterface.php ================================================ $interface */ public function __construct(public string $interface, public string $name = '') { } } ================================================ FILE: src/di/Exception/DirectoryNotWritable.php ================================================ getMessage())]; $e = $this->getPrevious(); if (! $e instanceof Exception) { return $this->getMainMessage($this); } if ($e instanceof self) { return $this->buildMessage($e, $messages) . "\n" . $e->getTraceAsString(); } return parent::__toString(); } /** * @param array $msg */ private function buildMessage(self $e, array $msg): string { $lastE = $e; while ($e instanceof self) { $msg[] = sprintf("- %s\n", $e->getMessage()); $lastE = $e; $e = $e->getPrevious(); } array_pop($msg); $msg = array_reverse($msg); return $this->getMainMessage($lastE) . implode('', $msg); } /** * @psalm-pure */ private function getMainMessage(self $e): string { return sprintf( "exception '%s' with message '%s'\n", $e::class, $e->getMessage() ); } } ================================================ FILE: src/di/Exception/Untargeted.php ================================================ install(new AssistedModule()); $this->container = $module->getContainer(); /** @psalm-suppress InvalidArgument */ $this->container->weaveAspects(new Compiler($this->classDir)); // builtin injection (new Bind($this->container, InjectorInterface::class))->toInstance(new Injector($module)); } /** * Wakeup */ public function __wakeup() { spl_autoload_register( function (string $class): void { $file = sprintf('%s/%s.php', $this->classDir, str_replace('\\', '_', $class)); if (file_exists($file)) { include $file; //@codeCoverageIgnore } } ); } /** * Build an object graph with give constructor parameters * * @param string $class class name * @param list $params construct parameters (MethodArguments) * * @return mixed */ public function newInstanceArgs(string $class, array $params) { return $this->container->getInstanceWithArgs($class, $params); } } ================================================ FILE: src/di/InjectableInterface.php ================================================ pFunction = $this->parameter->getDeclaringFunction()->name; $class = $this->parameter->getDeclaringClass(); $this->pClass = $class instanceof ReflectionClass ? $class->name : ''; $this->pName = $this->parameter->name; } /** * {@inheritdoc} */ public function getParameter(): ReflectionParameter { return $this->parameter; } /** * {@inheritdoc} */ public function getMethod(): ReflectionMethod { $this->parameter = $this->getParameter(); $class = $this->parameter->getDeclaringClass(); $method = $this->parameter->getDeclaringFunction()->getShortName(); assert($class instanceof \ReflectionClass); assert(class_exists($class->getName())); return new ReflectionMethod($class->getName(), $method); } /** * {@inheritdoc} */ public function getClass(): ReflectionClass { $this->parameter = $this->getParameter(); $class = $this->parameter->getDeclaringClass(); assert($class instanceof \ReflectionClass); return new ReflectionClass($class->getName()); } /** * {@inheritdoc} */ public function getQualifiers(): array { $qualifiers = []; $annotations = $this->getMethod()->getAnnotations(); foreach ($annotations as $annotation) { $maybeQualifier = (new ReflectionClass($annotation))->getAnnotation(Qualifier::class); if ($maybeQualifier instanceof Qualifier) { $qualifiers[] = $annotation; } } return $qualifiers; } /** * @return array */ public function __serialize(): array { return [$this->pClass, $this->pFunction, $this->pName]; } /** * @param array $array */ public function __unserialize(array $array): void { [$this->pClass, $this->pFunction, $this->pName] = $array; } } ================================================ FILE: src/di/InjectionPointInterface.php ================================================ */ public function getClass(): ReflectionClass; /** * Return Qualifier annotations * * @return QualifierList */ public function getQualifiers(): array; } ================================================ FILE: src/di/InjectionPoints.php ================================================ points as $point) { $points[] = $this->getSetterMethod($class, $point); } return new SetterMethods($points); } public function addMethod(string $method, string $name = Name::ANY): self { $this->points[] = [$method, $name, false]; return $this; } public function addOptionalMethod(string $method, string $name = Name::ANY): self { $this->points[] = [$method, $name, true]; return $this; } /** * @param class-string $class * @param InjectionPointDefinition $point * * @throws ReflectionException */ private function getSetterMethod(string $class, array $point): SetterMethod { $setterMethod = new SetterMethod(new ReflectionMethod($class, $point[0]), new Name($point[1])); if ($point[2]) { $setterMethod->setOptional(); } return $setterMethod; } } ================================================ FILE: src/di/Injector.php ================================================ classDir = $classDir; $this->container = (new ContainerFactory())($module, $this->classDir); // Bind injector (built-in bindings) (new Bind($this->container, InjectorInterface::class))->toInstance($this); $this->container->sort(); } /** * Wakeup */ public function __wakeup() { spl_autoload_register( function (string $class): void { $file = sprintf('%s/%s.php', $this->classDir, str_replace('\\', '_', $class)); if (file_exists($file)) { include $file; //@codeCoverageIgnore } } ); } /** * {@inheritDoc} */ public function getInstance($interface, $name = Name::ANY) { try { /** @psalm-suppress MixedAssignment */ $instance = $this->container->getInstance($interface, $name); } catch (Untargeted) { /** @psalm-var class-string $interface */ $this->bind($interface); /** @psalm-suppress MixedAssignment */ $instance = $this->getInstance($interface, $name); } /** @psalm-suppress MixedReturnStatement */ return $instance; } /** * @param BindableInterface $class */ private function bind(string $class): void { new Bind($this->container, $class); $bound = $this->container->getContainer()[$class . '-' . Name::ANY]; assert($bound instanceof Dependency); /** @psalm-suppress InvalidArgument */ $this->container->weaveAspect(new Compiler($this->classDir), $bound)->getInstance($class); } } ================================================ FILE: src/di/InjectorInterface.php ================================================ $interface * @param string $name * * @return ($interface is '' ? mixed : T) * * @template T of object */ public function getInstance($interface, $name = Name::ANY); } ================================================ FILE: src/di/Instance.php ================================================ value)) { return sprintf( '(%s) %s', gettype($this->value), (string) $this->value ); } if (is_object($this->value)) { return '(object) ' . $this->value::class; } return '(' . gettype($this->value) . ')'; } /** * {@inheritdoc} */ public function register(array &$container, Bind $bind): void { $index = (string) $bind; $container[$index] = $bind->getBound(); } /** * {@inheritdoc} */ public function inject(Container $container) { return $this->value; } /** * {@inheritdoc} * * @codeCoverageIgnore */ public function setScope($scope): void { } /** @inheritDoc */ public function accept(VisitorInterface $visitor) { return $visitor->visitInstance($this->value); } } ================================================ FILE: src/di/Matcher/AssistedInjectMatcher.php ================================================ getParameters(); foreach ($params as $param) { /** @var list $attributes */ $attributes = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF); if (isset($attributes[0])) { return true; } /** @var list $assisted */ $assisted = $param->getAttributes(Assisted::class); if (isset($assisted[0])) { return true; } } return false; } } ================================================ FILE: src/di/MethodInvocationProvider.php ================================================ */ final class MethodInvocationProvider implements ProviderInterface { /** @var ?MethodInvocation */ private ?MethodInvocation $invocation = null; /** * @param MethodInvocation $invocation */ public function set(MethodInvocation $invocation): void { $this->invocation = $invocation; } /** * @return MethodInvocation */ public function get(): MethodInvocation { if ($this->invocation === null) { throw new MethodInvocationNotAvailable(); } return $this->invocation; } } ================================================ FILE: src/di/ModuleString.php ================================================ true]); assert($container instanceof Container); $spy = new SpyCompiler(); foreach ($container->getContainer() as $dependencyIndex => $dependency) { if ($dependency instanceof Dependency) { $dependency->weaveAspects($spy, $pointcuts); } $log[] = sprintf( '%s => %s', $dependencyIndex, (string) $dependency ); } sort($log); return implode(PHP_EOL, $log); } } ================================================ FILE: src/di/MultiBinder.php ================================================ container = $module->getContainer(); $this->multiBindings = $this->container->multiBindings; $this->container->add( (new Bind($this->container, MultiBindings::class))->toInstance($this->multiBindings) ); } public static function newInstance(AbstractModule $module, string $interface): self { return new self($module, $interface); } public function addBinding(?string $key = null): self { $this->key = $key; return $this; } public function setBinding(?string $key = null): self { $this->container->multiBindings->exchangeArray([]); $this->key = $key; return $this; } /** * @param class-string $class */ public function to(string $class): void { $this->bind(new LazyTo($class), $this->key); } /** * @param class-string> $provider * * @template T of mixed */ public function toProvider(string $provider): void { $this->bind(new LazyProvider($provider), $this->key); } /** * @param mixed $instance */ public function toInstance($instance): void { $this->bind(new LazyInstance($instance), $this->key); } private function bind(LazyInterface $lazy, ?string $key): void { $bindings = []; if ($this->multiBindings->offsetExists($this->interface)) { $bindings = $this->multiBindings->offsetGet($this->interface); } if ($key === null) { $bindings[] = $lazy; $this->multiBindings->offsetSet($this->interface, $bindings); return; } $bindings[$key] = $lazy; $this->multiBindings->offsetSet($this->interface, $bindings); } } ================================================ FILE: src/di/MultiBinding/LazyInstance.php ================================================ instance; } } ================================================ FILE: src/di/MultiBinding/LazyInterface.php ================================================ $class */ public function __construct(private string $class) { } /** * @return mixed */ public function __invoke(InjectorInterface $injector) { $provider = $injector->getInstance($this->class); return $provider->get(); } } ================================================ FILE: src/di/MultiBinding/LazyTo.php ================================================ $class */ public function __construct(private string $class) { } /** * @return T */ public function __invoke(InjectorInterface $injector) { return $injector->getInstance($this->class); } } ================================================ FILE: src/di/MultiBinding/Map.php ================================================ * @implements IteratorAggregate */ final class Map implements IteratorAggregate, ArrayAccess, Countable { /** * @param array $lazies */ public function __construct(private array $lazies, private readonly InjectorInterface $injector) { } /** * @param array-key $offset * * @codeCoverageIgnore */ #[ReturnTypeWillChange] public function offsetExists($offset): bool { return array_key_exists($offset, $this->lazies); } /** * @param array-key $offset * * @return T * * @codeCoverageIgnore */ #[ReturnTypeWillChange] public function offsetGet($offset) { /** @var T $instance */ $instance = ($this->lazies[$offset])($this->injector); return $instance; } /** * @param array-key $offset * @param mixed $value * * @return never * * @codeCoverageIgnore */ #[ReturnTypeWillChange] public function offsetSet($offset, $value): void { unset($offset, $value); throw new LogicException(); } /** * @param array-key $offset * * @return never * * @codeCoverageIgnore */ #[ReturnTypeWillChange] public function offsetUnset($offset): void { unset($offset); throw new LogicException(); } /** @return Generator */ public function getIterator(): Iterator { foreach ($this->lazies as $key => $lazy) { /** @var T $object */ $object = ($lazy)($this->injector); yield $key => $object; } } public function count(): int { return count($this->lazies); } } ================================================ FILE: src/di/MultiBinding/MapProvider.php ================================================ */ final class MapProvider implements ProviderInterface { public function __construct(private readonly InjectionPointInterface $ip, private MultiBindings $multiBindings, private readonly InjectorInterface $injector) { } /** * @return Map */ public function get(): Map { $param = $this->ip->getParameter(); $setAttribute = $param->getAttributes(Set::class); if (! isset($setAttribute[0])) { throw new SetNotFound((string) $param); } /** @var Set $set */ $set = $setAttribute[0]->newInstance(); /** @var array> $lazies */ $lazies = $this->multiBindings[$set->interface]; return new Map($lazies, $this->injector); } } ================================================ FILE: src/di/MultiBinding/MultiBindingModule.php ================================================ bind(MultiBindings::class); $this->bind(Map::class)->toProvider(MapProvider::class); } } ================================================ FILE: src/di/MultiBinding/MultiBindings.php ================================================ */ final class MultiBindings extends ArrayObject { public function merge(self $multiBindings): void { $this->exchangeArray( array_merge_recursive($this->getArrayCopy(), $multiBindings->getArrayCopy()) ); } } ================================================ FILE: src/di/Name.php ================================================ * * @var ParameterNameMapping */ private array $names = []; /** * @param string|ParameterNameMapping $name */ public function __construct(string|array $name) { if (is_string($name)) { $this->setName($name); return; } $this->names = $name; } /** * Create instance from PHP8 attributes * * psalm does not know ReflectionAttribute?? PHPStan produces no type error here. */ public static function withAttributes(ReflectionMethod $method): ?self { $params = $method->getParameters(); $names = []; foreach ($params as $param) { /** @var array $attributes */ $attributes = $param->getAttributes(); if ($attributes) { $name = self::getName($attributes); $names[$param->name] = $name; } } // Backward compatibility: Get parameter qualifiers from method-level attribute if ($names === []) { /** @psalm-suppress DeprecatedClass */ $names = BcParameterQualifier::getNames($method); } if ($names !== []) { return new self($names); } return null; } /** * @param non-empty-array $attributes * * @throws ReflectionException * * @psalm-suppress MixedAssignment * @psalm-suppress MixedArgument */ private static function getName(array $attributes): string { $refAttribute = $attributes[0]; $attribute = $refAttribute->newInstance(); if ($attribute instanceof Named) { return $attribute->value; } $isQualifier = (bool) (new ReflectionClass($attribute))->getAttributes(Qualifier::class); if ($isQualifier) { return $attribute::class; } return ''; } public function __invoke(ReflectionParameter $parameter): string { // single variable named binding if ($this->name) { return $this->name; } $parameterName = $parameter->name; // multiple variable named binding return $this->names[$parameterName] ?? $this->names[self::ANY] ?? self::ANY; } private function setName(string $name): void { // annotation if (class_exists($name, false)) { $this->name = $name; return; } // single name // @Named(name) if ($name === self::ANY || preg_match('/^\w+$/', $name)) { $this->name = $name; return; } // name list (backward compatibility) // @Named(varName1=name1, varName2=name2) or toConstructor string format /** @psalm-suppress DeprecatedClass */ $this->names = BcStringParser::parse($name); } } ================================================ FILE: src/di/NewInstance.php ================================================ $class */ public function __construct( ReflectionClass $class, SetterMethods $setterMethods, ?Name $constructorName = null ) { $constructorName = $constructorName ?: new Name(Name::ANY); $this->class = $class->getName(); $constructor = $class->getConstructor(); if ($constructor) { $this->arguments = new Arguments($constructor, $constructorName); } $this->setterMethods = $setterMethods; } /** * @throws ReflectionException */ public function __invoke(Container $container): object { $reflection = new ReflectionClass($this->class); /** @psalm-suppress MixedMethodCall */ $instance = $this->arguments instanceof Arguments ? $reflection->newInstanceArgs($this->arguments->inject($container)) : new $this->class(); return $this->postNewInstance($container, $instance); } /** * @return class-string */ public function __toString(): string { return $this->class; } /** * @param MethodArguments $params * * @throws ReflectionException */ public function newInstanceArgs(Container $container, array $params): object { $instance = (new ReflectionClass($this->class))->newInstanceArgs($params); return $this->postNewInstance($container, $instance); } /** * @param class-string $class */ public function weaveAspects(string $class, AopBind $bind): void { $this->class = $class; $this->bind = new AspectBind($bind); } public function accept(VisitorInterface $visitor): void { $visitor->visitNewInstance( $this->class, $this->setterMethods, $this->arguments, $this->bind ); } private function postNewInstance(Container $container, object $instance): object { $this->enableAop($instance, $container); // setter injection ($this->setterMethods)($instance, $container); return $instance; } public function enableAop(object $instance, Container $container): void { if (! $this->bind instanceof AspectBind) { return; } assert($instance instanceof WeavedInterface); $instance->_setBindings($this->bind->inject($container)); // Ray.Aop ^2.18 } } ================================================ FILE: src/di/NullDependency.php ================================================ getBound(); } /** * {@inheritdoc} */ public function setScope($scope): void { } } ================================================ FILE: src/di/NullModule.php ================================================ getBound(); } /** * {@inheritdoc} */ public function setScope($scope): void { } public function toNull(string $scriptDir): Dependency { assert(is_dir($scriptDir)); $nullObject = new NullObject(); $class = $nullObject->save($this->interface, $scriptDir); return new Dependency(new NewInstance(new ReflectionClass($class), new SetterMethods([]))); } } ================================================ FILE: src/di/ProviderInterface.php ================================================ * @template T of object */ final class ProviderProvider implements ProviderInterface { /** @param Set $set */ public function __construct(private InjectorInterface $injector, private Set $set) { } /** @return mixed */ public function get() { return $this->injector->getInstance($this->set->interface, $this->set->name); } } ================================================ FILE: src/di/ProviderSetModule.php ================================================ bind(ProviderInterface::class)->toProvider(ProviderSetProvider::class); } } ================================================ FILE: src/di/ProviderSetProvider.php ================================================ * @template T of object */ final class ProviderSetProvider implements ProviderInterface { public function __construct(private InjectionPointInterface $ip, private InjectorInterface $injector) { } /** @phpstan-return ProviderProvider */ public function get(): ProviderProvider { $param = $this->ip->getParameter(); $setAttribute = $param->getAttributes(Set::class); if (! isset($setAttribute[0])) { throw new SetNotFound((string) $this->ip->getParameter()); } $set = $setAttribute[0]->newInstance(); return new ProviderProvider($this->injector, $set); } } ================================================ FILE: src/di/Scope.php ================================================ method = $method->name; $this->arguments = new Arguments($method, $name); } /** * @param object $instance * * @throws Unbound * @throws Exception */ public function __invoke($instance, Container $container): void { try { $parameters = $this->arguments->inject($container); } catch (Unbound $unbound) { if ($this->isOptional) { return; } throw $unbound; } $callable = [$instance, $this->method]; if (! is_callable($callable)) { throw new LogicException(); // @codeCoverageIgnore } call_user_func_array($callable, $parameters); } public function setOptional(): void { $this->isOptional = true; } /** @inheritDoc */ public function accept(VisitorInterface $visitor): void { try { $visitor->visitSetterMethod($this->method, $this->arguments); } catch (Unbound $unbound) { if ($this->isOptional) { // Return when no dependency given and @Inject(optional=true) annotated to setter method. return; } // Throw exception when no dependency given and @Inject(optional=false) annotated to setter method. throw $unbound; } } } ================================================ FILE: src/di/SetterMethods.php ================================================ setterMethods as $setterMethod) { ($setterMethod)($instance, $container); } } public function add(?SetterMethod $setterMethod = null): void { if (! $setterMethod instanceof SetterMethod) { return; } $this->setterMethods[] = $setterMethod; } /** @inheritDoc */ public function accept(VisitorInterface $visitor): void { $visitor->visitSetterMethods($this->setterMethods); } } ================================================ FILE: src/di/SpyCompiler.php ================================================ hasNoBinding($class, $bind)) { return $class; } return $class . $this->getInterceptors($bind); // @phpstan-ignore-line } /** * @param class-string $class */ private function hasNoBinding(string $class, BindInterface $bind): bool { $hasMethod = $this->hasBoundMethod($class, $bind); return ! $bind->getBindings() && ! $hasMethod; } /** * @param class-string $class */ private function hasBoundMethod(string $class, BindInterface $bind): bool { $bindingMethods = array_keys($bind->getBindings()); $hasMethod = false; foreach ($bindingMethods as $bindingMethod) { if (method_exists($class, $bindingMethod)) { $hasMethod = true; } } return $hasMethod; } private function getInterceptors(BindInterface $bind): string { $bindings = $bind->getBindings(); if (! $bindings) { return ''; // @codeCoverageIgnore } $log = ' (aop)'; foreach ($bindings as $method => $interceptors) { /** * @phpstan-var array $interceptors * @psalm-ignore-var */ $log .= sprintf( ' +%s(%s)', $method, implode(', ', $interceptors) ); } return $log; } } ================================================ FILE: src/di/Types.php ================================================ * @psalm-type DependencyIndex = non-empty-string * @psalm-type PointcutList array * @psalm-type BindingName = non-empty-string * @psalm-type BindableInterface = class-string|'' * @psalm-type ConstructorNameMapping = array * @psalm-type ParameterNameMapping = array * @psalm-type NamedParameterString = non-empty-string * @psalm-type ScriptDir = non-empty-string * * Enhanced Injection and Argument Types * @psalm-type InjectableValue object|scalar|array|null * @psalm-type InjectionPointDefinition = array{0: string, 1: string, 2: bool} * @psalm-type InjectionPointsList = list * @psalm-type MethodArguments = list * @psalm-type ArgumentSerializationData = array{0: DependencyIndex, 1: bool, 2: string, 3: string, 4: string, 5: array{0: string, 1: string, 2: string}} * @psalm-type UnboundTypeList = list<'bool'|'int'|'float'|'string'|'array'|'resource'|'callable'|'iterable'|'object'|'mixed'> * @psalm-type QualifierList = array * * Scope and Lifecycle Types * @psalm-type ScopeType = Scope::SINGLETON|Scope::PROTOTYPE * @psalm-type ProviderContext = string * * MultiBinding Types * @psalm-type MultiBindingMap = array> * @psalm-type LazyBindingList = non-empty-array * * AOP and Aspect Types * @psalm-type MethodInterceptorBindings array> * @psalm-type InterceptorClassList array> * @psalm-type VisitorResult = object|array|null * * Reflection and Metadata Types * @psalm-type ReflectionMethodReference = array{0: string, 1: string, 2: string} * @psalm-type DependencyMeta = string * * Exception and Error Types * @psalm-type DiException = Exception\Unbound|Exception\Untargeted|Exception\NotFound|Exception\InvalidProvider|Exception\InvalidType * * Annotation Types * @psalm-type AnnotationType = Di\Named|Di\Inject|Di\Qualifier|Di\PostConstruct|Di\Assisted|Di\Set * @psalm-type DependencyImplementation = Dependency|DependencyProvider|Instance|NullDependency|NullObjectDependency * @psalm-type LazyImplementation = MultiBinding\LazyInstance|MultiBinding\LazyProvider>|MultiBinding\LazyTo * * Core Component Types * @psalm-type SetterMethodsList = array * @psalm-type ArgumentsList = array * * Domain-Specific Array Types * @psalm-type ModuleList = non-empty-array * @psalm-type NamedArguments = array */ final class Types { } ================================================ FILE: src/di/Untarget.php ================================================ * @psalm-var ReflectionClass */ private readonly ReflectionClass $class; private string $scope = Scope::PROTOTYPE; /** * @param class-string $class */ public function __construct(string $class) { $this->class = new ReflectionClass($class); } /** * Bind untargeted binding */ public function __invoke(Container $container, Bind $bind): void { $bound = (new DependencyFactory())->newAnnotatedDependency($this->class); $bound->setScope($this->scope); $bind->setBound($bound); $container->add($bind); } public function setScope(string $scope): void { $this->scope = $scope; } } ================================================ FILE: src/di/VisitorInterface.php ================================================ Parameter name to qualifier mapping (empty if not applicable) */ public static function getNames(ReflectionMethod $method): array { $params = $method->getParameters(); // Only for single-parameter methods if (count($params) !== 1) { return []; } $qualifier = self::getQualifier($method); if ($qualifier === '') { return []; } return [$params[0]->name => $qualifier]; } /** * Get parameter qualifier from method-level attribute if applicable * * Returns the qualifier name if: * 1. Method has exactly one parameter * 2. Parameter has no qualifier attribute * 3. For setters: Method has an attribute implementing both InjectInterface and Qualifier * 4. For constructors: Method has a Qualifier attribute (InjectInterface is implicit) * * @param ReflectionMethod $method The method to analyze * * @return string The qualifier class name, or empty string if not applicable */ private static function getQualifier(ReflectionMethod $method): string { $params = $method->getParameters(); // Only for single-parameter methods if (count($params) !== 1) { return ''; } $param = $params[0]; // Check if parameter already has a qualifier if (self::hasParameterQualifier($param->getAttributes())) { return ''; } $isConstructor = $method->name === '__construct'; // Check method-level attributes for Qualifier $methodAttributes = $method->getAttributes(); foreach ($methodAttributes as $attr) { $instance = $attr->newInstance(); $attrClass = new ReflectionClass($attr->getName()); $qualifierAttr = $attrClass->getAttributes(Qualifier::class); // Skip if not a Qualifier if ($qualifierAttr === []) { continue; } // For constructors: Qualifier alone is sufficient (InjectInterface is implicit) if ($isConstructor) { return $attr->getName(); } // For setters: Must also implement InjectInterface if ($instance instanceof InjectInterface) { return $attr->getName(); } } return ''; } /** * Check if parameter already has a qualifier attribute * * @param array $attributes */ private static function hasParameterQualifier(array $attributes): bool { foreach ($attributes as $attr) { $attrClass = new ReflectionClass($attr->getName()); // Check for Qualifier marker if ($attrClass->getAttributes(Qualifier::class) !== []) { return true; } } return false; } } ================================================ FILE: src-deprecated/di/BcStringParser.php ================================================ 'name']) * * @internal */ final class BcStringParser { /** * Parse "key=value,key=value" format * * @return array * * @psalm-pure */ public static function parse(string $name): array { $names = []; $keyValues = explode(',', $name); foreach ($keyValues as $keyValue) { $exploded = explode('=', $keyValue, 2); if (isset($exploded[1])) { [$key, $value] = $exploded; if (isset($key[0]) && $key[0] === '$') { $key = substr($key, 1); } $trimmedKey = trim($key); if ($trimmedKey === '') { continue; } $names[$trimmedKey] = trim($value); } } return $names; } } ================================================ FILE: src-deprecated/di/EmptyModule.php ================================================ annotatedClass = new AnnotatedClass(); } public function testInvoke(): void { $newInstance = $this->annotatedClass->getNewInstance(new ReflectionClass(FakeCar::class)); // @phpstan-ignore-line $this->assertInstanceOf(NewInstance::class, $newInstance); $container = new Container(); (new Bind($container, FakeTyreInterface::class))->to(FakeTyre::class); (new Bind($container, FakeEngineInterface::class))->to(FakeEngine::class); (new Bind($container, FakeHandleInterface::class))->toProvider(FakeHandleProvider::class); (new Bind($container, FakeMirrorInterface::class))->annotatedWith('right')->to(FakeMirrorRight::class); (new Bind($container, FakeMirrorInterface::class))->annotatedWith('left')->to(FakeMirrorRight::class); (new Bind($container, FakeGearStickInterface::class))->annotatedWith(FakeGearStickInject::class)->toProvider(FakeGearStickProvider::class); $car = $newInstance($container); if (! $car instanceof FakeCar) { throw new LogicException(); } $this->assertInstanceOf(FakeCar::class, $car); $this->assertInstanceOf(FakeTyre::class, $car->frontTyre); $this->assertInstanceOf(FakeTyre::class, $car->rearTyre); $this->assertInstanceOf(FakeLeatherGearStick::class, $car->gearStick); $this->assertNull($car->hardtop); } /** * @phpstan-param class-string $class * * @dataProvider classProvider */ public function testAnnotatedByAnnotation(string $class): void { $newInstance = $this->annotatedClass->getNewInstance(new ReflectionClass($class)); $container = new Container(); (new Bind($container, FakeMirrorInterface::class))->annotatedWith(FakeLeft::class)->to(FakeMirrorLeft::class); (new Bind($container, FakeMirrorInterface::class))->annotatedWith(FakeRight::class)->to(FakeMirrorRight::class); $handleBar = $newInstance($container); if (! $handleBar instanceof FakeHandleBar && ! $handleBar instanceof FakeHandleBarQualifier) { throw new LogicException(); } $this->assertInstanceOf(FakeMirrorLeft::class, $handleBar->leftMirror); $this->assertInstanceOf(FakeMirrorRight::class, $handleBar->rightMirror); } /** * @return array> */ public function classProvider(): array { return [ [FakeHandleBar::class], [FakeHandleBarQualifier::class], ]; } } ================================================ FILE: tests/di/ArgumentTest.php ================================================ argument = new Argument(new ReflectionParameter([FakeCar::class, '__construct'], 'engine'), Name::ANY); } public function testToString(): void { $this->assertSame('Ray\Di\FakeEngineInterface-' . Name::ANY, (string) $this->argument); } public function testToStringScalar(): void { $argument = new Argument(new ReflectionParameter([FakeInternalTypes::class, 'stringId'], 'id'), Name::ANY); $this->assertSame('-' . Name::ANY, (string) $argument); } public function testSerializable(): void { $argument = unserialize(serialize(new Argument(new ReflectionParameter([FakeInternalTypes::class, 'stringId'], 'id'), Name::ANY))); assert($argument instanceof Argument); $class = $argument->get()->getDeclaringFunction(); $this->assertInstanceOf(ReflectionMethod::class, $class); } } ================================================ FILE: tests/di/ArgumentsTest.php ================================================ arguments = new Arguments(new ReflectionMethod(FakeCar::class, 'setTires'), new Name(Name::ANY)); } public function testInject(): void { $container = (new FakeCarModule())->getContainer(); $parameters = $this->arguments->inject($container); $this->assertInstanceOf(FakeTyre::class, $parameters[0]); $this->assertInstanceOf(FakeTyre::class, $parameters[1]); $param0 = $parameters[0]; $this->assertNotSame(spl_object_hash($param0), $parameters[1]); } public function testParameterDefaultValue(): void { $defaultValue = (new ReflectionParameter([FakeHandleProvider::class, '__construct'], 'logo'))->getDefaultValue(); $emptyContainer = new Container(); $parameters = new Arguments(new ReflectionMethod(FakeHandleProvider::class, '__construct'), new Name(Name::ANY)); $parametersValue = $parameters->inject($emptyContainer); $this->assertSame($defaultValue, $parametersValue[0]); } } ================================================ FILE: tests/di/AssistedTest.php ================================================ injector = new Injector(new FakeToBindModule()); } public function testAssisted(): void { $consumer = $this->injector->getInstance(FakeAssistedConsumer::class); $assistedDependency = $consumer->assistOne('a', 'b'); $expected = FakeRobot::class; $this->assertInstanceOf($expected, $assistedDependency); } public function testAssistedWithName(): void { $this->injector = new Injector(new FakeInstanceBindModule()); $consumer = $this->injector->getInstance(FakeAssistedConsumer::class); $assistedDependency = $consumer->assistWithName('a7'); $expected = 1; $this->assertSame($expected, $assistedDependency); } public function testAssistedAnyWithName(): void { $injector = new Injector(new FakeToBindModule(new FakeInstanceBindModule())); $consumer = $injector->getInstance(FakeAssistedConsumer::class); [$assistedDependency1, $assistedDependency2] = $consumer->assistAny(); $expected1 = 1; $this->assertSame($expected1, $assistedDependency1); $this->assertInstanceOf(FakeRobot::class, $assistedDependency2); } public function testAssistedMethodInvocation(): void { $assistedConsumer = (new Injector(new FakeAssistedDbModule()))->getInstance(FakeAssistedParamsConsumer::class); [$id, $db] = $assistedConsumer->getUser(1); /** @var FakeAbstractDb $db */ $this->assertSame(1, $id); $this->assertSame(1, $db->dbId); } public function testAssistedMethodInvocationNotAvailable(): void { $this->expectException(MethodInvocationNotAvailable::class); $assistedDbProvider = (new Injector(new FakeAssistedDbModule()))->getInstance(FakeAssistedDbProvider::class); $assistedDbProvider->get(); } public function testAssistedCustomInject(): void { $assistedConsumer = (new Injector(new FakeAssistedDbModule()))->getInstance(FakeAssistedParamsConsumer::class); [$id] = $assistedConsumer->getUser(1); $this->assertSame(1, $id); } } ================================================ FILE: tests/di/BcParameterQualifierIntegrationTest.php ================================================ getInstance(FakeClassWithBcParameterQualifier::class); // The setGearStick method uses #[FakeInjectOne] at method level only // BcParameterQualifier should apply it to the parameter $this->assertInstanceOf(FakeLeatherGearStick::class, $instance->gearStick); } } ================================================ FILE: tests/di/BcParameterQualifierTest.php ================================================ assertSame(['param' => FakeInjectOne::class], $names); } public function testNoNamesForMultipleParameters(): void { $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setMultipleParams'); /** @psalm-suppress DeprecatedClass */ $names = BcParameterQualifier::getNames($method); $this->assertSame([], $names); } public function testNoNamesWhenParameterHasQualifier(): void { $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setSingleParamWithQualifier'); /** @psalm-suppress DeprecatedClass */ $names = BcParameterQualifier::getNames($method); $this->assertSame([], $names); } public function testNoNamesForNonQualifierAttribute(): void { $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setSingleParamWithInjectOnly'); /** @psalm-suppress DeprecatedClass */ $names = BcParameterQualifier::getNames($method); $this->assertSame([], $names); } public function testNoNamesForMethodWithoutInjectInterface(): void { $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setSingleParamNoInject'); /** @psalm-suppress DeprecatedClass */ $names = BcParameterQualifier::getNames($method); $this->assertSame([], $names); } public function testNamesForTargetMethodOnly(): void { // FakeGearStickInject has TARGET_METHOD only // BC parameter qualifier now supports TARGET_METHOD-only attributes for backward compatibility $method = new ReflectionMethod(FakeBcParameterQualifierClass::class, 'setSingleParamMethodOnly'); /** @psalm-suppress DeprecatedClass */ $names = BcParameterQualifier::getNames($method); $this->assertSame(['param' => FakeGearStickInject::class], $names); } public function testConstructorWithQualifierOnly(): void { // Constructor with Qualifier-only attribute (no InjectInterface) // BC parameter qualifier should apply for constructors (InjectInterface is implicit) $method = new ReflectionMethod(FakeBcConstructorQualifierClass::class, '__construct'); /** @psalm-suppress DeprecatedClass */ $names = BcParameterQualifier::getNames($method); $this->assertSame(['param' => FakeQualifierOnly::class], $names); } } ================================================ FILE: tests/di/BcStringParserTest.php ================================================ 'engine_name', 'var' => 'var_name']; $this->assertSame($expected, $result); } public function testParseStringFormatWithSpaces(): void { $result = BcStringParser::parse('engine=engine_name, var=var_name'); $expected = ['engine' => 'engine_name', 'var' => 'var_name']; $this->assertSame($expected, $result); } public function testParseStringFormatWithDollarPrefix(): void { $result = BcStringParser::parse('$engine=engine_name,$var=var_name'); $expected = ['engine' => 'engine_name', 'var' => 'var_name']; $this->assertSame($expected, $result); } } ================================================ FILE: tests/di/BindTest.php ================================================ bind = new Bind(new Container(), FakeTyreInterface::class); } public function testGetBound(): void { $this->bind->to(FakeTyre::class); $bound = $this->bind->getBound(); $this->assertInstanceOf(Dependency::class, $bound); } public function testToString(): void { $this->assertSame('Ray\Di\FakeTyreInterface-' . Name::ANY, (string) $this->bind); } public function testInvalidToTest(): void { $this->expectException(NotFound::class); $this->bind->to('invalid-class'); // @phpstan-ignore-line } public function testInvalidToProviderTest(): void { $this->expectException(NotFound::class); $this->bind->toProvider('invalid-class'); // @phpstan-ignore-line } public function testInValidInterfaceBinding(): void { $this->expectException(NotFound::class); new Bind(new Container(), 'invalid-interface'); } public function testUntargetedBind(): void { $container = new Container(); $bind = new Bind($container, FakeEngine::class); unset($bind); $container = $container->getContainer(); $this->assertArrayHasKey(FakeEngine::class . '-' . Name::ANY, $container); } public function testUntargetedBindSingleton(): void { $container = new Container(); $bind = (new Bind($container, FakeEngine::class))->in(Scope::SINGLETON); unset($bind); $dependency1 = $container->getInstance(FakeEngine::class, Name::ANY); $dependency2 = $container->getInstance(FakeEngine::class, Name::ANY); $this->assertSame(spl_object_hash($dependency1), spl_object_hash($dependency2)); } /** * @return array>> */ public function nameProvider(): array { return [ [['tmpDir' => 'tmp_dir', 'leg' => 'left']], ]; } /** * @param array|string $name * * @dataProvider nameProvider */ public function testToConstructor($name): void { $container = new Container(); $container->add((new Bind($container, ''))->annotatedWith('tmp_dir')->toInstance('/tmp')); $container->add((new Bind($container, FakeLegInterface::class))->annotatedWith('left')->to(FakeLeftLeg::class)); $container->add((new Bind($container, FakeRobotInterface::class))->toConstructor(FakeToConstructorRobot::class, $name)); $instance = $container->getInstance(FakeRobotInterface::class, Name::ANY); assert($instance instanceof FakeToConstructorRobot); $this->assertInstanceOf(FakeLeftLeg::class, $instance->leg); $this->assertSame('/tmp', $instance->tmpDir); } public function testToConstructorWithMethodInjection(): void { $container = new Container(); $container->add((new Bind($container, ''))->annotatedWith('tmp_dir')->toInstance('/tmp')); $container->add((new Bind($container, FakeLegInterface::class))->annotatedWith('left')->to(FakeLeftLeg::class)); $container->add((new Bind($container, FakeEngineInterface::class))->to(FakeEngine::class)); $container->add( (new Bind($container, FakeRobotInterface::class))->toConstructor( FakeToConstructorRobot::class, ['tmpDir' => 'tmp_dir', 'leg' => 'left'], (new InjectionPoints())->addMethod('setEngine') ) ); $instance = $container->getInstance(FakeRobotInterface::class, Name::ANY); assert($instance instanceof FakeToConstructorRobot); $this->assertInstanceOf(FakeEngine::class, $instance->engine); } public function testToValidation(): void { $this->expectException(InvalidType::class); (new Bind(new Container(), FakeHandleInterface::class))->to(FakeEngine::class); } public function testToProvider(): void { $this->expectException(InvalidProvider::class); (new Bind(new Container(), FakeHandleInterface::class))->toProvider(FakeEngine::class); } public function testBindProviderAsProvider(): void { $container = new Container(); (new Bind($container, ProviderInterface::class))->annotatedWith('handle')->to(FakeHandleProvider::class); $instance = $container->getInstance(ProviderInterface::class, 'handle'); $this->assertInstanceOf(FakeHandleProvider::class, $instance); } public function testBindProviderAsProviderInSingleton(): void { $container = new Container(); (new Bind($container, ProviderInterface::class))->annotatedWith('handle')->to(FakeHandleProvider::class)->in(Scope::SINGLETON); $instance1 = $container->getInstance(ProviderInterface::class, 'handle'); $instance2 = $container->getInstance(ProviderInterface::class, 'handle'); $this->assertSame(spl_object_hash($instance1), spl_object_hash($instance2)); } public function testProviderContext(): void { $container = new Container(); (new Bind($container, ProviderInterface::class))->toProvider(FakeContextualProvider::class, 'context_string'); $instance = $container->getInstance(ProviderInterface::class, Name::ANY); assert(property_exists($instance, 'context')); $this->assertSame('context_string', $instance->context); } } ================================================ FILE: tests/di/ContainerTest.php ================================================ container = new Container(); $this->engine = new FakeEngine(); (new Bind($this->container, FakeEngineInterface::class))->toInstance($this->engine); } public function testGetDependency(): void { $dependencyIndex = FakeEngineInterface::class . '-' . Name::ANY; $instance = $this->container->getDependency($dependencyIndex); $this->assertInstanceOf(FakeEngine::class, $instance); $this->assertSame($this->engine, $instance); } public function testClassGetDependency(): void { (new Bind($this->container, FakeEngine::class))->toInstance($this->engine); $dependencyIndex = FakeEngine::class . '-' . Name::ANY; $instance = $this->container->getDependency($dependencyIndex); $this->assertInstanceOf(FakeEngine::class, $instance); $this->assertSame($this->engine, $instance); } public function testProviderGetDependency(): void { (new Bind($this->container, FakeEngine::class))->toProvider(FakeEngineProvider::class); $dependencyIndex = FakeEngine::class . '-' . Name::ANY; $instance = $this->container->getDependency($dependencyIndex); $this->assertInstanceOf(FakeEngine::class, $instance); } public function testGetInstance(): void { $instance = $this->container->getInstance(FakeEngineInterface::class, Name::ANY); $this->assertInstanceOf(FakeEngine::class, $instance); $this->assertSame($this->engine, $instance); } public function testClassGetInstance(): void { (new Bind($this->container, FakeEngine::class))->toInstance($this->engine); $instance = $this->container->getInstance(FakeEngine::class, Name::ANY); $this->assertInstanceOf(FakeEngine::class, $instance); $this->assertSame($this->engine, $instance); } public function testProviderGetInstance(): void { (new Bind($this->container, FakeEngine::class))->toProvider(FakeEngineProvider::class); $instance = $this->container->getInstance(FakeEngine::class, Name::ANY); $this->assertInstanceOf(FakeEngine::class, $instance); } public function testGetContainer(): void { $array = $this->container->getContainer(); $dependencyIndex = FakeEngineInterface::class . '-' . Name::ANY; $this->assertArrayHasKey($dependencyIndex, $array); } public function testClassGetContainer(): void { (new Bind($this->container, FakeEngine::class))->toInstance($this->engine); $array = $this->container->getContainer(); $dependencyIndex = FakeEngine::class . '-' . Name::ANY; $this->assertArrayHasKey($dependencyIndex, $array); } public function testMerge(): void { $extraContainer = new Container(); $bind = (new Bind($this->container, FakeRobotInterface::class))->to(FakeRobot::class); $this->container->add($bind); $this->container->merge($extraContainer); $array = $this->container->getContainer(); $this->assertArrayHasKey(FakeEngineInterface::class . '-' . Name::ANY, $array); $this->assertArrayHasKey(FakeRobotInterface::class . '-' . Name::ANY, $array); } public function testMergePointcuts(): void { $extraContainer = new Container(); $pointcut1 = new Pointcut((new Matcher())->any(), (new Matcher())->any(), [FakeDoubleInterceptor::class]); $pointcut2 = new Pointcut((new Matcher())->any(), (new Matcher())->any(), [FakeDoubleInterceptor::class]); $this->container->addPointcut($pointcut1); $extraContainer->addPointcut($pointcut2); $this->container->merge($extraContainer); $array = []; foreach ($this->container->getPointcuts() as $pointcut) { $array[] = $pointcut->interceptors[0]; } $this->assertContains(FakeDoubleInterceptor::class, $array); } public function testMove(): void { $newName = 'new'; $this->container->move(FakeEngineInterface::class, Name::ANY, FakeEngineInterface::class, $newName); $dependencyIndex = FakeEngineInterface::class . '-' . $newName; $instance = $this->container->getDependency($dependencyIndex); $this->assertInstanceOf(FakeEngine::class, $instance); } public function testMoveUnbound(): void { $this->expectException(Unbound::class); $this->container->move(FakeEngineInterface::class, 'invalid', FakeEngineInterface::class, 'new'); } public function testAbstractClassUnbound(): void { try { $this->container->getInstance('_INVALID_INTERFACE_', Name::ANY); // @phpstan-ignore-line } catch (Throwable $e) { $this->assertSame(Unbound::class, get_class($e)); } } public function testAnnotateConstant(): void { $container = new Container(); //FakeConstantInterface (new Bind($container, ''))->annotatedWith(FakeConstant::class)->toInstance('kuma'); (new Bind($container, FakeConstantConsumer::class)); $instance = $container->getInstance(FakeConstantConsumer::class, Name::ANY); $this->assertSame('kuma', $instance->constantByConstruct); $this->assertSame('kuma', $instance->constantBySetter); $this->assertSame('kuma', $instance->setterConstantWithoutVarName); $this->assertSame('default_construct', $instance->defaultByConstruct); $this->assertSame('default_setter', $instance->defaultBySetter); } public function testBadMethodCall(): void { $this->expectException(BadMethodCallException::class); $container = new Container(); //FakeConstantInterface (new Bind($container, FakeEngineInterface::class))->toInstance(new FakeEngine()); $container->getInstanceWithArgs(FakeEngineInterface::class, []); } /** * @covers \Ray\Di\Container::getInstanceWithArgs */ public function testUnbound(): void { $this->expectException(Unbound::class); (new Container())->getInstanceWithArgs(FakeEngineInterface::class, []); } public function testWeaveAspectsWithEmptyPointcuts(): void { $container = new Container(); (new Bind($container, FakeEngine::class)); // Should work fine even when no pointcuts are defined $tmpDir = sys_get_temp_dir(); assert($tmpDir !== ''); $container->weaveAspects(new Compiler($tmpDir)); $instance = $container->getInstance(FakeEngine::class); $this->assertInstanceOf(FakeEngine::class, $instance); } } ================================================ FILE: tests/di/ContextualProviderTest.php ================================================ getInstance(FakeRobotInterface::class); /** @var FakeContextualRobot $robot */ $this->assertSame($robot->context, 'main'); } } ================================================ FILE: tests/di/DependencyTest.php ================================================ $class */ $class = new ReflectionClass(FakeCar::class); $setters = []; $name = new Name(Name::ANY); $setters[] = new SetterMethod(new ReflectionMethod(FakeCar::class, 'setTires'), $name); $setters[] = new SetterMethod(new ReflectionMethod(FakeCar::class, 'setHardtop'), $name); $setterMethods = new SetterMethods($setters); $newInstance = new NewInstance($class, $setterMethods); $this->dependency = new Dependency($newInstance, new ReflectionMethod(FakeCar::class, 'postConstruct')); } /** * @return Container[][] * @psalm-return array{0: array{0: Container}} */ public function containerProvider(): array { $container = new Container(); (new Bind($container, FakeTyreInterface::class))->to(FakeTyre::class); (new Bind($container, FakeEngineInterface::class))->to(FakeEngine::class); (new Bind($container, FakeHardtopInterface::class))->to(FakeHardtop::class); return [[$container]]; } /** * @dataProvider containerProvider */ public function testInject(Container $container): void { $car = $this->dependency->inject($container); /** @var FakeCar $car */ $this->assertInstanceOf(FakeCar::class, $car); } /** * @dataProvider containerProvider */ public function testSetterInjection(Container $container): void { $car = $this->dependency->inject($container); /** @var FakeCar $car */ $this->assertInstanceOf(FakeCar::class, $car); $this->assertInstanceOf(FakeTyre::class, $car->frontTyre); } /** * @dataProvider containerProvider */ public function testPostConstruct(Container $container): void { $car = $this->dependency->inject($container); /** @var FakeCar $car */ $this->assertTrue($car->isConstructed); } /** * @dataProvider containerProvider */ public function testPrototype(Container $container): void { $this->dependency->setScope(Scope::PROTOTYPE); $car1 = $this->dependency->inject($container); $car2 = $this->dependency->inject($container); assert(is_object($car1) && is_object($car2)); $this->assertNotSame(spl_object_hash($car1), spl_object_hash($car2)); } /** * @dataProvider containerProvider */ public function testSingleton(Container $container): void { $this->dependency->setScope(Scope::SINGLETON); $car1 = $this->dependency->inject($container); $car2 = $this->dependency->inject($container); assert(is_object($car1) && is_object($car2)); $this->assertSame(spl_object_hash($car1), spl_object_hash($car2)); } public function testInjectInterceptor(): void { /** @var ReflectionClass $class */ $class = new ReflectionClass(FakeAop::class); $dependency = new Dependency(new NewInstance($class, new SetterMethods([]))); $pointcut = new Pointcut((new Matcher())->any(), (new Matcher())->any(), [FakeDoubleInterceptor::class]); $dependency->weaveAspects(new Compiler(__DIR__ . '/tmp'), [$pointcut]); $container = new Container(); $container->add((new Bind($container, FakeDoubleInterceptor::class))->to(FakeDoubleInterceptor::class)); $instance = $dependency->inject($container); assert(is_object($instance)); $isWeave = (new ReflectionClass($instance))->implementsInterface(WeavedInterface::class); $this->assertTrue($isWeave); assert(property_exists($instance, 'bindings')); $this->assertArrayHasKey('returnSame', (array) $instance->bindings); } /** * @dataProvider containerProvider * @covers \Ray\Di\Dependency::injectWithArgs */ public function testInjectWithArgsPostConstruct(Container $container): void { $car = $this->dependency->injectWithArgs($container, [new FakeEngine()]); $this->assertInstanceOf(FakeCar::class, $car); } /** * @dataProvider containerProvider * @covers \Ray\Di\Dependency::injectWithArgs */ public function testInjectWithArgsSingleton(Container $container): void { $this->dependency->setScope(Scope::SINGLETON); $this->dependency->injectWithArgs($container, [new FakeEngine()]); $car = $this->dependency->injectWithArgs($container, [new FakeEngine()]); $this->assertInstanceOf(FakeCar::class, $car); } } ================================================ FILE: tests/di/Fake/Annotation/FakeInjectOne.php ================================================ dbId = $id; } } ================================================ FILE: tests/di/Fake/FakeAnnoClass.php ================================================ proceed(); } } ================================================ FILE: tests/di/Fake/FakeAnnoInterceptor2.php ================================================ proceed(); } } ================================================ FILE: tests/di/Fake/FakeAnnoInterceptor3.php ================================================ proceed(); } } ================================================ FILE: tests/di/Fake/FakeAnnoInterceptor4.php ================================================ proceed(); } } ================================================ FILE: tests/di/Fake/FakeAnnoInterceptor5.php ================================================ proceed(); } } ================================================ FILE: tests/di/Fake/FakeAnnoInterceptorInterface.php ================================================ bindInterceptor( $this->matcher->any(), $this->matcher->annotatedWith(FakeAnnoMethod1::class), [FakeAnnoInterceptor1::class] ); $this->bindInterceptor( $this->matcher->any(), $this->matcher->annotatedWith(FakeAnnoMethod2::class), [FakeAnnoInterceptor2::class] ); $this->bindInterceptor( $this->matcher->any(), $this->matcher->annotatedWith(FakeAnnoMethod3::class), [FakeAnnoInterceptor3::class] ); $this->bindPriorityInterceptor( $this->matcher->annotatedWith(FakeAnnoClass::class), $this->matcher->any(), [FakeAnnoInterceptor4::class] ); $this->bindInterceptor( $this->matcher->annotatedWith(FakeAnnoClass::class), $this->matcher->any(), [FakeAnnoInterceptor5::class] ); } } ================================================ FILE: tests/di/Fake/FakeAnnoOrderClass.php ================================================ install(new FakeAopInterceptorModule()); $this->install(new FakeAopInterceptorModule()); $this->install(new FakeAopInterfaceModule()); } } ================================================ FILE: tests/di/Fake/FakeAopGrapher.php ================================================ a = $a; } public function returnSame($a) { return $a; } } ================================================ FILE: tests/di/Fake/FakeAopGrapherModule.php ================================================ bind(FakeAopInterface::class)->to(FakeAopGrapher::class); $this->bindInterceptor( $this->matcher->any(), $this->matcher->any(), [FakeDoubleInterceptor::class] ); } } ================================================ FILE: tests/di/Fake/FakeAopInstallModule.php ================================================ install(new FakeAopInterceptorModule()); $this->install(new FakeAopInterfaceModule()); } } ================================================ FILE: tests/di/Fake/FakeAopInterceptorModule.php ================================================ bindInterceptor( $this->matcher->any(), $this->matcher->any(), [FakeDoubleInterceptor::class] ); } } ================================================ FILE: tests/di/Fake/FakeAopInterface.php ================================================ bind(FakeAopInterface::class)->to(FakeAop::class); } } ================================================ FILE: tests/di/Fake/FakeAopModule.php ================================================ bind(FakeAopInterface::class)->to(FakeAop::class); $this->bindInterceptor( $this->matcher->any(), $this->matcher->any(), [FakeDoubleInterceptor::class] ); } } ================================================ FILE: tests/di/Fake/FakeAssistedConsumer.php ================================================ bind(FakeAbstractDb::class)->toProvider(FakeAssistedDbProvider::class); } } ================================================ FILE: tests/di/Fake/FakeAssistedDbProvider.php ================================================ invocationProvider = $invocationProvider; } public function get() { $parameters = $this->invocationProvider->get()->getArguments()->getArrayCopy(); [$id] = $parameters; return new FakeAssistedDb($id); } } ================================================ FILE: tests/di/Fake/FakeAssistedInjectConsumer.php ================================================ singleParam = $param; } /** * Multiple parameters - should NOT infer */ #[FakeGearStickInject('test')] public function setMultipleParams(FakeGearStickInterface $param1, FakeTyreInterface $param2): void { $this->multipleParams1 = $param1; $this->multipleParams2 = $param2; } /** * Single parameter but already has parameter-level qualifier - should NOT infer */ #[FakeGearStickInject('test')] public function setSingleParamWithQualifier(#[FakeInjectOne] FakeGearStickInterface $param): void { $this->singleParamWithQualifier = $param; } /** * Method attribute only implements InjectInterface, not Qualifier - should NOT infer */ #[Inject] public function setSingleParamWithInjectOnly(FakeGearStickInterface $param): void { $this->singleParamWithInjectOnly = $param; } /** * No InjectInterface at all - should NOT infer */ public function setSingleParamNoInject(FakeGearStickInterface $param): void { $this->singleParamNoInject = $param; } /** * Method-level attribute with TARGET_METHOD only * BC parameter qualifier now supports this for backward compatibility */ #[FakeGearStickInject('test')] public function setSingleParamMethodOnly(FakeGearStickInterface $param): void { $this->singleParam = $param; } } ================================================ FILE: tests/di/Fake/FakeBcParameterQualifierModule.php ================================================ bind(FakeGearStickInterface::class) ->annotatedWith(FakeInjectOne::class) ->to(FakeLeatherGearStick::class); } } ================================================ FILE: tests/di/Fake/FakeBuiltin.php ================================================ injector = $Injector; } } ================================================ FILE: tests/di/Fake/FakeCar.php ================================================ engine = $engine; } #[Inject] public function setTires(FakeTyreInterface $frontTyre, FakeTyreInterface $rearTyre): void { $this->frontTyre = $frontTyre; $this->rearTyre = $rearTyre; } #[Inject(optional: true)] public function setHardtop(FakeHardtopInterface $hardtop): void { $this->hardtop = $hardtop; } #[Inject] public function setMirrors(#[Named('right')] FakeMirrorInterface $rightMirror, #[Named('left')] FakeMirrorInterface $leftMirror): void { $this->rightMirror = $rightMirror; $this->leftMirror = $leftMirror; } #[Inject] public function setSpareMirror(#[Named('right')] FakeMirrorInterface $rightMirror): void { $this->spareMirror = $rightMirror; } #[Inject] public function setHandle(FakeHandleInterface $handle): void { $this->handle = $handle; } #[FakeGearStickInject('leather')] public function setGearStick(FakeGearStickInterface $stick): void { $this->gearStick = $stick; } #[PostConstruct] public function postConstruct(): void { $isEngineInstalled = $this->engine instanceof FakeEngine; $isTyreInstalled = $this->frontTyre instanceof FakeTyre; if ($isEngineInstalled && $isTyreInstalled) { $this->isConstructed = true; } } } ================================================ FILE: tests/di/Fake/FakeCarEngine.php ================================================ bind(FakeEngine::class)->to(FakeCarEngine::class); } } ================================================ FILE: tests/di/Fake/FakeCarInterface.php ================================================ bind(FakeCarInterface::class)->to(FakeCar::class); // dependent $this->bind(FakeEngineInterface::class)->to(FakeEngine::class); // constructor $this->bind(FakeHardtopInterface::class)->to(FakeHardtop::class); // optional setter $this->bind(FakeTyreInterface::class)->to(FakeTyre::class); // setter $this->bind(FakeMirrorInterface::class)->annotatedWith('right')->to(FakeMirrorRight::class)->in(Scope::SINGLETON); // named binding $this->bind(FakeMirrorInterface::class)->annotatedWith('left')->to(FakeMirrorLeft::class)->in(Scope::SINGLETON); // named binding $this->bind('')->annotatedWith('logo')->toInstance('momo'); $this->bind(FakeHandleInterface::class)->toProvider(FakeHandleProvider::class); $this->bind(FakeGearStickInterface::class)->annotatedWith(FakeGearStickInject::class)->to(FakeLeatherGearStick::class); $this->bindInterceptor( $this->matcher->any(), $this->matcher->any('start'), [NullInterceptor::class] ); } } ================================================ FILE: tests/di/Fake/FakeClassInstanceBindModule.php ================================================ object = $object; parent::__construct(); } protected function configure() { $this->bind(FakeEngine::class)->toInstance($this->object); } } ================================================ FILE: tests/di/Fake/FakeClassWithBcParameterQualifier.php ================================================ gearStick = $gearStick; } } ================================================ FILE: tests/di/Fake/FakeConcreteClass.php ================================================ constantByConstruct = $constant; $this->defaultByConstruct = $default; } #[Inject] public function setFakeConstant(#[FakeConstant('constant')] $constant, $default = 'default_setter'): void { $this->constantBySetter = $constant; $this->defaultBySetter = $default; } #[Inject] public function setFakeConstantWithoutVarName(#[FakeConstant] $constant): void { $this->setterConstantWithoutVarName = $constant; } } ================================================ FILE: tests/di/Fake/FakeConstantInterface.php ================================================ bind()->annotatedWith(FakeConstant::class)->toInstance('kuma'); } } ================================================ FILE: tests/di/Fake/FakeContextualModule.php ================================================ context = $context; parent::__construct(); } protected function configure() { $this->bind(FakeRobotInterface::class)->toProvider(FakeContextualProvider::class, $this->context); } } ================================================ FILE: tests/di/Fake/FakeContextualProvider.php ================================================ context = $context; } public function get() { return new FakeContextualRobot($this->context); } } ================================================ FILE: tests/di/Fake/FakeContextualRobot.php ================================================ context = $context; } } ================================================ FILE: tests/di/Fake/FakeDoubleInterceptor.php ================================================ proceed(); return $result * 2; } } ================================================ FILE: tests/di/Fake/FakeDoubleInterceptorInterface.php ================================================ bind(FakeEngine::class)->toProvider(FakeEngineProvider::class); } } ================================================ FILE: tests/di/Fake/FakeFormerBindingHasPriorityModule.php ================================================ install(new FakeInstanceBindModule()); $this->install(new FakeInstanceBindModuleOneTo3()); } } ================================================ FILE: tests/di/Fake/FakeGearStickInject.php ================================================ value = $value['value'] ?? $value; } } ================================================ FILE: tests/di/Fake/FakeGearStickInterface.php ================================================ ip = $ip; } public function get() { $qualifiers = $this->ip->getQualifiers(); $type = null; foreach ($qualifiers as $qualifier) { if ($qualifier instanceof FakeGearStickInject) { $type = $qualifier->value; } } if ($type !== 'leather') { throw new InvalidArgumentException('Invalid Gear Stick Type'); } return new FakeLeatherGearStick(); } } ================================================ FILE: tests/di/Fake/FakeHandle.php ================================================ rightMirror = $rightMirror; } #[Inject] public function setLeftMirror(#[FakeLeft] FakeMirrorInterface $leftMirror): void { $this->leftMirror = $leftMirror; } } ================================================ FILE: tests/di/Fake/FakeHandleBarQualifier.php ================================================ logo = $logo; } public function get() { $handle = new FakeHandle(); $handle->logo = $this->logo; return $handle; } } ================================================ FILE: tests/di/Fake/FakeHardtop.php ================================================ ip = $ip; } public function get() { if ($this->ip->getName()) { return $this->ip; } } } ================================================ FILE: tests/di/Fake/FakeInstallModule.php ================================================ install(new FakeInstanceBindModule()); $this->install(new FakeInstanceBindModule2()); } } ================================================ FILE: tests/di/Fake/FakeInstanceBindModule.php ================================================ bind('')->annotatedWith('one')->toInstance(1); $this->bind('')->annotatedWith(FakeInjectOne::class)->toInstance(1); } } ================================================ FILE: tests/di/Fake/FakeInstanceBindModule2.php ================================================ bind('')->annotatedWith('two')->toInstance(2); } } ================================================ FILE: tests/di/Fake/FakeInstanceBindModuleOneTo3.php ================================================ bind('')->annotatedWith('one')->toInstance(3); } } ================================================ FILE: tests/di/Fake/FakeInternalTypeModule.php ================================================ bind('')->annotatedWith('type-bool')->toInstance(false); $this->bind('')->annotatedWith('type-int')->toInstance(1); $this->bind('')->annotatedWith('type-string')->toInstance('1'); $this->bind('')->annotatedWith('type-array')->toInstance([1]); $this->bind('')->annotatedWith('type-callable')->toInstance(static function () { }); $this->bind('')->annotatedWith('type-object')->toInstance(new stdClass()); $this->bind('')->annotatedWith('type-resource')->toInstance(fopen('data://,', 'w')); } } ================================================ FILE: tests/di/Fake/FakeInternalTypes.php ================================================ bool = $bool; $this->int = $int; $this->string = $string; $this->array = $array; $this->callable = $callable; } public function stringId(string $id): void { unset($id); } } ================================================ FILE: tests/di/Fake/FakeLeatherGearStick.php ================================================ bind()->annotatedWith('null')->toInstance(null); $this->bind()->annotatedWith('bool')->toInstance(true); $this->bind()->annotatedWith('int')->toInstance(1); $this->bind()->annotatedWith('string')->toInstance('1'); $this->bind()->annotatedWith('array')->toInstance([1]); $this->bind()->annotatedWith('object')->toInstance(new stdClass()); $this->bind(FakeAopInterface::class)->to(FakeAop::class); $this->bind(FakeRobotInterface::class)->toProvider(FakeRobotProvider::class)->in(Scope::SINGLETON); $this->bindInterceptor( $this->matcher->any(), $this->matcher->startsWith('returnSame'), [FakeDoubleInterceptor::class] ); } } ================================================ FILE: tests/di/Fake/FakeMirrorInterface.php ================================================ install(new FakeInstanceBindModule2(new FakeInstanceBindModule())); } } ================================================ FILE: tests/di/Fake/FakeModuleInModuleOverride.php ================================================ install(new FakeInstanceBindModuleOneTo3(new FakeInstanceBindModule())); } } ================================================ FILE: tests/di/Fake/FakeMultiBindingAnnotation.php ================================================ */ public $engines; /** @var Map */ public $robots; public function __construct( #[Set(FakeEngineInterface::class)] Map $engines, #[Set(FakeRobotInterface::class)] Map $robots ){ $this->engines = $engines; $this->robots = $robots; } } ================================================ FILE: tests/di/Fake/FakeMultiBindingConsumer.php ================================================ $engines * @param Map $robots */ public function __construct( #[Set(FakeEngineInterface::class)] public Map $engines, #[Set(FakeRobotInterface::class)] public Map $robots ){} public function testValid(): void { $f = $this->engines['one']; // cause no error in psalm $f->foo(); } /** * Test generic * * This tests generic expression of Map * Not called from any place. Created to confirm "@psalm-suppress" works with psalm * * @psalm-suppress UndefinedInterfaceMethod */ public function testInvalid(): void { $f = $this->engines['one']; // cause error in psalm $f->warnUndefinedInterfaceMethod(); } } ================================================ FILE: tests/di/Fake/FakeOnionInterceptor2.php ================================================ proceed(); } } ================================================ FILE: tests/di/Fake/FakeOnionInterceptor3.php ================================================ proceed(); } } ================================================ FILE: tests/di/Fake/FakeOnionInterceptor4.php ================================================ proceed(); } } ================================================ FILE: tests/di/Fake/FakeOpenCarModule.php ================================================ bind(FakeCarInterface::class)->to(FakeCar::class); $this->bind(FakeEngineInterface::class)->to(FakeEngine::class); // No hardtop, Go open ! // $this->bind(FakeHardtopInterface::class)->to(FakeHardtop::class); $this->bind(FakeTyreInterface::class)->to(FakeTyre::class); } } ================================================ FILE: tests/di/Fake/FakeOverrideInstallModule.php ================================================ install(new FakeInstanceBindModule()); $this->override(new FakeInstanceBindModuleOneTo3()); } } ================================================ FILE: tests/di/Fake/FakePdoModule.php ================================================ bind(PDO::class)->in(Scope::SINGLETON); $this->bind(PDO::class)->toConstructor(PDO::class, ['dsn' => 'pdo_dsn']); $this->bind()->annotatedWith('pdo_dsn')->toInstance('sqlite::memory:'); } } ================================================ FILE: tests/di/Fake/FakePhp8Car.php ================================================ engine = $engine; $this->constructerInjectedRightMirror = $rightMirror; } #[Inject] public function setTires(FakeTyreInterface $frontTyre, FakeTyreInterface $rearTyre): void { $this->frontTyre = $frontTyre; $this->rearTyre = $rearTyre; } #[Inject(optional: true)] public function setHardtop(FakeHardtopInterface $hardtop): void { $this->hardtop = $hardtop; } #[Inject] public function setMirrors(#[Named('right')] FakeMirrorInterface $rightMirror, #[Named('left')] FakeMirrorInterface $leftMirror): void { $this->rightMirror = $rightMirror; $this->leftMirror = $leftMirror; } #[Inject] public function setQualiferMirrors(#[FakeRight] FakeMirrorInterface $rightMirror, #[FakeLeft] FakeMirrorInterface $leftMirror): void { $this->qualfiedRightMirror = $rightMirror; $this->qualfiedLeftMirror = $leftMirror; } #[Inject] public function notQualifer(#[FakeNotQualifer] ?FakeMirrorInterface $rightMirror = null): void { } /** * Test provider attribute * * @see FakeHandleProvider */ #[Inject] public function setHandle(FakeHandleInterface $handle){ $this->handle = $handle; } /** * Custom inject annotation */ #[FakeGearStickInject('leather')] public function setGearStick(FakeGearStickInterface $stick): void { $this->gearStick = $stick; } #[PostConstruct] public function postConstruct(): void { $isEngineInstalled = $this->engine instanceof FakeEngine; $isTyreInstalled = $this->frontTyre instanceof FakeTyre; if ($isEngineInstalled && $isTyreInstalled) { $this->isConstructed = true; } } #[Inject] public function setOne(#[FakeInjectOne] int $one) { $this->one = $one; } } ================================================ FILE: tests/di/Fake/FakePhp8CarModule.php ================================================ bind(FakeCarInterface::class)->to(FakePhp8Car::class); // dependent $this->bind(FakeEngineInterface::class)->to(FakeEngine::class); // constructor $this->bind(FakeTyreInterface::class)->to(FakeTyre::class); // setter $this->bind(FakeMirrorInterface::class)->annotatedWith('right')->to(FakeMirrorRight::class)->in(Scope::SINGLETON); // named binding $this->bind(FakeMirrorInterface::class)->annotatedWith('left')->to(FakeMirrorLeft::class)->in(Scope::SINGLETON); // named binding $this->bind(FakeMirrorInterface::class)->annotatedWith(FakeLeft::class)->to(FakeMirrorLeft::class); // named binding $this->bind(FakeMirrorInterface::class)->annotatedWith(FakeRight::class)->to(FakeMirrorRight::class); // named binding $this->bind('')->annotatedWith('logo')->toInstance('momo'); $this->bind(FakeHandleInterface::class)->toProvider(FakeHandleProvider::class); $this->bind(FakeGearStickInterface::class)->annotatedWith(FakeGearStickInject::class)->toProvider(FakeGearStickProvider::class); $this->bind()->annotatedWith(FakeInjectOne::class)->toInstance(1); } } ================================================ FILE: tests/di/Fake/FakePhp8HandleProvider.php ================================================ logo = $logo; } public function get() { $handle = new FakeHandle(); $handle->logo = $this->logo; return $handle; } } ================================================ FILE: tests/di/Fake/FakePriorityModule.php ================================================ bindInterceptor( $this->matcher->any(), $this->matcher->any(), [FakeDoubleInterceptor::class] ); $this->bindPriorityInterceptor( $this->matcher->any(), $this->matcher->any(), [FakeDoubleInterceptor::class] ); } } ================================================ FILE: tests/di/Fake/FakePropConstruct.php ================================================ rename(FakeRobotInterface::class, 'original'); } } ================================================ FILE: tests/di/Fake/FakeRight.php ================================================ robot1 = $robot1; $this->robot2 = $robot2; } } ================================================ FILE: tests/di/Fake/FakeSet.php ================================================ $provider */ public function __construct(#[Set(FakeEngineInterface::class)] public ProviderInterface $provider) { $this->provider = $provider; } public function warn(): void { // valid method $this->provider->get()->foo(); // invalid method (but phpstan does not detect the error) /** @psalm-suppress UndefinedInterfaceMethod */ $this->provider->get()->bar(); } } ================================================ FILE: tests/di/Fake/FakeSetNotFoundWithMap.php ================================================ */ public $engines; /** * This property should Set annotated for setProviderButNotSetFound method. * SetNotFound exception will be thrown. */ public $engineProvider; public function __construct( Map $engines ){ $this->engines = $engines; } } ================================================ FILE: tests/di/Fake/FakeSetNotFoundWithProvider.php ================================================ $engineProvider = $engineProvider; } } ================================================ FILE: tests/di/Fake/FakeToBindInvalidClassModule.php ================================================ bind('\Ray\Di\Mock\Dependency\RobotInterface')->to('InvalidClassXXX'); } } ================================================ FILE: tests/di/Fake/FakeToBindModule.php ================================================ bind(FakeRobotInterface::class)->to(FakeRobot::class); } } ================================================ FILE: tests/di/Fake/FakeToBindSingletonModule.php ================================================ bind(FakeRobotInterface::class)->to(FakeRobot::class)->in(Scope::SINGLETON); } } ================================================ FILE: tests/di/Fake/FakeToConstructorRobot.php ================================================ leg = $leg; $this->tmpDir = $tmpDir; } public function setEngine(FakeEngineInterface $engine): void { $this->engine = $engine; } } ================================================ FILE: tests/di/Fake/FakeToProviderBindModule.php ================================================ bind(FakeRobotInterface::class)->toProvider(FakeRobotProvider::class); } } ================================================ FILE: tests/di/Fake/FakeToProviderSingletonBindModule.php ================================================ bind(FakeRobotInterface::class)->toProvider(FakeRobotProvider::class)->in(Scope::SINGLETON); } } ================================================ FILE: tests/di/Fake/FakeTyre.php ================================================ bind(FakeUnNamedClass::class); } } ================================================ FILE: tests/di/Fake/FakeUntarget.php ================================================ child = $child; } } ================================================ FILE: tests/di/Fake/FakeUntargetChild.php ================================================ val = $val; } } ================================================ FILE: tests/di/Fake/FakeUntargetModule.php ================================================ bind(FakeUntargetChild::class); } } ================================================ FILE: tests/di/Fake/FakeUntargetToIntanceModule.php ================================================ bind(FakeUntarget::class)->toInstance($instance); } } ================================================ FILE: tests/di/Fake/FakeWalkRobot.php ================================================ rightLeg = $rightLeg; $this->leftLeg = $leftLeg; } } ================================================ FILE: tests/di/Fake/FakeWalkRobotLegProvider.php ================================================ ip = $ip; } public function get() { $varName = $this->ip->getParameter()->getName(); if ($varName === 'leftLeg') { return new FakeLeftLeg(); } if ($varName === 'rightLeg') { return new FakeRightLeg(); } throw new InvalidArgumentException('Unexpected var name for LegInterface: ' . $varName); } } ================================================ FILE: tests/di/Fake/FakeWalkRobotModule.php ================================================ bind(FakeLegInterface::class)->toProvider(FakeWalkRobotLegProvider::class); } } ================================================ FILE: tests/di/Fake/FakelNoConstructorCallModule.php ================================================ accept($this); return true; } /** @inheritDoc */ public function visitProvider( Dependency $dependency, string $context, bool $isSingleton ) { return true; } /** @inheritDoc */ public function visitInstance($value) { return $value; } /** @inheritDoc */ public function visitAspectBind(Bind $aopBind) { return $aopBind; } /** @inheritDoc */ public function visitNewInstance( string $class, SetterMethods $setterMethods, ?Arguments $arguments, ?AspectBind $bind ) { $setterMethods->accept($this); if ($arguments) { $arguments->accept($this); } if ($bind instanceof AspectBind) { $bind->accept($this); } } /** @inheritDoc */ public function visitSetterMethods( array $setterMethods ) { foreach ($setterMethods as $setterMethod) { $setterMethod->accept($this); } } /** @inheritDoc */ public function visitSetterMethod(string $method, Arguments $arguments) { $arguments->accept($this); } /** @inheritDoc */ public function visitArguments(array $arguments) { foreach ($arguments as $argument) { $argument->accept($this); } } /** @inheritDoc */ public function visitArgument( string $index, bool $isDefaultAvailable, $defaultValue, ReflectionParameter $parameter ): void { } } ================================================ FILE: tests/di/GrapherTest.php ================================================ assertInstanceOf(Grapher::class, $grapher); } public function testGetInstanceWithArgs(): void { $grapher = new Grapher(new FakeUntargetModule(), __DIR__ . '/tmp'); $instance = $grapher->newInstanceArgs(FakeUntargetChild::class, ['1']); $this->assertInstanceOf(FakeUntargetChild::class, $instance); $this->assertSame('1', $instance->val); } public function testAopClassAutoloader(): void { passthru('php ' . __DIR__ . '/script/grapher.php'); $cacheFile = __DIR__ . '/script/grapher.php.txt'; $cache = file_get_contents($cacheFile); if (! is_string($cache)) { throw new LogicException(); } $grapher = unserialize($cache); assert($grapher instanceof Grapher); $this->assertInstanceOf(Grapher::class, $grapher); $instance = $grapher->newInstanceArgs(FakeAopInterface::class, ['a']); /** @var FakeAopGrapher $instance */ $result = $instance->returnSame(2); $this->assertSame(4, $result); $this->assertSame('a', $instance->a); } } ================================================ FILE: tests/di/InjectionPointTest.php ================================================ parameter = new ReflectionParameter([FakeWalkRobot::class, '__construct'], 'rightLeg'); $this->ip = new InjectionPoint($this->parameter); } public function testGetParameter(): void { $actual = $this->ip->getParameter(); $this->assertSame($this->parameter, $actual); } public function testGetMethod(): void { $actual = $this->ip->getMethod(); $this->assertSame((string) $this->parameter->getDeclaringFunction(), (string) $actual); } public function testGetClass(): void { $actual = $this->ip->getClass(); $this->assertSame((string) $this->parameter->getDeclaringClass(), (string) $actual); } public function testGetQualifiers(): void { $annotations = $this->ip->getQualifiers(); $this->assertCount(1, $annotations); $this->assertInstanceOf(FakeConstant::class, $annotations[0]); } } ================================================ FILE: tests/di/InjectionPointsTest.php ================================================ injectionPoints = (new InjectionPoints())->addMethod('setTires')->addOptionalMethod('setHardtop'); } public function testNew(): void { $this->assertInstanceOf(InjectionPoints::class, $this->injectionPoints); } public function testInvoke(): SetterMethods { $car = new FakeCar(new FakeEngine()); $setterMethods = $this->injectionPoints->__invoke(get_class($car)); $this->assertInstanceOf(SetterMethods::class, $setterMethods); return $setterMethods; } /** * @depends testInvoke */ public function testSetterMethod(SetterMethods $setterMethod): void { $car = new FakeCar(new FakeEngine()); $container = (new FakeCarModule())->getContainer(); $setterMethod($car, $container); $this->assertInstanceOf(FakeTyre::class, $car->frontTyre); $this->assertInstanceOf(FakeTyre::class, $car->rearTyre); $this->assertInstanceOf(FakeHardtop::class, $car->hardtop); } /** * @depends testInvoke */ public function testSetterMethodOptional(SetterMethods $setterMethod): void { $car = new FakeCar(new FakeEngine()); // no hardtop installed with this module $container = (new FakeOpenCarModule())->getContainer(); $setterMethod($car, $container); $this->assertInstanceOf(FakeTyre::class, $car->frontTyre); $this->assertInstanceOf(FakeTyre::class, $car->rearTyre); $this->assertNull($car->hardtop); } } ================================================ FILE: tests/di/InjectorTest.php ================================================ assertInstanceOf(Injector::class, $injector); } public function testGetToInstance(): void { $injector = new Injector(new FakeInstanceBindModule()); $instance = $injector->getInstance('', 'one'); $this->assertSame(1, $instance); } public function testToInstance(): void { $engine = new FakeEngine(); $injector = new Injector(new FakeClassInstanceBindModule($engine)); $this->assertSame($engine, $injector->getInstance(FakeEngine::class)); } public function testUnbound(): void { $this->expectException(Unbound::class); $injector = new Injector(new FakeInstanceBindModule()); $injector->getInstance('', 'invalid-binding-xxx'); } public function testInstall(): void { $injector = new Injector(new FakeInstallModule()); $instance = $injector->getInstance('', 'one'); $this->assertSame(1, $instance); $instance = $injector->getInstance('', 'two'); $this->assertSame(2, $instance); } public function testFormerBindingHasPriority(): void { $injector = new Injector(new FakeFormerBindingHasPriorityModule()); $instance = $injector->getInstance('', 'one'); $this->assertSame(1, $instance); } public function testLatterBindingHasPriorityWithThisParameter(): void { $injector = new Injector(new FakeOverrideInstallModule()); $instance = $injector->getInstance('', 'one'); $this->assertSame(3, $instance); } public function testModuleInModule(): void { $injector = new Injector(new FakeModuleInModule()); $instance = $injector->getInstance('', 'one'); $this->assertSame(1, $instance); $instance = $injector->getInstance('', 'two'); $this->assertSame(2, $instance); } public function testModuleInModuleOverride(): void { $injector = new Injector(new FakeModuleInModuleOverride()); $instance = $injector->getInstance('', 'one'); $this->assertSame(3, $instance); } public function testToBinding(): void { $injector = new Injector(new FakeToBindModule()); $instance = $injector->getInstance(FakeRobotInterface::class); $this->assertInstanceOf(FakeRobot::class, $instance); } public function testClassToClassBinding(): void { $injector = new Injector(new FakeCarEngineModule()); $instance = $injector->getInstance(FakeEngine::class); $this->assertInstanceOf(FakeCarEngine::class, $instance); } public function testToBindingPrototype(): void { $injector = new Injector(new FakeToBindModule()); $instance1 = $injector->getInstance(FakeRobotInterface::class); $instance2 = $injector->getInstance(FakeRobotInterface::class); $this->assertNotSame(spl_object_hash($instance1), spl_object_hash($instance2)); } public function testToBindingSingleton(): void { $injector = new Injector(new FakeToBindSingletonModule()); $instance1 = $injector->getInstance(FakeRobotInterface::class); $instance2 = $injector->getInstance(FakeRobotInterface::class); $this->assertSame(spl_object_hash($instance1), spl_object_hash($instance2)); } public function testToProviderBinding(): void { $injector = new Injector(new FakeToProviderBindModule()); $instance1 = $injector->getInstance(FakeRobotInterface::class); $instance2 = $injector->getInstance(FakeRobotInterface::class); $this->assertNotSame(spl_object_hash($instance1), spl_object_hash($instance2)); } public function testClassToProviderBinding(): void { $injector = new Injector(new FakeEngineToProviderModule()); $instance = $injector->getInstance(FakeEngine::class); $this->assertInstanceOf(FakeEngine::class, $instance); } public function testToProviderBindingSingleton(): void { $injector = new Injector(new FakeToProviderSingletonBindModule()); $instance1 = $injector->getInstance(FakeRobotInterface::class); $instance2 = $injector->getInstance(FakeRobotInterface::class); $this->assertSame(spl_object_hash($instance1), spl_object_hash($instance2)); } public function testGetConcreteClass(): void { $injector = new Injector(); $robot = $injector->getInstance(FakeRobot::class); $this->assertInstanceOf(FakeRobot::class, $robot); } public function testGetConcreteHavingDependency(): void { $injector = new Injector(new class extends AbstractModule{ protected function configure() { $this->bind(FakeRobot::class); } }); $team = $injector->getInstance(FakeRobotTeam::class); $this->assertInstanceOf(FakeRobotTeam::class, $team); $this->assertInstanceOf(FakeRobot::class, $team->robot1); $this->assertInstanceOf(FakeRobot::class, $team->robot2); } public function testGetConcreteClassWithModule(): void { $injector = new Injector(new FakeCarModule()); $car = $injector->getInstance(FakeCar::class); $this->assertInstanceOf(FakeCar::class, $car); } public function testAnnotationBasedInjection(): Injector { $injector = new Injector(new FakeCarModule()); $car = $injector->getInstance(FakeCarInterface::class); /** @var FakeCar $car */ $this->assertInstanceOf(FakeCar::class, $car); $this->assertInstanceOf(FakeTyre::class, $car->frontTyre); $this->assertInstanceOf(FakeTyre::class, $car->rearTyre); $this->assertInstanceOf(FakeHardtop::class, $car->hardtop); $this->assertInstanceOf(FakeMirrorInterface::class, $car->rightMirror); $this->assertInstanceOf(FakeMirrorInterface::class, $car->spareMirror); $this->assertSame(spl_object_hash($car->rightMirror), spl_object_hash($car->spareMirror)); $this->assertInstanceOf(FakeHandle::class, $car->handle); $this->assertSame($car->handle->logo, 'momo'); return $injector; } /** * @depends testAnnotationBasedInjection */ public function testSerialize(Injector $injector): void { $extractedInjector = unserialize(serialize($injector)); assert($extractedInjector instanceof InjectorInterface); $car = $extractedInjector->getInstance(FakeCarInterface::class); $this->assertInstanceOf(FakeCar::class, $car); } public function testAop(): void { $injector = new Injector(new FakeAopModule()); $instance = $injector->getInstance(FakeAopInterface::class); /** @var FakeAop $instance */ $result = $instance->returnSame(2); $this->assertSame(4, $result); } public function testIntefaceBindingAop(): void { $module = new class extends AbstractModule{ protected function configure() { $this->bind(FakeAopInterface::class)->to(FakeAop::class); $this->bind(FakeDoubleInterceptorInterface::class)->to(FakeDoubleInterceptor::class); $this->bindInterceptor( $this->matcher->any(), $this->matcher->any(), [FakeDoubleInterceptorInterface::class] ); } }; $injector = new Injector($module); $instance = $injector->getInstance(FakeAopInterface::class); /** @var FakeAop $instance */ $result = $instance->returnSame(2); $this->assertSame(4, $result); } public function testBuiltinBinding(): void { $instance = (new Injector())->getInstance(FakeBuiltin::class); $this->assertInstanceOf(Injector::class, $instance->injector); } public function testSerializeBuiltinBinding(): void { $injector = unserialize(serialize(new Injector())); assert($injector instanceof InjectorInterface); $instance = $injector->getInstance(FakeBuiltin::class); $this->assertInstanceOf(Injector::class, $instance->injector); } public function testAopBoundInDifferentModule(): void { $injector = new Injector(new FakeAopInstallModule()); $instance = $injector->getInstance(FakeAopInterface::class); /** @var FakeAop $instance */ $result = $instance->returnSame(2); $this->assertSame(4, $result); } public function testAopBoundInDifferentModuleAfterAnotherBinding(): void { $injector = new Injector(new FakeAopInstallModule(new FakeAopModule())); $instance = $injector->getInstance(FakeAopInterface::class); /** @var FakeAop $instance */ $result = $instance->returnSame(2); $this->assertSame(8, $result); } public function testAopBoundDoublyInDifferentModule(): void { $injector = new Injector(new FakeAopDoublyInstallModule()); $instance = $injector->getInstance(FakeAopInterface::class); /** @var FakeAop $instance */ $result = $instance->returnSame(2); $this->assertSame(8, $result); } public function testAopClassAutoloader(): void { passthru('php ' . __DIR__ . '/script/aop.php'); $cacheFile = __DIR__ . '/script/aop.php.cache.txt'; $cache = file_get_contents($cacheFile); if (! is_string($cache)) { throw new LogicException(); } $injector = unserialize($cache); if (! $injector instanceof Injector) { throw new LogicException(); } $instance = $injector->getInstance(FakeAopInterface::class); /** @var FakeAop $instance */ $result = $instance->returnSame(2); $this->assertSame(4, $result); unlink($cacheFile); } public function testAopOnDemandByUnboundConcreteClass(): void { $injector = new Injector(new FakeAopInterceptorModule()); $instance = $injector->getInstance(FakeAop::class); $result = $instance->returnSame(2); $this->assertSame(4, $result); } public function testBindOrder(): void { $injector = new Injector(new FakeAnnoModule()); $instance = $injector->getInstance(FakeAnnoOrderClass::class); $instance->get(); $expect = [FakeAnnoInterceptor4::class, FakeAnnoInterceptor1::class, FakeAnnoInterceptor2::class, FakeAnnoInterceptor3::class, FakeAnnoInterceptor5::class]; $this->assertSame($expect, FakeAnnoClass::$order); } public function testAnnotateConstant(): void { $instance = (new Injector(new FakeConstantModule()))->getInstance(FakeConstantConsumer::class); $this->assertSame('default_construct', $instance->defaultByConstruct); } public function testContextualDependencyInjection(): void { $injector = new Injector(new FakeWalkRobotModule()); $robot = $injector->getInstance(FakeWalkRobot::class); $this->assertInstanceOf(FakeLeftLeg::class, $robot->leftLeg); $this->assertInstanceOf(FakeRightLeg::class, $robot->rightLeg); } public function testNewAbstract(): void { $this->expectException(Unbound::class); (new Injector())->getInstance(FakeConcreteClass::class); } /** @requires OS ^Linux|^Darwin */ public function testIsOptionalValue(): void { if (! defined('HHVM_VERSION')) { $pdo = (new Injector(new FakePdoModule()))->getInstance(PDO::class); $this->assertInstanceOf(PDO::class, $pdo); } } public function testInternalTypes(): void { $injector = new Injector(new FakeInternalTypeModule()); $types = $injector->getInstance(FakeInternalTypes::class); $this->assertInstanceOf(FakeInternalTypes::class, $types); } /** @requires OS ^Linux|^Darwin */ public function testToConstructor(): void { $module = new class extends AbstractModule { protected function configure() { $this->bind(PDO::class)->toConstructor( PDO::class, ['dsn' => 'pdo_dsn'] )->in(Scope::SINGLETON); $this->bind()->annotatedWith('pdo_dsn')->toInstance('sqlite::memory:'); } }; $injector = new Injector($module); $pdo = $injector->getInstance(PDO::class); $this->assertInstanceOf(PDO::class, $pdo); } public function testNullObject(): void { $injector = (new Injector(new class extends AbstractModule { protected function configure() { $this->bind(FakeTyreInterface::class)->toNull(); } })); $nullObject = $injector->getInstance(FakeTyreInterface::class); $this->assertInstanceOf(FakeTyreInterface::class, $nullObject); } public function testBindInterfeceInterceptor(): void { $injector = (new Injector(new class extends AbstractModule { protected function configure() { $this->bind(FakeAop::class); $this->bind(FakeDoubleInterceptorInterface::class)->to(FakeDoubleInterceptor::class); $this->bindInterceptor( $this->matcher->any(), $this->matcher->any(), [FakeDoubleInterceptorInterface::class] ); } })); $instance = $injector->getInstance(FakeAop::class); $result = $instance->returnSame(2); $this->assertSame(4, $result); } public function testBindInterfeceNullInterceptor(): void { $injector = (new Injector(new class extends AbstractModule { protected function configure() { $this->bind(FakeAop::class); $this->bind(FakeDoubleInterceptorInterface::class)->to(NullInterceptor::class); $this->bindInterceptor( $this->matcher->any(), $this->matcher->any(), [FakeDoubleInterceptorInterface::class] ); } })); $instance = $injector->getInstance(FakeAop::class); $result = $instance->returnSame(2); $this->assertSame(2, $result); $this->assertInstanceOf(NullInterceptor::class, $instance->bindings['returnSame'][0]); // @phpstan-ignore-line } public function testModuleArray(): void { $modules = [ new class extends AbstractModule { protected function configure() { $this->bind()->annotatedWith('var')->toInstance('a'); $this->bind()->annotatedWith('first')->toInstance('1'); } }, new class extends AbstractModule { protected function configure() { $this->bind()->annotatedWith('var')->toInstance('b'); $this->bind()->annotatedWith('second')->toInstance('2'); } }, ]; $injector = new Injector($modules); $this->assertSame('a', $injector->getInstance('', 'var')); $this->assertSame('2', $injector->getInstance('', 'second')); $this->assertSame('1', $injector->getInstance('', 'first')); } public function testSingleArray(): void { $modules = [ new class extends AbstractModule { protected function configure() { $this->bind()->annotatedWith('var')->toInstance('a'); $this->bind()->annotatedWith('first')->toInstance('1'); } }, ]; $injector = new Injector($modules); $this->assertSame('a', $injector->getInstance('', 'var')); } /** * @requires PHP 8.0 */ public function testProviderInjectWithSet(): void { $injector = new Injector(new class extends AbstractModule{ protected function configure() { $this->bind(FakeEngineInterface::class)->to(FakeEngine::class); } }); $fakeSet = $injector->getInstance(FakeSet::class); $this->assertInstanceOf(ProviderInterface::class, $fakeSet->provider); $this->assertInstanceOf(FakeEngine::class, $fakeSet->provider->get()); } } ================================================ FILE: tests/di/ModuleMergerTest.php ================================================ bind()->annotatedWith('var')->toInstance('a'); $this->bind()->annotatedWith('first')->toInstance('1'); } }, new class extends AbstractModule { protected function configure() { $this->bind()->annotatedWith('var')->toInstance('b'); $this->bind()->annotatedWith('second')->toInstance('2'); } }, ]; $injector = new Injector($modules); $this->assertSame('a', $injector->getInstance('', 'var')); $this->assertSame('2', $injector->getInstance('', 'second')); $this->assertSame('1', $injector->getInstance('', 'first')); } } ================================================ FILE: tests/di/ModuleTest.php ================================================ assertInstanceOf(AbstractModule::class, $module); } public function testInstall(): void { $module = new FakeInstallModule(); $this->assertInstanceOf(AbstractModule::class, $module); } public function testToInvalidClass(): void { $this->expectException(NotFound::class); new FakeToBindInvalidClassModule(); } public function testRename(): void { $module = new FakeRenameModule(new FakeToBindModule()); $instance = $module->getContainer()->getInstance(FakeRobotInterface::class, 'original'); $this->assertInstanceOf(FakeRobotInterface::class, $instance); } public function testConstructorCallModule(): void { $module = new FakelNoConstructorCallModule(); $container = $module->getContainer(); $this->assertInstanceOf(Container::class, $container); } public function testActivate(): void { $module = new FakeInstanceBindModule(); $this->assertInstanceOf(Container::class, $module->getContainer()); } public function testtoString(): void { $string = (string) new FakeLogStringModule(); $normalize = static function (string $str): string { return str_replace(["\r\n", "\r"], "\n", $str); }; $this->assertSame($normalize('-array => (array) -bool => (boolean) 1 -int => (integer) 1 -null => (NULL) -object => (object) stdClass -string => (string) 1 Ray\Di\FakeAopInterface- => (dependency) Ray\Di\FakeAop (aop) +returnSame(Ray\Di\FakeDoubleInterceptor) Ray\Di\FakeDoubleInterceptor- => (dependency) Ray\Di\FakeDoubleInterceptor Ray\Di\FakeRobotInterface- => (provider) (dependency) Ray\Di\FakeRobotProvider'), $normalize($string)); } } ================================================ FILE: tests/di/MultiBinding/MultiBinderTest.php ================================================ addBinding('one')->to(FakeEngine::class); $binder->addBinding('two')->to(FakeEngine2::class); /** @var MultiBindings $multiBindings */ $multiBindings = $module->getContainer()->getInstance(MultiBindings::class); $this->assertArrayHasKey('one', (array) $multiBindings[FakeEngineInterface::class]); $this->assertArrayHasKey('two', (array) $multiBindings[FakeEngineInterface::class]); } public function testSet(): void { $module = new NullModule(); $binder = MultiBinder::newInstance($module, FakeEngineInterface::class); $binder->addBinding('one')->to(FakeEngine::class); $binder->addBinding('two')->to(FakeEngine2::class); $binder->setBinding('one')->to(FakeEngine::class); /** @var MultiBindings $multiBindings */ $multiBindings = $module->getContainer()->getInstance(MultiBindings::class); $this->assertArrayHasKey('one', (array) $multiBindings[FakeEngineInterface::class]); $this->assertArrayNotHasKey('two', (array) $multiBindings[FakeEngineInterface::class]); } } ================================================ FILE: tests/di/MultiBinding/MultiBindingModuleTest.php ================================================ module = new class extends AbstractModule { protected function configure(): void { $engineBinder = MultiBinder::newInstance($this, FakeEngineInterface::class); $engineBinder->addBinding('one')->to(FakeEngine::class); $engineBinder->addBinding('two')->to(FakeEngine2::class); $engineBinder->addBinding()->to(FakeEngine3::class); $robotBinder = MultiBinder::newInstance($this, FakeRobotInterface::class); $robotBinder->addBinding('to')->to(FakeRobot::class); $robotBinder->addBinding('provider')->toProvider(FakeRobotProvider::class); $robotBinder->addBinding('instance')->toInstance(new FakeRobot()); } }; } /** * @return Map */ public function testInjectMap(): Map { $injector = new Injector($this->module); $consumer = $injector->getInstance(FakeMultiBindingConsumer::class); $this->assertInstanceOf(Map::class, $consumer->engines); return $consumer->engines; } /** * @param Map $map * * @depends testInjectMap */ public function testMapInstance(Map $map): void { $this->assertInstanceOf(FakeEngine::class, $map['one']); $this->assertInstanceOf(FakeEngine2::class, $map['two']); } /** * @param Map $map * * @depends testInjectMap */ public function testMapIteration(Map $map): void { $this->assertContainsOnlyInstancesOf(FakeEngineInterface::class, $map); $this->assertSame(3, count($map)); } /** * @param Map $map * * @depends testInjectMap */ public function testIsSet(Map $map): void { $this->assertTrue(isset($map['one'])); $this->assertTrue(isset($map['two'])); } /** * @param Map $map * * @depends testInjectMap */ public function testOffsetSet(Map $map): void { $this->expectException(LogicException::class); $map['one'] = 1; } /** * @param Map $map * * @depends testInjectMap */ public function testOffsetUnset(Map $map): void { $this->expectException(LogicException::class); unset($map['one']); } public function testAnotherBinder(): void { $injector = new Injector($this->module); $consumer = $injector->getInstance(FakeMultiBindingConsumer::class); $this->assertInstanceOf(Map::class, $consumer->robots); $this->assertContainsOnlyInstancesOf(FakeRobot::class, $consumer->robots); $this->assertSame(3, count($consumer->robots)); } public function testMultipileModule(): void { $module = new NullModule(); $binder = MultiBinder::newInstance($module, FakeEngineInterface::class); $binder->addBinding('one')->to(FakeEngine::class); $binder->addBinding('two')->to(FakeEngine2::class); $module->install(new class extends AbstractModule { protected function configure() { $binder = MultiBinder::newInstance($this, FakeEngineInterface::class); $binder->addBinding('three')->to(FakeEngine::class); $binder->addBinding('four')->to(FakeEngine::class); } }); /** @var ArrayAccess $multiBindings */ $multiBindings = $module->getContainer()->getInstance(MultiBindings::class); $this->assertArrayHasKey('one', (array) $multiBindings[FakeEngineInterface::class]); $this->assertArrayHasKey('two', (array) $multiBindings[FakeEngineInterface::class]); $this->assertArrayHasKey('three', (array) $multiBindings[FakeEngineInterface::class]); $this->assertArrayHasKey('four', (array) $multiBindings[FakeEngineInterface::class]); } public function testAnnotation(): void { $injector = new Injector($this->module); $fake = $injector->getInstance(FakeMultiBindingAnnotation::class); $this->assertContainsOnlyInstancesOf(FakeEngineInterface::class, $fake->engines); $this->assertSame(3, count($fake->engines)); $this->assertContainsOnlyInstancesOf(FakeRobotInterface::class, $fake->robots); $this->assertSame(3, count($fake->robots)); } public function testSetNotFoundInMap(): void { $this->expectException(SetNotFound::class); $injector = new Injector($this->module); $injector->getInstance(FakeSetNotFoundWithMap::class); } public function testSetNotFoundInProvider(): void { $this->expectException(SetNotFound::class); $injector = new Injector(); $injector->getInstance(FakeSetNotFoundWithProvider::class); } } ================================================ FILE: tests/di/NameTest.php ================================================ assertSame(Name::ANY, $boundName); } public function testSingleName(): void { $name = new Name('turbo'); $parameter = new ReflectionParameter([FakeCar::class, '__construct'], 'engine'); $boundName = $name($parameter); $this->assertSame('turbo', $boundName); } public function testSetName(): void { $name = new Name(FakeMirrorRight::class); $parameter = new ReflectionParameter([FakeHandleBar::class, 'setMirrors'], 'rightMirror'); $boundName = $name($parameter); $expected = FakeMirrorRight::class; $this->assertSame($expected, $boundName); } /** * @dataProvider keyPairStringProvider */ public function testKeyValuePairName(string $keyPairValueString): void { $name = new Name($keyPairValueString); $parameter = new ReflectionParameter([FakeCar::class, '__construct'], 'engine'); $boundName = $name($parameter); $this->assertSame('engine_name', $boundName); } /** * @return string[][] * @psalm-return array{0: array{0: string}, 1: array{0: string}, 2: array{0: string}, 3: array{0: string}} */ public function keyPairStringProvider(): array { return [ ['engine=engine_name,var=var_name'], ['engine=engine_name, var=var_name'], ['var=var_name,engine=engine_name'], ['var=var_name, engine=engine_name'], ]; } public function testKeyValuePairButNotFound(): void { $name = new Name('foo=bar'); $parameter = new ReflectionParameter([FakeCar::class, '__construct'], 'engine'); $boundName = $name($parameter); $this->assertSame(Name::ANY, $boundName); } public function testKeyValuePairWithDollarPrefix(): void { $name = new Name('$engine=engine_name,$var=var_name'); $parameter = new ReflectionParameter([FakeCar::class, '__construct'], 'engine'); $boundName = $name($parameter); $this->assertSame('engine_name', $boundName); } } ================================================ FILE: tests/di/NewInstanceTest.php ================================================ $class */ $class = new ReflectionClass(FakeCar::class); $setters = []; $name = new Name(Name::ANY); $setters[] = new SetterMethod(new ReflectionMethod(FakeCar::class, 'setTires'), $name); $setters[] = new SetterMethod(new ReflectionMethod(FakeCar::class, 'setHardtop'), $name); $setterMethods = new SetterMethods($setters); $this->newInstance = new NewInstance($class, $setterMethods); } public function testInvoke(): void { $container = new Container(); (new Bind($container, FakeTyreInterface::class))->to(FakeTyre::class); (new Bind($container, FakeEngineInterface::class))->to(FakeEngine::class); (new Bind($container, FakeHardtopInterface::class))->to(FakeHardtop::class); $car = $this->newInstance->__invoke($container); assert($car instanceof FakeCar); $this->assertInstanceOf(FakeCar::class, $car); $this->assertInstanceOf(FakeTyre::class, $car->frontTyre); $this->assertInstanceOf(FakeTyre::class, $car->rearTyre); $this->assertInstanceOf(FakeHardtop::class, $car->hardtop); } } ================================================ FILE: tests/di/NoHintTest.php ================================================ assertInstanceOf(Unbound::class, $e); } public function testNoHintThrownForNoTypeNoName(): void { $this->expectException(NoHint::class); $injector = new Injector(new FakeUnNamedModule()); $injector->getInstance(FakeUnNamedClass::class); } public function testNoHintMessageFormat(): void { $injector = new Injector(new FakeUnNamedModule()); try { $injector->getInstance(FakeUnNamedClass::class); $this->fail('NoHint exception should be thrown'); } catch (NoHint $e) { // Message format: ${var} (file:line) $this->assertMatchesRegularExpression( '/^\$\w+ \(.+:\d+\)$/', $e->getMessage(), ); $this->assertStringContainsString('$value', $e->getMessage()); $this->assertStringContainsString('FakeUnNamedClass.php', $e->getMessage()); } } } ================================================ FILE: tests/di/NullModuleTest.php ================================================ getContainer(); $this->assertSame([], $container->getContainer()); } } ================================================ FILE: tests/di/ProviderProviderTest.php ================================================ bind(FakeEngineInterface::class)->toInstance(new FakeEngine()); } } ); $set = new Set(FakeEngineInterface::class); $provider = new ProviderProvider($injector, $set); $instance = $provider->get(); $this->assertInstanceOf(FakeEngine::class, $instance); } } ================================================ FILE: tests/di/SetterMethodTest.php ================================================ setterMethods = new SetterMethods([$setterMethod]); } public function testInvoke(): void { $container = new Container(); (new Bind($container, FakeTyreInterface::class))->to(FakeTyre::class); $car = new FakeCar(new FakeEngine()); // setter injection $this->setterMethods->__invoke($car, $container); $this->assertInstanceOf(FakeTyre::class, $car->frontTyre); $this->assertInstanceOf(FakeTyre::class, $car->rearTyre); $this->assertNotSame(spl_object_hash($car->frontTyre), spl_object_hash($car->rearTyre)); } public function testUnbound(): void { $this->expectException(Unbound::class); $container = new Container(); $car = new FakeCar(new FakeEngine()); $this->setterMethods->__invoke($car, $container); } public function testAcceptWithUnboundException(): void { $this->expectException(Unbound::class); $method = new ReflectionMethod(FakeCar::class, 'setTires'); $setterMethod = new SetterMethod($method, new Name(Name::ANY)); $visitor = new class implements VisitorInterface { public function visitSetterMethod(string $method, Arguments $arguments): void { throw new Unbound(FakeTyreInterface::class); } public function visitDependency(NewInstance $newInstance, ?string $postConstruct, bool $isSingleton): void { } public function visitProvider(Dependency $dependency, string $context, bool $isSingleton): string { return ''; } /** @param mixed $value */ public function visitInstance($value): string { return ''; } public function visitAspectBind(\Ray\Aop\Bind $aopBind): void { } public function visitNewInstance(string $class, SetterMethods $setterMethods, ?Arguments $arguments, ?AspectBind $bind): void { } /** @param array $setterMethods */ public function visitSetterMethods(array $setterMethods): void { } /** @param array $arguments */ public function visitArguments(array $arguments): void { } /** @param mixed $defaultValue */ public function visitArgument(string $index, bool $isDefaultAvailable, $defaultValue, ReflectionParameter $parameter): void { } }; $setterMethod->accept($visitor); } public function testAcceptWithUnboundExceptionOptional(): void { $method = new ReflectionMethod(FakeCar::class, 'setTires'); $setterMethod = new SetterMethod($method, new Name(Name::ANY)); $setterMethod->setOptional(); $state = (object) ['exceptionThrown' => false]; $visitor = new class ($state) implements VisitorInterface { public function __construct(private stdClass $state) { } public function visitSetterMethod(string $method, Arguments $arguments): void { $this->state->exceptionThrown = true; throw new Unbound(FakeTyreInterface::class); } public function visitDependency(NewInstance $newInstance, ?string $postConstruct, bool $isSingleton): void { } public function visitProvider(Dependency $dependency, string $context, bool $isSingleton): string { return ''; } /** @param mixed $value */ public function visitInstance($value): string { return ''; } public function visitAspectBind(\Ray\Aop\Bind $aopBind): void { } public function visitNewInstance(string $class, SetterMethods $setterMethods, ?Arguments $arguments, ?AspectBind $bind): void { } /** @param array $setterMethods */ public function visitSetterMethods(array $setterMethods): void { } /** @param array $arguments */ public function visitArguments(array $arguments): void { } /** @param mixed $defaultValue */ public function visitArgument(string $index, bool $isDefaultAvailable, $defaultValue, ReflectionParameter $parameter): void { } }; $setterMethod->accept($visitor); // Verify that exception was thrown but caught due to optional binding /** @phpstan-ignore-next-line */ $this->assertTrue($state->exceptionThrown, 'Unbound exception should have been thrown'); } } ================================================ FILE: tests/di/SetterMethodsTest.php ================================================ setterMethod = new SetterMethod($method, new Name(Name::ANY)); } public function testInvoke(): void { $car = new FakeCar(new FakeEngine()); $container = (new FakeCarModule())->getContainer(); $this->setterMethod->__invoke($car, $container); $this->assertInstanceOf(FakeTyre::class, $car->frontTyre); $this->assertInstanceOf(FakeTyre::class, $car->rearTyre); } } ================================================ FILE: tests/di/SpyCompilerTest.php ================================================ compile(FakeHandle::class, new AopBind()); $this->assertSame(FakeHandle::class, $class); } } ================================================ FILE: tests/di/UnboundTest.php ================================================ assertStringContainsString('Ray\\Di\\Exception\\Unbound', $string); $this->assertStringContainsString('dep1-', $string); $this->assertStringContainsString('dep2-', $string); } public function testNoPrevious(): void { $e = new Unbound('dep0-', 0); $string = (string) $e; $this->assertStringContainsString('Ray\\Di\\Exception\\Unbound', $string); } public function testNonUnboundPrevious(): void { $string = (string) new Unbound('', 0, new LogicException()); $expected = 'LogicException'; $this->assertStringContainsString($expected, $string); } } ================================================ FILE: tests/di/VisitorTest.php ================================================ visitor = new NullVisitor(); $this->container = (new ContainerFactory())(new FakeCarModule(), __DIR__ . '/tmp'); $container = $this->container->getContainer(); $dependency = $container['Ray\Di\FakeCarInterface-']; assert($dependency instanceof Dependency); $this->dependency = $dependency; $dependencyProvider = $container['Ray\Di\FakeHandleInterface-']; assert($dependencyProvider instanceof DependencyProvider); $this->dependencyProvider = $dependencyProvider; } public function testNullVisit(): void { $maybeTrue = $this->dependency->accept($this->visitor); $this->assertTrue($maybeTrue); } public function testCollectVisit(): void { $collector = new class () { /** @var array */ public $args = []; /** @var array */ public $methods = []; /** @var string */ public $newInstance; /** @var AopBind */ public $bind; public function pushArg(string $arg, bool $isSingleton): void { $type = $isSingleton ? 'singleton.' : 'prototype.'; $this->args[] = $type . $arg; } public function pushMethod(string $method): void { $this->methods[] = sprintf('%s(%s)', $method, implode(',', $this->args)); $this->args = []; } public function pushNewInstance(string $class): void { $this->newInstance = sprintf('%s(%s)', $class, implode(',', $this->args)); $this->args = []; } public function pushAopBind(AopBind $bind): void { $this->bind = $bind; } }; $visitor = new class ($collector, $this->container) implements VisitorInterface { /** @var object */ private $collector; /** @var Container */ private $container; public function __construct(object $collector, Container $container) { $this->collector = $collector; $this->container = $container; } public function visitDependency(NewInstance $newInstance, ?string $postConstruct, bool $isSingleton) { $newInstance->accept($this); } public function visitProvider(Dependency $dependency, string $context, bool $isSingleton): string { return 'visitProvider'; } /** @inheritDoc */ public function visitInstance($value): string { return 'visitInstance'; } public function visitAspectBind(AopBind $aopBind) { assert(method_exists($this->collector, 'pushAopBind')); $this->collector->pushAopBind($aopBind); } public function visitNewInstance(string $class, SetterMethods $setterMethods, ?Arguments $arguments, ?AspectBind $bind) { if ($arguments) { $arguments->accept($this); } $setterMethods->accept($this); assert(method_exists($this->collector, 'pushNewInstance')); $this->collector->pushNewInstance($class); if ($bind instanceof AspectBind) { $bind->accept($this); } } /** @inheritDoc */ public function visitSetterMethods(array $setterMethods) { foreach ($setterMethods as $setterMethod) { $setterMethod->accept($this); } } /** @inheritDoc */ public function visitSetterMethod(string $method, Arguments $arguments) { assert(method_exists($this->collector, 'pushMethod')); $this->collector->pushMethod($method); $arguments->accept($this); } /** @inheritDoc */ public function visitArguments(array $arguments) { foreach ($arguments as $argument) { $argument->accept($this); } } /** @inheritDoc */ public function visitArgument(string $index, bool $isDefaultAvailable, $defaultValue, ReflectionParameter $parameter) { $container = $this->container->getContainer(); $dependency = $container[$index]; assert($dependency instanceof Dependency || $dependency instanceof DependencyProvider); $isSingleton = $dependency->isSingleton(); assert(method_exists($this->collector, 'pushArg')); $this->collector->pushArg($index, $isSingleton); } }; $this->dependency->accept($visitor); $this->assertStringContainsString('Ray\Di\FakeCar', $collector->newInstance); $this->assertStringContainsString('(prototype.Ray\Di\FakeGearStickInterface-Ray\Di\FakeGearStickInject)', $collector->newInstance); $this->assertSame('setTires(prototype.Ray\Di\FakeEngineInterface-)', $collector->methods[0]); $this->assertSame('setHardtop(prototype.Ray\Di\FakeTyreInterface-,prototype.Ray\Di\FakeTyreInterface-)', $collector->methods[1]); } public function testVisitDependencyProvider(): void { $result = $this->dependencyProvider->accept($this->visitor); $this->assertTrue($result); } public function testVisitInsntace(): void { $instance = new Instance('1'); $this->assertSame('1', $instance->accept($this->visitor)); } } ================================================ FILE: tests/di/script/aop.php ================================================ getInstance(FakeCarInterface::class); } $timer1 = microtime(true) - $timer; $timer = microtime(true); $injector = unserialize($serialize); assert($injector instanceof InjectorInterface); foreach (range(1, $n) as $i) { $injector->getInstance(FakeCarInterface::class); } $timer2 = microtime(true) - $timer; $compiler = new DiCompiler(new FakeCarModule(), __DIR__ . '/tmp'); $compiler->compile(); $timer = microtime(true); $injector = new ScriptInjector(__DIR__ . '/tmp'); foreach (range(1, $n) as $i) { $injector->getInstance(FakeCarInterface::class); } $timer3 = microtime(true) - $timer; // Microsecond per inject printf("micro second per inject (MPI):%f speed:x%d\n", $timer2 / $n, $timer1 / $timer2); printf("micro second per inject (MPI):%f speed:x%d\n", $timer3 / $n, $timer1 / $timer3); ================================================ FILE: tests/di/script/grapher.php ================================================ [$interceptorA, ...][] * * @return array>> */ public function getBindings(); /** * Return hash * * @param string $salt * * @return string */ public function toString($salt); } ================================================ FILE: tests/type/InjectorInterfaceTest.php ================================================ getInstance(DateTimeInterface::class); $this->a = $injector->getInstance(DateTimeInterface::class); /** @psalm-suppress PropertyTypeCoercion */ $this->b = $injector->getInstance(DateTimeInterface::class); // @phpstan-ignore-line /** @psalm-suppress */ $this->m = $injector->getInstance('a'); // @phpstan-ignore-line } } ================================================ FILE: tests-php8/AssistedInjectTest.php ================================================ injector = new Injector(new FakeToBindModule(), __DIR__ . '/tmp'); } public function testAssisted(): void { $consumer = $this->injector->getInstance(FakeAssistedInjectConsumer::class); /** @var FakeAssistedConsumer $consumer */ $assistedDependency = $consumer->assistOne('a', 'b'); $expecetd = FakeRobot::class; $this->assertInstanceOf($expecetd, $assistedDependency); } public function testAssistedWithName(): void { $this->injector = new Injector(new FakeInstanceBindModule()); $consumer = $this->injector->getInstance(FakeAssistedInjectConsumer::class); /** @var FakeAssistedConsumer $consumer */ $assistedDependency = $consumer->assistWithName('a7'); $expecetd = 1; $this->assertSame($expecetd, $assistedDependency); } public function testAssistedAnyWithName(): void { $injector = new Injector(new FakeToBindModule(new FakeInstanceBindModule())); $consumer = $injector->getInstance(FakeAssistedInjectConsumer::class); /** @var FakeAssistedConsumer $consumer */ [$assistedDependency1, $assistedDependency2] = $consumer->assistAny(); $expected1 = 1; $this->assertSame($expected1, $assistedDependency1); $this->assertInstanceOf(FakeRobot::class, $assistedDependency2); } public function testAssistedMethodInvocation(): void { $assistedConsumer = (new Injector(new FakeAssistedDbModule(), __DIR__ . '/tmp'))->getInstance(FakeAssistedInjectDb::class); /** @var FakeAssistedParamsConsumer $assistedConsumer */ [$id, $db] = $assistedConsumer->getUser(1); /** @var FakeAbstractDb $db */ $this->assertSame(1, $id); $this->assertSame(1, $db->dbId); } public function testAssistedCustomeInject(): void { $injector = new Injector(new FakeInstanceBindModule()); $assistedConsumer = $injector->getInstance(FakeAssistedInjectConsumer::class); /** @var FakeAssistedInjectConsumer $assistedConsumer */ $i = $assistedConsumer->assistCustomeAssistedInject(); $this->assertSame(1, $i); } /** * @requires PHP 8.1 */ public function testConstructorPropertyPromotion(): void { $injector = new Injector( new class extends AbstractModule { protected function configure() { $this->bind()->annotatedWith('abc')->toInstance('abc'); } } ); $fake = $injector->getInstance(FakePropConstruct::class); $this->assertSame('abc', $fake->abc); } } ================================================ FILE: tests-php8/DualReaderTest.php ================================================ getInstance(FakePhp8Car::class); $this->assertInstanceOf(FakePhp8Car::class, $car); return $car; } /** * @depends testPhp8Attribute */ public function testNamedParameterInMethod(FakePhp8Car $car): void { $this->assertInstanceOf(FakeMirrorRight::class, $car->rightMirror); $this->assertInstanceOf(FakeMirrorRight::class, $car->qualfiedRightMirror); $this->assertInstanceOf(FakeMirrorLeft::class, $car->leftMirror); $this->assertInstanceOf(FakeMirrorLeft::class, $car->qualfiedLeftMirror); } /** * @depends testPhp8Attribute */ public function testNamedParameterInConstructor(FakePhp8Car $car): void { $this->assertInstanceOf(FakeMirrorRight::class, $car->constructerInjectedRightMirror); } /** * @depends testPhp8Attribute */ public function testPostConstruct(FakePhp8Car $car): void { $this->assertTrue($car->isConstructed); } /** * @depends testPhp8Attribute */ public function testCunstomInjectAnnotation(FakePhp8Car $car): void { $this->assertInstanceOf(FakeGearStickInterface::class, $car->gearStick); } /** * @depends testPhp8Attribute */ public function testProviderAttribute(FakePhp8Car $car): void { assert($car->handle instanceof FakeHandle); $this->assertSame('momo', $car->handle->logo); } /** * @depends testPhp8Attribute */ public function testCumstomInject(FakePhp8Car $car): void { $this->assertSame(1, $car->one); } } ================================================ FILE: vendor-bin/tools/composer.json ================================================ { "require-dev": { "doctrine/coding-standard": "^9.0", "phpmd/phpmd": "^2.9", "phpmetrics/phpmetrics": "^2.7 || v3.0.0rc8", "phpstan/phpstan": "^2.0", "squizlabs/php_codesniffer": "^3.5", "vimeo/psalm": "^6.8", "phpstan/phpstan-phpunit": "^2.0" }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } }