Dependency Injection Container API

Service container, dependency injection, service providers, and service discovery. These classes provide comprehensive dependency management, automatic service resolution, and lifecycle management for your Forge Engine applications.

Container

The main dependency injection container that manages service registration, resolution, and lifecycle. The Container class provides automatic dependency resolution, singleton management, and service provider support.

Service Registration

bind(string $abstract, mixed $concrete = null, bool $shared = false): void

Register a binding in the container.

$container = new Container();

// Bind an interface to an implementation
$container->bind(UserRepositoryInterface::class, UserRepository::class);

// Bind with a closure
$container->bind('mailer', function($container) {
    return new Mailer($container->make('config'));
});

// Bind as singleton
$container->bind('cache', Cache::class, true);

singleton(string $abstract, mixed $concrete = null): void

Register a shared binding in the container.

// Register a singleton
$container->singleton('config', Config::class);
$container->singleton('db', DatabaseConnection::class);

instance(string $abstract, mixed $instance): void

Register an existing instance as shared in the container.

// Register an existing instance
$config = new Config(['app.name' => 'My App']);
$container->instance('config', $config);

Service Resolution

make(string $abstract, array $parameters = []): mixed

Resolve the given type from the container.

// Resolve a class
$userRepository = $container->make(UserRepository::class);

// Resolve with parameters
$mailer = $container->make(Mailer::class, ['driver' => 'smtp']);

// Resolve an interface (will return bound implementation)
$repository = $container->make(UserRepositoryInterface::class);

get(string $id): mixed

Alias for make() - PSR-11 container interface.

$service = $container->get('mailer');

has(string $id): bool

Check if the container has a binding.

if ($container->has('mailer')) {
    $mailer = $container->get('mailer');
}

Advanced Features

when(bool $value, callable $callback, callable $default = null): Container

Register a conditional binding.

$container->when(true, function($container) {
    $container->bind('cache', RedisCache::class);
})->when(false, function($container) {
    $container->bind('cache', FileCache::class);
});

extend(string $abstract, callable $closure): void

Extend a binding with additional functionality.

$container->extend('mailer', function($mailer, $container) {
    $mailer->setLogger($container->get('logger'));
    return $mailer;
});

tag(array|string $abstracts, array|string $tags): void

Tag a binding or group of bindings.

$container->tag([UserRepository::class, PostRepository::class], 'repositories');
$container->tag([Cache::class, Session::class], 'caches');

tagged(string $tag): array

Get all services tagged with a specific tag.

$repositories = $container->tagged('repositories');
foreach ($repositories as $repository) {
    $repository->initialize();
}

ServiceProvider

Base class for service providers that register services and boot functionality in the container. Service providers are the primary way to bootstrap your application services and dependencies.

Creating Service Providers

Basic Service Provider

Extend the base ServiceProvider class to create your own providers.

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Register services in the container
        $this->container->singleton('config', Config::class);
        $this->container->bind(UserRepositoryInterface::class, UserRepository::class);
        
        // Register with closures
        $this->container->bind('mailer', function($container) {
            return new Mailer($container->get('config'));
        });
    }
    
    public function boot(): void
    {
        // Boot functionality after all services are registered
        $this->loadViews();
        $this->loadTranslations();
        $this->registerCommands();
    }
    
    protected function loadViews(): void
    {
        // Load view templates
        View::addPath(__DIR__ . '/../views');
    }
    
    protected function registerCommands(): void
    {
        // Register console commands
        $this->container->bind('command.migrate', MigrateCommand::class);
        $this->container->bind('command.seed', SeedCommand::class);
    }
}

Provider Methods

register(): void

Register services in the container. This method is called first.

public function register(): void
{
    // Only register services, don't use them yet
    $this->container->bind('logger', Logger::class);
    $this->container->bind('cache', Cache::class);
}

boot(): void

Boot the provider after all services are registered.

public function boot(): void
{
    // Now you can use services from the container
    $logger = $this->container->get('logger');
    $logger->info('Application booted');
    
    // Register event listeners
    $this->registerEventListeners();
}

provides(): array

Return the services provided by this provider.

public function provides(): array
{
    return [
        'config',
        'logger',
        'cache',
        UserRepositoryInterface::class
    ];
}

ServiceDiscoverSetup

Automatically discovers and registers services, event listeners, and middleware from your application. This class provides convention-based service discovery to reduce manual configuration.

Service Discovery

discoverServices(string $namespace, string $path): void

Discover and register services from a namespace.

$discoverer = new ServiceDiscoverSetup($container);

// Discover services from App\Services namespace
$discoverer->discoverServices('App\\Services', __DIR__ . '/../app/Services');

// Discover repository services
$discoverer->discoverServices('App\\Repositories', __DIR__ . '/../app/Repositories');

Event Listener Discovery

discoverListeners(string $namespace, string $path): void

Discover and register event listeners.

// Discover event listeners
$discoverer->discoverListeners('App\\Listeners', __DIR__ . '/../app/Listeners');

// Example listener that would be discovered
namespace App\Listeners;

class UserRegisteredListener
{
    public function handle(UserRegisteredEvent $event): void
    {
        // Send welcome email
        $this->sendWelcomeEmail($event->user);
    }
    
    private function sendWelcomeEmail(User $user): void
    {
        // Email sending logic
    }
}

Middleware Discovery

discoverMiddleware(string $namespace, string $path): void

Discover and register middleware classes.

// Discover middleware
$discoverer->discoverMiddleware('App\\Middleware', __DIR__ . '/../app/Middleware');

// Example middleware that would be discovered
namespace App\Middleware;

use Forge\Http\Request;
use Forge\Http\Response;

class AuthMiddleware
{
    public function handle(Request $request, callable $next): Response
    {
        if (!$request->user()) {
            return new Response('Unauthorized', 401);
        }
        
        return $next($request);
    }
}

Usage Examples

Common patterns and examples for working with the Dependency Injection Container.

Basic Container Usage

// Create container
$container = new Container();

// Register simple bindings
$container->bind('logger', Logger::class);
$container->singleton('config', Config::class);

// Register with closures
$container->bind('mailer', function($container) {
    return new Mailer(
        $container->get('config')->get('mail'),
        $container->get('logger')
    );
});

// Resolve services
$logger = $container->get('logger');
$config = $container->get('config');
$mailer = $container->get('mailer');

Interface Binding

// Define interfaces
interface UserRepositoryInterface
{
    public function find($id);
    public function create(array $data);
}

interface CacheInterface
{
    public function get($key);
    public function set($key, $value, $ttl = null);
}

// Bind interfaces to implementations
$container->bind(UserRepositoryInterface::class, UserRepository::class);
$container->bind(CacheInterface::class, RedisCache::class);

// Constructor injection
class UserService
{
    private $repository;
    private $cache;
    
    public function __construct(
        UserRepositoryInterface $repository,
        CacheInterface $cache
    ) {
        $this->repository = $repository;
        $this->cache = $cache;
    }
    
    public function getUser($id)
    {
        $cacheKey = "user.{$id}";
        
        if ($cached = $this->cache->get($cacheKey)) {
            return $cached;
        }
        
        $user = $this->repository->find($id);
        $this->cache->set($cacheKey, $user, 3600);
        
        return $user;
    }
}

// Resolve with automatic injection
$userService = $container->make(UserService::class);

Service Provider Registration

// Application bootstrap
class Application
{
    protected $container;
    protected $providers = [];
    
    public function __construct()
    {
        $this->container = new Container();
        $this->registerBaseProviders();
        $this->registerApplicationProviders();
    }
    
    protected function registerBaseProviders(): void
    {
        $this->register(new ConfigServiceProvider($this->container));
        $this->register(new LoggingServiceProvider($this->container));
        $this->register(new DatabaseServiceProvider($this->container));
    }
    
    protected function registerApplicationProviders(): void
    {
        $this->register(new AppServiceProvider($this->container));
        $this->register(new EventServiceProvider($this->container));
        $this->register(new RouteServiceProvider($this->container));
    }
    
    public function register(ServiceProvider $provider): void
    {
        $provider->register();
        $this->providers[] = $provider;
    }
    
    public function boot(): void
    {
        foreach ($this->providers as $provider) {
            $provider->boot();
        }
    }
    
    public function getContainer(): Container
    {
        return $this->container;
    }
}

// Usage
$app = new Application();
$app->boot();

// Get services from container
$logger = $app->getContainer()->get('logger');
$db = $app->getContainer()->get('db');

Tagged Services

// Register tagged services
$container->bind('cache.redis', RedisCache::class);
$container->bind('cache.file', FileCache::class);
$container->bind('cache.memory', MemoryCache::class);

$container->tag(['cache.redis', 'cache.file', 'cache.memory'], 'cache.drivers');

// Get all tagged services
$cacheDrivers = $container->tagged('cache.drivers');

// Use in a cache manager
class CacheManager
{
    protected $drivers = [];
    protected $default;
    
    public function __construct(array $drivers, string $default)
    {
        $this->drivers = $drivers;
        $this->default = $default;
    }
    
    public function driver(string $name = null)
    {
        $name = $name ?: $this->default;
        
        if (!isset($this->drivers[$name])) {
            throw new InvalidArgumentException("Cache driver [{$name}] not found.");
        }
        
        return $this->drivers[$name];
    }
    
    public function getDefaultDriver(): string
    {
        return $this->default;
    }
}

// Register cache manager
$container->bind('cache.manager', function($container) {
    $drivers = $container->tagged('cache.drivers');
    $default = $container->get('config')->get('cache.default', 'file');
    
    return new CacheManager($drivers, $default);
});

Best Practices

Recommended patterns and guidelines for working with the Dependency Injection Container.

Do's

  • • Use constructor injection for required dependencies
  • • Program to interfaces, not implementations
  • • Use singletons for stateless services
  • • Keep service providers focused and small
  • • Use method injection for optional dependencies
  • • Register services in register(), boot in boot()
  • • Use tags for related services
  • • Document service contracts clearly

Don'ts

  • • Don't use the container as a service locator
  • • Don't inject the container into services
  • • Don't create circular dependencies
  • • Don't register services in boot() method
  • • Don't use property injection
  • • Don't make every service a singleton
  • • Don't ignore dependency cycles
  • • Don't hardcode service names