Service Container
TinyMVC's service container is a powerful tool for managing class dependencies and performing dependency injection.
Basic Usage
Binding Dependencies
// Bind interface to implementation
container()->bind(LoggerInterface::class, FileLogger::class);
// Bind with closure
container()->bind('cache', function() {
return new RedisCache(config('redis'));
});
// Singleton binding (same instance each time)
container()->singleton(Database::class, function() {
return new Database(config('database'));
});
Resolving Dependencies
// Get instance from container
$logger = container()->get(LoggerInterface::class);
// Using helper function
$cache = get('cache');
// Automatic resolution
$userService = get(UserService::class);
Helper Functions
// Check if binding exists
if (has(LoggerInterface::class)) {
// ...
}
// Bind interface to implementation
bind(LoggerInterface::class, FileLogger::class);
// Singleton binding
singleton(Database::class, function() {
return new Database(config('database'));
});
// Get instance
$db = get(Database::class);
// or, use app helper function
$db = app(Database::class);
Dependency Injection
The container automatically resolves class dependencies:
class UserController
{
public function __construct(
private UserRepository $users,
private LoggerInterface $logger
) {}
}
// Controller will be automatically resolved with dependencies
$controller = get(UserController::class);
Method Injection
// Call class method with dependency injection
$result = container()->call('UserController@store', [
'request' => $specificRequest // Specific parameter
]);
// Call closure with dependency injection
container()->call(function(UserRepository $users, Request $request) {
// ...
});
Aliases
// Create alias
container()->alias('db', Database::class);
// Resolve using alias
$db = get('db');
Practical Examples
Database Service Setup
// In service provider
singleton(Connection::class, function() {
return new PDOConnection(config('database'));
});
// In controller
class UserController
{
public function __construct(private Connection $db) {}
public function index()
{
$users = $this->db->query('SELECT * FROM users');
// ...
}
}
Custom Logger Binding
// Bind interface to environment-specific implementation
if (config('app.env') === 'production') {
bind(LoggerInterface::class, CloudLogger::class);
} else {
bind(LoggerInterface::class, FileLogger::class);
}
// Resolved automatically
class OrderService
{
public function __construct(private LoggerInterface $logger) {}
}
Best Practices:
- Use interfaces for important dependencies
- Register bindings in service providers
- Use singleton for services that should maintain state
- Prefer constructor injection over direct container access
Troubleshooting
# Common issues:
# - "Class not found" → Check binding exists and class is autoloadable
# - "Cannot instantiate interface" → Forgot to bind implementation
# - "Circular dependency" → Two classes depend on each other
# Debug bindings
dd(container()->debug());
# Check if binding exists
if (has(LoggerInterface::class)) {
// ...
}