Життєвий цикл запиту

Життєвий цикл запиту

Що відбувається від моменту, коли браузер постукав на сайт, до моменту, коли віддається 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 на проді трейс не витече.