0Pricing
PHP Academy · Урок

Порты и адаптеры

Изолируйте ядро с помощью портов и подключаемых адаптеров

«Порты и адаптеры» — бесплатный урок PHP Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.

Идея шестиугольника

Порты и адаптеры — шестиугольная архитектура Алистера Кокберна — представляет приложение в виде шестиугольника. Внутри находится чистая бизнес-логика. Каждое взаимодействие с внешним миром (HTTP, база данных, очередь, часы, электронная почта) проходит через порт, а каждый порт реализуется одним или несколькими адаптерами. У этой формы нет привилегированной верхней или нижней части: пользовательский интерфейс и база данных симметричны, поскольку оба являются просто адаптерами.

Порты — это интерфейсы

Порт — это интерфейс, принадлежащий ядру приложения и выражающий потребность или возможность в терминах домена. Он не должен пропускать лексику инфраструктуры: никаких PDOStatement, GuzzleResponse или 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;
}

Ведущие и ведомые порты

Существует два вида:

  • Ведущие (первичные, входящие) порты — API, которое внешний мир вызывает, чтобы управлять приложением. Обычно это интерфейсы вариантов использования.
  • Ведомые (вторичные, исходящие) порты — интерфейсы, через которые приложение обращается к внешнему миру: репозитории, почтовые сервисы, часы.

Ведущие адаптеры вызывают ядро, а ядро обращается наружу через ведомые адаптеры.

<?php
// Driving (inbound) port — the public capability of the core
interface RegisterUser
{
    public function handle(string $email, string $plainPassword): UserId;
}

Ядро реализует ведущие порты

Вариант использования реализует ведущий порт и зависит от ведомых портов. Обратите внимание: он получает через внедрение порты PasswordHasher и Clock — даже время и хеширование абстрагированы, чтобы ядро оставалось детерминированным и тестируемым.

<?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();
    }
}

Ведомый адаптер

Ведомый адаптер реализует ведомый порт с помощью конкретной технологии. Здесь адаптер PDO удовлетворяет интерфейсу UserRepository. Его можно заменить на Doctrine, Redis или клиент HTTP API, не затрагивая ядро.

<?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
    }
}

Ведущий адаптер

Ведущий адаптер преобразует внешний сигнал в вызов ведущего порта. Контроллер HTTP, команда CLI или обработчик сообщений — все они являются взаимозаменяемыми ведущими адаптерами для одного и того же варианта использования.

<?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;
    }
}

Адаптеры в памяти для тестов

Главное преимущество: каждый ведомый порт получает быструю тестовую реализацию. Тесты проверяют реальный вариант использования с адаптерами в памяти, детерминированными часами и хешировщиком без действий.

<?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;

Адаптеры переводят, но не принимают решений

Распространённая ошибка — позволять бизнес-правилам просачиваться в адаптеры. Простое правило: адаптер только преобразует форматы данных и протоколы. Если внутри контроллера или репозитория Вы находите if, связанное с ценообразованием, правом на действие или статусом, ему место в ядре.

  • Сопоставление JSON ↔ объектов передачи данных: адаптер
  • SQL ↔ заполнение сущности данными: адаптер
  • «VIP получает скидку 10 %»: ядро

Один порт — много адаптеров

Порты позволяют заменять адаптеры и даже использовать параллельные адаптеры. У NotificationPort могут быть адаптеры электронной почты, SMS и Slack, объединённые в композицию. Ядро вызывает один метод, а настройка связей решает, сколько каналов ответит.

<?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;

Как шестиугольник отображается на структуру каталогов

Практичная структура PHP для ограниченного контекста:

  • Domain/ — сущности, объекты-значения, сервисы предметной области
  • Application/Port/In/ — интерфейсы входных портов (сценарии использования)
  • Application/Port/Out/ — интерфейсы выходных портов (репозитории, часы)
  • Application/ — реализации сценариев использования
  • Infrastructure/Adapter/In/ — контроллеры, CLI, обработчики
  • Infrastructure/Adapter/Out/ — адаптеры PDO/Doctrine/HTTP

Корень композиции (конфигурация DI-контейнера) связывает адаптеры In и Out с портами.

Проверка всего шестиугольника

Помимо модульных проверок, порты позволяют быстро выполнять приёмочные проверки: приложение запускается через его основной порт, а результат проверяется с помощью вторичных адаптеров в памяти. Так Вы охватываете полный сценарий использования без HTTP и базы данных. Позднее тот же набор проверок можно запускать с настоящими адаптерами как интеграционные проверки, получая многоуровневую стратегию проверки без переписывания кода.

<?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;

Быстрая проверка

Какое утверждение о портах и адаптерах верно?

Итоги

Архитектура портов и адаптеров изолирует ядро за интерфейсами:

  • Порты — это интерфейсы на языке предметной области, которыми владеет ядро.
  • Входные порты вызываются входными адаптерами; выходные порты вызываются ядром, а их реализацию предоставляют исходящие адаптеры.
  • Адаптеры только преобразуют протоколы и форматы — они никогда не принимают бизнес-решения.
  • Один и тот же порт поддерживает множество адаптеров (имитации для проверок, составные адаптеры для распределения вызовов).
  • Корень композиции связывает всё вместе, а шестиугольник остаётся независимым от фреймворков.

Часто задаваемые вопросы

Урок «Порты и адаптеры» бесплатный?

Да — полный текст урока «Порты и адаптеры» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.

Чему я научусь в уроке «Порты и адаптеры»?

Изолируйте ядро с помощью портов и подключаемых адаптеров Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать PHP Academy?

Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Порты и адаптеры»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке PHP Academy?

Да. Каждый урок PHP Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. От многоуровневой к чистой архитектуре
  2. Порты и адаптеры
  3. Варианты использования и прикладные службы
  4. Инверсия зависимостей на практике
← Назад к PHP Academy