Контролери
У документації «контролер» — клас-handler з публічними методами,
на які вказують маршрути. Окремого базового
Controller у Core немає: навіщо зайвий шар, якщо
League Route і так резолвить instance з DI, а параметри методу —
через argument resolvers.
Тримайте handler тонким: бізнес-кроки сторінки в методі, інфраструктура (CSRF, flash, shared view data) — у middleware / extensions. Так легше читати історію endpoint і тестувати без «глобальних хелперів».
Прив’язка до маршруту
Class-string у route file — контракт: контейнер створить handler,
ви не робите new у routes. Це дозволяє підміняти
залежності в тестах і не тягнути wiring у HTTP surface.
$router->get('/ping', [PingHandler::class, 'ping']);
$router->get('/hello/{name}', [PingHandler::class, 'hello']);
Constructor injection
Залежності конструктора резолвляться
ReflectionContainer delegate — якщо сервіси
зареєстровані або самі автовайряться. Контракти (інтерфейси)
зареєструйте у власному service provider: Reflection не знає,
яку реалізацію обрати.
final class PingHandler
{
public function __construct(
private readonly ClockInterface $clock,
) {}
public function ping(): ResponseInterface
{
return new JsonResponse(['time' => $this->clock->now()]);
}
}
Параметри методу
Те, що змінюється від запиту до запиту (request, {name}),
— у сигнатурі методу, не в constructor. Інакше shared controller
тримав би «минулий» request. ServerRequestInterface —
через ServerRequestArgumentResolver;
string $name — через
RouteParameterArgumentResolver (плейсхолдер
{name}).
public function hello(ServerRequestInterface $request, string $name): ResponseInterface
{
return new JsonResponse([
'name' => $name,
'path' => $request->getUri()->getPath(),
]);
}
Відповідь
Handler повертає ResponseInterface. Інакше
RouteStrategy кидає RuntimeException —
ядро не вгадує, чи ви хотіли JSON чи HTML. На рівні Core зручні
три базові формати Diactoros; ResponseFactory з Http extension —
коли збірка вже підключила brick Http.
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\Response\HtmlResponse;
use Laminas\Diactoros\Response\TextResponse;
return new JsonResponse(['ok' => true]);
return new HtmlResponse('<h1>Hello</h1>', 200);
return new TextResponse('pong', 200);
Приклад контролера для HTML
Рядок HTML у handler — ок для мінімального демо й health page. Коли з’являться layout, partials, escaping у багатьох місцях — підключайте View extension (Twig тощо); це вже поза межами ядра, і так і має бути.
use Laminas\Diactoros\Response\HtmlResponse;
use Psr\Http\Message\ResponseInterface;
final class PageHandler
{
public function home(): ResponseInterface
{
$html = '<!doctype html><html lang=\"uk\"><body>'
. '<h1>Мінімальна HTML-сторінка</h1>'
. '<p>Сторінка віддана напряму з core handler.</p>'
. '</body></html>';
return new HtmlResponse($html, 200);
}
}
RequestContext
Під час handler RequestContextInterface містить
enriched request. Коли корисно: сервіс глибоко в call stack
потребує поточного request, а прокидати параметр через усі шари
боляче. Не зловживайте — для action достатньо type-hint у методі.
Після handler context скидається (reset()), щоб
наступний запит не бачив чужих attributes.
$enriched = ContainerDependency::get($container, RequestContextInterface::class)->current();