Паттерн MVC и жизненный цикл запроса

MVC — это способ разделить приложение на три слоя: данные, логику и внешний вид — чтобы код оставался понятным даже когда проект разрастается.

Суть: Model отвечает за данные и базу, View — за то, что видит пользователь, Controller связывает их вместе и принимает решения. Запрос идёт по строго заданному пути.

Когда приложение маленькое, можно свалить весь код в один файл. Но как только страниц становится десятки, такой проект превращается в кашу: SQL-запросы перемешаны с HTML, бизнес-логика — с версткой, и любая правка грозит всё сломать. Паттерн MVC (Model-View-Controller) решает эту проблему, раскладывая код по трём чётким полкам.

Идея проста. Контроллер — это диспетчер: он получает запрос, решает, что делать, обращается к моделям за данными и выбирает, какую страницу показать. Модель знает всё о данных: как достать товары из базы, как их сохранить, какие у них правила. View (представление) — это шаблон, который превращает данные в готовый HTML. Каждый слой занимается своим делом и не лезет в чужой.

Как работает под капотом: путь запроса

Любой запрос проходит один и тот же конвейер. Понимание этого пути — фундамент для отладки и осмысленной работы с фреймворком.

  ЖИЗНЕННЫЙ ЦИКЛ ЗАПРОСА В LARAVEL

  HTTP-запрос
      |
      v
  public/index.php        (единая точка входа)
      |
      v
  bootstrap/app.php       (сборка приложения)
      |
      v
  middleware (вход)       (сессии, CSRF, auth)
      |
      v
  Router                  (какой маршрут совпал?)
      |
      v
  Controller              (выполняет логику)
      |
      +--> Model --> База данных
      |
      v
  View (Blade)            (данные -> HTML)
      |
      v
  middleware (выход)      (правка ответа)
      |
      v
  HTTP-ответ -> Браузер

Обратите внимание: middleware пройдёт дважды — на входе (до контроллера) и на выходе (после). Это «слои фильтров», через которые проходит каждый запрос. О них будет отдельный урок.

Разбор на примере

Допустим, пользователь открывает страницу профиля. Маршрут передаёт запрос в контроллер, контроллер просит модель достать пользователя, а потом отдаёт его в шаблон:

<?php
// Controller: диспетчер
class ProfileController extends Controller
{
    public function show(int $id)
    {
        // 1. Просим Model достать данные
        $user = User::findOrFail($id);

        // 2. Передаём данные во View
        return view('profile.show', ['user' => $user]);
    }
}

Здесь видно разделение: контроллер не пишет SQL (это дело модели User) и не пишет HTML (это дело шаблона profile.show). Он только координирует. Смоделируем сам конвейер MVC на Python — как запрос последовательно проходит слои.

Попробуй сам ▶

# Мини-модель MVC: запрос проходит через слои
def model(user_id):                 # MODEL: данные
    db = {1: 'Анна', 2: 'Борис'}
    return {'id': user_id, 'name': db.get(user_id, 'неизвестно')}

def view(data):                     # VIEW: внешний вид
    return f"<h1>Профиль: {data['name']}</h1>"

def controller(user_id):            # CONTROLLER: координатор
    user = model(user_id)           # просим данные у модели
    return view(user)               # отдаём их во view

print('Запрос /profile/1')
print('Ответ:', controller(1))
print('Ответ:', controller(2))

Частые ошибки

  • «Толстые» контроллеры. Когда вся логика и SQL свалены в контроллер — это нарушение MVC. Бизнес-логика принадлежит моделям и сервисам.
  • SQL в шаблоне. View должен только отображать готовые данные, а не лезть в базу.
  • HTML в контроллере. Верстку собирает Blade, контроллер только передаёт данные.

Best practices

  • Держите контроллеры тонкими: получил запрос, вызвал модель/сервис, вернул view.
  • Сложную бизнес-логику выносите в отдельные классы-сервисы или методы моделей.
  • Думайте о каждом слое отдельно — это упрощает тесты и поддержку.

Полезно понимать роль сервис-провайдеров в жизненном цикле — это «настройщики» приложения. На этапе загрузки, ещё до маршрутизации, Laravel проходит по всем зарегистрированным провайдерам и вызывает их методы register() (привязка сервисов в контейнер) и boot() (финальная настройка). Именно здесь подключаются ваши сервисы, наблюдатели событий, политики. Сердце этого механизма — контейнер зависимостей (service container): когда контроллеру в конструкторе или методе нужен какой-то класс, контейнер сам его создаёт и подставляет. Это и есть внедрение зависимостей (dependency injection), благодаря которому код слабо связан и легко тестируется. Понимание того, что приложение сначала собирается провайдерами, а уже потом обрабатывает запрос, помогает осознанно расширять Laravel и не дублировать инициализацию в каждом контроллере.

Итог: MVC раскладывает приложение на данные, логику и вид, а жизненный цикл запроса проводит каждый HTTP-запрос по предсказуемому конвейеру. Зная этот путь, вы понимаете, где искать любую часть кода.

Проверьте себя
1. За что отвечает Controller в MVC?
AХранит данные и пишет SQL
BКоординирует: получает запрос, вызывает модель, выбирает view
CРисует HTML-разметку
DНастраивает сервер
2. Сколько раз запрос проходит через middleware за жизненный цикл?
AНи разу
BОдин раз — только на входе
CДважды — на входе и на выходе
DПостоянно в цикле