Чистая архитектура в Php: как организовать проект для простой поддержки и развития

Чистая архитектура в PHP - это способ организовать проект слоями так, чтобы бизнес-правила не зависели от фреймворка, БД и внешних сервисов. Практически это означает: домен в центре, вокруг - application/use cases, затем адаптеры и инфраструктура. Так clean architecture php пример становится воспроизводимым и упрощает поддержку.

Основные принципы для поддерживаемой архитектуры

  • Доменная модель и правила бизнеса живут отдельно от HTTP, ORM, очередей и файловой системы.
  • Зависимости направлены внутрь: инфраструктура зависит от домена, а не наоборот.
  • Границы слоёв фиксируются интерфейсами и контрактами (DTO/Request/Response), а не договорённостями.
  • Контроллеры/консольные команды тонкие: они собирают входные данные и вызывают use case.
  • Интеграции делаются через адаптеры; инфраструктура меняется без переписывания бизнес-логики.
  • Тесты строятся вокруг use cases и домена; интеграционные тесты закрывают риски контейнера и адаптеров.

Слои приложения и их чёткие границы

Чистая архитектура в PHP: как организовать проект, чтобы его было легко поддерживать - иллюстрация

Кому подходит. Если у вас растущая архитектура 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 везде) приводят к скрытому состоянию и флапающим тестам.
  1. Определите композиционный корень. Собирайте контейнер и все wiring только в точках входа (HTTP/CLI/worker), а не в домене или use cases. Это делает зависимости наблюдаемыми и облегчает сопровождение, особенно в сценариях "разработка php приложения под ключ", где проект должен быть понятен новой команде.

    • Пример: public/index.php и bin/console подключают src/Infrastructure/DI/*.php.
    • Контроллеры получают уже готовые use case-сервисы.
  2. Вынесите контракты в Application (порты). Репозитории, внешние шлюзы и любые I/O объявляйте интерфейсами в Application/.../Port. Use case зависит только от них.

    • Плохой признак: use case импортирует классы ORM или HTTP-клиента.
    • Хороший признак: use case типизирован интерфейсом, а реализация подменяема.
  3. Сделайте явную регистрацию реализаций. В Infrastructure свяжите интерфейсы с реализациями (bindings). Держите конфигурацию рядом с инфраструктурой, чтобы изменение драйвера БД или клиента API было локальным.

    • Регистрируйте реализации группами по модулю: UserPersistenceBindings, PaymentsHttpBindings.
    • Отдельно держите параметры окружения (DSN, ключи) и их валидацию на старте.
  4. Отделите жизненные циклы объектов. Явно решите, что является request-scoped (например, UnitOfWork/EntityManager), что stateless singleton (мапперы, валидаторы), а что создаётся на вызов (команды).

    • Минимизируйте глобальное состояние, особенно в воркерах очереди.
    • Не храните в singleton текущего пользователя/locale/request.
  5. Стабилизируйте границу use case через DTO. Вход/выход use case делайте простыми DTO (или команда/результат). Это предотвращает протекание HTTP/ORM моделей внутрь Application.

    • Контроллер маппит Request → InputDTO.
    • Presenter маппит OutputDTO → JSON/HTML/CLI-вывод.
  6. Добавьте минимальные проверочные тесты на wiring. Один-два интеграционных теста, которые поднимают контейнер и создают ключевые use cases, быстро ловят поломки регистрации зависимостей.

    • Проверяйте создание use case без выполнения реальных сетевых вызовов (моки/фейки адаптеров).
    • Старайтесь падать на старте, если не хватает конфигурации.

Интерфейсы адаптеров: как отделить домен от инфраструктуры

Чистая архитектура в PHP: как организовать проект, чтобы его было легко поддерживать - иллюстрация
  • Домен (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. Так вы сразу получаете тестируемое ядро и понятные границы.

Прокрутить вверх