Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
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
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
14 changes: 11 additions & 3 deletions src/di/Argument.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@
use function sprintf;
use function unserialize;

/**
* @psalm-import-type DependencyIndex from Types
*/
final class Argument implements Serializable, AcceptInterface
{
public const UNBOUND_TYPE = ['bool', 'int', 'float', 'string', 'array', 'resource', 'callable', 'iterable'];

/** @var string */
/** @var DependencyIndex */
private $index;

/** @var bool */
Expand Down Expand Up @@ -57,6 +60,11 @@ public function __construct(ReflectionParameter $parameter, string $name)
);
}

/**
* Return index
*
* @return DependencyIndex
*/
public function __toString(): string
{
return $this->index;
Expand Down Expand Up @@ -103,7 +111,7 @@ public function serialize(): ?string // @phpstan-ignore-line
*/
public function unserialize($data): void
{
/** @var array{0: string, 1: bool, 2: string, 3: string, 4: string, 5: array{0: string, 1: string, 2:string}} $array */
/** @var array{0: DependencyIndex, 1: bool, 2: string, 3: string, 4: string, 5: array{0: string, 1: string, 2:string}} $array */
$array = unserialize($data, ['allowed_classes' => false]);
$this->__unserialize($array);
}
Expand Down Expand Up @@ -131,7 +139,7 @@ public function __serialize(): array
}

/**
* @param array{0: string, 1: bool, 2: string, 3: string, 4: string, 5: array{0: string, 1: string, 2:string}} $unserialized
* @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
{
Expand Down
8 changes: 6 additions & 2 deletions src/di/Arguments.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
use Ray\Di\Exception\Unbound;
use ReflectionMethod;

/**
* @psalm-import-type ArgumentsList from Types
* @psalm-import-type MethodArguments from Types
*/
final class Arguments implements AcceptInterface
{
/** @var Argument[] */
/** @var ArgumentsList */
private $arguments = [];

public function __construct(ReflectionMethod $method, Name $name)
Expand All @@ -23,7 +27,7 @@ public function __construct(ReflectionMethod $method, Name $name)
/**
* Return arguments
*
* @return array<int, mixed>
* @return list<mixed>
*
* @throws Exception\Unbound
*/
Expand Down
5 changes: 4 additions & 1 deletion src/di/AspectBind.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

use function assert;

/**
* @psalm-import-type MethodInterceptorBindings from Types
*/
final class AspectBind implements AcceptInterface
{
/** @var AopBind */
Expand All @@ -22,7 +25,7 @@ public function __construct(AopBind $bind)
/**
* Instantiate interceptors
*
* @return array<non-empty-string, list<MethodInterceptor>>
* @return MethodInterceptorBindings
*/
public function inject(Container $container): array
{
Expand Down
7 changes: 7 additions & 0 deletions src/di/AssistedInjectInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@
use function in_array;
use function is_callable;

/**
* @psalm-import-type NamedArguments from Types
* @psalm-import-type InjectableValue from Types
*/

/**
* Assisted injection interceptor for #[Inject] attributed parameter
*
* @psalm-import-type NamedArguments from Types
*/
final class AssistedInjectInterceptor implements MethodInterceptor
{
Expand Down
22 changes: 16 additions & 6 deletions src/di/Bind.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
use function is_array;
use function is_string;

/**
* @psalm-import-type BindableInterface from Types
* @psalm-import-type BindingName from Types
* @psalm-import-type ParameterNameMapping from Types
*/
final class Bind
{
/** @var Container */
Expand All @@ -43,8 +48,8 @@ final class Bind
private $untarget;

/**
* @param Container $container dependency container
* @param class-string<MethodInterceptor>|string $interface interface or concrete class name
* @param Container $container dependency container
* @param BindableInterface $interface interface or concrete class name
*/
public function __construct(Container $container, string $interface)
{
Expand All @@ -70,13 +75,18 @@ public function __destruct()
}
}

/**
* @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
{
Expand All @@ -103,8 +113,8 @@ public function to(string $class): self
/**
* Bind to constructor
*
* @param class-string<T> $class class name
* @param array<string, string>|string $name "varName=bindName,..." or [$varName => $bindName, $varName => $bindName...]
* @param class-string<T> $class class name
* @param ParameterNameMapping|string $name "varName=bindName,..." or [$varName => $bindName, $varName => $bindName...]
*
* @throws ReflectionException
*
Expand Down Expand Up @@ -205,7 +215,7 @@ private function isRegistered(string $interface): bool
* input: ['varA' => 'nameA', 'varB' => 'nameB']
* output: "varA=nameA,varB=nameB"
*
* @param array<string, string> $name
* @param ParameterNameMapping $name
*/
private function getStringName(array $name): string
{
Expand All @@ -222,7 +232,7 @@ static function (array $carry, $key) use ($name): array {
throw new InvalidToConstructorNameParameter((string) $key);
}

$varName = $name[$key];
$varName = $name[$key] ?? '';
$carry[] = $key . '=' . $varName;

return $carry;
Expand Down
Loading
Loading