0Pricing
PHP Academy · Lekcja

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 domenowe
  • Application/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życia
  • Infrastructure/Adapter/In/ — kontrolery, CLI, konsumenci
  • Infrastructure/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

  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