Жизненный цикл запроса

Жизненный цикл запроса

Что происходит от момента, когда браузер постучался на сайт, до момента, когда отдаётся HTML.

Шаг за шагом

  1. Веб-сервер. nginx стоит спереди и отдаёт статику (css/js/img) напрямую. PHP-запросы проксируются в Apache/PHP-FPM, который по .htaccess направляет всё на index.php (фронт) или admin.php (админка).
  1. Точка входа. index.php проверяет наличие install.lock (иначе — редирект на установку) и подключает core/bootstrap.php.
  1. Bootstrap + Kernel. core/bootstrap.php создаёт Kernel, который:
    • подключает Autoloader (PSR-4);
    • строит DI-Container;
    • читает Config из .env и config/*.php;
    • регистрирует сервисы: Connection (БД, ленивый), Engine (шаблоны), Router, Logger, Locale, Translator, Auth, Options и др.;
    • запускает сессию (если не CLI).
  1. Локаль. Определяется активный язык (Locale::detect(): ?lang → cookie → Accept-Language → дефолт) и устанавливается в Translator.
  1. Маршруты. bootstrap.php регистрирует публичные и админские маршруты. В конце подключаются активные плагины (PluginManager::load()) — их plugin.php добавляют свои маршруты, хуки и теги.
  1. Диспетчеризация. Kernel::handle($request) вызывает Router::dispatch(). Роутер сопоставляет путь с шаблонами маршрутов и находит обработчик.
  1. Middleware. Если у маршрута есть middleware (например AuthMiddleware для /admin/*), запрос проходит через них.
  1. Контроллер. Роутер достаёт контроллер из контейнера (с авто-внедрением зависимостей) и вызывает метод (Request $request, array $params). Контроллер возвращает Response.
  1. Ответ. Kernel::send($response) выставляет статус, заголовки и отдаёт тело.

Роутер

Маршрут регистрируется так:

$router->get('/news/{slug:[^/]+}', NewsController::class . '@publicShow');
$router->post('/api/leads/submit', LeadsController::class . '@submit');

$router->group(['prefix' => '/admin', 'middleware' => [AuthMiddleware::class]], function (Router $r) {
    $r->get('/users', UsersController::class . '@index');
});
  • {slug} — параметр (по умолчанию [^/]+), можно задать своё регулярное выражение: {id:\d+}.
  • Группы добавляют общий префикс и middleware.
  • Обработчик Class@method ленится: класс создаётся через контейнер только при совпадении маршрута.

Контейнер и авто-внедрение

Container создаёт объекты по рефлексии: смотрит типы аргументов конструктора и подставляет нужные сервисы. Поэтому контроллер просто объявляет зависимости:

final class MyController
{
    public function __construct(
        private readonly Engine $engine,
        private readonly Options $options,
    ) {}

    public function show(Request $request, array $params): Response
    {
        $this->engine->setTheme((string) $this->options->get('theme', 'default'));
        return Response::html($this->engine->render('my-template', ['x' => 1]));
    }
}

Регистрации: bind() — новый экземпляр на каждый запрос, singleton() — один на приложение, instance() — заранее готовый объект.

Response

Объект ответа строится фабриками:

Response::html($html);            // text/html
Response::json($data);            // application/json
Response::redirect('/url');       // 302
Response::notFound();             // 404
Response::text('ok');             // text/plain

Обработка ошибок

В production (APP_ENV=production) трассировка наружу не отдаётся — пользователь видит «500 Server Error», а детали пишутся в storage/logs. В dev-режиме показывается полный трейс. Это поведение принудительное: даже при случайно оставленном APP_DEBUG=true на проде трейс не утечёт.