gacela-project / container
A minimalistic container dependency resolver
Fund package maintenance!
chemaclass.com/sponsor
Installs: 62 247
Dependents: 2
Suggesters: 0
Security: 0
Stars: 9
Watchers: 1
Forks: 0
pkg:composer/gacela-project/container
Requires
- php: >=8.1
- psr/container: >=1.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.75
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^10.5
- psalm/plugin-phpunit: ^0.18
- symfony/var-dumper: ^5.4
- vimeo/psalm: ^5.26
This package is auto-updated.
Last update: 2025-11-08 22:43:23 UTC
README
A minimalistic, PSR-11 compliant dependency injection container with automatic constructor injection and zero configuration.
Features
- 🚀 Zero Configuration: Automatic constructor injection without verbose setup
- 🔄 Circular Dependency Detection: Clear error messages when dependencies form a loop
- 📦 PSR-11 Compliant: Standard container interface for interoperability
- ⚡ Performance Optimized: Built-in caching and warmup capabilities
- 🔍 Introspection: Debug and inspect container state easily
- 🎯 Type Safe: Requires type hints for reliable dependency resolution
- 🏷️ PHP 8 Attributes: Declarative configuration with #[Inject], #[Singleton], and #[Factory]
Installation
composer require gacela-project/container
Quick Start
Basic Usage
use Gacela\Container\Container; // Simple auto-wiring $container = new Container(); $instance = $container->get(YourClass::class);
With Bindings
Map interfaces to concrete implementations:
$bindings = [ LoggerInterface::class => FileLogger::class, CacheInterface::class => new RedisCache('localhost'), ConfigInterface::class => fn() => loadConfig(), ]; $container = new Container($bindings); $logger = $container->get(LoggerInterface::class); // Returns FileLogger
Contextual Bindings
Different implementations based on which class needs them:
// UserController gets FileLogger, AdminController gets DatabaseLogger $container->when(UserController::class) ->needs(LoggerInterface::class) ->give(FileLogger::class); $container->when(AdminController::class) ->needs(LoggerInterface::class) ->give(DatabaseLogger::class); // Multiple classes can share the same contextual binding $container->when([ServiceA::class, ServiceB::class]) ->needs(CacheInterface::class) ->give(RedisCache::class);
PHP 8 Attributes
Use attributes for declarative dependency configuration:
#[Inject] - Specify Implementation
Override type hints to inject specific implementations:
use Gacela\Container\Attribute\Inject; class NotificationService { public function __construct( #[Inject(EmailLogger::class)] private LoggerInterface $logger, ) {} } // EmailLogger will be injected even if LoggerInterface is bound to FileLogger $service = $container->get(NotificationService::class);
#[Singleton] - Single Instance
Mark a class to be instantiated only once:
use Gacela\Container\Attribute\Singleton; #[Singleton] class DatabaseConnection { public function __construct(private string $dsn) {} } $conn1 = $container->get(DatabaseConnection::class); $conn2 = $container->get(DatabaseConnection::class); // $conn1 === $conn2 (same instance)
#[Factory] - New Instances
Always create fresh instances:
use Gacela\Container\Attribute\Factory; #[Factory] class RequestContext { public function __construct(private LoggerInterface $logger) {} } $ctx1 = $container->get(RequestContext::class); $ctx2 = $container->get(RequestContext::class); // $ctx1 !== $ctx2 (different instances)
Performance Note: Attribute checks are cached internally, so repeated instantiations of the same class avoid expensive reflection operations, providing 15-20% performance improvement.
How It Works
The container automatically resolves dependencies based on type hints:
- Primitive types: Uses default values (must be provided)
- Classes: Instantiates and resolves dependencies recursively
- Interfaces: Resolves using bindings defined in the container
Example
class UserService { public function __construct( private UserRepository $repository, private LoggerInterface $logger, ) {} } class UserRepository { public function __construct(private PDO $pdo) {} } // Setup $bindings = [ LoggerInterface::class => FileLogger::class, PDO::class => new PDO('mysql:host=localhost;dbname=app', 'user', 'pass'), ]; $container = new Container($bindings); // Auto-resolves UserService -> UserRepository -> PDO $service = $container->get(UserService::class);
Advanced Features
Factory Services
Create new instances on every call:
$factory = $container->factory(fn() => new TempFile()); $container->set('temp_file', $factory); $file1 = $container->get('temp_file'); // New instance $file2 = $container->get('temp_file'); // Different instance
Extending Services
Wrap or modify services (even before they're created):
$container->set('logger', fn() => new FileLogger('/var/log/app.log')); $container->extend('logger', function ($logger, $container) { return new LoggerDecorator($logger); });
Protecting Closures
Prevent closures from being executed:
$closure = fn() => 'Hello World'; $container->set('greeting', $container->protect($closure)); $result = $container->get('greeting'); // Returns the closure itself
Resolving Callables
Automatically inject dependencies into any callable:
$result = $container->resolve(function (LoggerInterface $logger, CacheInterface $cache) { $logger->info('Cache cleared'); return $cache->clear(); });
Service Introspection
Debug and inspect container state:
// Get all registered service IDs $services = $container->getRegisteredServices(); // Check if service is a factory if ($container->isFactory('temp_file')) { // Returns new instance each time } // Check if service is frozen (accessed) if ($container->isFrozen('logger')) { // Cannot be modified anymore } // Get all bindings $bindings = $container->getBindings(); // Get comprehensive statistics $stats = $container->getStats(); /* [ 'registered_services' => 42, 'frozen_services' => 15, 'factory_services' => 3, 'bindings' => 8, 'cached_dependencies' => 25, 'memory_usage' => '2.34 MB' ] */
Performance Optimization
Pre-resolve dependencies for faster runtime:
// During application bootstrap $container->warmUp([ UserService::class, OrderService::class, PaymentProcessor::class, ]); // Later requests benefit from cached dependency resolution $service = $container->get(UserService::class); // Faster!
Service Aliasing
Create multiple names for the same service:
// Create an alias $container->alias('db', PDO::class); // Access via alias or original name $db1 = $container->get('db'); // Same instance $db2 = $container->get(PDO::class); // Same instance // Alias resolution is cached for optimal performance
API Reference
Container Methods
| Method | Description |
|---|---|
get(string $id): mixed |
Retrieve or create a service |
has(string $id): bool |
Check if service exists |
set(string $id, mixed $instance): void |
Register a service |
remove(string $id): void |
Remove a service |
resolve(callable $callable): mixed |
Execute callable with dependency injection |
factory(Closure $instance): Closure |
Mark service as factory (new instance each time) |
extend(string $id, Closure $instance): Closure |
Wrap/modify a service |
protect(Closure $instance): Closure |
Prevent closure execution |
getRegisteredServices(): array |
Get all service IDs |
isFactory(string $id): bool |
Check if service is a factory |
isFrozen(string $id): bool |
Check if service is frozen |
getBindings(): array |
Get all bindings |
warmUp(array $classNames): void |
Pre-resolve dependencies |
alias(string $alias, string $id): void |
Create an alias for a service (with caching) |
getStats(): array |
Get container statistics for debugging and performance monitoring |
when(string|array $concrete): ContextualBindingBuilder |
Define contextual bindings for specific classes |
Static Methods
// Quick instantiation without container setup $instance = Container::create(YourClass::class);
Best Practices
1. Use Constructor Injection
// Good class UserController { public function __construct( private UserService $userService, private LoggerInterface $logger ) {} } // Avoid setter injection (not supported)
2. Always Use Type Hints
// Good - type hint required public function __construct(LoggerInterface $logger) {} // Bad - will throw exception public function __construct($logger) {}
3. Provide Default Values for Scalars
// Good public function __construct( UserRepository $repo, int $maxRetries = 3, string $env = 'production' ) {} // Bad - scalars without defaults cannot be resolved public function __construct(string $apiKey) {} // Exception!
4. Use Bindings for Interfaces
// Always bind interfaces to implementations $bindings = [ LoggerInterface::class => FileLogger::class, CacheInterface::class => RedisCache::class, ];
5. Warm Up in Production
// In your bootstrap file $container->warmUp([ // List frequently used services UserService::class, AuthService::class, Router::class, ]);
Error Handling
The container provides clear, actionable error messages with helpful suggestions:
Missing Type Hint
No type hint found for parameter '$logger'.
Type hints are required for dependency injection to work properly.
Add a type hint to the parameter, for example:
public function __construct(YourClass $logger) { ... }
Circular Dependency
Circular dependency detected: ClassA -> ClassB -> ClassC -> ClassA
This happens when classes depend on each other in a loop.
Consider using setter injection or the factory pattern to break the cycle.
Unresolvable Scalar
Unable to resolve parameter of type 'string' in 'UserService'.
Scalar types (string, int, float, bool, array) cannot be auto-resolved.
Provide a default value for the parameter:
public function __construct(string $param = 'default') { ... }
Service Not Found (with suggestions)
No concrete class was found that implements:
"App\LogerInterface"
Did you forget to bind this interface to a concrete class?
Did you mean one of these?
- App\LoggerInterface
- App\Service\LoggerInterface
You might find some help here: https://gacela-project.com/docs/bootstrap/#bindings
Requirements
- PHP >= 8.1
- PSR-11 Container Interface
Testing
composer test # Run tests composer quality # Run static analysis composer test-coverage # Generate coverage report
Real-World Example
See how it's used in the Gacela Framework
License
MIT License. See LICENSE file for details.