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

HTTP-запит проходить від веб-сервера через public/index.php, реєстрацію service providers, резолв Router і PSR-7 Request, диспетчеризацію League Route і відправку відповіді через SapiEmitter.

Передумови: налаштування точок входу, конфігурація.

Огляд

flowchart TB
    WS["Веб-сервер"]
    IDX["public/index.php"]
    BOOT["bootstrap/app.php"]
    PROV["registerServiceProviders()"]
    RUN["App::run()"]
    ROUTER["Router::dispatch(Request)"]
    MW["Middleware stack"]
    STRAT["RouteStrategy"]
    INT["Route interceptors"]
    RES["Autowire: FormRequest, params, Request"]
    CTRL["Controller / closure"]
    EMIT["SapiEmitter"]

    WS --> IDX --> BOOT --> PROV
    IDX --> RUN --> ROUTER --> MW --> STRAT
    STRAT --> INT --> RES --> CTRL --> EMIT
Веб-сервер → public/index.php
    → vendor/autoload.php
    → bootstrap/app.php
        → App::create() + registerServiceProviders()
            → для кожного провайдера: register(), потім boot()
            → ComponentsServiceProvider::boot() підключає routes компонентів
    → App::run()
        → container->get(RouterInterface)
        → container->get(ServerRequestInterface)
        → Router::dispatch(Request)
            → middleware (глобальний + маршруту)
            → RouteStrategy::invokeRouteCallable()
                → route interceptors
                → autowire аргументів handler
            → PSR-7 Response
        → SapiEmitter → клієнт

1. Точка входу

public/index.php — єдиний файл, на який має дивитися document root. Він не містить бізнес-логіки:

require_once __DIR__ . '/../vendor/autoload.php';

/** @var \Concept\Core\App $app */
$app = require_once __DIR__ . '/../bootstrap/app.php';

$app->run();

2. Bootstrap (до run)

bootstrap/app.php виконує:

  1. App::create($rootPath, $paths) — League Container, ReflectionContainer, PathManager, ранній Whoops (читає APP_DEBUG з $_ENV).
  2. registerServiceProviders() — підключення списку з bootstrap/providers/app.php.

League Container для кожного провайдера викликає register(), потім одразу boot() (якщо реалізовано BootableServiceProviderInterface) — див. Провайдери → register і boot. Порядок у providers/app.php критичний: провайдер N у boot() може використовувати сервіси лише від провайдерів вище у списку.

Порядок провайдерів (HTTP, skeleton)

Фактичний порядок у bootstrap/providers/app.php:

  1. ConfigServiceProvider — конфіг, .env, часовий пояс
  2. LocaleServiceProvider — локаль для перекладів валідації
  3. TelemetryServiceProvider — збір телеметрії (якщо увімкнено)
  4. ErrorHandlerServiceProvider — Whoops (HTML / JSON / production fallback)
  5. HttpServiceProvider — PSR-7 запит, Router, фабрики відповідей
  6. SessionServiceProvider — Symfony Session (стартує при першому зверненні)
  7. DataMaskerServiceProvider — маскування чутливих даних у логах
  8. LogServiceProvider — Monolog
  9. ViewRegistryServiceProvider, TwigServiceProvider
  10. ValidationServiceProvider, DatabaseServiceProvider, CastingServiceProvider
  11. DebugLoggerServiceProvider — in-memory debug log (dev)
  12. ComponentsServiceProvider — модулі (останній у списку)

Що робить ComponentsServiceProvider::boot()

ComponentsServiceProvider стоїть останнім, щоб у boot() уже були ConfigInterface, RouterInterface, view registries тощо. Під час boot() (ще до App::run()), якщо увімкнені компоненти:

  • реєструються CLI-команди, міграції та сидери компонентів;
  • підключаються додаткові service providers компонентів (addServiceProvider());
  • require файлів routes.php кожного компонента — маршрути додаються в той самий Router;
  • реєструються Twig paths, extensions, contexts для views компонентів (лише HTTP, не CLI).

Глобальні маршрути з routes/web.php підключаються при першому резолві RouterInterface у фабриці HttpServiceProvider — зазвичай під час ComponentsServiceProvider::boot(), коли він запитує Router для component routes.

3. PSR-7 запит

ServerRequestInterface створюється в контейнері через Laminas\Diactoros\ServerRequestFactory::fromGlobals() — дані з $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES. У типовому HTTP-циклі резолв відбувається в App::run() перед dispatch().

Після збігу маршруту RouteStrategy додає параметри URL у атрибути запиту ({id}$request->getAttribute('id')) і оновлює shared-екземпляр у контейнері.

4. App::run()

/** @var RouterInterface $router */
$router = $this->container->get(RouterInterface::class);
/** @var ServerRequestInterface $request */
$request = $this->container->get(ServerRequestInterface::class);

$response = $router->dispatch($request);
(new SapiEmitter)->emit($response);

Router (клас Concept\Core\Http\Routing\Router) — обгортка над League Route з RouteStrategy: DI параметрів handler, FormRequest, route interceptors, телеметрія.

5. Middleware

Middleware реєструється в файлах маршрутів через $router->lazyMiddleware(ClassName::class) (League Route). Класи створюються з контейнера під час обробки запиту. Кожен middleware реалізує MiddlewareInterface::process($request, $handler) і викликає $handler->handle($request) для передачі далі по ланцюгу.

У ядрі є, зокрема:

  • HandleValidationExceptionMiddleware — перетворює ValidationException на відповідь
  • VerifyCsrfTokenMiddleware — CSRF для небезпечних методів
  • StorePreviousUrlMiddleware — зберігає URL для редіректів
  • ShareViewDataMiddleware — спільні змінні для Twig (csrf_token тощо)
  • ParseJsonBodyMiddleware, ForceJsonResponseMiddleware — JSON API

Мінімальний skeleton не підключає middleware глобально — лише маршрут /. Детальніше — Middleware.

6. RouteStrategy: interceptors і handler

Після middleware League Route викликає RouteStrategy::invokeRouteCallable():

  1. Route interceptors — класи з config/routes.phproutes.interceptors (RouteInterceptorInterface::intercept()); для ACL, політик тощо — див. Route interceptors.
  2. Параметри URL у Request{id} стає атрибутом запиту.
  3. Autowire аргументів handler — для методу контролера або closure:
    • FormRequest — з контейнера, валідація; при помилці — ValidationException
    • ServerRequestInterface — поточний запит
    • параметри маршруту — з приведенням типів через CasterInterface (Valinor)
    • значення за замовчуванням у сигнатурі

Приклад маршруту:

$router->get('/', [IndexController::class, 'index'])->setName('home');

Контролер [Class, 'method'] резолвиться з контейнера — залежності конструктора (ViewResponseFactoryInterface тощо) інжектуються через League Container + ReflectionContainer. Детальніше — Контролери → автовайринг, аргументи методу.

Якщо telemetry.enabled, RouteStrategy фіксує TelemetryEvent::HTTP_ROUTE_INTERCEPTOR_EXECUTED (кожен interceptor) і TelemetryEvent::HTTP_ROUTE_CALLABLE_INVOKED (час виконання handler).

7. Відповідь

Handler повертає ResponseInterface (PSR-7). Типові варіанти:

  • ViewResponseFactoryInterface::create('@frontend/index') — HTML через Twig
  • ResponseFactoryInterface — JSON, redirect, порожня відповідь

SapiEmitter (Laminas HttpHandlerRunner) відправляє заголовки та тіло в PHP SAPI (Apache, FPM, built-in server). Детальніше — Відповіді.

8. Обробка помилок

  • До провайдерів — ранній Whoops у App::create() (читає APP_DEBUG з $_ENV).
  • ErrorHandlerServiceProvider::boot() — перемикає Whoops: PrettyPage (debug), PlainText (CLI), JSON (RequestFormat::expectsJson()), production fallback з PathName::ERRORS_FALLBACK_VIEWS.
  • ВалідаціяValidationException з FormRequest; HandleValidationExceptionMiddleware може повернути 422 / redirect.
  • CSRFCsrfException при невалідному токені.
  • 404 / метод — League Route; відповідь залежить від налаштувань router і middleware.

9. Телеметрія (опційно)

Якщо telemetry.enabled = true, ядро записує етапи bootstrap і HTTP через TelemetryCollector: пробудження сервісів (FRAMEWORK_SERVICE_AWAKENING), реєстрацію компонентів, завантаження route-файлів (FRAMEWORK_ROUTES_REGISTERED), span interceptor і handler, підсумок запиту (HTTP_REQUEST_HANDLED у App::run()). Опційно: SQL (telemetry.db_queries) і дзеркало логів (telemetry.logs).

CLI — окремий цикл

bin/console.php не викликає App::run(). Використовує той самий paths.php і App::create(), але провайдери з bootstrap/providers/console.php і Symfony Console Application::run(). HTTP middleware, PSR-7 запит і Router::dispatch() у CLI не застосовуються.