A lightweight, PSR-11 compliant dependency injection container for modern PHP applications, featuring autowiring, contextual bindings, attribute-based injection, and comprehensive service management. It provides strong type safety through PHP's type system and PHPStan integration, enabling excellent IDE autocompletion and runtime type checking. While universally applicable for any PHP project, it includes optimizations that make it particularly effective for WordPress plugin development.
- PSR-11 Compliant - Fully implements the PHP-FIG PSR-11 container standard
- Autowiring - Automatic dependency resolution based on type hints
- Constructor Injection - Automatic dependency resolution through constructor parameters
- Property Injection - Inject dependencies using PHP 8 attributes on class properties
- Method/Setter Injection - Automatic dependency resolution through setter methods
- Contextual Bindings - Define specific implementations for different contexts
- Service Providers - Organize related bindings and bootstrapping logic
- Service Tagging - Group related services for collective resolution
- Interface Binding - Easily bind interfaces to implementations with full type safety
- Type Safety - Use interfaces and class names as service IDs for better type hints and IDE completion
- Singleton & Factory Patterns - Register services as shared instances or factories
- Service Extension - Decorate and extend existing services
- Circular Dependency Detection - Identify and report circular dependencies with detailed context
- PHPStan Level 8 Support - Comprehensive type safety with generics and template types
- Detailed Error Reporting - All exceptions include dependency chain visualization and context
- PHP 8.1 or higher
- PSR Container 2.0+ package
You can install the package via composer:
composer require wptechnix/di-container
use WPTechnix\DI\Container;
// Create a new container instance
$container = new Container();
The container offers four main ways to register services with different lifecycles:
Use bind()
when you want a new instance created each time the service is resolved:
// Basic binding: new instance each time
$container->bind(LoggerInterface::class, FileLogger::class);
// With a factory closure
$container->bind(LoggerInterface::class, function ($container) {
return new FileLogger('/path/to/logs');
});
Use singleton()
when you want the same instance shared across the application:
// Interface to implementation
$container->singleton(LoggerInterface::class, FileLogger::class);
// For concrete classes, the implementation is optional
$container->singleton(Database::class); // Equivalent to: singleton(Database::class, Database::class)
// With a factory closure
$container->singleton(Config::class, function ($container) {
return new Config(['env' => 'production']);
});
Use factory()
for complex instantiation logic (creates new instance each time):
$container->factory(RequestHandler::class, function ($container, $params) {
$logger = $container->get(LoggerInterface::class);
$timeout = $params['timeout'] ?? 30;
return new RequestHandler($logger, $timeout);
});
Use instance()
to register an already created object:
$config = new Config(['debug' => true]);
$container->instance(Config::class, $config);
All binding methods are built on top of the core bind()
method:
// Full bind() method signature:
// bind(id, implementation, shared = false, override = false)
// These two are equivalent:
$container->singleton(LoggerInterface::class, FileLogger::class);
$container->bind(LoggerInterface::class, FileLogger::class, true);
// These two are equivalent:
$container->factory(RequestHandler::class, $factoryClosure);
$container->bind(RequestHandler::class, $factoryClosure, false);
Using interfaces or class names as service IDs provides better type hinting:
// Using interface as ID - great for type hinting
$logger = $container->get(LoggerInterface::class); // IDE knows this returns LoggerInterface
// Using string as ID - works but loses type hinting
$container->bind('logger', FileLogger::class);
$logger = $container->get('logger'); // IDE doesn't know the return type
The container provides type-safe resolution through PHP's type system:
// Resolve a class with dependencies - fully type-hinted in IDEs
$logger = $container->get(LoggerInterface::class); // IDE knows this returns LoggerInterface
// This works because of PHP's generics support in PHPDoc and is enforced by PHPStan
/** @var FileLogger $specificLogger */
$specificLogger = $container->get(LoggerInterface::class);
// Use resolve method with parameters
$controller = $container->resolve(UserController::class, [
'userId' => 123
]);
// Type-safety with resolve is also preserved
/** @var UserController $controller */
$controller = $container->resolve(UserController::class);
Contextual bindings allow you to specify different implementations based on where the dependency is being used:
// Register different logger implementations based on context
$container->when(UserController::class)
->needs(LoggerInterface::class)
->give(UserLogger::class);
$container->when(PaymentController::class)
->needs(LoggerInterface::class)
->give(PaymentLogger::class);
Property injection can be performed using PHP 8 attributes:
use WPTechnix\DI\Attributes\Inject;
class UserRepository
{
#[Inject]
public LoggerInterface $logger;
#[Inject(CacheService::class)]
public CacheInterface $cache;
}
Organize related bindings in provider classes:
use WPTechnix\DI\Contracts\ProviderInterface;
use WPTechnix\DI\Contracts\ContainerInterface;
class LoggingServiceProvider implements ProviderInterface
{
public function register(ContainerInterface $container): void
{
$container->singleton(LoggerInterface::class, FileLogger::class);
$container->singleton(LoggerFactory::class);
}
public function boot(ContainerInterface $container): void
{
// Bootstrap the service if needed
$logger = $container->get(LoggerInterface::class);
$logger->info('Logging service started');
}
}
// Register the provider
$container->provider(LoggingServiceProvider::class);
Group related services for collective resolution. Using interfaces as tag names provides better type safety and IDE autocomplete support:
// Using string tags (works, but lacks type safety)
$container->tag('validators', [
EmailRule::class,
PasswordRule::class,
UsernameRule::class
]);
// Resolve with string tag
$validators = $container->resolveTagged('validators');
// RECOMMENDED: Using interfaces as tags for better type hinting
$container->tag(ValidationRuleInterface::class, [
EmailRule::class, // All these classes should implement ValidationRuleInterface
PasswordRule::class,
UsernameRule::class
]);
// Resolve with interface - provides proper type hinting in IDE
$validators = $container->resolveTagged(ValidationRuleInterface::class); // $validators will be ValidationRuleInterface[]
The container integrates seamlessly with WordPress:
// In your main plugin file
class MyPlugin {
private ContainerInterface $container;
public function __construct() {
$this->container = new Container();
$this->registerServices();
$this->boot();
}
private function registerServices(): void {
// Register core WordPress services
$this->container->instance('wpdb', $GLOBALS['wpdb']);
// Register plugin services
$this->container->provider(SettingsServiceProvider::class);
$this->container->provider(AdminServiceProvider::class);
$this->container->provider(FrontendServiceProvider::class);
}
private function boot(): void {
// Resolve and initialize hooks manager
$hooks = $this->container->get(HooksManager::class);
$hooks->register();
}
}
Extend or decorate existing services:
$container->extend(LoggerInterface::class, function ($logger, $container) {
return new LoggerDecorator($logger, $container->get(MetricsService::class));
});
Remove a service from the container:
$container->unbind(ServiceInterface::class);
Remove contextual bindings:
// Remove all contextual bindings for UserController
$container->forgetWhen(UserController::class);
// Remove specific contextual binding
$container->forgetWhen(UserController::class, LoggerInterface::class);
The container provides a comprehensive exception hierarchy with detailed diagnostic information:
ServiceNotFoundException
: When a requested service cannot be foundServiceAlreadyBoundException
: When a service is already bound and override is not set to trueCircularDependencyException
: When circular dependencies are detectedAutowiringException
: When a dependency cannot be autowiredBindingException
: When a binding cannot be registeredInstantiationException
: When a class cannot be instantiatedResolutionException
: When a class cannot be resolvedInjectionException
: When property or method injection fails
All exceptions provide detailed context including dependency chains for easier debugging.
composer test
composer phpstan
composer phpcs
Please see CHANGELOG for version update information.
We welcome contributions to this package. Please see CONTRIBUTING for details.
If you discover a security vulnerability, please email security@wptechnix.com.
The MIT License (MIT). Please see License File for more information.
- WPTechnix - Creator and maintainer