Расширение разрещения аргумента действия
Дата обновления перевода 2024-07-17
Расширение разрещения аргумента действия
В руководстве о контроллерах, вы узнали, что вы можете получить объект
Request через аргумент в вашем контроллере.
Этот аргумент должен иметь подсказку в виде класса Request
, чтобы быть распознанным.
Это делается через ArgumentResolver.
Создавая и регистрируя пользовательские разрешители значений, вы можете расширить эту
функциональность.
Встроенные разрешители значений
Symfony поставляется со следующими разрешителями значений в компоненте HttpKernel:
- BackedEnumValueResolver
-
Пытается разрешить случай исчисляемого бэк-энда из параметра пути маршрута, который совпадает с именем аругмента. Ведёт к ответу 404 "Не найдено", если значение не является валидным опорным значением для типа исчисления.
Например, если ваше исчисление бэк-энда:
1 2 3 4 5 6 7 8 9
namespace App\Model; enum Suit: string { case Hearts = 'H'; case Diamonds = 'D'; case Clubs = 'C'; case Spades = 'S'; }
А ваш контроллер содержит следующее:
1 2 3 4 5 6 7 8 9 10
class CardController { #[Route('/cards/{suit}')] public function list(Suit $suit): Response { // ... } // ... }
При запросе URL
/cards/H
, переменная$suit
будет хранить случайSuit::Hearts
.Более того, вы можете ограничить разрешённые значения параметра маршрута до одного (или более) с помощью
EnumRequirement
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use Symfony\Component\Routing\Requirement\EnumRequirement; // ... class CardController { #[Route('/cards/{suit}', requirements: [ // это позволяет все значения, определённые в Enum 'suit' => new EnumRequirement(Suit::class), // это ограничивает возможные значения до значений Enum, перечисленных здесь 'suit' => new EnumRequirement([Suit::Diamonds, Suit::Spades]), ])] public function list(Suit $suit): Response { // ... } // ... }
Пример выше позволяет запрос только URL
/cards/D
и/cards/S
, и приводит к ответу 404 "Не найдено" в двух других случаях.
RequestPayloadValueResolver сопоставляет полезную нагрузку запроса или строку запроса с объектом с подсказкой типа.
Поскольку это целевой разрешитель значений , вам придется использовать либо MapRequestPayload либо атрибут MapQueryString . для использования этого разрешителя.
- RequestAttributeValueResolver
- Пробует найти атрибут запроса, который совпадает с именем аргумента.
- DateTimeValueResolver
-
Пробует найти атрибут запроса, который совпадает с именем аргумента и внедряет объект
DateTimeInterface
, если есть подсказка класса, расширяющегоDateTimeInterface
.По умолчанию, любой ввод, который можно разобрать как строку даты путём PHP, принимается. Вы можете ограничить то, как может быть отформатирован ввод, с помощью атрибута MapDateTime.
Tip
Объект
DateTimeInterface
создается с помощью компонента Clock. Это дает вам полный контроль над значениями даты и времени, которые контроллер получает при тестировании вашего приложения и использовании реализации MockClock. - RequestValueResolver
-
Внедряет текущий
Request
, если есть подсказкаRequest
или класса, расширяющегоRequest
. - ServiceValueResolver
- Внедряет сервис, если есть подсказка в виде валидного класса сервиса или интерфейса. Это работает как автомонтирование.
- SessionValueResolver
-
Внедряет сконфигурированный класс сессии, реализующий
SessionInterface
, если есть подсказкаSessionInterface
или класса, реализующегоSessionInterface
. - DefaultValueResolver
- Установит значение по умолчанию для аргумента, если он присутствует и аргумент необязательный.
- UidValueResolver
-
Пробует прреобразовать любые значения UID из параметра пути маршрута в объекты UID. Приводит к ответу 404 "Не найдено", если значение не является валидным UID.
Например, следующее преобразует параметр токена в объект
UuidV4
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/DefaultController.php namespace App\Controller; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Uid\UuidV4; class DefaultController { #[Route('/share/{token}')] public function share(UuidV4 $token): Response { // ... } }
- VariadicValueResolver
- Верифицирует, являются ли данные запроса массивом, и добавит их все к списку аргументов. Когда вызывается действие, последний (вариативный) аргумент будет содержать все значения этого массива.
Кроме того, некоторые компоненты и официальные пакеты предоставляют другие разрешители значений:
- UserValueResolver
-
Внедряет объект, который представляет текущего пользователя в системе, если есть подсказка
UserInterface
. Вы можете также добавить подсказку собственного классаUser
, но вы должны затем добавить атрибут#[CurrentUser]
к аргументу. Значение по умолчанию может быть установлено какnull
в случае, если к контроллеру можно получить доступ анонимным пользователям. Требует установки SecurityBundle.Если аргумент не может быть null и нет пользователя в системе, или пользователь в системе имеет класс пользователя, не совпадающий с классом подсказки, разрешителем вызыввается
AccessDeniedException
, чтобы предотвратить доступ к контроллеру. - SecurityTokenValueResolver
-
Внедряет объект, представляющий текущий токен входа в систему, если тип подсказан с помощью
TokenInterface
или класса, расширяющего его.Если аргумент не является нулевым и не существует токена входа в систему, разрешителем вызывается
HttpException
с кодом состояния 401, чтобы предотвратить доступ к контроллеру. - EntityValueResolver
-
Автоматически запрашивайте сущность и передавайте ее в качестве аргумента вашему контроллеру.
Например, следующее запросит сущность
Product
, которая имеет{id}
в качестве основного ключа:// src/Controller/DefaultController.php namespace AppController;
use SymfonyComponentHttpFoundationResponse; use SymfonyComponentRoutingAttributeRoute;
class DefaultController { #[Route('/product/{id}')] public function share(Product $product): Response { // ... } }
Чтобы узнать больше об использовании
EntityValueResolver
, обратитесь к специальному разделу Автоматическое получение объектов . - Разрешитель объектов PSR-7:
-
Внедряет объект Symfony HttpFoundation
Request
, созданный из объекта PSR-7 типаPsr
,\ \Http \ \Message \ \ServerRequestInterface Psr
или\ \Http \ \Message \ \RequestInterface Psr
. Требует установки компонента Мост PSR-7.\ \Http \ \Message \ \MessageInterface
Управление разрешителями значений
Для каждого аргумента будет вызываться каждый разрешитель с тегом controller.argument_value_resolver
до тех пор, пока один из них не предоставит значение. Порядок их вызова зависит от
от их приоритета. Например, SessionValueResolver
будет вызван раньше, чем DefaultValueResolver
,
поскольку его приоритет выше. Это позволяет написать, например, SessionInterface $session = null
,
чтобы получить сессию, если она есть, или null
, если её нет.
В этом конкретном случае вам не нужно запускать никакой разрешитель перед
SessionValueResolver
, поэтому их пропуск не только повысит производительность,
но и не позволит одному из них предоставить значение раньше, чем это сделает SessionValueResolver
.
Атрибут ValueResolver позволяет сделать это, "нацеливаясь" на нужный разрешитель:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Controller/SessionController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Attribute\ValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver;
use Symfony\Component\Routing\Attribute\Route;
class SessionController
{
#[Route('/')]
public function __invoke(
#[ValueResolver(SessionValueResolver::class)]
SessionInterface $session = null
): Response
{
// ...
}
}
В приведенном выше примере SessionValueResolver
будет вызван первым, поскольку
поскольку он является целевым. Следующим будет вызван DefaultValueResolver
, если
значение не было предоставлено; поэтому в качестве значения по умолчанию для $session
вы можете назначить null
.
Вы можете нацелить разрешитель, передав его имя в качестве первого аргумента ValueResolver
.
Для удобства имена встроенных разрешителей - это их FQCN.
Целевой разрешитель можно также отключить, передав аргументу ValueResolver
$disabled
значение
true
; именно так
MapEntity позволяет отключить EntityValueResolver для конкретного контроллера .
Да, MapEntity
расширяет ValueResolver
!
Добавление пользовательского разрешителя значений
В следующем примере вы создадите разрешитель значений для внедрения объекта
значения ID, если аргумент контроллера имеет тип, реализующий IdentifierInterface
(например, BookingId
):
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/Controller/BookingController.php
namespace App\Controller;
use App\Reservation\BookingId;
use Symfony\Component\HttpFoundation\Response;
class BookingController
{
public function index(BookingId $id): Response
{
// ... сделать что-то с $id
}
}
Добавление нового разрешителя значений требует создания класса, который реализует ValueResolverInterface, и определения сервиса для него.
Этот интерфейс содержит метод resolve()
, который вызывается для каждого аргумента
контроллера. Он получает текущий объект Request
и экземпляр
ArgumentMetadata, который
содержит всю информацыию из подписи метода.
Метод resolve()
должен вернуть либо пустой массив (если он не может разрешить этот
аргумент) или массив с разрешённым(и) значениям(и). Обычно аргументы разрешаются как
одно значение, но вариативные аргументы требуют разрешения нескольких значений. Поэтому
вы должна всегда возвращать массив, даже для одиночных значений:
// src/ValueResolver/IdentifierValueResolver.php namespace AppValueResolver;
use AppIdentifierInterface; use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpKernelControllerValueResolverInterface; use SymfonyComponentHttpKernelControllerMetadataArgumentMetadata;
class BookingIdValueResolver implements ValueResolverInterface { public function resolve(Request $request, ArgumentMetadata $argument): iterable { // получить тип аргумента (например, BookingId) $argumentType = $argument->getType(); if ( !$argumentType || !is_subclass_of($argumentType, IdentifierInterface::class, true) ) { return []; }
- // получить значение из запроса, основываясь на имени аргумента
$value = $request->attributes->get($argument->getName()); if (!is_string($value)) { return []; }
// создать и вернуть объект значения return [$argumentType::fromString($value)];
}
}
Этот метод сначала проверяет, может ли он разрешить значение:
- Аргумент должен иметь подсказку класса, реализующего пользовательский
IdentifierInterface
; - Имя аргумента (например,
$id
) должно совпадать с именем атрибута запроса (например, используя заполнитель маршрута/booking/{id}
).
Когда эти требования выполнены, метод создаёт новый экземпляр пользовательского объекта значения и возвращает его как значение этого аргумента.
Это всё! Теперь всё, что вам нужно сделать, - это добавить конфигурацию для сервис-контейнера. Это можно сделать добавив к вашему разрешителю значений один из следующих тегов.
controller.argument_value_resolver
Этот тег автоматически добавляется к каждому сервису, реализующему ValueResolverInterface
,
но вы можете установить его самостоятельно, чтобы изменить его атрибуты priority
или name
.
1 2 3 4 5 6 7 8 9 10 11 12
# config/services.yaml
services:
_defaults:
# ... убедитесь в том, что включено автомонтирование
autowire: true
# ...
App\ValueResolver\BookingIdValueResolver:
tags:
- controller.argument_value_resolver:
name: booking_id
priority: 150
Хотя добавление приоритета является необязательным, рекомендуется его добавить, чтобы
гарантировать, что ожидаемое значение будет внедрено. Встроенный RequestAttributeValueResolver
,
который также извлекает атрибуты Request
, должен иметь приоритет 100
или более.
В других случаях, установите приоритет ниже 100
, чтобы убедиться, что разрешитель
аргументов не запускается, когда присутствует атрибут Request
.
Чтобы гарантировать, что ваши разрешители добавляются в правильном месте, вы можете выполнить следующую команду, чтобы увидеть, какие разрешители аргументов присутствуют, и в каком порядке они запукаются:
1
$ php bin/console debug:container debug.argument_resolver.inner --show-arguments
Вы также можете сконфигурировать имя, переданное атрибуту ValueResolver
, чтобы указать
ваш разрешитель. Иначе он по умолчанию будет id сервиса.
controller.targeted_value_resolver
Установите этот тег, если вы хотите, чтобы ваш разрешитель вызывался только в том случае, если
на него указывает атрибут ValueResolver
. Как и в случае с controller.argument_value_resolver
,
вы можете настроить имя, под которым будет вызываться ваш разрешитель.
В качестве альтернативы вы можете добавить атрибут
AsTargetedValueResolver к своему
разрешителю и передавать в качестве первого аргумента ваше пользовательское имя:
1 2 3 4 5 6 7 8 9 10 11
// src/ValueResolver/IdentifierValueResolver.php
namespace App\ValueResolver;
use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
#[AsTargetedValueResolver('booking_id')]
class BookingIdValueResolver implements ValueResolverInterface
{
// ...
}
Затем вы можете передать это имя как первый аргумент ValueResolver
, чтобы указать ваш
разрешитель:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// src/Controller/BookingController.php
namespace App\Controller;
use App\Reservation\BookingId;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\ValueResolver;
class BookingController
{
public function index(#[ValueResolver('booking_id')] BookingId $id): Response
{
// ... сделать что-то с $id
}
}