Контролер у Laravel — це точка входу запиту в вашу бізнес-логіку. Його робота проста: прийняти request, викликати потрібну дію і повернути response. Проблеми починаються тоді, коли контролер перетворюється на “все в одному”: SQL, обробка файлів, логіка доступів, форматування даних і ще й шматки верстки. Такий код важко підтримувати, тестувати й розширювати. Тому стандарт звучить жорстко: контролер має бути тонким, а логіка має жити в сервісах, моделях та окремих класах.
Щоб не перетворювати структуру на хаос, Laravel дає два зручні підходи: resource controller і single action controller. Вони не взаємовиключні — вони під різні задачі. Якщо ти зрозумієш, де який застосовувати, у тебе зникне 80% болю з організацією коду.
Resource controller — це контролер “під CRUD”. CRUD — це стандартний набір дій: список, форма створення, створення, перегляд, форма редагування, оновлення, видалення. У Laravel під це є типовий набір методів: index, create, store, show, edit, update, destroy. Ідея проста: більшість адмінських сутностей живуть саме так, і коли ти робиш їх одним стандартом, команді легко орієнтуватися в коді.
Коли resource controller доречний. Якщо ти робиш довідник, тег, розділ, ресурс, користувача, шаблон договору, клієнта — це майже завжди CRUD. Resource-контролер дає тобі передбачувану структуру: усі знають, що index — список, store — створення, update — оновлення. Це важливо, коли проєкт росте і файлів стає сотні.
Коли resource controller не доречний. Якщо дія не вписується в CRUD — наприклад “опублікувати на соцмережі”, “перегенерувати прев’ю”, “поставити статус погоджено”, “експорт у CSV”, “підрахувати статистику”, “прогнати синхронізацію” — робити це методом update часто погано. Такі дії краще винести в окремий endpoint і, відповідно, в окремий контролер або метод із чіткою назвою, щоб не ламати семантику.
Single action controller — це контролер з однією дією. У Laravel це зазвичай клас, який має метод __invoke. Він ідеальний, коли у тебе є одна чітка операція, і ти хочеш тримати її ізольовано. Це прибирає спокусу “дописати ще один метод сюди ж”, і з часом підтримка стає простішою.
Коли single action — найкращий вибір. Коли ти робиш “одну кнопку — одна дія”: наприклад, ApproveNewsController, PublishToSocialController, RebuildCacheController, ExportReportsController. У таких діях часто є специфічні правила доступу, специфічні валідації і специфічні побічні ефекти. Single action робить код очевидним: назва класу = що він робить.
Тепер про залежності через DI, бо це ключ до нормальної архітектури. Dependency Injection означає, що контролер не створює сервіси всередині себе через new, а отримує їх з контейнера Laravel. Це дає три вигоди: по-перше, контролер не “прив’язаний” до конкретних реалізацій; по-друге, код легше тестувати; по-третє, ти не роздуваєш контролер зайвими деталями налаштування.
Як DI виглядає логічно. Контролер не має знати, як саме створюється сервіс, як він конфігурується, які в нього залежності. Контролер має знати лише, що існує сервіс, який робить дію. Контейнер Laravel сам збирає об’єкт, якщо ти правильно типізував параметри. Це і є “дорослий” спосіб будувати проєкт.
Найзручніші місця для DI — конструктор і методи. Якщо сервіс потрібен багатьом методам resource контролера, його вводять через конструктор, щоб не повторюватися. Якщо сервіс потрібен лише в одному методі, його можна інжектити прямо в цей метод. Обидва варіанти нормальні, питання лише в чистоті та читабельності.
Далі важливий момент: контролер і DI не замінюють валідацію. Стандарт такий: вхідні дані перевіряються FormRequest, а вже “чисті” дані передаються в сервіс. Якщо ти тягнеш сирий $request->all() у сервіс, ти множиш ризики і змішуєш відповідальність. Правильний потік: Request → FormRequest (валідація) → Controller (координація) → Service (логіка) → Model/DB (дані).
Ще одна точка, де новачки роблять помилки, — доступи. Контролер не повинен мати “ліс” з if для прав. Мінімальний стандарт: доступ у розділ — через middleware на маршрутах, доступ до конкретної дії/ресурсу — через policy або gate. Контролер у цьому сценарії просто викликає авторизацію і йде далі, не перетворюючись на “суддю” правил.
Як зрозуміти, що контролер став неправильним. Є простий тест: якщо метод контролера не вміщається в один екран і в ньому багато гілок, запитів до БД, обробки файлів і форматування — контролер товстий. Якщо при зміні правила доводиться правити кілька контролерів — логіка розмазана. Якщо нову дію складно протестувати — DI і сервісний шар не використані як треба.
Практичний мінімальний стандарт, який варто зафіксувати з першого місяця. Для CRUD — resource controller з передбачуваними методами. Для одиночних дій — single action контролери з назвами, що відображають дію. Для логіки — сервісний шар і транзакції там, де зачіпається кілька таблиць. Для залежностей — DI, а не new. Для валідації — FormRequest. Для доступів — middleware + policies.
Висновок: контролери — це не місце, де “пишеться весь код”, а місце, де запит правильно направляється. Resource дає стандартизацію для CRUD, single action дає чистоту для конкретних операцій, а DI робить код керованим і тестованим. Якщо ти привчишся до цього стандарту на старті, у тебе не буде моменту, коли “проєкт став великий і нічого не зрозуміло”, бо структура буде тримати систему в руках.