Argument resolvers
Argument resolvers відповідають за те, як саме параметри
методу handler-а отримують значення перед викликом.
Без них RouteStrategy не знав би, звідки взяти
ServerRequestInterface $request чи string $name
з URL — і довелося б або магічний switch у ядрі, або вручну
діставати все з request у кожному action.
Ядро проходить chain resolver-ів по черзі: перший, чий
supports() повернув true, формує значення через
resolve(). Навіщо саме chain: пріоритет і розширення
(FormRequest, casting) без змін RouteStrategy — лише
новий клас у масиві від збірки (Bootstrap / Stack).
Мета resolver-ів
- Розділити відповідальність: кожен resolver обслуговує один тип аргументів.
- Уникнути монолітного switch в
RouteStrategy. - Керувати пріоритетом через порядок у масиві (FormRequest має йти до generic request).
- Додавати нову логіку без змін ядра — власними resolver-класами.
Контракт
$request у resolve() — уже enriched (після
prepareRequest(), з route attributes). $vars —
сирі значення з $route->getVars(). Два джерела навмисно:
іноді потрібен string з vars, іноді attribute / body з request.
interface ArgumentResolverInterface
{
public function supports(ReflectionParameter $parameter, array $vars): bool;
public function resolve(
ReflectionParameter $parameter,
ServerRequestInterface $request,
array $vars,
): mixed;
}
Вбудовані resolvers
У Core лише два — мінімум для «request + URL params». Усе складніше (валідована форма, typed cast) живе в extensions і підставляється збіркою в той самий масив.
| Клас | Для чого | supports | resolve |
|---|---|---|---|
ServerRequestArgumentResolver |
Інжектити весь поточний HTTP request у метод handler-а | Тип параметра — ServerRequestInterface або підклас |
Enriched request |
RouteParameterArgumentResolver |
Підставити route параметр за іменем ({id}, {slug}, ...) |
array_key_exists($parameter->getName(), $vars) |
Значення з $vars (string) |
Який resolver коли спрацьовує
Порядок у сигнатурі методу не важливий для match — важливий
порядок у chain і supports(). Нижче обидва параметри
покриваються двома вбудованими resolvers:
// route: /hello/{name}
public function hello(ServerRequestInterface $request, string $name): ResponseInterface
{
// $request → ServerRequestArgumentResolver
// $name → RouteParameterArgumentResolver
}
Реєстрація chain
Порожній chain = handler без аргументів з request/URL (лише constructor DI). Базовий набір — два core resolvers; розширення вставляють себе перед generic, щоб специфічний type-hint не «з’їв» ServerRequest resolver.
new HttpKernelServiceProvider(
routePaths: [$routesFile],
resolvers: [
new ServerRequestArgumentResolver(),
new RouteParameterArgumentResolver(),
],
);
Якщо два resolver-и теоретично можуть підтримати один параметр, спрацює той, що стоїть першим у масиві — тому порядок є контрактом.
Fallback
Якщо жоден resolver не підійшов: спочатку default value параметра
(якщо є), інакше null. Якщо тип параметра не допускає
null, під час invoke можливий TypeError.
Навіщо м’який fallback: не ламати optional параметри; але для
обов’язкових type-hint краще мати resolver у chain, ніж ловити
TypeError у проді.
Власний resolver
Коли: аргумент з’являється часто (заголовок, tenant id, DTO) і
не хочете діставати його вручну в кожному action. Додайте instance
у масив resolvers у потрібній позиції — пріоритет без
зміни коду ядра.
final class HeaderArgumentResolver implements ArgumentResolverInterface
{
public function supports(ReflectionParameter $parameter, array $vars): bool
{
return $parameter->getName() === 'userAgent';
}
public function resolve(
ReflectionParameter $parameter,
ServerRequestInterface $request,
array $vars,
): mixed {
return $request->getHeaderLine('User-Agent');
}
}
Приклад handler
// GET /hello/{name}
public function hello(ServerRequestInterface $request, string $name): ResponseInterface