<? phpukraine PHP-ПЛАТФОРМА
⌕Пошук по платформі
КОНЦЕПЦІЯ DDD · КРОК 1 З 10

MVC і де живе логікаMVC

Маршрут веде запит у контролер, контролер бере дані й віддає їх у view. MVC ділить вхід, дані й показ, але нічого не каже про те, де жити бізнес-правилам. Тому контролери й виростають до 400 рядків.

6 хв читання Junior → Middle
01 · ЩО ЦЕ

Три ролі й одне питання без відповіді

У Laravel запит проходить знайомий шлях: маршрут з routes/web.php веде на метод , контролер бере дані з й повертає , тобто Blade-шаблон. У Symfony так само, тільки маршрут описано атрибутом #[Route] на методі, а шаблон це Twig, який рендерить render() з AbstractController.

СЛОВНИККонтролер. Клас, на який маршрут передає запит. Читає вхідні дані, викликає потрібний код і повертає відповідь: view, redirect чи JSON.
СЛОВНИКМодель. У MVC це дані й правила предметної області. У Laravel так часто називають лише Eloquent-клас, і звідси половина плутанини.
СЛОВНИКView. Шаблон, який показує готові дані: Blade у Laravel, Twig у Symfony. Нічого не вирішує й нічого не змінює.

MVC розводить вхід, дані й показ, але не відповідає, де мають жити правила на кшталт «відкликати відгук можна лише до рішення компанії». У Laravel-проєкті для них є два звичні місця. Перше: , у якому валідація, правило, запит до бази й лист живуть в одному методі. Друге: Eloquent-модель, у якій правила стоять поруч із $casts і звʼязками. Обидва варіанти привʼязують правило до HTTP або до таблиці.

СЛОВНИКТовстий контролер. Контролер, у якому живе бізнес-логіка. Її неможливо викликати з консолі чи черги й важко перевірити без HTTP.

У класичному MVC модель означає всю предметну область, дані разом із правилами. Eloquent-модель це : рядок таблиці з методами збереження. У PHP Ukraine модель із MVC розкладено на шари: сценарії в src/*/Application, правила в Domain, а Eloquent-моделі в Infrastructure, без бізнес-правил. Контролер лишається : перекладає запит на виклик сценарію чи query-сервісу й вибирає відповідь.

СЛОВНИКActive Record. Патерн, у якому обʼєкт це рядок таблиці й сам себе зберігає: save(), delete(). Так влаштовано Eloquent.
СЛОВНИКТонкий контролер. Контролер, який лише перекладає HTTP на виклик сценарію й результат на відповідь. Правил у ньому немає.
02 · МЕЖА

Шлях запиту до сторінки вакансії

ДІАГРАМА · УСЯ СХЕМА
Кроки: 4
find()view()маршрутroutes/web.phpGET /jobs/{idAndSlug}контролерJobControllershow()viewjobs/show.blade.php«interface»JobPageQueryfind()EloquentJobModelEloquentJobPageQuery
маршрутroutes/web.phpGET /jobs/{idAndSlug}контролерJobControllershow()viewjobs/show.blade.php«interface»JobPageQueryfind()EloquentJobModelEloquentJobPageQuery
Уся схема. Натисніть → або виберіть крок нижче, щоб пройти по ній частинами.
Ключовий учасник Домен Клієнт ✕ заборонено
  • routes/web.php використовує JobController.
  • JobController використовує JobPageQuery (find()).
  • EloquentJobPageQuery реалізує JobPageQuery.
  • EloquentJobPageQuery використовує JobModel.
  • JobController використовує jobs/show.blade.php (view()).
  • JobController не може змінювати JobModel напряму, лише через корінь.
  1. 1Контролер перекладає запит на виклик сценарію чи query-сервісу й результат на відповідь. Власних правил у ньому немає.
  2. 2Eloquent-модель зберігає й читає рядки. Бізнес-правила живуть у доменних класах.
  3. 3View отримує готові дані й нічого не дочитує з бази.
  4. 4Одна й та сама дія доступна з контролера, Livewire чи Artisan-команди, бо живе в сценарії.
03 · ЯК У PHP UKRAINE

Тонкий контролер відкликання відгуку

Маршрут POST /profile/applications/{id}/withdraw веде сюди. Контролер знаходить розробника, викликає сценарій WithdrawApplication і вибирає відповідь. Правило «відгук з рішенням не відкликати» живе в агрегаті Application, і контролер про нього не знає: він лише показує текст помилки. Файл резюме видаляє контролер, бо сховище не справа домену. Сам ProfileController при цьому великий: у ньому весь кабінет розробника, і така довжина вже підказує поділити його на кілька контролерів.

php ProfileController.php фрагмент, скорочено
1class ProfileController extends Controller
2{
3 public function withdrawApplication(Request $request, int $id, WithdrawApplication $withdraw): RedirectResponse← сценарій з контейнера
4 {
5 $user = $this->developer($request);
6
7 try {
8 $result = $withdraw->handle($id, $user->id);← уся логіка тут
9 } catch (\InvalidArgumentException $e) {← відмова домену
10 return back()->with('error', $e->getMessage());
11 }
12
13 if ($result['resumePath'] !== null) {
14 Storage::disk(self::resumeDisk())->delete($result['resumePath']);← файл не справа домену
15 }
16
17 return redirect()->route('profile.applications.show', $id)->with('status', 'Відгук відкликано. Компанія бачить лише, що ви його забрали.');← відповідь
18 }
19}
04 · ТИПОВІ ПОМИЛКИ

Куди зазвичай тікає логіка

✕ 1

Усе в контролері

Метод store() валідує форму, рахує вилку, пише в три таблиці й шле лист. Ту саму дію потрібно запустити з Artisan-команди, і код копіюють.

Натомість: правило в домені, дія в сценарії чи Action-класі. Контролер, Livewire-компонент і команда викликають той самий клас.

✕ 2

Товста Eloquent-модель

Модель на 1000 рядків: правила, scope, звʼязки, хук booted(), який шле листи. Перевірити правило без бази неможливо, а будь-який save() у сідері відправляє пошту.

Натомість: Eloquent лише зберігає й читає. Правила в простих PHP-класах, які тестуються без Laravel.

✕ 3

Запити з view

Шаблон звертається до $job->company->name у циклі, і сторінка робить сотню запитів. Логіку показу ніде не видно, крім Blade.

Натомість: контролер чи query-сервіс віддає готовий DTO, як JobPage у PHP Ukraine. View лише виводить його поля.

05 · ДЕРЕВО РІШЕНЬ

Чи час виносити логіку з контролера

1
Чи є в дії правила, складніші за валідацію форми?
Наприклад: відкликати відгук можна лише доти, доки компанія не прийняла рішення.
НІ →Лишайте як є. Контролер, Form Request і модель для такої дії це нормальний Laravel.
2
Чи викликають цю дію ще звідкись, крім одного контролера?
Livewire-компонент, Artisan-команда, API, задача в черзі.
НІ →Можна почекати. Винесіть логіку, щойно зʼявиться друге місце виклику.
3
Чи хочете ви перевіряти правило без HTTP і бази?
Unit-тест на мілісекунди замість feature-тесту з міграціями.
НІ →Винесіть дію в Action-клас поруч із фреймворком і перевіряйте feature-тестами.
ТАК НА ВСІ →Час. Сценарій у шарі застосунку, правило в домені, а контролер лише перекладає запит.
06 · СЛОВНИК
Контролер
Клас, на який маршрут передає запит. Читає вхідні дані, викликає потрібний код і повертає відповідь: view, redirect чи JSON.
Модель
У MVC це дані й правила предметної області. У Laravel так часто називають лише Eloquent-клас, і звідси половина плутанини.
View
Шаблон, який показує готові дані: Blade у Laravel, Twig у Symfony. Нічого не вирішує й нічого не змінює.
Товстий контролер
Контролер, у якому живе бізнес-логіка. Її неможливо викликати з консолі чи черги й важко перевірити без HTTP.
Active Record
Патерн, у якому обʼєкт це рядок таблиці й сам себе зберігає: save(), delete(). Так влаштовано Eloquent.
Тонкий контролер
Контролер, який лише перекладає HTTP на виклик сценарію й результат на відповідь. Правил у ньому немає.
07 · ПЕРЕВІР СЕБЕ

Питання зі співбесід

Усі питання з архітектури →

Прийняти запит, який передав маршрут, перевірити вхідні дані, викликати код, що виконує дію, і повернути відповідь: view, redirect чи JSON. Бізнес-правил у ньому бути не повинно.

Коли правил мало, у моделі чи Form Request це прийнятно. Коли їх багато, обидва місця погані: контролер відповідає за HTTP, Eloquent-модель за таблицю. Логіку виносять у сценарій чи Action-клас і доменні обʼєкти, які перевіряються без бази.

Ні. M у MVC означає всю предметну область, дані й правила. Eloquent-модель реалізує Active Record і повʼязана з однією таблицею. Якщо їх ототожнити, правила опиняються поруч зі схемою бази, а будь-яка зміна таблиці зачіпає бізнес-логіку.