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

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

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

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

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

Note

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

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

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

Возьмём эти строки из routing.yml в Symfony Standard Edition:

1
2
3
4
# app/config/routing.yml
app:
    resource: '@AppBundle/Controller/'
    type:     annotation

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

Note

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

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

Для того, чтобы загружать маршруты из пользовательского источника (т.е. из чего-либо кроме аннотаций и файлов 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/AppBundle/Routing/ExtraLoader.php
namespace AppBundle\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' => 'AppBundle:Extra: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/AppBundle/Controller/ExtraController.php
namespace AppBundle\Controller;

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

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

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

  • YAML
    1
    2
    3
    4
    5
    6
    # app/config/services.yml
    services:
        # ...
    
        AppBundle\Routing\ExtraLoader:
            tags: [routing.loader]
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <?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="AppBundle\Routing\ExtraLoader">
                <tag name="routing.loader" />
            </service>
        </services>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    use AppBundle\Routing\ExtraLoader;
    
    $container
        ->autowire(ExtraLoader::class)
        ->addTag('routing.loader')
    ;
    

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

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

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

  • YAML
    1
    2
    3
    4
    # app/config/routing.yml
    app_extra:
        resource: .
        type: extra
    
  • XML
    1
    2
    3
    4
    5
    6
    7
    8
    <?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
    // app/config/routing.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/AppBundle/Routing/AdvancedLoader.php
namespace AppBundle\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 = '@AppBundle/Resources/config/import_routing.yml';
        $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.