Http brick
Без HTTP capability немає маршрутів, контролерів і PSR-7
відповідей — лише «голий» Core без web-входу. addHttp()
на ConceptStack вмикає саме це:
kernel, router, response factory. Повертає HttpBuilder
для того, що відрізняє ваш додаток: які route-файли,
чи потрібні FormRequest у сигнатурі, чи JSON-404 замість League
default. Мінімальна збірка — у рецепті
Перший HTTP-запит.
Middleware lists живуть у routes/*.php, не в Stack:
порядок pipeline — історія запиту, а не глобальний конфіг brick.
Stack відповідає за «що взагалі вміє kernel», routes —
за «як обробляється конкретна група URL».
Методи HttpBuilder:
| Метод | Параметри | Опис | Залежність |
|---|---|---|---|
setRoutes() |
list<string> $routes |
Абсолютні шляхи до route-файлів ($router у scope) |
— (обов’язково) |
setInterceptors() |
list<RouteInterceptorInterface|Closure(ContainerInterface): mixed> $interceptors |
Глобальні route interceptors перед invoke handler-а | — |
setNotFoundMiddleware() |
MiddlewareInterface|Closure(ContainerInterface): mixed|null $middleware |
Кастомний 404 middleware замість League default | — |
withFormRequests() |
— | FormRequest у argument resolver chain | addValidation() |
withTypedRouteParameters() |
— | Явний Valinor-cast route params (суворіше за Reflection; DTO з URL) | addCasting() |
addHttp()
$stack = ConceptStack::create();
$stack->addHttp()
->setRoutes([$rootPath . 'routes/web.php']);
Точка входу: реєструє HTTP capability і повертає builder. Без цього виклику View, ErrorHandling з HTML-fallback і UrlGenerator не мають сенсу — їм потрібен уже зібраний Http. Модель збірки — у огляді Stack.
setRoutes()
$stack->addHttp()
->setRoutes([
$rootPath . 'routes/web.php',
$rootPath . 'routes/api.php',
]);
Навіщо окремі файли, а не один гігантський список у Bootstrap:
web і API мають різний middleware (CSRF / flash vs JSON), і їх
зручно розвести. HttpKernel під час boot створює League
$router, робить require кожного файла
(змінна вже в scope) і реєструє
маршрути. Порядок у масиві = порядок
підключення. Без хоча б одного шляху збірка не пройде
валідацію — інакше kernel «вміє HTTP», але не знає жодного URL.
Приклад одного файла і GET / —
у кроці 1.
setInterceptors()
$stack->addHttp()
->setRoutes([$rootPath . 'routes/web.php'])
->setInterceptors([
static fn (ContainerInterface $c) => $c->get(AuthRouteInterceptor::class),
static fn (ContainerInterface $c) => $c->get(LocaleRouteInterceptor::class),
]);
Коли логіка залежить від метаданих маршруту
(name / path / метод), а не від «будь-якого» PSR-15
запиту — interceptors зручніші за middleware. Вони виконуються в
RouteStrategy після match і
перед resolve аргументів / викликом
контролера (див.
RouteInterceptorInterface).
Типовий кейс — ACL за іменем маршруту. На відміну від
middleware, список тут глобальний
для всього kernel; per-route лишається в route-файлах.
Порядок у масиві зберігається. Передавайте instance
або Closure(ContainerInterface); class-string Core
більше не резолвить — фабрика викликає
$c->get(...) сама.
setNotFoundMiddleware()
$stack->addHttp()
->setRoutes([$rootPath . 'routes/api.php'])
->setNotFoundMiddleware(
static fn (ContainerInterface $c) => $c->get(NotFoundJsonMiddleware::class),
);
За замовчуванням League кидає NotFoundException —
зручно для HTML з Whoops/fallback, погано для API-клієнта, який
чекає JSON. Метод замінює 404 на власний PSR-15
middleware
(instance або
Closure(ContainerInterface): MiddlewareInterface —
не class-string). Коли не викликати: лишається core default.
Параметр потрапляє в
RouteStrategy::getNotFoundDecorator().
withFormRequests()
$stack->addValidation();
$stack->addHttp()
->setRoutes([$rootPath . 'routes/web.php'])
->withFormRequests();
Opt-in саме тоді, коли action має приймати
ContactFormRequest $form без ручного
FormRequestFactory::make(). Додає
FormRequestArgumentResolver на початок
resolver chain.
Stack робить require(http, validation) — потрібен
addValidation(). Factory уже є після validation;
цей метод потрібен лише для auto-injection. Без нього type-hint
FormRequest просто не резолвиться — як у ранніх кроках рецепта,
поки валідацію ще викликали вручну.
withTypedRouteParameters()
$stack->addCasting();
$stack->addHttp()
->setRoutes([$rootPath . 'routes/web.php'])
->withTypedRouteParameters();
Route vars з URL завжди string. Core викликає
action через ReflectionMethod::invokeArgs: для
простого int $id PHP у weak mode часто сам
коерсить "1" → int — без цього
opt-in і без Casting. Це side effect Reflection, не заявлений
typed-routing API.
withTypedRouteParameters() вставляє
TypedRouteParameterArgumentResolver
(Valinor / Casting)
перед raw
RouteParameterArgumentResolver: явний cast
до invokeArgs. Потребує
addCasting(). Навіщо, якщо Reflection уже «робить
int»:
- суворіші правила (наприклад
"1.5"→ помилка, а не тихий1) CastingExceptionзамість гологоTypeError- не лише scalar — DTO / value object з сегмента URL
Без opt-in лишаються базові resolvers з Core: сирий string у аргументах + можлива coercion Reflection на виклику.
Порядок argument resolvers
Порядок важливий: FormRequest має впасти до controller,
якщо дані погані; typed route (якщо увімкнено) — до
raw string resolver, щоб Valinor встиг до Reflection coercion.
HttpStackProvider збирає ланцюжок так
(деталі — Argument resolvers):
FormRequestArgumentResolver— лише післяwithFormRequests()ServerRequestArgumentResolver— завжди (PSR-7 request)TypedRouteParameterArgumentResolver— лише післяwithTypedRouteParameters()RouteParameterArgumentResolver— завжди (сирі route vars)
Розширена збірка
$stack = ConceptStack::create();
$stack->addCasting();
$stack->addValidation()
->withLogging($rootPath . 'storage/logs/validation.log');
$stack->addHttp()
->setRoutes([
$rootPath . 'routes/web.php',
$rootPath . 'routes/api.php',
])
->setInterceptors([
static fn (ContainerInterface $c) => $c->get(AuthRouteInterceptor::class),
])
->withTypedRouteParameters()
->withFormRequests()
->setNotFoundMiddleware(
static fn (ContainerInterface $c) => $c->get(NotFoundJsonMiddleware::class),
);
Capabilities реєструють окремими гілками від кореня
$stack, не змішуючи fluent-контекст. Порядок
add* у коді не визначає порядок providers: Stack
топологічно ставить залежності (validation, casting) перед http —
щоб не думати про це вручну.
Що дає extension-http
Builder не конфігурує сервіси пакета напряму: після
addHttp() у контейнері з’являються
Http
ResponseFactory / UrlGenerator (і console
route:list). Controller інжектить factory і пише
body — без знання про Stack. Життєвий цикл від entry point до emit —
у Життєвий цикл запиту.