Container API Reference¶
The Container class is the heart of Tusk's dependency injection system.
Class: Tusk\Core\Container\Container¶
Methods¶
set(string $id, callable $factory): void¶
Register a service factory in the container.
Parameters:
- $id (string) - Service identifier (typically the class name)
- $factory (callable) - Factory function that returns the service instance
Example:
use Tusk\Core\Container\Container;
$container = new Container();
// Register a simple service
$container->set(LoggerInterface::class, function() {
return new FileLogger('/var/log/app.log');
});
// Register with dependencies
$container->set(UserService::class, function(Container $c) {
return new UserService(
$c->get(UserRepository::class),
$c->get(LoggerInterface::class)
);
});
get(string $id): mixed¶
Resolve and retrieve a service from the container.
Parameters:
- $id (string) - Service identifier
Returns: The resolved service instance
Throws: ContainerException if service cannot be resolved
Example:
$logger = $container->get(LoggerInterface::class);
$logger->info('Application started');
has(string $id): bool¶
Check if a service is registered in the container.
Parameters:
- $id (string) - Service identifier
Returns: true if registered, false otherwise
Example:
if ($container->has(CacheInterface::class)) {
$cache = $container->get(CacheInterface::class);
} else {
// Use fallback
}
Auto-Wiring¶
The container automatically resolves constructor dependencies using reflection.
Example:
class UserController
{
public function __construct(
private UserRepository $repo,
private LoggerInterface $logger,
private EventDispatcher $events
) {}
}
// No manual registration needed!
$controller = $container->get(UserController::class);
// Container automatically injects all dependencies
Auto-Wiring Rules¶
- Type-hinted parameters are resolved by their type
- Interfaces must be explicitly bound to implementations
- Scalar values cannot be auto-wired (use factory functions)
- Circular dependencies will throw an exception
Singleton Pattern¶
Register services as singletons to reuse the same instance:
$container->set(DatabaseConnection::class, function() {
static $instance = null;
if ($instance === null) {
$instance = new DatabaseConnection('mysql:host=localhost');
}
return $instance;
});
// Or use a helper method (if implemented)
$container->singleton(DatabaseConnection::class, function() {
return new DatabaseConnection('mysql:host=localhost');
});
Service Providers¶
Organize service registration using service providers:
use Tusk\Core\ServiceProviderInterface;
class DatabaseServiceProvider implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->set(ConnectionInterface::class, function() {
return new PdoConnection(
dsn: getenv('DB_DSN'),
username: getenv('DB_USER'),
password: getenv('DB_PASS')
);
});
$container->set(UserRepository::class, function(Container $c) {
return new UserRepository($c->get(ConnectionInterface::class));
});
}
}
// Register the provider
$provider = new DatabaseServiceProvider();
$provider->register($container);
Advanced Patterns¶
Factory Pattern¶
$container->set(EmailServiceFactory::class, function() {
return new class {
public function create(string $type): EmailServiceInterface {
return match($type) {
'smtp' => new SmtpEmailService(),
'sendgrid' => new SendGridEmailService(),
default => throw new \InvalidArgumentException("Unknown type: $type")
};
}
};
});
$factory = $container->get(EmailServiceFactory::class);
$emailService = $factory->create('smtp');
Contextual Binding¶
// Different logger for different contexts
$container->set(LoggerInterface::class, function(Container $c) {
// Default logger
return new FileLogger('/var/log/app.log');
});
$container->set(AuditService::class, function(Container $c) {
// Audit service gets a special logger
return new AuditService(
new FileLogger('/var/log/audit.log')
);
});
Best Practices¶
- Bind interfaces, not implementations in your application code
- Use service providers to organize related services
- Avoid service locator pattern - inject dependencies via constructor
- Register services early in your bootstrap process
- Use singletons sparingly - only for stateless or expensive-to-create services
Complete Example¶
<?php
// bootstrap.php
use Tusk\Core\Container\Container;
$container = new Container();
// Configuration
$container->set('config', fn() => require __DIR__ . '/config.php');
// Database
$container->set(ConnectionInterface::class, function(Container $c) {
$config = $c->get('config');
return new PdoConnection(
$config['db']['dsn'],
$config['db']['user'],
$config['db']['pass']
);
});
// Repositories
$container->set(UserRepository::class, function(Container $c) {
return new UserRepository($c->get(ConnectionInterface::class));
});
// Services
$container->set(AuthService::class, function(Container $c) {
return new AuthService(
$c->get(UserRepository::class),
$c->get(LoggerInterface::class)
);
});
// Controllers (auto-wired)
// No need to register - container will auto-wire them!
return $container;