0Pricing
PHP Academy · Lekcja

Odwrócenie zależności w praktyce

Proszę łączyć adaptery z rdzeniem za pomocą odwrócenia zależności.

Odwrócenie zależności w praktyce to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.

Odwrócenie zależności a wstrzykiwanie

Te dwa odrębne pojęcia bywają mylone. Wstrzykiwanie zależności to technika: przekazywanie współpracujących obiektów zamiast ich konstruowania. Odwrócenie zależności (D w SOLID) to zasada: polityka wysokiego poziomu i szczegóły niskiego poziomu zależą od abstrakcji, a abstrakcja jest własnością modułu wysokiego poziomu. Ta lekcja pokazuje, jak urzeczywistnić tę drugą zasadę — łącząc konkretne adaptery z rdzeniem, który definiuje interfejsy.

Zasada w ścisłym ujęciu

DIP oznacza, że:

  • Moduły wysokiego poziomu nie powinny zależeć od modułów niskiego poziomu. Oba powinny zależeć od abstrakcji.
  • Abstrakcje nie powinny zależeć od szczegółów. Szczegóły powinny zależeć od abstrakcji.

Najważniejszy szczegół: interfejs należy do konsumenta, a nie do implementującego go komponentu. Domena deklaruje PaymentGateway; adapter Stripe jest z nim zgodny — nie odwrotnie.

Zdefiniuj abstrakcję w rdzeniu

Interfejs należy umieścić obok kodu, który go potrzebuje, i wyrazić go w terminach domenowych. Typy Stripe nie mogą przenikać do rdzenia.

<?php
// src/Billing/Application/Port/PaymentGateway.php
interface PaymentGateway
{
    public function charge(Money $amount, CardToken $token): ChargeId;
}

final class Money {
    public function __construct(public readonly int $cents, public readonly string $currency) {}
}
final class CardToken { public function __construct(public readonly string $value) {} }
final class ChargeId { public function __construct(public readonly string $value) {} }

Dostosuj do niej adapter

Adapter infrastruktury implementuje interfejs rdzenia i tłumaczy jego wywołania na SDK dostawcy. Strzałka zależności wskazuje od adaptera (szczegółu) do abstrakcji (polityki) — odwrócenie zostało osiągnięte.

<?php
final class StripePaymentGateway implements PaymentGateway
{
    public function __construct(private \Stripe\StripeClient $stripe) {}

    public function charge(Money $amount, CardToken $token): ChargeId {
        $intent = $this->stripe->paymentIntents->create([
            'amount'   => $amount->cents,
            'currency' => strtolower($amount->currency),
            'payment_method' => $token->value,
            'confirm'  => true,
        ]);
        return new ChargeId($intent->id);
    }
}

Wstrzykiwanie przez konstruktor jest domyślnym wyborem

Zależności należy wstrzykiwać przez konstruktor, określając typ abstrakcji. Stają się wtedy jawne, niemutowalne i niemożliwe do pominięcia. Należy unikać wstrzykiwania przez settery i właściwości w przypadku wymaganych współpracujących obiektów — pozwalają one tworzyć nie w pełni zainicjalizowane obiekty.

<?php
final class CheckoutService
{
    public function __construct(
        private PaymentGateway $payments,   // abstraction, not StripeClient
        private OrderRepository $orders,
    ) {}

    public function pay(OrderId $id, CardToken $token): ChargeId {
        $order  = $this->orders->get($id);
        $charge = $this->payments->charge($order->total(), $token);
        $order->markPaid($charge);
        $this->orders->save($order);
        return $charge;
    }
}

Composition root

Cała konkretna konfiguracja zależności odbywa się dokładnie w jednym miejscu — w composition root — możliwie najbliżej main()/punktu wejścia. Nigdzie indziej nie tworzy się infrastruktury. To jedyne miejsce, które wie o istnieniu Stripe.

<?php
// public/index.php — composition root
$stripe   = new \Stripe\StripeClient(getenv('STRIPE_SECRET'));
$gateway  = new StripePaymentGateway($stripe);
$orders   = new PdoOrderRepository(new PDO(getenv('DB_DSN')));
$checkout = new CheckoutService($gateway, $orders);

// Everything below depends only on abstractions
$controller = new CheckoutController($checkout);

Konfiguracja za pomocą kontenera DI

W przypadku nietrywialnych aplikacji kontener (PHP-DI, Symfony) automatyzuje konfigurowanie zależności. Kluczowym działaniem jest powiązanie interfejsów z implementacjami. Autowiring rozwiązuje zależności konstruktora na podstawie typów; wystarczy zadeklarować mapę interfejs→klasa.

<?php
use function DI\autowire;
use function DI\get;

return [
    PaymentGateway::class => autowire(StripePaymentGateway::class),
    OrderRepository::class => autowire(PdoOrderRepository::class),
    \Stripe\StripeClient::class => fn() => new \Stripe\StripeClient(getenv('STRIPE_SECRET')),
    PDO::class => fn() => new PDO(getenv('DB_DSN')),
];

Podmiana adapterów dowodzi słuszności

Ponieważ rdzeń zależy wyłącznie od PaymentGateway, przełączenie dostawcy lub testowanie offline wymaga zmiany w jednym wierszu konfiguracji. Oto fake używany w teście jednostkowym — CheckoutService pozostaje niezmieniony i nigdy nie importuje Stripe.

<?php
final class FakeGateway implements PaymentGateway {
    public array $charges = [];
    public function charge(Money $a, CardToken $t): ChargeId {
        $this->charges[] = $a;
        return new ChargeId('ch_test_' . count($this->charges));
    }
}

$fake = new FakeGateway();
$id = $fake->charge(new Money(2500, 'EUR'), new CardToken('tok_visa'));
echo $id->value, ' charges=', count($fake->charges), PHP_EOL; // ch_test_1 charges=1

Unikaj pułapki service locatora

Wstrzykiwanie samego kontenera i pobieranie zależności wewnątrz metod to antywzorzec lokalizatora usług. Ukrywa on zależności, utrudnia sprawdzanie typów i ponownie wiąże kod z kontenerem. Należy jawnie wstrzykiwać to, czego kod potrzebuje.

<?php
// ANTI-PATTERN: hidden dependencies, container leaks everywhere
final class BadCheckout {
    public function __construct(private ContainerInterface $c) {}
    public function pay($id, $token) {
        $gateway = $this->c->get(PaymentGateway::class); // hidden!
        // ...
    }
}

Gdzie może pojawić się kontener

Kontener jest uzasadniony dokładnie w jednej warstwie: w composition root i otaczającym go spoiwie frameworka (np. fabryce kontrolerów). Klasy domenowe i aplikacyjne muszą pozostać niezależne od kontenera — otrzymują zwykłe obiekty przez konstruktory i można je tworzyć ręcznie. Dobry test brzmi: czy można skonfigurować całą aplikację w jednym pliku PHP bez kontenera? Jeśli tak, zależności są jawne.

Leniwe łączenie zależności bez lokalizatora usług

Czasami zależność jest kosztowna w utworzeniu lub potrzebna tylko warunkowo. Należy oprzeć się pokusie wstrzykiwania kontenera — zamiast tego należy wstrzyknąć fabrykę w postaci domknięcia. Zależność pozostaje jawna i typowana, a jej tworzenie zostaje odroczone do momentu faktycznego użycia.

<?php
final class ReportService
{
    /** @param Closure():PaymentGateway $gatewayFactory */
    public function __construct(private Closure $gatewayFactory) {}

    public function refundIfNeeded(bool $needed): void {
        if (!$needed) return;
        $gateway = ($this->gatewayFactory)(); // built only when required
        // $gateway->charge(...) etc.
    }
}

// Composition root supplies the factory, not the container
$svc = new ReportService(fn() => new StripePaymentGateway($stripe ?? null));
echo 'lazy dependency wired', PHP_EOL;

Szybkie sprawdzenie

Kto powinien być właścicielem interfejsu PaymentGateway?

Podsumowanie

Adaptery zostały prawidłowo połączone z rdzeniem:

  • Odwrócenie zależności ≠ wstrzykiwanie: wstrzykiwanie to mechanizm, a odwrócenie zależności oznacza posiadanie abstrakcji przez konsumenta.
  • Rdzeń definiuje interfejs; adaptery infrastruktury dostosowują się do niego.
  • Należy stosować wstrzykiwanie przez konstruktor abstrakcji i wykonywać całą konkretną konfigurację w jednym composition root (lub konfiguracji kontenera wiążącej interfejs→klasa).
  • Podmiana adapterów i używanie atrap w testach sprowadza się do zmiany w jednym wierszu.
  • Należy unikać antywzorca lokalizatora usług — kontener powinien pozostawać wyłącznie na brzegu systemu.

Często zadawane pytania

Czy lekcja „Odwrócenie zależności w praktyce” jest bezpłatna?

Tak — pełny tekst „Odwrócenie zależności w praktyce” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Odwrócenie zależności w praktyce”?

Proszę łączyć adaptery z rdzeniem za pomocą odwrócenia zależności. Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć PHP Academy?

Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Odwrócenie zależności w praktyce”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?

Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Od architektury warstwowej do Clean Architecture
  2. Wyjaśnienie portów i adapterów
  3. Przypadki użycia i usługi aplikacyjne
  4. Odwrócenie zależności w praktyce
← Powrót do PHP Academy