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

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

Пользовательский загрузчик маршрутов позволяет вам генерировать маршруты, основанные на неких договорённостях или шаблонах. Отличным примером его применения является FOSRestBundle, где маршруты генерируются на основании имён методов действий в контроллере.

Вам все еще нужно настроить вашу конфигурацию маршрутизации (например, app/config/routing.yaml) вручную, даже если вы используете пользовательский загрузчик маршрутов.

Note

Существует множество пакетов, которые используют собственные загрузчики маршрутов, чтобы справиться с задачами как описано выше, например FOSRestBundle, JMSI18nRoutingBundle, KnpRadBundle и SonataAdminBundle.

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

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

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

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

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

Note

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

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

New in version 2.8: Опция загрузки маршрутов, используя сервисы Symfony была представлена в Symfony 2.8.

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

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

  • YAML
    1
    2
    3
    4
    # app/config/routing.yml
    admin_routes:
        resource: 'admin_route_loader:loadRoutes'
        type: service
    
  • XML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- app/config/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <import resource="admin_route_loader:loadRoutes" type="service"/>
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // app/config/routing.php
    use Symfony\Component\Routing\RouteCollection;
    
    $collection = new RouteCollection();
    $collection->addCollection(
        $loader->import("admin_route_loader:loadRoutes", "service")
    );
    
    return $collection;
    

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

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

Для того, чтобы загружать маршруты из пользовательского источника (т.е. из чего-либо кроме аннотаций и файлов 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 $loaded = false;

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

        $routes = new RouteCollection();

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

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

        $this->loaded = true;

        return $routes;
    }

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

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

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

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

class ExtraController extends Controller
{
    public function extra($parameter)
    {
        return new Response($parameter);
    }
}

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

  • YAML
    1
    2
    3
    4
    5
    6
    # config/services.yaml
    services:
        # ...
    
        App\Routing\ExtraLoader:
            tags: [routing.loader]
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- config/services.xml -->
    <?xml version="1.0" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <!-- ... -->
    
            <service id="App\Routing\ExtraLoader">
                <tag name="routing.loader" />
            </service>
        </services>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    // config/services.php
    use App\Routing\ExtraLoader;
    
    $container
        ->autowire(ExtraLoader::class)
        ->addTag('routing.loader')
    ;
    

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

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

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

  • YAML
    1
    2
    3
    4
    # config/routes.yaml
    app_extra:
        resource: .
        type: extra
    
  • XML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <import resource="." type="extra" />
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    // config/routes.php
    use Symfony\Component\Routing\RouteCollection;
    
    $collection = new RouteCollection();
    $collection->addCollection($loader->import('.', 'extra'));
    
    return $collection;
    

Важной частью здесь является ключ 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, $type = null)
    {
        $collection = new RouteCollection();

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

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

        $collection->addCollection($importedRoutes);

        return $collection;
    }

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

Note

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

Note

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

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.