Жизненный цикл запроса в Laravel: как устроено внутри
От index.php до JSON-ответа запрос в Laravel проходит через контейнер, ядро, стек middleware и роутер. Разбираем порядок вызовов и точки, где чаще всего ломается поведение.

Когда вы открываете страницу на Laravel, между строкой в браузере и готовым HTML происходит около десятка передач управления: сервер отдаёт запрос в единственную точку входа, фреймворк собирает контейнер зависимостей, прогоняет объект Request через два слоя middleware, находит маршрут, вызывает контроллер и превращает результат в Response. Если вы не понимаете этот порядок, то плохо представляете, почему middleware из группы web не срабатывает на API-роутах, почему сервис-провайдер видит одни данные, а контроллер уже другие, и в какой момент вообще стартует сессия.
Разбор написан для Laravel 11.x. В этой версии структура bootstrap заметно изменилась по сравнению с 10.x и ниже: файла app/Http/Kernel.php в новых проектах больше нет, конфигурация middleware переехала в bootstrap/app.php. На это отдельно обращаю внимание — половина устаревших статей описывает старую схему.
Единственная точка входа
Любой HTTP-запрос к приложению приходит в public/index.php. Веб-сервер (Nginx или Apache) настроен так, что все URL, кроме существующих файлов, переписываются на этот файл. Внутри происходит четыре вещи по порядку:
- Подключается автозагрузчик Composer —
vendor/autoload.php. - Загружается приложение из
bootstrap/app.php— это фабрика, которая собирает и настраивает объектApplication(сервис-контейнер). - Из контейнера извлекается HTTP-ядро — реализация
Illuminate\Contracts\Http\Kernel. - Ядру передаётся объект
Request, построенный из глобальных массивов PHP ($_GET,$_POST,$_SERVERи других), а полученныйResponseотправляется в браузер.
Схематично последний шаг выглядит так:
// public/index.php (Laravel 11.x, упрощённо)
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);Обратите внимание на последнюю строку — terminate. Она выполняется уже после того, как ответ ушёл клиенту. Именно туда попадают «завершающие» задачи: запись сессии, отложенные задания у terminable middleware. Если вы кладёте тяжёлую логику в обычный контроллер, пользователь ждёт; если в terminate — он уже получил ответ.
Что делает ядро при загрузке
Метод handle у ядра оборачивает всё в try/catch (чтобы поймать исключение и превратить его в человекочитаемый ответ) и вызывает sendRequestThroughRouter. Перед тем как запрос доберётся до роутера, ядро прогоняет так называемые bootstrappers — набор классов, которые готовят окружение:
- загрузка переменных окружения из
.env; - загрузка и слияние конфигурации из каталога
config; - настройка обработки ошибок и логирования;
- регистрация фасадов;
- регистрация и загрузка сервис-провайдеров.
Здесь кроется частая путаница. У каждого сервис-провайдера два метода: register и boot. Сначала фреймворк вызывает register у всех провайдеров, и только потом boot у всех. Поэтому обращаться к сервису другого провайдера внутри register опасно — он может быть ещё не зарегистрирован. Всё, что зависит от готового контейнера, кладите в boot.
register — это «объявить, как собирать». boot — это «сделать, когда всё уже собрано». Смешаете местами — получите ошибку, которая воспроизводится только при определённом порядке провайдеров.
Два слоя middleware
После bootstrap запрос проходит через глобальный стек middleware — прослойки, которые выполняются для каждого запроса независимо от маршрута. В Laravel 11.x сюда входят, например, обработка trusted proxies, проверка на режим обслуживания и валидация размера POST. Настраиваются они в bootstrap/app.php в замыкании withMiddleware.
Дальше запрос попадает в роутер, и уже роутер применяет группы middleware — web и api. Это ключевой момент: группа web добавляет сессию, CSRF-защиту и работу с cookie, а группа api их не добавляет. Поэтому если вы обращаетесь к session() внутри контроллера, привязанного к API-роуту, вы получаете сессию, которая не сохраняется между запросами — стартующей её middleware в этом стеке просто нет.
Middleware работает по принципу «луковицы»: код до $next($request) выполняется на входе, код после — на выходе, в обратном порядке.
// app/Http/Middleware/MeasureTime.php (Laravel 11.x)
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class MeasureTime
{
public function handle(Request $request, Closure $next): Response
{
$startedAt = microtime(true);
// Передаём управление дальше по стеку
$response = $next($request);
// Этот код выполнится уже на обратном пути
$durationMs = round((microtime(true) - $startedAt) * 1000, 2);
$response->headers->set('X-Response-Time', $durationMs.'ms');
return $response;
}
}Порядок middleware в группе важен: тот, что зарегистрирован раньше, «оборачивает» те, что зарегистрированы позже. Если middleware аутентификации стоит позже middleware, которое уже читает пользователя, вы получите пустого пользователя.
Роутер и разрешение маршрута
Роутер сопоставляет метод и URI запроса с зарегистрированными маршрутами. Совпадение ищется по первому подходящему маршруту — не по «самому точному». Найдя маршрут, роутер выполняет привязку моделей (route model binding): если параметр типизирован как модель Eloquent, фреймворк сам находит запись по идентификатору из URL. Если записи нет — до контроллера дело не дойдёт, вернётся 404.
После этого управление уходит в контроллер или замыкание. Результат может быть чем угодно: строкой, массивом, объектом модели, Response. Всё, что не является объектом Response, фреймворк приводит к нему на следующем шаге — массив и модель превратятся в JSON, строка в HTML.
Обратный путь: как собирается ответ
Возвращённое значение поднимается обратно через тот же стек middleware, но в обратном порядке. Каждое middleware, у которого есть логика после $next($request), может модифицировать ответ — добавить заголовок, записать cookie. Сессия, например, сохраняется именно на обратном пути, а не в момент, когда вы пишете в неё в контроллере.
Затем ядро возвращает Response в index.php, вызывается send() — заголовки и тело уходят клиенту.
Где это ломается на практике
- Данные из сессии на API-роуте. Стек
apiне стартует сессию. Симптом:session()->get()всегда пустой между запросами. - CSRF-ошибка 419. Форма ушла на web-роут без токена
@csrf. MiddlewareVerifyCsrfTokenиз группыwebотклоняет запрос до контроллера. - Обращение к конфигу внутри
register. Конфиг ещё не загружен на момент части регистраций — читайте его вboot. - Логика в контроллере, которую можно вынести в
terminate. Пользователь ждёт завершения тяжёлой операции, хотя ответ уже готов. - Неверный порядок middleware. Прослойка, зависящая от результата другой, стоит перед ней в группе.
Как посмотреть цикл своими глазами
Теория без наблюдения плохо запоминается. Чтобы увидеть порядок вызовов на реальном проекте:
- Установите Laravel Debugbar или используйте встроенный Telescope — оба показывают, какие middleware и в каком порядке отработали.
- Поставьте
dd()или лог в разных точках: вregisterиbootодного провайдера, в middleware до и после$next, в контроллере. Сравните порядок строк в логе — он и есть жизненный цикл. - Команда
php artisan route:listпокажет, какие группы middleware привязаны к каждому маршруту. Это первое, что стоит проверить при непонятном 419 или пустой сессии.
Дальше имеет смысл разобраться с очередями и событиями — они меняют картину: часть работы уходит из синхронного цикла запроса в фоновые воркеры, и «жизненный цикл» задания живёт по своим правилам. Это отдельная тема, здесь мы рассматривали только синхронный HTTP-путь.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Laravel 11.x — Request Lifecycle (официальная документация), Laravel 11.x — Middleware (официальная документация)
Частые вопросы
У меня Laravel 10, а не 11 — статья подходит?
Почему session() пустой на моём маршруте?
Чем register отличается от boot в сервис-провайдере?
Что выполняется в методе terminate и зачем он нужен?
Я добавил middleware, но оно не срабатывает — что проверить?
Где посмотреть точный порядок middleware в проекте?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.