Коротко: простая архитектура Symfony — слои с единственной ответственностью: контроллеры валидируют, сервисы содержат логику, репозитории делают запросы, Entity отражают таблицы.
Простейшая адекватная архитектура Symfony-приложения — это разделение на слои: контроллеры принимают и валидируют данные, сервисы содержат бизнес-логику, репозитории делают сложные запросы к БД, Entity отражают таблицы. Главное правило — единственная ответственность каждого слоя. Разберём схему и типичные ошибки. Статья для новичков.
Архитектура — большой и во многом спорный вопрос, но с чего-то начинать надо. Сам фреймворк имеет наработки в этом направлении, но не все им следуют, поэтому предложу максимально близкую к фреймворку и, на мой субъективный взгляд, адекватную схему распределения функционала.
Как разделить приложение на слои?
Entity, как и диктует фреймворк, храним в папке Entity. Контроллеры по умолчанию в Controller — тут тоже всё хорошо. Но дальше начинается самое интересное: у нас есть точка входа (контроллер) и БД — их надо увязать и где-то написать бизнес-логику. Фреймворк предлагает много подходов, но в документации это подано сумбурно, и новички часто пишут «ерунду»: то всё слито в один класс, то раскидано по случайно названным классам.
Наша цель — систематизировать и хотя бы первично разделить приложение на слои:
- Контроллеры — приём данных, валидация, выдача данных на фронтенд;
- Сервисы — бизнес-логика приложения;
- Репозитории — сложные запросы в БД (нативно или через QueryBuilder);
- Entity — сущность, отражающая таблицу в БД;
- Listener / Security / Interfaces и т.д. — прочие сервисы, встроенные во фреймворк или написанные самостоятельно.
Теперь схематически расположим их так, как они должны взаимодействовать:
На схеме чёрным цветом показано, как классы должны общаться между собой, красным — как делать не стоит.
Как проходит запрос: зелёный сценарий
От фронтенда приходит запрос в контроллер (1). Контроллер принимает данные (Request) и валидирует их через ValidatorInterface — это логически его работа (приём и валидация ради безопасности приложения). Дальше нужна логика, не относящаяся к обязанностям контроллера.
Поняв, что данные валидны, контроллер зовёт нужного «работника» — вызывает соответствующий сервис и его метод, передав туда полученные с фронта данные.
Сервис обычно логически относится к одной бизнес-сущности: есть Entity/Article — будет и src/Service/ArticleService с бизнес-логикой работы со статьями.
Контроллер хочет получить статью и связанные сущности — например, 3 статьи того же автора не старше 3 месяцев. С этими «хотелками» он обращается в сервис, а тот — в репозиторий (3). Репозиторий составляет несколько запросов в БД, выполняет их и отдаёт данные обратно в сервис (4).
Дальше сервис знает: по бизнес-логике при просмотре статьи надо увеличить счётчик её просмотров и счётчик показов рекомендованных статей. Сервис взаимодействует, возможно несколько раз, с Entity/Article (5 и 6), а затем обращается к EntityManagerInterface для сохранения данных методом flush() (7 и 8).
Выполнив всю бизнес-логику, сервис отдаёт данные контроллеру (9). Контроллер проверяет лишь одно — валиден ли ответ: вернул сервис ожидаемое — отдаёт фронту 200 и данные; вернул ошибку — отдаёт 400 и текст ошибки. Это простейший способ организации архитектуры в Symfony.
Чего делать не стоит (красные стрелки)
Это не строгие запреты, но за такой код потом в лучшем случае стыдно, а в худшем он принесёт кучу проблем. Почти всё ниже следует из первого принципа SOLID — Single Responsibility (единственной ответственности), то есть деления приложения на слои ответственности.
- Нельзя вызывать
EntityManagerв контроллере — это сервис фреймворка для работы с Entity, поиска и сохранения в БД; работе с ним не место в контроллере. - Нельзя из репозитория отдавать наверх объекты типа
QueryBuilder— это объекты для работы с БД, им не место за пределами репозитория. - Не нужно писать простые запросы вида
select * from user where id = 2в репозитории — они спокойно выполняются черезEntityManagerInterfaceв сервисе методамиfindBy,findOneBy,find. Методы репозитория должны быть действительно сложными. - Не нужно располагать в Entity методы для работы с сущностью, даже простые, и тем более с осторожностью добавлять магические методы с логикой, меняющей штатное поведение Entity.
- Каждый класс должен работать только со своими объектами. Например, в сервис лучше передать массив или данные поштучно, а не
Request, с которым логически должен работать только контроллер.
Всё перечисленное — рекомендации, а не жёсткие правила. Но помните: если вы внедряете собственные практики или архитектурные подходы, всегда будьте готовы аргументированно их защитить, особенно в сильной команде. И главный принцип в архитектурных спорах — не бойтесь признавать ошибки: важно не оказаться правым, а прийти к истине. Всегда чётко отличайте аргумент, основанный на фактах, от чьей-то привычки, «хотелки» или легаси.
Если хотите выстроить понятную слоистую архитектуру на Symfony — помогу на консультации.
Vel
10 августа 2023, 09:11Кто автор статьи ?
OtezVikentiy
16 октября 2023, 23:14Я автор статьи ))) Тот же автор, что и автор сайта.
Tem
14 мая 2025, 18:06🙏👍✌