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

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

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

В справочнике контроллера, вы узнали, что вы можете получить объект Request через аргумент в вашем контроллере. Этот аргумент должен быть типизирован классом Request, чтобы его можно было распознать. Это делается через ArgumentResolver. Создавая и регистрируя пользовательские разрешители значений аргумента, вы можете расширить эту функциональность.

Функциональность, поставляемая с HttpKernel

Symfony поставляется с пятью разрешителями значений в компоненте HttpKernel:

RequestAttributeValueResolver
Пытается найти атрибут запроса, совпадающий с именем аргумента.
RequestValueResolver
Внедряет текущий Request при типизации Request или классе, расширяющем Request.
ServiceValueResolver
Внедряет сервис при типизации валидным классом сервиса или интерфейсом. Это работает как автомонтирование.
SessionValueResolver
Внедряет сконфигурированный класс сессии, расширяющий SessionInterface при типизации SessionInterface или классом, расширяющим SessionInterface.
DefaultValueResolver
При наличии, установит значение аргумента по умолчанию, если аргумент необязательный.
VariadicValueResolver
Верифицирует является ли данные запроса массивом, и добавляет их в список аргументов. При вызове действия, последний (переменный) аргумент будет содержать все значения этого массива.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
namespace App\Controller;

use App\Entity\User;
use Symfony\Component\HttpFoundation\Response;

class UserController
{
    public function index(User $user)
    {
        return new Response('Hello '.$user->getUsername().'!');
    }
}

Каким-то образом вам нужно будет получить объект User и внедрить его в контроллер. Это можно сделать, реализовав ArgumentValueResolverInterface. Этот интерфейс указывает на то, что вы должны реализовать два метода:

supports()
Этот метод используется, чтобы проверить, поддерживает ли разрешитель значения данный аргумент. resolve() будет выполнен только, когда будет возвращено true.
resolve()
Этот метод разрешит настоящее значение аргумента. Как только значение будет разрешено, вы должны yield значение в ArgumentResolver.

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

Теперь, когда вы знаете, что делать, вы можете реализовать этот интерфейс. Чтобы получить текущего User, вам нужен текущий токен безопасности. Этот токен можно получить из хранилища токенов:

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
32
33
34
35
36
// src/ArgumentResolver/UserValueResolver.php
namespace App\ArgumentResolver;

use App\Entity\User;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class UserValueResolver implements ArgumentValueResolverInterface
{
    private $tokenStorage;

    public function __construct(TokenStorageInterface $tokenStorage)
    {
        $this->tokenStorage = $tokenStorage;
    }

    public function supports(Request $request, ArgumentMetadata $argument)
    {
        if (User::class !== $argument->getType()) {
            return false;
        }

        $token = $this->tokenStorage->getToken();

        if (!$token instanceof TokenInterface) {
            return false;
        }

        return $token->getUser() instanceof User;
    }

    public function resolve(Request $request, ArgumentMetadata $argument)
    {
        yield $this->tokenStorage->getToken()->getUser();
    }
}

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

  • Аргумент должен быть типизирован, как User в вашей подписи метода действия;
  • Должен иметься токен безопасности;
  • Значение должно быть экземпляром User.

Когда все требования выполнены и возвращён true, ArgumentResolver вызывает resolve() с теми же значение, как вызывал supports().

Вот и всё! Теперь всё, что вам осталось сделать - это добавить конфигурацию для сервис-контейнера. Это можно сделать, тегировав сервис с помощью controller.argument_value_resolver и добавив приоритетность.

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

    App\ArgumentResolver\UserValueResolver:
        tags:
            - { name: controller.argument_value_resolver, priority: 50 }

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

Tip

Как вы можете увидеть в методе UserValueResolver::supports(), пользователь может быть недоступен (например, когда контроллер на находится за брандмауэром). В таких случаях, разрешитель не будет выполнен. Если не разрешено ни одно значение аргумента, будет вызвано исключение.

Чтобы избежать этого, вы можете добавить значение по умолчанию в контроллер (например, User $user = null). DefaultValueResolver выполняется, как последний разрешитель, и будет использовать значение по умолчанию, если до этого не было разрешено ни одного значения.