Розширення розвʼязування аргументу дії

Дата оновлення перекладу 2022-12-14

Розширення розвʼязування аргументу дії

У довіднику контролера ви дізналися, що ви можете отримати обʼєкт 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
    {
        // ...
    }

    // ...
}

При запиті /cards/H URL, змінна $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
Верифікує, чи є дані запиту масивом, та додає їх до списку аргументів. При виклику дії, останній (змінний) аргумент буде містити всі значення цього масиву.

На додаток, деякі компоненти та офіційні пакети надають інші розвʼязувачі значень:

UserValueResolver

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

Якщо аргумент не null і немає коритувача в системі, або користувач в системі має клас, що не співпадає з типізованим класом, розвʼязувачем викликається AccessDeniedException, щоб запобігти доступу до контролера.

Розвʼязувач обʼєктів PSR-7:
Впроваджує обʼєкт Symfony HttpFoundation Request, створений з обʼєкту PSR-7 типу ServerRequestInterface, RequestInterface або MessageInterface. Це вимагає встановлення компонента PSR-7 Bridge.

Додавання користувацького розвʼязувача значення

У наступному прикладі ви створите розвʼязувач значення, щоб впровадити 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 було представлено в 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. Якщо ваш розвʼязувач також добуває атрибути Request, встановіть пріоритет 100 або більше. Інакше, встановіть пріоритет нижче за 100, щоб гарантувати, що розвʼязувач аргументів не буде запущено, коли наявний атрибут Request.

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

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