Dependency injection container

Maintainers

Details

github.com/duyler/di

Source

Issues

Installs: 8 909

Dependents: 4

Suggesters: 0

Security: 0

Stars: 0

Watchers: 1

Forks: 0

Open Issues: 0

pkg:composer/duyler/di

dev-main 2025-11-24 08:43 UTC

README

Quality Gate Status Coverage type-coverage psalm-level

Duyler Dependency Injection Container

A modern, flexible, and type-safe dependency injection container for PHP applications. This container implements the PSR-11 Container Interface and provides additional features for dependency management.

Features

  • PSR-11 Container Interface implementation
  • Type-safe dependency injection
  • Provider-based service registration
  • Support for interface bindings
  • Service finalization
  • Reflection caching
  • Dependency tree visualization
  • Strict type checking
  • Scoped services (Singleton, Transient)
  • Tagged services for grouping and bulk retrieval
  • Binding validation
  • Callback factories
  • Compile-time dependency validation
  • Enhanced error messages with suggestions and context
  • Debug mode with profiling and statistics
  • Attribute-based configuration (PHP 8+ attributes)
  • Auto-tagging by interface
  • Event system with lifecycle hooks
  • Service decorators for aspect-oriented programming

Installation

composer require duyler/dependency-injection

Basic Usage

Simple Container Usage

use Duyler\DI\Container;

$container = new Container();

// Register a service
$container->set(new MyService());

// Get a service
$service = $container->get(MyService::class);

Using Container Configuration

use Duyler\DI\Container;
use Duyler\DI\ContainerConfig;

$config = new ContainerConfig();
$config
    ->withBind([
        MyInterface::class => MyImplementation::class,
    ])
    ->withProvider([
        AnotherInterface::class => MyProvider::class,
    ]);

$container = new Container($config);

Creating a Service Provider

use Duyler\DI\Provider\ProviderInterface;
use Duyler\DI\ContainerService;

class MyServiceProvider implements ProviderInterface
{
    public function getArguments(ContainerService $containerService): array
    {
        return [
            'dependency' => $containerService->getInstance(Dependency::class),
        ];
    }

    public function bind(): array
    {
        return [
            MyInterface::class => MyImplementation::class,
        ];
    }

    public function accept(object $definition): void
    {
        // Handle definition if needed
    }

    public function finalizer(): ?callable
    {
        return function (MyImplementation $service) {
            // Perform finalization
            $service->finalize();
        };
    }

    public function factory(ContainerService $containerService): ?object
    {
        return new MyImplementation(
            $containerService->getInstance(Dependency::class)
        );
    }
}

Using Service Definitions

use Duyler\DI\Definition;

$definition = new Definition(
    MyService::class,
    [
        'dependencyOne' => new AnotherService(),
        'dependencyTwo' => 'Hello, World!',
    ]
);

$container->addDefinition($definition);

Service Finalization

use Duyler\DI\Attribute\Finalize;

#[Finalize]
class MyService
{
    public function finalize(): void
    {
        // Finalization logic
    }
}

// Or using container method
$container->addFinalizer(MyService::class, function (MyService $service) {
    $service->finalize();
});

// Execute finalizers
$container->finalize();

Resetting Container

// Reset all services and providers
$container->reset();

Getting Dependency Tree

// Get the dependency tree for a specific class
$tree = $container->getDependencyTree();

Dependency Mapping

// Get current class mappings

$container = new Container();
$myObject = $container->get(MyClass::class);
$classMap = $container->getClassMap();

Advanced Features

Tagged Services

Tag services to group them by functionality and retrieve them as a collection:

use Duyler\DI\Container;
use Duyler\DI\ContainerConfig;

$container = new Container();

// Tag services at runtime
$container->tag(EventListener1::class, 'event.listener');
$container->tag(EventListener2::class, 'event.listener');
$container->tag(EventListener3::class, 'event.listener');

// Get all services with a specific tag
$listeners = $container->tagged('event.listener');
// Returns: [EventListener1, EventListener2, EventListener3]

// Tag a service with multiple tags
$container->tag(LoggerService::class, ['logger', 'monitor', 'debug']);

// Configure tags via ContainerConfig
$config = new ContainerConfig();
$config->withTag(CacheListener::class, 'event.listener');
$config->withTag(EmailListener::class, ['event.listener', 'notification']);

$container = new Container($config);

Common use cases:

  • Event listeners and subscribers
  • Middleware stacks
  • Plugin systems
  • Decorator chains
  • Command handlers

Service Scopes

Control the lifecycle of your services with scopes:

use Duyler\DI\Container;
use Duyler\DI\ContainerConfig;
use Duyler\DI\Scope;

$config = new ContainerConfig();
$config->withScope(TransientService::class, Scope::Transient);

$container = new Container($config);

// Singleton (default) - same instance every time
$singleton1 = $container->get(MySingletonService::class);
$singleton2 = $container->get(MySingletonService::class);
// $singleton1 === $singleton2

// Transient - new instance every time
$transient1 = $container->get(TransientService::class);
$transient2 = $container->get(TransientService::class);
// $transient1 !== $transient2

Callback Factories

Register custom factory functions for complex service creation:

use Duyler\DI\Container;
use Duyler\DI\ContainerInterface;

$container = new Container();

$container->factory(MyService::class, function (ContainerInterface $c) {
    $dependency = $c->get(Dependency::class);
    return new MyService($dependency, 'custom_config_value');
});

$service = $container->get(MyService::class);
// Factory is called once, result is cached (singleton)

Binding Validation

Bindings are automatically validated to prevent configuration errors:

use Duyler\DI\Container;
use Duyler\DI\ContainerConfig;
use Duyler\DI\Exception\InvalidBindingException;

$config = new ContainerConfig();

// Valid binding
$config->withBind([
    MyInterface::class => MyImplementation::class,
]);

// Invalid bindings throw InvalidBindingException:
// - Interface does not exist
// - Implementation does not exist  
// - Implementation does not implement interface
// - Binding concrete class to concrete class
try {
    $config->withBind([
        ConcreteClass::class => AnotherClass::class, // Error!
    ]);
    $container = new Container($config);
} catch (InvalidBindingException $e) {
    // Handle validation error
}

Compile-time Validation

Validate all dependencies before runtime to catch configuration errors early:

use Duyler\DI\Container;
use Duyler\DI\ContainerConfig;

$config = new ContainerConfig();
$config->withBind([
    MyInterface::class => MyImplementation::class,
    AnotherInterface::class => AnotherImplementation::class,
]);

$container = new Container($config);

// Validate all bindings without instantiating services
$errors = $container->compile();

if (empty($errors)) {
    echo "All dependencies are valid!";
} else {
    foreach ($errors as $error) {
        echo "Dependency error: $error\n";
    }
}

Enhanced Error Messages

The container provides detailed error messages with helpful context and suggestions:

Service Not Found with Suggestions:

When a service cannot be found, the container suggests similar service names and provides multiple solutions.

Circular Reference with Full Chain:

Shows the complete dependency chain leading to the circular reference, making it easy to identify and fix the issue.

Invalid Binding with Requirements:

Explains why a binding is invalid and shows the requirements that must be met, along with a correct example.

Dependency Resolution Errors with Context:

When dependency resolution fails, shows the full dependency chain and the reason for failure with actionable solutions.

Debug Mode

Enable debug mode to monitor and profile your container's dependency resolution:

use Duyler\DI\Container;
use Duyler\DI\ContainerConfig;

// Enable via configuration
$config = new ContainerConfig();
$config->withDebugMode(true);
$container = new Container($config);

// Or enable manually
$container = new Container();
$container->enableDebug();

// Resolve some services
$container->get(MyService::class);
$container->get(AnotherService::class);

// Get debug information
$debugInfo = $container->getDebugInfo();

// View statistics
$stats = $debugInfo->getStatistics();
echo "Total resolutions: {$stats['total_resolutions']}\n";
echo "Unique services: {$stats['unique_services']}\n";
echo "Total time: {$stats['total_time']}s\n";
echo "Peak memory: {$stats['peak_memory']} bytes\n";
echo "Average time: {$stats['avg_time']}s\n";

// Get detailed resolution data
$resolutions = $debugInfo->getResolutions();
foreach ($resolutions as $serviceId => $data) {
    echo "$serviceId: {$data['count']} times, {$data['total_time']}s total\n";
}

// Find performance bottlenecks
$slowest = $debugInfo->getSlowestServices(5);
$mostResolved = $debugInfo->getMostResolvedServices(5);

// View resolution log with timestamps
$log = $debugInfo->getResolutionLog();
foreach ($log as $entry) {
    echo "{$entry['service']}: {$entry['time']}s, {$entry['memory']} bytes, depth: {$entry['depth']}\n";
}

// Disable debug mode when not needed (improves performance)
$container->disableDebug();

Debug mode features:

  • Track resolution count for each service
  • Measure time and memory usage per service
  • Calculate average resolution time
  • Identify slowest services
  • Find most frequently resolved services
  • Complete resolution log with timestamps
  • Minimal overhead when disabled

Attribute-Based Configuration

Use PHP 8+ attributes to configure services declaratively:

use Duyler\DI\Attribute\Singleton;
use Duyler\DI\Attribute\Transient;
use Duyler\DI\Attribute\Tag;
use Duyler\DI\Attribute\Bind;

#[Singleton]
class CachedUserRepository
{
}

#[Transient]
class RequestHandler
{
}

#[Tag(['logger', 'monitor'])]
class FileLogger
{
}

#[Bind(LoggerInterface::class)]
class ConsoleLogger implements LoggerInterface
{
}

$container = new Container();
$logger = $container->get(LoggerInterface::class);

Attributes work seamlessly with all container features.

Auto-Tagging by Interface

Automatically tag services by their implemented interfaces:

use Duyler\DI\Container;
use Duyler\DI\ContainerConfig;

interface EventListenerInterface {}

class UserListener implements EventListenerInterface {}
class OrderListener implements EventListenerInterface {}

$config = new ContainerConfig();
$config->withAutoTagging(true);

$container = new Container($config);

$container->get(UserListener::class);
$container->get(OrderListener::class);

$listeners = $container->tagged(EventListenerInterface::class);

Auto-tagging eliminates manual tag registration for interface-based grouping.

Event System

Hook into container lifecycle events:

use Duyler\DI\Container;
use Duyler\DI\Event\ContainerEvents;

$container = new Container();

$container->on(ContainerEvents::BEFORE_RESOLVE, function ($event) {
    echo "Resolving: {$event->serviceId}\n";
});

$container->on(ContainerEvents::AFTER_RESOLVE, function ($event) {
    echo "Resolved: {$event->serviceId} in {$event->time}s\n";
});

$container->on(ContainerEvents::BEFORE_FINALIZE, function ($event) {
    echo "Starting finalization\n";
});

$container->on(ContainerEvents::AFTER_FINALIZE, function ($event) {
    echo "Finalization complete\n";
});

$service = $container->get(MyService::class);
$container->finalize();

Events are perfect for logging, monitoring, and debugging.

Service Decorators

Wrap services with additional functionality using decorators:

use Duyler\DI\Container;

interface CacheInterface
{
    public function get(string $key): mixed;
}

class RedisCache implements CacheInterface
{
    public function get(string $key): mixed
    {
        return 'value from Redis';
    }
}

class CachedDecorator implements CacheInterface
{
    public function __construct(private CacheInterface $inner) {}

    public function get(string $key): mixed
    {
        echo "Cache hit check\n";
        return $this->inner->get($key);
    }
}

$container = new Container();

$container->decorate(CacheInterface::class, fn($service, $c) => new CachedDecorator($service));

$container->bind([CacheInterface::class => RedisCache::class]);
$cache = $container->get(CacheInterface::class);

Decorators enable aspect-oriented programming patterns like logging, caching, and validation.

Roadmap

Future planned features:

Container Compilation

Pre-compile dependency resolution for maximum performance:

  • Generate optimized PHP code for service instantiation
  • Eliminate reflection overhead in production
  • Cache compiled container configuration
  • Warmup command for production deployments

Benefits: 5-10x faster service resolution.

Lazy Loading with Proxies

Defer service instantiation until first use:

  • Generate proxy classes for services
  • Automatic proxy generation for interfaces
  • Transparent lazy loading without code changes
  • Integration with existing scopes

Benefits: Reduced memory usage and faster bootstrap.

Container Freeze

Lock container configuration after initialization:

  • Prevent runtime modifications in production
  • Improve security by disabling dynamic registration
  • Performance optimizations for immutable containers
  • Development/production mode switching

Benefits: Enhanced security and performance.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.