https://github.com/gacela-project/container
A minimalistic container dependency.
https://github.com/gacela-project/container
container dependency-resolver gacela php resolver
Last synced: 28 days ago
JSON representation
A minimalistic container dependency.
- Host: GitHub
- URL: https://github.com/gacela-project/container
- Owner: gacela-project
- License: other
- Created: 2023-04-10T11:14:51.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2025-11-08T22:43:20.000Z (4 months ago)
- Last Synced: 2026-01-11T14:44:01.460Z (about 2 months ago)
- Topics: container, dependency-resolver, gacela, php, resolver
- Language: PHP
- Homepage: https://packagist.org/packages/gacela-project/container
- Size: 163 KB
- Stars: 10
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: .github/CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: .github/CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# Gacela Container
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
```bash
composer require gacela-project/container
```
## Quick Start
### Basic Usage
```php
use Gacela\Container\Container;
// Simple auto-wiring
$container = new Container();
$instance = $container->get(YourClass::class);
```
### With Bindings
Map interfaces to concrete implementations:
```php
$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:
```php
// 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:
```php
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:
```php
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:
```php
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
```php
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:
```php
$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):
```php
$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:
```php
$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:
```php
$result = $container->resolve(function (LoggerInterface $logger, CacheInterface $cache) {
$logger->info('Cache cleared');
return $cache->clear();
});
```
### Service Introspection
Debug and inspect container state:
```php
// 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:
```php
// 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:
```php
// 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
```php
// Quick instantiation without container setup
$instance = Container::create(YourClass::class);
```
## Best Practices
### 1. Use Constructor Injection
```php
// Good
class UserController {
public function __construct(
private UserService $userService,
private LoggerInterface $logger
) {}
}
// Avoid setter injection (not supported)
```
### 2. Always Use Type Hints
```php
// Good - type hint required
public function __construct(LoggerInterface $logger) {}
// Bad - will throw exception
public function __construct($logger) {}
```
### 3. Provide Default Values for Scalars
```php
// 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
```php
// Always bind interfaces to implementations
$bindings = [
LoggerInterface::class => FileLogger::class,
CacheInterface::class => RedisCache::class,
];
```
### 5. Warm Up in Production
```php
// 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
```bash
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](https://github.com/gacela-project/gacela/blob/main/src/Framework/ClassResolver/AbstractClassResolver.php#L142)
## License
MIT License. See [LICENSE](LICENSE) file for details.