Kurs PHP Dependency Injection – Master PHP

03.09.2025

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.

Kup pełny kurs PHP 8.4 🚀 Pobierz darmowy fragment 📥

Zobacz też inne standardy PSR