Порты и адаптеры
Изолируйте ядро с помощью портов и подключаемых адаптеров
«Порты и адаптеры» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- От многоуровневой к чистой архитектуре
- Порты и адаптеры
- Варианты использования и прикладные службы
- Инверсия зависимостей на практике