Junior: питання на співбесіді з теми «MVC та архітектура»
Найпопулярніші питання з реальних Laravel/PHP співбесід для всіх рівнів
2 питання
MVC (Model-View-Controller) - це архітектурний патерн, який розділяє застосунок на три шари, аби логіка обробки даних, бізнес-правила та відображення не змішувались між собою. Laravel побудований навколо MVC, тож кожному шару відповідає своя директорія.
Три складові:
- Model - відповідає за дані та бізнес-логіку: спілкування з базою, зв'язки, валідаційні правила домену. У Laravel моделі розширюють Eloquent і лежать у
app/Models. - View - відповідає лише за відображення. У Laravel це Blade-шаблони у
resources/views; вони не повинні містити складної логіки. - Controller - приймає HTTP-запит, координує роботу моделей, готує дані й повертає View або відповідь. Лежать у
app/Http/Controllers.
Як це працює разом: запит потрапляє на маршрут (routes/web.php), той викликає метод контролера. Контролер звертається до моделі за даними й передає їх у view:
class PostController extends Controller
{
// Route Model Binding: $post резолвиться автоматично
public function show(Post $post)
{
// контролер координує: бере дані з моделі й віддає у view
return view('posts.show', ['post' => $post]);
}
}
Навіщо: розділення відповідальностей спрощує тестування, підтримку й командну роботу - верстку можна змінити, не чіпаючи запити до БД, і навпаки. Laravel розширює класичний MVC сервісним контейнером, middleware та провайдерами.
Контролер - це шар HTTP: він приймає запит, дістає з нього дані, викликає бізнес-логіку й формує відповідь. Сервіс - це сама бізнес-логіка, яка про HTTP нічого не знає.
Як виглядає змішування:
public function store(Request $request)
{
$order = Order::create($request->validated());
$order->items()->createMany($request->input('items'));
$total = collect($request->input('items'))->sum(fn ($i) => $i['price'] * $i['qty']);
$order->update(['total' => $total]);
Mail::to($order->email)->send(new OrderPlaced($order));
Http::post('https://warehouse.example/orders', $order->toArray());
return redirect()->route('orders.show', $order);
}
Тут порахунок суми, лист і виклик складу існують лише всередині HTTP-запиту.
Те саме з сервісом:
class PlaceOrder
{
public function handle(array $data): Order
{
return DB::transaction(function () use ($data) {
$order = Order::create($data);
$order->items()->createMany($data['items']);
$order->update(['total' => $this->total($data['items'])]);
OrderPlaced::dispatch($order);
return $order;
});
}
}
public function store(StoreOrderRequest $request, PlaceOrder $placeOrder)
{
$order = $placeOrder->handle($request->validated());
return redirect()->route('orders.show', $order);
}
Що це дає:
- Ту саму логіку можна викликати з Artisan-команди, черги чи імпорту - не тільки з форми.
- Тестувати можна без HTTP-запиту: створили масив, викликали
handle(), перевірили результат. - Транзакція охоплює всю операцію цілком.
- Контролер знову читається за кілька секунд.
Не поспішайте. Для Post::create($request->validated()) окремий сервіс - зайвий шар. Виносити варто тоді, коли з'являється щось із трьох: кілька моделей в одній операції, потреба викликати логіку не з HTTP, або контролер перестав вміщатися на екран.