Чистая архитектура в PHP - это способ организовать проект слоями так, чтобы бизнес-правила не зависели от фреймворка, БД и внешних сервисов. Практически это означает: домен в центре, вокруг - application/use cases, затем адаптеры и инфраструктура. Так clean architecture php пример становится воспроизводимым и упрощает поддержку.
Основные принципы для поддерживаемой архитектуры
- Доменная модель и правила бизнеса живут отдельно от HTTP, ORM, очередей и файловой системы.
- Зависимости направлены внутрь: инфраструктура зависит от домена, а не наоборот.
- Границы слоёв фиксируются интерфейсами и контрактами (DTO/Request/Response), а не договорённостями.
- Контроллеры/консольные команды тонкие: они собирают входные данные и вызывают use case.
- Интеграции делаются через адаптеры; инфраструктура меняется без переписывания бизнес-логики.
- Тесты строятся вокруг use cases и домена; интеграционные тесты закрывают риски контейнера и адаптеров.
Слои приложения и их чёткие границы

Кому подходит. Если у вас растущая архитектура PHP приложения с несколькими сценариями (use cases), интеграциями и долгим сроком жизни, чистая архитектура снижает стоимость изменений: можно заменять фреймворк/ORM/шину сообщений, не трогая домен.
Когда не стоит усложнять. Для небольшого CRUD, одноразовых промо, прототипов и скриптов с коротким жизненным циклом слой use cases и адаптеры часто избыточны. Начните с аккуратной модульности и введите границы позже, когда появятся признаки сложности: повторяющиеся правила, много интеграций, растущий набор сценариев.
- Domain: сущности, value objects, доменные сервисы, инварианты.
- Application: use cases, интерфейсы портов (репозитории, шлюзы), транзакционные границы.
- Adapters (Interface): HTTP/CLI/Queue-обработчики, маппинг DTO, presenter/response.
- Infrastructure: ORM, HTTP-клиенты, реализация репозиториев, логирование, кэш, контейнер.
Организация каталогов и неймспейсов: практические шаблоны
Чтобы php структура проекта оставалась читаемой, заранее определите правила автозагрузки и границы модулей. Минимально понадобится:
- Composer с PSR-4 автозагрузкой (и отдельными namespace для Domain/Application/Infrastructure).
- Единый стиль кода и статанализ (например, через правила проекта; конкретные инструменты выбирайте под стек).
- Доступ к CI (или хотя бы локальный запуск тестов и линтеров) для безопасных шагов миграции.
- Явная точка входа: public/index.php (HTTP) и/или bin/console (CLI), где собирается контейнер.
Один из практичных шаблонов структуры (по слоям):
src/
Domain/
User/
Entity/
ValueObject/
Service/
Exception/
Application/
User/
UseCase/
Port/
DTO/
Interface/
Http/
Controller/
Request/
Presenter/
Cli/
Infrastructure/
Persistence/
HttpClient/
Logging/
DI/
tests/
Unit/
Integration/
public/
index.php
Альтернатива (по модулям/контекстам): сначала модуль (User/Order), внутри - слои. Этот вариант лучше масштабируется, если домен большой и команд несколько.
Если вы ищете "чистая архитектура php", не привязывайтесь к названиям папок: важнее, чтобы слой Domain не импортировал Infrastructure, а Application зависел от портов, а не от реализаций.
Внедрение зависимостей и контейнеры: минимизация связности
Риски и ограничения перед внедрением (risk-aware):
- Контейнер легко превращается в "магическую коробку": сложно понять, откуда берутся зависимости, если нет явной конфигурации.
- Смешение DTO, ORM-сущностей и доменных сущностей увеличивает связность и ломает границы.
- Переусложнение абстракциями: интерфейсы ради интерфейсов замедляют разработку.
- Неправильные области видимости (singleton везде) приводят к скрытому состоянию и флапающим тестам.
-
Определите композиционный корень. Собирайте контейнер и все wiring только в точках входа (HTTP/CLI/worker), а не в домене или use cases. Это делает зависимости наблюдаемыми и облегчает сопровождение, особенно в сценариях "разработка php приложения под ключ", где проект должен быть понятен новой команде.
- Пример: public/index.php и bin/console подключают src/Infrastructure/DI/*.php.
- Контроллеры получают уже готовые use case-сервисы.
-
Вынесите контракты в Application (порты). Репозитории, внешние шлюзы и любые I/O объявляйте интерфейсами в Application/.../Port. Use case зависит только от них.
- Плохой признак: use case импортирует классы ORM или HTTP-клиента.
- Хороший признак: use case типизирован интерфейсом, а реализация подменяема.
-
Сделайте явную регистрацию реализаций. В Infrastructure свяжите интерфейсы с реализациями (bindings). Держите конфигурацию рядом с инфраструктурой, чтобы изменение драйвера БД или клиента API было локальным.
- Регистрируйте реализации группами по модулю: UserPersistenceBindings, PaymentsHttpBindings.
- Отдельно держите параметры окружения (DSN, ключи) и их валидацию на старте.
-
Отделите жизненные циклы объектов. Явно решите, что является request-scoped (например, UnitOfWork/EntityManager), что stateless singleton (мапперы, валидаторы), а что создаётся на вызов (команды).
- Минимизируйте глобальное состояние, особенно в воркерах очереди.
- Не храните в singleton текущего пользователя/locale/request.
-
Стабилизируйте границу use case через DTO. Вход/выход use case делайте простыми DTO (или команда/результат). Это предотвращает протекание HTTP/ORM моделей внутрь Application.
- Контроллер маппит Request → InputDTO.
- Presenter маппит OutputDTO → JSON/HTML/CLI-вывод.
-
Добавьте минимальные проверочные тесты на wiring. Один-два интеграционных теста, которые поднимают контейнер и создают ключевые use cases, быстро ловят поломки регистрации зависимостей.
- Проверяйте создание use case без выполнения реальных сетевых вызовов (моки/фейки адаптеров).
- Старайтесь падать на старте, если не хватает конфигурации.
Интерфейсы адаптеров: как отделить домен от инфраструктуры

- Домен (src/Domain) не импортирует классы из Infrastructure и не знает про контейнер.
- Use case принимает зависимости через конструктор (интерфейсы портов), а не создаёт их через
newвнутри. - Контроллеры/команды не содержат бизнес-правил: максимум оркестрация и маппинг.
- ORM-сущности не используются как доменные сущности; маппинг вынесен в инфраструктуру.
- Транспортные модели (HTTP Request/Response) не проходят дальше слоя адаптеров.
- Внешние сервисы завернуты в gateway-классы; их интерфейсы живут в Application.
- Ошибки I/O (таймауты, недоступность) преобразуются в ошибки уровня Application/Domain понятным образом.
- Логирование и метрики вызываются на границах (адаптеры/инфраструктура), не засоряя доменные правила.
Тестируемость: стратегии для модульных и интеграционных тестов
- Тестируют контроллеры вместо use cases. В итоге логика размазана, а тесты хрупкие из-за HTTP-деталей.
- Мокают всё подряд. Избыточные моки делают тесты привязанными к реализации; мокайте порты, а не внутренние методы.
- Смешивают доменные сущности с ORM. Это усложняет unit-тесты и вынуждает тянуть БД везде.
- Нет контрактов на порты. Без интерфейсов сложно подменять инфраструктуру и писать фейки.
- Интеграционные тесты не покрывают wiring. Ошибки контейнера и конфигов всплывают только в проде.
- Транзакции и границы консистентности не определены. Use case начинает "частично сохранять" данные при ошибках.
- Тестовые данные создаются обходными путями. Лучше использовать фабрики/фикстуры, соответствующие доменным инвариантам.
Эволюция кода: безопасные рефакторы и миграции архитектуры
- Стратегия "обвязка вокруг монолита" (Strangler). Уместно, если у вас legacy: новые сценарии делайте как use cases с портами, старые постепенно переводите, не ломая всё сразу.
- Модульный монолит без строгих слоёв. Подходит, если команда пока не готова к полной чистой архитектуре: разделите модули, запретите циклические зависимости, затем постепенно вводите порты и адаптеры.
- Вертикальные срезы (feature folders) с правилами границ. Уместно, когда важна скорость: группируйте код по фичам, но фиксируйте, что доменная логика не импортирует инфраструктуру.
- Фреймворк-ориентированная архитектура с выделенным доменом. Иногда практично оставить фреймворк в центре, но вынести доменные правила в отдельный слой/пакет и ограничить протекание зависимостей.
Ответы на типичные затруднения при внедрении
Нужно ли внедрять чистую архитектуру сразу во всём проекте?
Нет. Безопаснее начать с одного модуля или одного нового use case, зафиксировать правила зависимостей и только затем расширять подход.
Как понять, что границы слоёв реально соблюдаются?
Проверьте импорты: Domain не должен зависеть от Infrastructure, а use cases не должны знать про HTTP/ORM. Дополнительно помогает статанализ и простой аудит зависимостей по неймспейсам.
Где держать валидацию: в контроллере или в use case?
Формат и обязательность полей ввода - в адаптере (HTTP). Бизнес-инварианты и правила принятия решения - в домене/use case.
Что делать с исключениями и ошибками внешних сервисов?
Обрабатывайте их в инфраструктуре/адаптере и преобразуйте в ошибки уровня приложения, понятные use case. Не протаскивайте наружу исключения конкретного клиента или ORM.
Можно ли использовать контейнер фреймворка и всё равно соблюдать clean architecture?
Да, если контейнер остаётся в Infrastructure, а домен и use cases о нём не знают. Связывайте интерфейсы портов с реализациями в конфигурации инфраструктуры.
Как выглядит clean architecture php пример на практике: что первым вынести?
Обычно первым выносят use case и порты (репозиторий/шлюз), затем адаптер HTTP и только потом реализацию в Infrastructure. Так вы сразу получаете тестируемое ядро и понятные границы.

