Как создать пользовательский загрузчик маршрутов

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

Как создать пользовательский загрузчик маршрутов

Базовые приложения могут определять все свои маршруты в одном файле конфигурации - обычно, config/routes.yaml (см. ). Однако, в большинстве приложений распространено импортирование обозначения маршрутов из разных источников: PHP-атрибутов в файлах контроллера, YAML, XML или PHP файлов, храняшихся в каком-то каталоге и т.д.

Встроенные загрузчики маршрутов

Symfony предоставляет несколько загррузчиков для наиболее распорстраненных нужд:

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
# config/routes.yaml
app_file:
    # загружает маршруты из заданого файла маршрутизации, хранящегося в каком-то пакете
    resource: '@AcmeBundle/Resources/config/routing.yaml'

app_psr4:
    # загружает маршруты из PHP-атрибутов контроллеров, найденных в заданном корне пространства имен PSR-4
    resource:
        path: '../src/Controller/'
        namespace: App\Controller
    type: attribute

app_attributes:
    # загружает маршруты из PHP-атибутов контроллеров, найденных в этом каталоге
    resource: '../src/Controller/'
    type:     attribute

app_class_attributes:
    # загружает маршруты из PHP-атрибутов заданного класса
    resource: App\Controller\MyController
    type:     attribute

app_directory:
    # загружает маршруты из YAML, XML или PHP файлов, найденных в этом каталоге
    resource: '../legacy/routing/'
    type:     directory

app_bundle:
    # загружает маршруты из YAML, XML или PHP файлов, найденных в каком-то каталоге пакета
    resource: '@AcmeOtherBundle/Resources/config/routing/'
    type:     directory

Note

При импорте источников, ключ (например, app_file) - это имя коллекции. Просто убедитесь в том, что он уникальный для файла, чтобы никакие другие строки его не переопределяли.

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

Что такое пользовательский загрузчик маршрутов

Пользовательский загрузчик маршрутов позволяет вам генерировать маршруты, основанные на неких договорённостях или шаблонах. Примером такого использования является библиотека OpenAPI-Symfony-Routing, где маршруты генерируются в зависимости от атрибутов OpenAPI/Swagger. Другой пример - SonataAdminBundle, который создает маршруты, основыванясь на соглашениях CRUD.

Загрузка маршрутов

Маршруты в приложении Symfony загружаются с помощью DelegatingLoader. Этот загрузчик использует несколько других загрузчиков (делегатов) для загрузки ресурсов разных типов, например YAML-файлов или атрибутов #[Route] в файлах контроллера. Специализированные загрузчики внедряют LoaderInterface и таким образом имеют два важных метода: supports() и load().

Возьмём эти строки из routes.yaml:

1
2
3
4
# config/routes.yaml
controllers:
    resource: ../src/Controller/
    type: attribute

Когда главный загрузчик разбирает это, он обращается ко всем зарегистрированным загрузчикам-делегатам и вызывает их метод supports() с заданным ресусром (../src/Controller/) и типом (attribute) в качестве аргументов. Когда один из загрузчиков возвращает true, будет вызван его метод load(), который должен вернуть RouteCollection, содержащий объекты Route.

Note

Маршруты, загруженные этим способом, будут кешированы маршрутизатором так же, как и когда они определны в одном из форматов по умолчанию (например, XML, YAML, PHP файлах).

Загрузка маршрутов с пользовательским сервисом

Использование обычных сервисов Symfony является самым простым способом загрузки маршрутов пользовательским образом. Это намного проще, чем создавать полностью пользовательский загручик маршрута, так что вам стоит всегда рассматривать эту опцию в первую очередь.

Чтобы сделать это, определите type: service в качестве типа загружаемого источника маршрутизации, и сконфигурируйте сервис и метод, чтобы они вызывали:

1
2
3
4
# config/routes.yaml
admin_routes:
    resource: 'admin_route_loader::loadRoutes'
    type: service

В этом примере, маршруты загружаются, вызывая метод loadRoutes() сервиса, ID которого admin_route_loader. Ваш сервис не должен расширять или реализовывать никакой особенный класс, но вызванный метод должен возвращать объект RouteCollection.

Если вы используете автоконфигурацию , ваш класс должен реализовывать интерфейс RouteLoaderInterface, чтобы быть тегированным автоматически. Если вы не используете автоконфигурацию, добавьте вручную тег routing.route_loader.

Note

Маршруты, определенные с использованием сервиса загрузчика маршрутов, будут автоматически кешированы фреймворком. Поэтому когда вашему сервису нужно загрузить новые маршруты, не забудьте очистить кеш.

Tip

Если ваш сервис вызываемый, вам не нужно указывать метод для использования.

Создание пользовательского загрузчика

Для того, чтобы загружать маршруты из пользовательского источника (т.е. из чего-либо кроме аннотаций и файлов YAML или XML), вам нужно создать пользовательский загрузчик маршрутов. Этот загрузчик должен внедрять LoaderInterface.

В большинстве случаев, его легче расширить из
Loader вместо того, чтобы
внедрять LoaderInterface самостоятельно.

Пример загрузчика ниже поддерживает загрузку ресурсов маршрутизации с типом extra. Имя типа не должно пересекаться с другими загрузчиками, которые могут поддерживать такой же тип ресурса. Просто придумайте имя, специально соответствующее тому, что вы делаете. Имя ресурса само по себе на самом деле не используется в этом примере:

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
37
38
39
40
41
42
43
// src/Routing/ExtraLoader.php
namespace App\Routing;

use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

class ExtraLoader extends Loader
{
    private bool $isLoaded = false;

    public function load($resource, string $type = null): RouteCollection
    {
        if (true === $this->isLoaded) {
            throw new \RuntimeException('Do not add the "extra" loader twice');
        }

        $routes = new RouteCollection();

        // подготовьте новый маршрут
        $path = '/extra/{parameter}';
        $defaults = [
            '_controller' => 'App\Controller\ExtraController::extra',
        ];
        $requirements = [
            'parameter' => '\d+',
        ];
        $route = new Route($path, $defaults, $requirements);

        // добавьте новый маршрут в коллекцию маршрутов
        $routeName = 'extraRoute';
        $routes->add($routeName, $route);

        $this->isLoaded = true;

        return $routes;
    }

    public function supports($resource, string $type = null): bool
    {
        return 'extra' === $type;
    }
}

Убедитесь в том, что контроллер, который вы указываете, действительно существует. В этом случае, вам нужно создать метод extra() в ExtraController:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Controller/ExtraController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class ExtraController extends AbstractController
{
    public function supports($resource, string $type = null): bool
    {
        return new Response($parameter);
    }
}

Теперь, определите сервис для ExtraLoader:

1
2
3
4
5
6
# config/services.yaml
services:
    # ...

    App\Routing\ExtraLoader:
        tags: [routing.loader]

Отметьте тег routing.loader. Все сервисы с этим тегом будут отмечены, как потенциальные загрузчики маршрутов и добавлены в качестве специализированных загрузчиков маршрутов в сервис routing.loader service, который является экзеплмяром DelegatingLoader.

Использование пользовательского загрузчика

Если вы больше ничего не делали, то ваш пользовательский загрузчик маршрутов не будет вызван. Что вам осталось сделать, так это добавить несколько строчек в конфигурацию маршрутизации:

1
2
3
4
# config/routes.yaml
app_extra:
    resource: .
    type: extra

Важной частью здесь является ключ type. Его значение должно быть extra, так как это тот тип, который поддерживает ExtraLoader, и таким образом его метод load() точно будет вызван. Ключ resource неважен для ExtraLoader, поэтому он установлен в значении . (одна точка).

Note

Маршруты, определённые с использованием пользовательских загрузчиков маршрутов, будут автоматически кешированы фреймворком. Так что когда вы будете что-либо изменять в самом классе загрузчика, не забудьте очистить кеш.

Более продвинутые загрузчики

Если ваш пользовательский загрузчик маршрутов расширен из Loader, как показано выше, вы также можете с пользой использовать предоставленный распознаватель, экземпляр LoaderResolver, для загрузки вторичных ресурсов маршрутизации.

Конечно же, вам все еще нужно внедрить supports() и load(). Когда вам захочется загрузить другой ресурс - например, YAML-файл конфигурации маршрутизации - вы можете вызвать метод import():

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
// src/Routing/AdvancedLoader.php
namespace App\Routing;

use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\RouteCollection;

class AdvancedLoader extends Loader
{
    public function load($resource, string $type = null): RouteCollection
    {
        $routes = new RouteCollection();

        $resource = '@ThirdPartyBundle/Resources/config/routes.yaml';
        $type = 'yaml';

        $importedRoutes = $this->import($resource, $type);

        $routes->addCollection($importedRoutes);

        return $routes;
    }

    public function supports($resource, string $type = null): bool
    {
        return 'advanced_extra' === $type;
    }
}

Note

Имя ресурса и тип импортированной конфигурации маршрутизации могут быть любыми, если они будут поддерживаться загрузчиком конфигурации маршрутизации ((YAML, XML, PHP, аннотация, и т.д.).

Note

Для более продвинутого использования, посмотрите ChainRouter, предоставленный проектом Symfony CMF. Этот маршрутизатор позволяет приложениям исползовать сочетание двух и более маршрутизаторов, например, для того, чтобы продолжать использовать систему маршрутизации Symfony по умолчанию, при написании пользовательского маршрутизатора.