Завантаження публікації
ОГОЛОШЕННЯ

Service layer у Laravel: як винести логіку з контролера в сервіс для create/update і не ламати систему

Пояснюємо практично: навіщо сервісний шар, що лишається в контролері, як будувати create/update, як перевіряти вхідні дані і коли обов’язково робити транзакцію, якщо зачіпаєш 2+ таблиці.


Максим Третяк
Максим Третяк
Газета Дейком | 30.12.2025, 18:00 GMT+3; 11:00 GMT-4

Сервіс у Laravel — це місце, де живе бізнес-логіка, а не “склейка” HTTP. Контролер повинен бути тонким: прийняв запит, прогнав валідацію, викликав сервіс, повернув відповідь. Як тільки ти починаєш писати логіку створення/оновлення прямо в контролері, він роздувається, логіка дублюється, з’являються різні сценарії “майже однаково, але трохи інакше”, і через місяць будь-яка правка стає ризиком.

Для навчального CRUD (Tags/Sections/Resources) сервіс може виглядати як “невелика надбудова”, але саме він формує правильну звичку. У реальному проєкті create/update майже завжди включає додаткові дії: запис активності, синхронізація зв’язків, генерація slug, робота з медіа, оновлення лічильників, логування. Якщо це все залишити в контролері — ти отримаєш комбайн, який ніхто не буде підтримувати.

Правильний розподіл відповідальності виглядає так. Контролер відповідає за HTTP: який маршрут, який метод, який FormRequest, який редирект, яке повідомлення. FormRequest відповідає за валідацію правил (required, max, unique тощо) і, інколи, за базову авторизацію запиту. Сервіс відповідає за сценарій: “створити сутність”, “оновити сутність”, “записати пов’язані речі” і гарантувати, що дані не стануть частково збереженими. Модель відповідає за зв’язки, casts і дрібну нормалізацію атрибутів.

Мінімальний сервіс для CRUD має два методи: create() і update(). Вони приймають вже валідовані дані, а не “весь request”. Це важлива дисципліна: сервіс не має сам вирішувати, які поля дозволені, це робить FormRequest і/або whitelist у сервісі. Якщо ти передаси в сервіс сирий $request->all(), ти повертаєшся до масових ризиків і непередбачуваних полів.

Валідація “входу” в сервісі — це не заміна FormRequest, а друга лінія захисту. Вона потрібна для двох речей. Перше — нормалізація: привести дані до потрібного вигляду (trim, lower, привести дату до формату, привести boolean). Друге — бізнес-правила, які не є “формальними” правилами валідації. Наприклад, “не можна ставити published_at, якщо статус не published”, або “не можна змінювати resource_id після публікації”, або “slug генерується автоматично, якщо не переданий”. Такі речі часто не лягають в прості правила required|max, але критично впливають на цілісність.

Найважливіша тема — транзакції. Транзакція потрібна завжди, коли одна операція змінює 2+ таблиці або кілька пов’язаних станів. Класичний приклад навіть для навчального модуля: ти створюєш тег і одразу записуєш активність “хто створив” у таблицю activity. Або ти оновлюєш розділ і синхронізуєш зв’язки в pivot-таблиці (belongsToMany). Якщо на другому кроці буде помилка, без транзакції перший крок уже запишеться, і ти отримаєш “напівзбережений” стан. Це завжди погано, бо потім важко відловити, чому дані виглядають дивно.

Логіка транзакції проста: або зберігається все, або не зберігається нічого. У Laravel це робиться через DB::transaction, і сервіс — ідеальне місце для цього, бо транзакція описує саме бізнес-операцію. Контролер не повинен думати про транзакції, він повинен думати про відповідь користувачу.

У create-сценарії стандартний потік такий. Сервіс бере валідовані дані, нормалізує їх, створює основний запис, потім виконує додаткові дії: синхронізує зв’язки, записує активність, додає метадані, і лише потім повертає створену модель. Якщо будь-який крок падає — транзакція відкатує все. Це гарантія, що база не перетвориться на “частково записане”.

У update-сценарії стандартний потік трохи інший. Ти завжди починаєш з того, що маєш модель (або її id) і дані. Сервіс визначає, які поля можна оновлювати в цьому сценарії, нормалізує їх, застосовує оновлення, і знову ж таки — якщо є додаткові таблиці або зв’язки, робить це в тій же транзакції. Дуже важлива дисципліна: update не повинен випадково перезаписати поля, які не були в формі. Тому або використовуються тільки ті ключі, що прийшли валідованими, або робиться явний whitelist.

Ще один реальний момент: сервіс — це правильне місце для “центральних” правил, які не можна дублювати. Наприклад, генерація slug. Якщо ти робиш slug у контролері, а потім ще десь — ти неминуче отримаєш різні правила і баги. Якщо slug робиться в сервісі або mutator’і моделі, він завжди буде однаковим. Але важливо не перетворювати mutator на “бізнес-движок”. Нормалізація — в mutator, сценарій — в сервіс.

Як виглядає правильний контролер поруч із сервісом. Контролер приймає FormRequest, бере $validated = $request->validated(), викликає $service->create($validated) або $service->update($model, $validated), і повертає redirect з flash-повідомленням. Це все. Якщо в контролері лишається складна логіка — значить сервіс недороблений або відповідальність змішана.

Типові помилки, які треба одразу прибрати. Перша — робити сервіс, але все одно тягнути $request всередину сервісу. Сервіс не має знати про HTTP. Друга — робити транзакцію в контролері, бо “так простіше”. Це ламає структуру. Третя — робити один “універсальний” метод saveEverything() на 500 рядків. Краще два чіткі методи create/update, і окремі приватні методи всередині сервісу для кроків.

Висновок: винесення create/update в Service — це не “архітектурна мода”, а спосіб тримати проєкт керованим. Контролер стає тонким і передбачуваним, логіка зосереджується в одному місці, її легше тестувати і розширювати. А транзакція в сервісі гарантує, що при роботі з 2+ таблицями ти не отримаєш напівзбережений стан, який потім зламає довіру до даних і з’їсть час на розслідування.


Максим Третяк — Кореспондент, який спеціалізується на суспільно важливих темах, пише про політику, фінансові ринки та економіку. Він проживає та працює в Україні.

Цей матеріал є частиною розгорнутої теми: Web-програмування, яка охоплює численні цікаві аспекти цієї події. Газета «Дейком» ретельно відстежує події, проводячи перевірку джерел та інформації, щоб забезпечити нашим читачам найбільш точне та актуальне інформування.

Цей матеріал опубліковано 30.12.2025 року о 18:00 GMT+3 Київ; 11:00 GMT-4 Вашингтон, розділ: Освіта, із заголовком: "Service layer у Laravel: як винести логіку з контролера в сервіс для create/update і не ламати систему". Якщо в публікації з'являться зміни, про це буде зазначено та описано у кінці публікації.

Читайте щоденну газету та загальну стрічку новин газети Дейком, яка поєднує багато цікавого в понад 40 розділах з усіх куточків світу.


Save
ОГОЛОШЕННЯ

Новини, які можуть Вас зацікавити:

Штатні та позаштатні журналісти газети «Дейком» щодня готують сотні публікацій, щоб читачі отримували найоперативнішу, перевірену й глибоку інформацію. Ми працюємо для тих, хто хоче розуміти суть подій, бачити широку картину та бути на крок попереду.

Останні новини

Вибір редакції