Service Container
The Zephyr Service Container is a powerful dependency injection container that manages class dependencies and performs dependency injection automatically.
Introduction
The container is PSR-11 compliant and provides features like automatic dependency resolution (autowiring), service tagging, lazy loading, and more.
Basic Usage
Getting the Container
use ZephyrPHP\Container\Container;
// Get singleton instance
$container = Container::getInstance();
// Or use helper function
$container = container();
Resolving Services
// PSR-11 compliant
$userService = $container->get(UserService::class);
// With parameters
$report = $container->make(ReportGenerator::class, ['format' => 'pdf']);
// Using helper functions
$service = resolve(UserService::class);
$app = app(UserService::class);
Method Injection
$result = $container->call([UserController::class, 'index']);
$result = $container->call('UserController@show', ['id' => 123]);
Binding Types
bind() - Transient
Creates a new instance each time (unless cached by the underlying container).
$container->bind(LoggerInterface::class, FileLogger::class);
// With closure
$container->bind(LoggerInterface::class, function ($container) {
return new FileLogger('/var/log/app.log');
});
singleton() - Shared Instance
Returns the same instance every time.
$container->singleton(DatabaseConnection::class, function ($container) {
return new DatabaseConnection(
host: env('DB_HOST'),
database: env('DB_DATABASE')
);
});
// Using helper
singleton(CacheInterface::class, RedisCache::class);
factory() - Always New
Always creates a fresh instance, never cached.
$container->factory(RequestId::class, function () {
return new RequestId(uniqid('req_', true));
});
scoped() - Request Singleton
Singleton within the current scope/request, cleared at scope end.
$container->scoped(AuthenticatedUser::class, function ($container) {
return User::fromSession();
});
// Clear at end of request
$container->clearScopedInstances();
instance() - Existing Object
Register an existing instance.
$config = new Config(['debug' => true]);
$container->instance(Config::class, $config);
Conditional Binding
// Only bind if not already bound
$container->bindIf(LoggerInterface::class, FileLogger::class);
$container->singletonIf(CacheInterface::class, ArrayCache::class);
Aliases
Create shortcuts to services.
$container->singleton(ZephyrPHP\Log\LogManager::class, LogManager::class);
$container->alias('logger', ZephyrPHP\Log\LogManager::class);
$container->alias('log', 'logger'); // Chained aliases work
// Resolve the alias chain
$source = $container->getAlias('log'); // Returns ZephyrPHP\Log\LogManager::class
// Check if alias
$container->isAlias('logger'); // true
Tagging
Group related services together.
$container->singleton(EmailNotifier::class);
$container->singleton(SmsNotifier::class);
$container->singleton(PushNotifier::class);
$container->tag([
EmailNotifier::class,
SmsNotifier::class,
PushNotifier::class,
], 'notifiers');
// Get all tagged services (resolved)
$notifiers = $container->tagged('notifiers');
// Get tag members without resolving
$members = $container->getTagged('notifiers');
Contextual Bindings
Different implementations based on the consuming class.
$container->when(PhotoController::class)
->needs(FileSystem::class)
->give(LocalFileSystem::class);
$container->when(VideoController::class)
->needs(FileSystem::class)
->give(S3FileSystem::class);
Environment Bindings
Different bindings per environment.
// Set the environment
$container->setEnvironment(env('APP_ENV', 'production'));
// Define environment-specific bindings
$container->whenEnvironment('production')
->singleton(CacheInterface::class, RedisCache::class);
$container->whenEnvironment('testing')
->singleton(CacheInterface::class, ArrayCache::class);
$container->whenEnvironment('development')
->singleton(CacheInterface::class, FileCache::class);
Lazy Loading
Delay instantiation until first use.
$container->lazy(ExpensiveService::class, function ($container) {
// This only runs when you actually use the service
return new ExpensiveService(
$container->get(DatabaseConnection::class)
);
});
// Get the proxy (instant - no instantiation yet)
$service = $container->get(ExpensiveService::class);
// Now it instantiates
$service->doSomething();
Decorators
Wrap services with additional functionality.
$container->singleton(LoggerInterface::class, FileLogger::class);
// Add decorators with priority (higher = outer)
$container->decorate(LoggerInterface::class, CachingLogger::class, priority: 10);
$container->decorate(LoggerInterface::class, AsyncLogger::class, priority: 20);
// Result: AsyncLogger -> CachingLogger -> FileLogger
// Using closures
$container->decorate(LoggerInterface::class, function ($logger, $container) {
return new MetricsLogger($logger, $container->get(Metrics::class));
});
Resolution Middleware
Add middleware that runs during service resolution.
$container->addResolutionMiddleware(function ($abstract, $next, $container) {
$start = microtime(true);
$result = $next();
$time = microtime(true) - $start;
if ($time > 0.1) {
Log::warning("Slow resolution: {$abstract} took {$time}s");
}
return $result;
});
Service Locator
Create a container with limited service access.
$locator = $container->createServiceLocator([
LoggerInterface::class,
CacheInterface::class,
EventDispatcher::class,
]);
// Use in controller
class UserController
{
public function __construct(private ServiceLocator $services) {}
public function index()
{
$logger = $this->services->get(LoggerInterface::class); // Works
$db = $this->services->get(DatabaseInterface::class); // Throws - not in locator
}
}
Resolving Events
Hooks that fire during resolution.
// Before resolving
$container->beforeResolving(ExpensiveService::class, function ($abstract, $params, $container) {
Log::info("About to resolve: {$abstract}");
});
// After resolving
$container->afterResolving(LoggerInterface::class, function ($logger, $container) {
$logger->setChannel('app');
});
// Global hooks (all services)
$container->beforeResolving(function ($abstract, $params, $container) {
// Runs for ALL resolutions
});
$container->afterResolving(function ($object, $container) {
// Runs for ALL resolutions
});
Service Providers
Basic Provider
<?php
namespace App\Providers;
use ZephyrPHP\Container\ServiceProvider;
use ZephyrPHP\Container\Container;
class AppServiceProvider extends ServiceProvider
{
public function register(Container $container): void
{
$container->singleton(CacheInterface::class, RedisCache::class);
}
public function boot(Container $container): void
{
// After all providers registered
}
}
Deferred Provider
Only loaded when its services are needed.
<?php
namespace App\Providers;
use ZephyrPHP\Container\DeferredServiceProvider;
use ZephyrPHP\Container\Container;
class MailServiceProvider extends DeferredServiceProvider
{
public function provides(): array
{
return [MailerInterface::class, 'mailer'];
}
public function register(Container $container): void
{
$container->singleton(MailerInterface::class, SmtpMailer::class);
$container->alias('mailer', MailerInterface::class);
}
}
Validation & Debugging
Validate Bindings
$errors = $container->validate();
if (!empty($errors)) {
foreach ($errors as $abstract => $error) {
echo "Error in {$abstract}: {$error}\n";
}
}
Get Binding Definition
$info = $container->getDefinition(LoggerInterface::class);
// Returns:
// [
// 'abstract' => 'LoggerInterface',
// 'type' => 'singleton',
// 'concrete' => FileLogger::class,
// 'resolved' => false,
// 'aliases' => ['logger', 'log'],
// 'tags' => ['logging'],
// 'decorators' => [...],
// ]
Dump Container State
$state = $container->dump();
// Returns all bindings, singletons, factories, aliases, tags, etc.
Production Optimization
Compilation
if (env('APP_ENV') === 'production') {
$container->enableCompilation(storage_path('cache/container'));
}
// Check status
if ($container->isCompiled()) {
echo "Container is compiled";
}
Helper Functions
container(); // Get container
app(); // Get application
app(UserService::class); // Resolve service
resolve(UserService::class); // Resolve service
singleton($abstract, $concrete); // Register singleton
bind($abstract, $concrete); // Register binding
tagged('notifiers'); // Get tagged services
API Reference
Binding Methods
| Method | Description |
|---|---|
bind($abstract, $concrete) | Transient binding |
singleton($abstract, $concrete) | Shared singleton |
factory($abstract, $concrete) | Always new instance |
scoped($abstract, $concrete) | Request-scoped singleton |
lazy($abstract, $concrete) | Delayed instantiation |
instance($abstract, $instance) | Register existing object |
Resolution Methods
| Method | Description |
|---|---|
get($id) | PSR-11 resolve |
has($id) | PSR-11 check |
make($abstract, $params) | Resolve with parameters |
call($callable, $params) | Call with DI |
Organization Methods
| Method | Description |
|---|---|
alias($alias, $abstract) | Create alias |
tag($abstracts, $tag) | Tag services |
tagged($tag) | Get tagged services |
decorate($abstract, $decorator) | Add decorator |
Learn about Helper Functions for container shortcuts, Middleware for request filtering, and Controllers which use the container for dependency injection.