Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -22,10 +22,10 @@ jobs:

- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache dependencies
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
Expand All @@ -35,4 +35,4 @@ jobs:
run: composer install --no-interaction --no-progress --prefer-dist

- name: Run Demo
run: php demo-php8/run.php
run: php demo/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v2
Expand Down
87 changes: 87 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# 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
9 changes: 3 additions & 6 deletions demo/01a-linked-binding.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@ interface MovieListerInterface

class MovieLister implements MovieListerInterface
{
public $finder;

public function __construct(FinderInterface $finder)
{
$this->finder = $finder;
}
public function __construct(
public FinderInterface $finder
){}
}

class FinderModule extends AbstractModule
Expand Down
6 changes: 2 additions & 4 deletions demo/01b-linked-binding-setter-injection.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@ interface MovieListerInterface

class MovieLister implements MovieListerInterface
{
public $finder;
public FinderInterface $finder;

/**
* @Inject
*/
#[Inject]
public function setFinder(FinderInterface $finder)
{
$this->finder = $finder;
Expand Down
9 changes: 3 additions & 6 deletions demo/02-provider-binding.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,9 @@ interface MovieListerInterface

class MovieLister implements MovieListerInterface
{
/** @var Finder */
public $finder;

public function __construct(FinderInterface $finder)
{
$this->finder = $finder;
public function __construct(
public FinderInterface $finder
){
}
}

Expand Down
17 changes: 3 additions & 14 deletions demo/02a-named-by-qualifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,21 @@ class LegacyFinder implements FinderInterface
{
}

class ModernFinder implements FinderInterface
{
}

interface MovieListerInterface
{
}

class MovieLister implements MovieListerInterface
{
public $finder;
public FinderInterface $finder;

/**
* @Legacy
*/
public function __construct(FinderInterface $finder)
public function __construct(#[Legacy] FinderInterface $finder)
{
$this->finder = $finder;
}
}

/**
* @Annotation
* @Target("METHOD")
* @Qualifier
*/
#[Attribute(Attribute::TARGET_PARAMETER), Qualifier]
class Legacy
{
}
Expand Down
12 changes: 3 additions & 9 deletions demo/02b-named-by-named.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,9 @@ interface MovieListerInterface

class MovieLister implements MovieListerInterface
{
public $finder;

/**
* @Named("legacy")
*/
public function __construct(FinderInterface $finder)
{
$this->finder = $finder;
}
public function __construct(
#[Named('legacy')] public FinderInterface $finder
){}
}

class FinderModule extends AbstractModule
Expand Down
19 changes: 8 additions & 11 deletions demo/03-injection-point.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,22 @@ interface MovieListerInterface

class Finder implements FinderInterface
{
private $className;
private string $className;

public function __construct($className)
public function __construct(string $className)
{
$this->className = $className;
}

public function find()
public function find(): string
{
return sprintf('search for [%s]', $this->className);
}
}

class MovieLister implements MovieListerInterface
{
/** @var Finder */
public $finder;
public FinderInterface $finder;

public function __construct(FinderInterface $finder)
{
Expand All @@ -45,12 +44,10 @@ public function __construct(FinderInterface $finder)

class FinderProvider implements ProviderInterface
{
private $ip;

public function __construct(InjectionPointInterface $ip)
{
$this->ip = $ip;
}
public function __construct(
public InjectionPointInterface $ip
)
{}

/**
* {@inheritdoc}
Expand Down
2 changes: 1 addition & 1 deletion demo/05b-constructor-binding-setter-injection.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Finder implements FinderInterface

class MovieLister implements MovieListerInterface
{
public $finder;
public FinderInterface $finder;

/**
* Setter Injection with no Inject annotation
Expand Down
4 changes: 1 addition & 3 deletions demo/finder/Db.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ public function __construct($dsn, $username, $password)
{
}

/**
* @PostConstruct
*/
#[PostConstruct]
public function init()
{
}
Expand Down
8 changes: 2 additions & 6 deletions demo/finder/DbFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@ public function __construct(DbInterface $db)
{
}

/**
* @Inject
*/
#[Inject]
public function setDb(DbInterface $db)
{
}

/**
* @Inject
*/
#[Inject]
public function setSorter(Sorter $sorter, Sorter $sorte2)
{
}
Expand Down
6 changes: 2 additions & 4 deletions demo/finder/MovieFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
declare(strict_types=1);

use Ray\Di\Di\Assisted;
use Ray\Di\Di\Inject;

class MovieFinder
{
/**
* @Assisted({"finder"})
*/
public function find($name, ?FinderInterface $finder = null)
public function find($name, #[Inject] ?FinderInterface $finder = null)
{
return sprintf('searching [%s] by [%s]', $name, get_class($finder));
}
Expand Down
2 changes: 1 addition & 1 deletion demo/finder/MovieLister.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public function __construct(FinderInterface $finder)
{
}

/** @Inject */
#[Inject]
public function setFinder01(FinderInterface $finder, FinderInterface $finder1, FinderInterface $finder2)
{
}
Expand Down
2 changes: 2 additions & 0 deletions demo/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

declare(strict_types=1);

putenv('TMPDIR=' . __DIR__ . '/tmp');

passthru('php ' . __DIR__ . '/01a-linked-binding.php');
passthru('php ' . __DIR__ . '/01b-linked-binding-setter-injection.php');
passthru('php ' . __DIR__ . '/02-provider-binding.php');
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
parameters:
errorFormat: raw
level: max
paths:
- src/di
Expand Down
12 changes: 8 additions & 4 deletions src/di/AbstractModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@

use Ray\Aop\AbstractMatcher;
use Ray\Aop\Matcher;
use Ray\Aop\MethodInterceptor;
use Ray\Aop\Pointcut;
use Ray\Aop\PriorityPointcut;

use function assert;
use function class_exists;
use function interface_exists;

/**
* @psalm-import-type BindableInterface from Types
* @psalm-import-type PointcutList from Types
* @psalm-import-type InterceptorClassList from Types
*/
abstract class AbstractModule
{
/** @var Matcher */
Expand Down Expand Up @@ -74,7 +78,7 @@ public function getContainer(): Container
/**
* Bind interceptor
*
* @param array<class-string<MethodInterceptor>> $interceptors
* @param InterceptorClassList $interceptors
*/
public function bindInterceptor(AbstractMatcher $classMatcher, AbstractMatcher $methodMatcher, array $interceptors): void
{
Expand All @@ -95,7 +99,7 @@ public function bindInterceptor(AbstractMatcher $classMatcher, AbstractMatcher $
/**
* Bind interceptor early
*
* @param array<class-string<MethodInterceptor>> $interceptors
* @param InterceptorClassList $interceptors
*/
public function bindPriorityInterceptor(AbstractMatcher $classMatcher, AbstractMatcher $methodMatcher, array $interceptors): void
{
Expand Down Expand Up @@ -134,7 +138,7 @@ abstract protected function configure();
/**
* Bind interface
*
* @phpstan-param class-string|string $interface
* @param BindableInterface $interface
*/
protected function bind(string $interface = ''): Bind
{
Expand Down
Loading
Loading