GitHub

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)) {
    // ...
}