Расширение разрещения аргумента действия

Дата обновления перевода 2023-01-18

Расширение разрещения аргумента действия

В руководстве о контроллерах, вы узнали, что вы можете получить объект 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 "Не найдено" в двух других случаях.

6.1

BackedEnumValueResolver и EnumRequirement были представлены в Symfony 6.1.

RequestAttributeValueResolver
Пробует найти атрибут запроса, который совпадает с именем аргумента.
DateTimeValueResolver

Пробует найти атрибут запроса, который совпадает с именем аргумента и внедряет объект DateTimeInterface, если есть подсказка класса, расширяющего DateTimeInterface.

По умолчанию, любой ввод, который можно разобрать как строку даты путём PHP, принимается. Вы можете ограничить то, как может быть отформатирован ввод, с помощью атрибута MapDateTime.

6.1

DateTimeValueResolver был представлен в Symfony 6.1.

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\Annotation\Route;
use Symfony\Component\Uid\UuidV4;

class DefaultController
{
    #[Route('/share/{token}')]
    public function share(UuidV4 $token): Response
    {
        // ...
    }
}

6.1

UidValueResolver был представлен в Symfony 6.1.

VariadicValueResolver
Верифицирует, являются ли данные запроса массивом, и добавит их все к списку аргументов. Когда вызывается действие, последний (вариативный) аргумент будет содержать все значения этого массива.

In addition, some components and official bundles provide other value resolvers:

UserValueResolver

Внедряет объект, который представляет текущего пользователя в системе, если есть подсказка UserInterface. Вы можете также добавить подсказку собственного класса User, но вы должны затем добавить атрибут #[CurrentUser] к аргументу. Значение по умолчанию может быть установлено как null в случае, если к контроллеру можно получить доступ анонимным пользователям. Требует установки SecurityBundle.

Если аргумент не может быть null и нет пользователя в системе, или пользователь в системе имеет класс пользователя, не совпадающий с классом подсказки, разрешителем вызыввается AccessDeniedException, чтобы предотвратить доступ к контроллеру.

Разрешитель объектов PSR-7:
Внедряет объект Symfony HttpFoundation Request, созданный из объекта PSR-7 типа ServerRequestInterface, RequestInterface или MessageInterface. Требует установки компонента Мост PSR-7.

Добавление пользовательского разрешителя значений

В следующем примере вы создадите разрешитель значений для внедрения объекта значения 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
    }
}

6.2

ValueResolverInterface был представлен в Symfony 6.2. До версии 6.2, вам нужно было использовать ArgumentValueResolverInterface, что определяет другие методы.

Добавление нового разрешителя значений требует создания класса, который реализует ValueResolverInterface, и определения сервиса для него.

Этот интерфейс содержит метод resolve(), который вызывается для каждого аргумента контроллера. Он получает текущий объект Request и экземпляр ArgumentMetadata, который содержит всю информацыию из подписи метода.

Метод resolve() должен вернуть либо пустой массив (если он не может разрешить этот аргумент) или массив с разрешённым(и) значениям(и). Обычно аргументы разрешаются как одно значение, но вариативные аргументы требуют разрешения нескольких значений. Поэтому вы должна всегда возвращать массив, даже для одиночных значений:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// src/ValueResolver/IdentifierValueResolver.php
namespace App\ValueResolver;

use App\IdentifierInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

class BookingIdValueResolver implements ValueResolverInterface
{
    public function resolve(Request $request, ArgumentMetadata $argument): array
    {
        // получить тип аргумента (например, 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 и приоритет:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
# config/services.yaml
services:
    _defaults:
        # ... убедитесь в том, что включено автомонтирование
        autowire: true
    # ...

    App\ArgumentResolver\BookingIdValueResolver:
        tags:
            - { name: controller.argument_value_resolver, priority: 150 }

Хотя добавление приоритета является необязательным, рекомендуется его добавить, чтобы гарантировать, что ожидаемое значение будет внедрено. Встроенный RequestAttributeValueResolver, который также извлекает атрибуты Request, должен иметь приоритет 100 или более. В других случаях, установите приоритет ниже 100, чтобы убедиться, что разрешитель аргументов не запускается, когда присутствует атрибут Request.

Чтобы гарантировать, что ваши разрешители добавляются в правильном месте, вы можете выполнить следующую команду, чтобы увидеть, какие разрешители аргументов присутствуют, и в каком порядке они запукаются:

1
$ php bin/console debug:container debug.argument_resolver.inner --show-arguments