Wyjaśnienie portów i adapterów
Proszę izolować rdzeń za pomocą portów i wymiennych adapterów.
Wyjaśnienie portów i adapterów to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 2 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.
Idea heksagonu
Porty i adaptery — architektura heksagonalna Alistaira Cockburna — przedstawiają aplikację jako heksagon. Wewnątrz znajduje się czysta logika biznesowa. Każda interakcja ze światem zewnętrznym (HTTP, DB, kolejka, zegar, e-mail) przechodzi przez port, a każdy port jest obsługiwany przez co najmniej jeden adapter. Kształt nie wyróżnia górnej ani dolnej części: interfejs użytkownika i baza danych są symetryczne, ponieważ oba są po prostu adapterami.
Porty są interfejsami
Port to interfejs należący do rdzenia aplikacji, który wyraża potrzebę lub możliwość w kategoriach domeny. Nie może ujawniać słownictwa infrastruktury — żadnego PDOStatement, żadnego GuzzleResponse ani Eloquent.
<?php
// Driven (outbound) port: the core needs to persist users
interface UserRepository
{
public function byId(UserId $id): ?User;
public function save(User $user): void;
}Porty sterujące a sterowane
Występują dwa rodzaje:
- Porty sterujące (pierwotne, przychodzące) — interfejs API wywoływany przez świat zewnętrzny, aby sterować aplikacją. Zwykle są to interfejsy przypadków użycia.
- Porty sterowane (wtórne, wychodzące) — interfejsy wywoływane przez aplikację w celu komunikacji ze światem zewnętrznym: repozytoria, moduły pocztowe, zegary.
Adaptery sterujące wywołują rdzeń, a rdzeń komunikuje się ze światem za pośrednictwem adapterów sterowanych.
<?php
// Driving (inbound) port — the public capability of the core
interface RegisterUser
{
public function handle(string $email, string $plainPassword): UserId;
}Rdzeń implementuje porty sterujące
Przypadek użycia implementuje port sterujący i zależy od portów sterowanych. Uwaga: przyjmuje PasswordHasher i Clock jako wstrzykiwane porty — nawet czas i haszowanie są abstrakcyjne, dzięki czemu rdzeń pozostaje deterministyczny i testowalny.
<?php
final class RegisterUserService implements RegisterUser
{
public function __construct(
private UserRepository $users,
private PasswordHasher $hasher,
private Clock $clock,
) {}
public function handle(string $email, string $plain): UserId {
if ($this->users->byEmail($email)) {
throw new EmailAlreadyTaken($email);
}
$user = User::register(
$email,
$this->hasher->hash($plain),
$this->clock->now()
);
$this->users->save($user);
return $user->id();
}
}Adapter sterowany
Adapter sterowany implementuje port sterowany za pomocą konkretnej technologii. W tym przypadku adapter PDO realizuje UserRepository. Można zastąpić go rozwiązaniem opartym na Doctrine, Redisie albo kliencie API HTTP bez modyfikowania rdzenia.
<?php
final class PdoUserRepository implements UserRepository
{
public function __construct(private PDO $pdo) {}
public function byId(UserId $id): ?User {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([(string) $id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? User::fromRow($row) : null;
}
public function save(User $user): void {
// INSERT ... ON CONFLICT UPDATE
}
}Adapter sterujący
Adapter sterujący tłumaczy zewnętrzny sygnał na wywołanie portu sterującego. Kontroler HTTP, polecenie CLI i konsument komunikatów — wszystkie te elementy są wymiennymi adapterami sterującymi dla tego samego przypadku użycia.
<?php
// CLI driving adapter
final class RegisterUserCommand
{
public function __construct(private RegisterUser $register) {}
public function run(array $argv): int {
[$email, $password] = array_slice($argv, 1);
$id = $this->register->handle($email, $password);
fwrite(STDOUT, "Created user $id\n");
return 0;
}
}Adaptery działające w pamięci do testów
Największa korzyść: każdy port sterowany otrzymuje szybki fake. Testy wykonują prawdziwy przypadek użycia z użyciem adapterów działających w pamięci, deterministycznych zegarów i modułu haszującego, który nic nie robi.
<?php
final class FixedClock implements Clock {
public function __construct(private DateTimeImmutable $t) {}
public function now(): DateTimeImmutable { return $this->t; }
}
final class PlainHasher implements PasswordHasher {
public function hash(string $p): string { return 'h:' . $p; }
}
$service = new RegisterUserService(
new InMemoryUsers(),
new PlainHasher(),
new FixedClock(new DateTimeImmutable('2026-01-01'))
);
echo 'wired OK', PHP_EOL;Adaptery tłumaczą, nigdy nie decydują
Częstym błędem jest dopuszczanie do przenikania reguł biznesowych do adapterów. Praktyczna zasada: adapter jedynie tłumaczy formaty danych i protokoły. Jeśli w kontrolerze lub repozytorium znajdą Państwo if dotyczące cen, uprawnień lub statusu, należy ono do rdzenia.
- mapowanie JSON ↔ DTO: adapter
- hydracja SQL ↔ encja: adapter
- „VIP otrzymuje 10% rabatu”: rdzeń
Jeden port, wiele adapterów
Porty umożliwiają podmianę, a nawet równoległe adaptery. Port NotificationPort może mieć adaptery poczty e-mail, SMS i Slack, złożone razem. Rdzeń wywołuje jedną metodę; konfiguracja połączeń decyduje, ile kanałów odpowie.
<?php
interface Notifier { public function send(string $to, string $msg): void; }
final class CompositeNotifier implements Notifier {
/** @param Notifier[] $channels */
public function __construct(private array $channels) {}
public function send(string $to, string $msg): void {
foreach ($this->channels as $c) $c->send($to, $msg);
}
}
$notifier = new CompositeNotifier([new EmailNotifier(), new SmsNotifier()]);
echo 'composed', PHP_EOL;Jak sześciokąt odwzorowuje się na foldery
Pragmatyczny układ PHP dla ograniczonego kontekstu:
Domain/— encje, obiekty wartości, usługi domenoweApplication/Port/In/— interfejsy portów wejściowych (przypadki użycia)Application/Port/Out/— interfejsy portów wyjściowych (repozytoria, zegar)Application/— implementacje przypadków użyciaInfrastructure/Adapter/In/— kontrolery, CLI, konsumenciInfrastructure/Adapter/Out/— adaptery PDO/Doctrine/HTTP
Composition root (konfiguracja kontenera DI) łączy adaptery In i Out z portami.
Testowanie całego sześciokąta
Oprócz testów jednostkowych porty umożliwiają szybkie testy akceptacyjne, które sterują aplikacją przez jej port wejściowy i sprawdzają wynik za pomocą działających w pamięci adapterów wyjściowych — obejmując cały przypadek użycia bez HTTP i bazy danych. Ten sam zestaw testów można później uruchomić z użyciem rzeczywistych adapterów jako testy integracyjne, uzyskując warstwową strategię testowania bez przepisywania testów.
<?php
// Acceptance test: real use case, fake driven adapters, no I/O
$users = new InMemoryUsers();
$service = new RegisterUserService($users, new PlainHasher(),
new FixedClock(new DateTimeImmutable('2026-01-01')));
$id = $service->handle('dev@coddykit.com', 'pw');
assert($users->byId($id) !== null);
echo 'acceptance: user persisted via in-memory adapter', PHP_EOL;Szybkie sprawdzenie
Które stwierdzenie dotyczące portów i adapterów jest poprawne?
Podsumowanie
Wzorzec Ports & Adapters izoluje rdzeń za interfejsami:
- Porty to interfejsy wyrażone w języku domeny i należące do rdzenia.
- Porty wejściowe są wywoływane przez adaptery wejściowe; porty wyjściowe są wywoływane przez rdzeń i obsługiwane przez adaptery wyjściowe.
- Adaptery jedynie tłumaczą protokoły i formaty — nigdy nie podejmują decyzji biznesowych.
- Ten sam port obsługuje wiele adapterów (atrapy do testów, kompozyty do rozsyłania wywołań).
- Composition root łączy wszystko; sześciokąt pozostaje niezależny od frameworków.
Często zadawane pytania
Czy lekcja „Wyjaśnienie portów i adapterów” jest bezpłatna?
Tak — pełny tekst „Wyjaśnienie portów i adapterów” 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 „Wyjaśnienie portów i adapterów”?
Proszę izolować rdzeń za pomocą portów i wymiennych adapterów. Ć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 2 z 4.
Ile czasu zajmuje lekcja „Wyjaśnienie portów i adapterów”?
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