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=1Unikaj 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
- Od architektury warstwowej do Clean Architecture
- Wyjaśnienie portów i adapterów
- Przypadki użycia i usługi aplikacyjne
- Odwrócenie zależności w praktyce