Compose ships with a lightweight dependency injection container designed specifically for the framework. It implements Psr\Container\ContainerInterface
, supports autowiring through reflection, and integrates tightly with the middleware pipeline.
The container is created in Compose\Starter::init()
and seeded with:
Compose\Support\Configuration
).config['services']
.Compose\Config
.Retrieve the container during bootstrapping by using the optional callback:
use Compose\Http\Pipeline;
use Compose\Starter;
use Psr\Container\ContainerInterface;
Starter::start($config, function (ContainerInterface $container, Pipeline $pipeline): void {
$logger = $container->get(Psr\Log\LoggerInterface::class);
// ...
});
Register services by updating the services
configuration key. Supported definitions:
Definition Type | Example | Notes |
---|---|---|
Class name | Compose\Http\OutputBufferMiddleware::class |
Instantiated lazily and treated as a shared singleton. |
Callable factory | static fn ($container) => new Logger('app') |
Receives the container and service id. |
Object instance | new Monolog\Logger('app') |
Stored exactly as provided. |
Alias | SomeInterface::class => SomeImplementation::class |
Resolves the alias when requested. |
Use setMany()
for programmatic registration:
$container = new Compose\Container\ServiceContainer();
$container->setMany([
Psr\Log\LoggerInterface::class => App\LoggerFactory::class,
App\Service\Emailer::class,
]);
Classes that implement Compose\Container\ResolvableInterface
opt into autowiring. When the container needs to instantiate such a class, it delegates to Compose\Container\ServiceResolver
, which:
If the container cannot resolve a required dependency, it throws a descriptive Compose\Container\ContainerException
.
For complex services that need full control over instantiation, implement Compose\Container\ServiceFactoryInterface
:
final class CacheFactory implements Compose\Container\ServiceFactoryInterface
{
public static function create(ContainerInterface $container, string $id): object
{
return new App\Cache(
$container->get(Redis::class),
ttl: (int) $container->get(Configuration::class)->getNestedValue('cache.ttl', 60)
);
}
}
Register the factory in configuration:
'services' => [
App\Cache::class => App\Factory\CacheFactory::class,
];
ServiceResolver
also supports invoking arbitrary callables via invoke()
by resolving dependencies from type hints. This is used internally by the Pages middleware but is available for your own factories or listeners.
$resolver = $container->get(Compose\Container\ServiceResolver::class);
$resolver->invoke(static function (Psr\Log\LoggerInterface $logger) {
$logger->info('Invoked via resolver');
});
Services that need the container instance can implement Compose\Container\ContainerAwareInterface
. The resolver automatically calls setContainer()
after instantiation.
final class ReportGenerator implements ContainerAwareInterface, ResolvableInterface
{
use Compose\Container\ContainerAwareTrait;
public function generate(): array
{
$config = $this->getContainer()->get(Compose\Support\Configuration::class);
// ...
}
}
ServiceContainer
wraps resolution errors in descriptive exceptions that include the service id and parameter name. When debugging resolution issues:
ResolvableInterface
.Enable the debug
flag in configuration to get additional stack traces through the error handler.