PSR-11 w praktyce: Container Interface w PHP
Standard PSR-11 definiuje jednolity interfejs dla kontenerów zależności w PHP, dzięki czemu biblioteki i frameworki mogą wymieniać komponenty bez wiązania z konkretną implementacją kontenera. W Twoim projekcie kontener zapewnia zarówno basic DI, jak i sensowny poziom autowiringu.
Czym jest Dependency Injection?
Dependency Injection (DI) to zaawansowana technika programowania obiektowego, która rewolucjonizuje sposób projektowania oprogramowania. To potężne narzędzie pozwalające na bardziej elastyczne i modularne podejście do tworzenia aplikacji w PHP.
Dlaczego PSR-11?
- Spójny kontrakt na pobieranie zależności (
get
/has
), - Łatwiejsza integracja bibliotek i modułów aplikacji,
- Elastyczna konfiguracja instancji i bindings,
- Możliwość łączenia z innymi PSR (np. PSR-15, PSR-7, PSR-3).
Kluczowe mechanizmy Dependency Injection
Podstawowe zasady
- Dostarczanie zależności z zewnątrz
- Luźne powiązanie komponentów
- Dynamiczna wymiana implementacji
Korzyści stosowania
- Wysoka skalowalność kodu
- Uproszczone testowanie jednostkowe
- Lepsza organizacja struktury aplikacji
- Zwiększona czytelność rozwiązań
Praktyczny przykład implementacji
<?php
class UserService {
private DatabaseInterface $database;
public function __construct(DatabaseInterface $database) {
$this->database = $database;
}
public function saveUser(User $user): void {
$this->database->save($user);
}
}
Dlaczego warto poznać Dependency Injection?
- Eliminacja sztywnych zależności między klasami
- Możliwość stosowania Test Driven Development
- Łatwiejsze mockowanie w testach
- Przejrzysty i czysty kod
Kluczowe klasy i wzorce projektowe
1) ContainerContract
Rozszerza Psr\Container\ContainerInterface
o metody do rejestracji usług, definicji i bindowania wartości
(np. klucze → skalarne ustawienia). Taki „wzbogacony kontrakt” ułatwia rozbudowę kontenera bez łamania PSR-11.
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Container\Contracts;
use DJWeb\Framework\Container\Definition;
/**
* The ContainerContract defines the methods that a service container must implement.
* It extends the PSR-11 ContainerContract and provides additional methods for setting
* services, adding definitions, and registering service providers.
*/
interface ContainerContract extends \Psr\Container\ContainerInterface
{
public function set(string $key, mixed $value): ContainerContract;
public function register(
ServiceProviderContract $provider
): ContainerContract;
public function bind(string $key, string|int|float|bool|null $value): void;
public function getBinding(string $key): string|int|float|bool|null;
}
Interfejs przewiduje m.in. set()
, register()
dla providerów oraz bind()
/getBinding()
dla wartości konfiguracyjnych. :contentReference[oaicite:0]{index=0}
2) Container
Główna implementacja kontenera opiera się na DotContainer
– strukturze pozwalającej
przechowywać i pobierać wpisy przy użyciu notacji kropkowej
(np. db.connections.mysql.host
). Dzięki temu ten sam magazyn może służyć zarówno
do przechowywania serwisów, jak i wartości konfiguracyjnych.
Container
działa więc jako Adapter do DotContainer
,
a dodatkowo realizuje kontrakt Psr\Container\ContainerInterface
. W praktyce daje to trzy
korzyści naraz:
- Elastyczny storage – zamiast płaskiego rejestru masz zagnieżdżoną przestrzeń, w której można trzymać i usługi, i konfigurację,
- Service Locator w granicach PSR-11 – metody
get()
/has()
obsługują zależności w spójny sposób, - Fallback do autowiringu – jeśli wpis nie istnieje, kontener potrafi samodzielnie
stworzyć obiekt przez moduł
Autowire
.
To sprawia, że taki kontener jest bardziej uniwersalny niż typowe implementacje PSR-11 – łączy w sobie prostotę Service Locator, wygodę dot-notation znaną z frameworków pokroju Laravela i nowoczesny autowiring.
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Container;
use DJWeb\Framework\Base\DotContainer;
use DJWeb\Framework\Container\Contracts\ContainerContract;
use DJWeb\Framework\Container\Contracts\ServiceProviderContract;
use DJWeb\Framework\Exceptions\Container\NotFoundError;
class Container implements ContainerContract
{
private DotContainer $entries;
private Autowire $autowire;
/**
* @var array<string, string|int|float|bool|null>
*/
private array $bindings = [];
public function __construct()
{
$this->entries = new DotContainer();
$this->autowire = new Autowire($this);
}
public function bind(string $key, string|int|float|bool|null $value): void
{
$this->bindings[$key] = $value;
}
public function getBinding(string $key): string|int|float|bool|null
{
return $this->bindings[$key] ?? null;
}
/**
* @param class-string $id
*
* @return mixed
*
* @throws NotFoundError
*/
public function get(string $id): mixed
{
$entry = $this->entries->get($id);
if (! $this->has($id)) {
return $this->autowire->instantiate($id);
}
if ($entry instanceof Definition) {
return $this->autowire->instantiate($entry::class);
}
return $entry;
}
public function has(string $id): bool
{
return $this->entries->has($id);
}
/**
* Sets item in container
*
* @param string $key
* @param mixed $value
*
* @return ContainerContract
*/
public function set(string $key, mixed $value): ContainerContract
{
$this->entries->set($key, $value);
return $this;
}
/**
* Register a service provider.
*
* @param ServiceProviderContract $provider
*
* @return self
*/
public function register(
ServiceProviderContract $provider
): ContainerContract {
$provider->register($this);
return $this;
}
}
3) Autowire
Moduł odpowiedzialny za automatyczne rozwiązywanie zależności przez refleksję. Najpierw sprawdza nazwę parametru w kontenerze, później typ parametru (klasę), następnie wartości domyślne/built-in, a na końcu – jeśli to obiekt – rekurencyjnie instancjonuje zależność.
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Container;
use DJWeb\Framework\Container\Contracts\ContainerContract;
use DJWeb\Framework\Exceptions\Container\NotFoundError;
use ReflectionClass;
use ReflectionException;
/**
* Class Autowire
*
* Responsible for automatically resolving and injecting dependencies.
*/
class Autowire
{
private ReflectionResolver $resolver;
public function __construct(
private ContainerContract $container,
) {
$this->resolver = new ReflectionResolver();
}
/**
* Autowire and instantiate a class.
*
* @template T of object
*
* @param class-string<T> $className The name of the class to instantiate
*
* @return T The instantiated object
*
* @throws ReflectionException If the class cannot be reflected
* @throws NotFoundError If a dependency cannot be resolved
*/
public function instantiate(string $className): object
{
$reflectionClass = new ReflectionClass($className);
$constructor = $reflectionClass->getConstructor();
if ($constructor === null) {
return $reflectionClass->newInstance();
}
$parameters = $this->resolver->getConstructorParameters($className);
$arguments = $this->resolveParameters($parameters);
return $reflectionClass->newInstanceArgs($arguments);
}
/**
* Resolve parameters for dependency injection.
*
* @param array<\ReflectionParameter> $parameters
*
* @return array<mixed>
*
* @throws NotFoundError If a dependency cannot be resolved
*/
private function resolveParameters(array $parameters): array
{
return array_map(function (\ReflectionParameter $parameter) {
$parameterName = $parameter->getName();
/** @var string $type */
$type = $this->resolver->getParameterType($parameter);
return match (true) {
// 1. return given value if exists
$this->container->has($parameterName) => $this->container->get($parameterName),
$this->container->has($type) => $this->container->get($type),
// 2. return default value if exist
$this->resolver->hasDefaultValue($parameter) => $this->resolver->getDefaultValue($parameter),
// 3. return null if allowed
$this->resolver->allowsNull($parameter) => null,
// 4. for builtin types return default value
$type && $this->isBuiltInType($type) => $this->resolver->getDefaultValueForBuiltInType($type),
// 5. for object check recursively
$type && class_exists($type) => $this->instantiate($type),
// otherwise throw not found exception
default => throw new NotFoundError(
"Unable to resolve parameter {$parameterName} of type {$type}"
)
};
}, $parameters);
}
private function isBuiltInType(string $type): bool
{
return in_array($type, ['int', 'float', 'string', 'bool', 'array']);
}
}
- Ścieżka rozwiązywania parametrów: name → type → default → null (jeśli dozwolone) → built-in default → rekursywna instancjacja, w przeciwnym razie wyjątek
NotFoundError
. :contentReference[oaicite:4]{index=4} - Rozpoznawanie typów i wartości domyślnych realizuje pomocniczy
ReflectionResolver
Jak to składa się z PSR-11?
Kontener realizuje minimalny kontrakt PSR-11 (get
/has
) i rozszerza go o wygodne
elementy (rejestracja providerów, bindowanie skalarnych wartości, autowiring). Dzięki temu biblioteki
„mówią” interfejsem PSR-11, a aplikacja wciąż może korzystać z bogatszego API implementacji.
Pozostałe definicje i rozszerzenia (np. informację o tym jak zbudowny jest DotContainer) znajdziesz w kodzie źródłowym kursu publicznie dostępnym na GitHubie.
Gdzie wykorzystasz PSR-11?
- Własne frameworki i mikroserwisy (centralny punkt tworzenia obiektów),
- Integracja bibliotek z ujednoliconym mechanizmem DI,
- Konfiguracja i testowanie (łatwe podmiany i mocki),
- Automatyczny wiring zależności na podstawie typów i nazw parametrów.
Technologie i narzędzia
Kurs kompleksowo przedstawia:
- Zaawansowane techniki wstrzykiwania zależności
- Symfony Dependency Injection
- Narzędzia wspierające jakość kodu (PHP CS Fixer)
- Praktyczne wzorce projektowe
Metodologia nauki
Publikacja łączy:
- Szczegółowe wyjaśnienia teoretyczne
- Liczne przykłady kodu - Case studies z realnych projektów
- Wskazówki dotyczące best practices
Rezultaty nauki
Po ukończeniu kursu będziesz potrafić:
- Stosować Dependency Injection w codziennej pracy
- Projektować bardziej elastyczne systemy
- Tworzyć łatwe w utrzymaniu aplikacje
- Pisać kod zrozumiały dla innych programistów
📖 Oficjalna specyfikacja PSR-11: https://www.php-fig.org/psr/psr-11/
🎓 Chcesz poznać więcej o PSR i Dependency Injection?
Ten wpis to fragment kompleksowego kursu PHP 8.4, w którym szczegółowo omawiam wszystkie standardy PSR wraz z praktycznymi przykładami z rzeczywistych projektów.