PHP PSR-11 Container Interface — Complete Guide to Dependency Injection
In this tutorial, you will learn about PHP PSR. We cover key concepts, practical examples, and best practices to help you master this topic.
PHP PSR-11 ContainerInterface provides a standard get() and has() interface for dependency injection containers, enabling framework-agnostic service location and dependency resolution.
What You'll Learn
By the end of this tutorial, you'll implement PSR-11 containers, use service providers, configure auto-wiring, understand service definition patterns, and integrate containers with frameworks.
Why PSR-11 Matters
Before PSR-11, every framework had its own container interface. PSR-11 enables portable libraries that request dependencies from any compatible container.
Real-World Use
A PHP package for file uploads requires a PSR-11 container to resolve its dependencies (logger, storage adapter). It works with Symfony DI, PHP-DI, Laravel container, or any PSR-11 container.
PSR-11 Path
flowchart LR
A[PSR-7 HTTP] --> B[PSR-11 Container]
B --> C[PSR-14 Events]
B --> D[PSR-15 Middleware]
C --> E[PSR-17 HTTP Factory]
B --> F{You Are Here}
style F fill:#f90,color:#fff
ContainerInterface Basics
PSR-11 defines two methods: get($id) and has($id).
<?php
use Psr\Container\ContainerInterface;
class Application {
public function __construct(
private ContainerInterface $container
) {}
public function handleRequest(): void {
if ($this->container->has(UserService::class)) {
$service = $this->container->get(UserService::class);
$service->listUsers();
}
}
}
Simple Container Implementation
A basic PSR-11 container using service definitions.
<?php
use Psr\Container\ContainerInterface;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class SimpleContainer implements ContainerInterface {
private array $services = [];
private array $instances = [];
public function set(string $id, callable $factory): void {
$this->services[$id] = $factory;
}
public function get(string $id): mixed {
if (!$this->has($id)) {
throw new class("Service {$id} not found") extends \RuntimeException implements NotFoundExceptionInterface {};
}
if (!isset($this->instances[$id])) {
$this->instances[$id] = ($this->services[$id])($this);
}
return $this->instances[$id];
}
public function has(string $id): bool {
return isset($this->services[$id]);
}
}
Auto-Wiring Container
An auto-wiring container resolves dependencies through reflection.
<?php
class AutoWiringContainer implements ContainerInterface {
private array $instances = [];
private array $definitions = [];
public function set(string $id, mixed $definition): void {
$this->definitions[$id] = $definition;
}
public function get(string $id): mixed {
if (!isset($this->instances[$id])) {
$this->instances[$id] = $this->resolve($id);
}
return $this->instances[$id];
}
public function has(string $id): bool {
return class_exists($id) || isset($this->definitions[$id]);
}
private function resolve(string $id): mixed {
if (isset($this->definitions[$id])) {
$definition = $this->definitions[$id];
return is_callable($definition) ? $definition($this) : $definition;
}
$ref = new ReflectionClass($id);
$constructor = $ref->getConstructor();
if (!$constructor) return $ref->newInstance();
$params = array_map(fn($p) => $this->get($p->getType()->getName()), $constructor->getParameters());
return $ref->newInstanceArgs($params);
}
}
Service Providers
Organize container configuration into service providers.
<?php
interface ServiceProviderInterface {
public function register(ContainerInterface $container): void;
}
class LoggerServiceProvider implements ServiceProviderInterface {
public function register(ContainerInterface $container): void {
$container->set(LoggerInterface::class, function ($c) {
return new Monolog\Logger("app", [new Monolog\Handler\StreamHandler("/var/log/app.log")]);
});
}
}
class DatabaseServiceProvider implements ServiceProviderInterface {
public function register(ContainerInterface $container): void {
$container->set(PDO::class, function ($c) {
return new PDO(getenv("DB_DSN"), getenv("DB_USER"), getenv("DB_PASS"));
});
}
}
Container Interop with Frameworks
PSR-11 containers work with Symfony, Laravel, and Slim.
<?php
// Symfony DI already implements ContainerInterface
use Symfony\Component\DependencyInjection\ContainerBuilder;
$container = new ContainerBuilder();
$container->autowire(UserService::class);
$service = $container->get(UserService::class);
Common Mistakes
1. Using get() Without Checking has()
Calling get() on a non-existent service throws NotFoundException. Always check has() first.
2. Storing Container as a Service Locator
Inject ContainerInterface only in service providers. Inject specific dependencies in application code.
3. Forgetting Shared vs Non-Shared Services
get() typically returns the same instance each time. For new instances, add a Factory method.
4. Circular Dependencies
A depends on B, B depends on A causes infinite Recursion. Detect and report circular references.
5. Not Catching ContainerExceptionInterface
Container operations can throw exceptions. Catch container-specific exceptions for error handling.
Practice Questions
1. What two methods does PSR-11 define?
get($id) returns a service, has($id) checks if a service is registered.
2. What exceptions can get() throw?
NotFoundExceptionInterface if the service is not found. ContainerExceptionInterface for other errors.
3. What is auto-wiring?
Automatic dependency resolution through reflection on constructor parameter types.
4. Should you inject the container or specific dependencies?
Inject specific dependencies. The container should only be used in configuration and Bootstrap code.
5. Challenge: Implement a PSR-11 container with factory support.
<?php
use Psr\Container\ContainerInterface;
class FactoryContainer implements ContainerInterface {
private array $factories = [];
private array $instances = [];
public function factory(string $id, callable $factory): void {
$this->factories[$id] = $factory;
}
public function get(string $id): mixed {
if (!isset($this->instances[$id])) {
if (!$this->has($id)) throw new \RuntimeException("Not found: {$id}");
$this->instances[$id] = ($this->factories[$id])($this);
}
return $this->instances[$id];
}
public function has(string $id): bool {
return isset($this->factories[$id]);
}
}
FAQ
Mini Project: Configuration-Aware Container
Build a container that reads service definitions from a PHP config file.
<?php
use Psr\Container\ContainerInterface;
class ConfigContainer implements ContainerInterface {
private array $services = [];
private array $instances = [];
public function __construct(string $configFile) {
$definitions = require $configFile;
foreach ($definitions as $id => $definition) {
$this->services[$id] = $definition;
}
}
public function get(string $id): mixed {
if (!isset($this->instances[$id])) {
$definition = $this->services[$id] ?? throw new \RuntimeException("Service {$id} not found");
$this->instances[$id] = is_callable($definition) ? $definition($this) : $definition;
}
return $this->instances[$id];
}
public function has(string $id): bool {
return isset($this->services[$id]);
}
}
What's Next
PHP PSR-14 Event Dispatcher PHP PSR-15 Middleware PHP PSR-17 HTTP Factory
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro