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
[](https://scrutinizer-ci.com/g/ray-di/Ray.Di/?branch=2.x)
[](https://codecov.io/gh/ray-di/Ray.Di)
[](https://shepherd.dev/github/ray-di/Ray.Di)
[](https://github.com/ray-di/Ray.Di/actions/workflows/continuous-integration.yml)
[](https://packagist.org/packages/ray/di)
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