PSR-11: Container Interface in PHP
PSR-11 defines a common interface for dependency injection containers in PHP. This standardization allows libraries to retrieve services from any container implementation, promoting better interoperability between different frameworks and libraries.
Understanding Dependency Injection Containers
A dependency injection container (DIC) is responsible for:
- Managing service definitions
- Creating service instances
- Resolving dependencies
- Managing object lifecycle
The Container Interface
<?php
namespace JonesRussell\PhpFigGuide\PSR11;
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id
* @return mixed
* @throws NotFoundExceptionInterface
* @throws ContainerExceptionInterface
*/
public function get($id);
/**
* Returns true if the container can return an entry for the given identifier.
*
* @param string $id
* @return bool
*/
public function has($id);
}
Basic Implementation
Here’s a simple implementation of a dependency injection container that adheres to PSR-11:
<?php
namespace JonesRussell\PhpFigGuide\PSR11;
use JonesRussell\PhpFigGuide\PSR11\ContainerInterface;
use JonesRussell\PhpFigGuide\PSR11\NotFoundExceptionInterface;
use JonesRussell\PhpFigGuide\PSR11\ContainerExceptionInterface;
class SimpleContainer implements ContainerInterface
{
private array $services = [];
public function set(string $id, $service): void
{
$this->services[$id] = $service;
}
public function get($id)
{
if (!$this->has($id)) {
throw new class extends \Exception implements NotFoundExceptionInterface {};
}
return $this->services[$id];
}
public function has($id): bool
{
return isset($this->services[$id]);
}
}
// Example usage
$container = new SimpleContainer();
$container->set('database', new DatabaseConnection());
$database = $container->get('database');
Advanced Usage of ExampleContainer
1. Registering Services with Dependencies
You can register services that depend on other services. For example, if you have a UserService
that requires a DatabaseConnection
, you can set it up like this:
<?php
class DatabaseConnection {
public function connect() {
return "Database connected!";
}
}
class UserService {
private DatabaseConnection $db;
public function __construct(DatabaseConnection $db) {
$this->db = $db;
}
public function getUser() {
return "User data from " . $this->db->connect();
}
}
// Example usage
$container = new SimpleContainer();
$container->set('database', new DatabaseConnection());
$container->set('userService', new UserService($container->get('database')));
$userService = $container->get('userService');
echo $userService->getUser(); // Output: User data from Database connected!
2. Using Factory Functions
You can also register services using factory functions, which allows for more complex instantiation logic:
<?php
$container->set('userService', function (ContainerInterface $c) {
return new UserService($c->get('database'));
});
// Example usage
$userService = $container->get('userService');
echo $userService->getUser(); // Output: User data from Database connected!
Real-World Usage
1. Service Registration
<?php
// Define services
$container = new Container();
// Simple value
$container->set('api.key', 'secret-key-123');
// Factory function
$container->set('database', function (ContainerInterface $container) {
return new PDO(
$container->get('db.dsn'),
$container->get('db.user'),
$container->get('db.pass')
);
});
// Service with dependencies
$container->set('userRepository', function (ContainerInterface $container) {
return new UserRepository($container->get('database'));
});
2. Service Retrieval
<?php
class UserController
{
private UserRepository $users;
public function __construct(ContainerInterface $container)
{
$this->users = $container->get('userRepository');
}
}
Framework Integration
1. Laravel Example
<?php
use Illuminate\Container\Container;
use JonesRussell\PhpFigGuide\PSR11\ContainerInterface;
class ServiceProvider extends \Illuminate\Support\ServiceProvider
{
public function register()
{
$this->app->bind(ContainerInterface::class, function ($app) {
return new class($app) implements ContainerInterface {
private $app;
public function __construct($app)
{
$this->app = $app;
}
public function get($id)
{
return $this->app->make($id);
}
public function has($id): bool
{
return $this->app->bound($id);
}
};
});
}
}
Best Practices
- Service Resolution
// Bad - Service locator pattern
class UserService
{
public function __construct(private ContainerInterface $container) {}
public function doSomething()
{
$dep = $this->container->get('some.service');
}
}
// Good - Explicit dependency injection
class UserService
{
public function __construct(
private SomeServiceInterface $someService
) {}
}
- Container Configuration
// Bad - Runtime service definition
if ($condition) {
$container->set('service', new ServiceA());
} else {
$container->set('service', new ServiceB());
}
// Good - Configuration-driven definition
$container->set('service', function (ContainerInterface $c) {
return $c->get('config')->get('use_service_a')
? new ServiceA()
: new ServiceB();
});
Next Steps
In our next post, we’ll explore PSR-14, which defines a standard event dispatcher interface. Check out our example repository for the implementation of these standards.
Resources
Baamaapii 👋