Компонент Routing

Компонент Routing соединяет HTTP запрос с набором переменных конфигурации.

Установка

1
$ composer require symfony/routing

Также вы можете клонировать репозиторий https://github.com/symfony/routing.

Note

If you install this component outside of a Symfony application, you must require the vendor/autoload.php file in your code to enable the class autoloading mechanism provided by Composer. Read this article for more details.

Использование

Эта статья объясняет как использовать функции Routing как независимого компонента в любом приложении PHP. Прочитайте статью Routing для понимания как использовать его в приложениях Symfony.

Для тог, чтобы установить базову систему маршрутизации, вам нужно три составляющие:

  • RouteCollection, который содержит определения маршрутов (экземпляры класса Route)
  • RequestContext, который содержит информацию о запросе
  • UrlMatcher, который выполняет соединение запроса с единственным маршрутом

Вот быстрый пример. Заметьте, что предполагается, что вы уже сконфигурировали ваш автозагрузчик, чтобы он загружал компонент Routing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;

$route = new Route('/foo', array('_controller' => 'MyController'));
$routes = new RouteCollection();
$routes->add('route_name', $route);

$context = new RequestContext('/');

$matcher = new UrlMatcher($routes, $context);

$parameters = $matcher->match('/foo');
// массив ('_controller' => 'MyController', '_route' => 'route_name')

Note

Параметры RequestContext могут быть наполнены значениями, хранящимися в $_SERVER, но легче использовать компонент HttpFoundation, как объясняется ниже.

Вы можете добавлять к RouteCollection столько маршрутов, сколько хотите.

Метод RouteCollection::add() берёт два аргумента. Первый - это имя маршрута. Второй - объект Route, который ожидает URL путь и некоторый массив пользовательских переменных в своём конструкторе. Этот массив пользовательских переменных может быть чем угодно важным для вашего приложения и возвращаться при сопоставлении маршрута.

UrlMatcher::match() возвращает переменные, которые вы установили в маршруте, а также рандомные заполнители (см. ниже). Теперь ваше приложение может использовать эту информацию, чтобы продолжать обработку запроса. В дополенение к сконфигурированным переменным, добавляется ключ _route, который содержит имя соответствующего маршрута.

Если соответствующий маршрут не может быть найден, будет вызвано ResourceNotFoundException.

Определение маршрутов

Полное определение маршрута может содержать до семи частей:

  1. Маршрут пути URL. Он сопоставляется с URL, переданным RequestContext, и может содержать названные рандомные заполнители (например, {placeholders}), чтобы соответствовать динамическим частям URL.
  2. Массив значений по умолчанию. Содержит массив арбитражных значений, которые будут возвращены, когда запрос соответствует маршруту.
  3. Массив требований. Он определяет ограничения значений заполнителей в виде регулярных выражений.
  4. Массив опций. Они содержит внутренние настройки для маршрута и является наименее необходимыми.
  5. Хост. Сопоставляется с хостом запроса. Смотрите How to Match a Route Based on the Host, чтобы узнать больше.
  6. Массив схем. Форсирует определённую HTTP схему (http, https).
  7. Массив методов. Форсирует определённый метод HTTP запроса (HEAD, GET, POST, ...).

Рассмотрите следующий маршрут, который объединяет несколько из этих идей:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$route = new Route(
    '/archive/{month}', // path
    array('_controller' => 'showArchive'), // default values
    array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requirements
    array(), // options
    '{subdomain}.example.com', // host
    array(), // schemes
    array() // methods
);

// ...

$parameters = $matcher->match('/archive/2012-01');
// array(
//     '_controller' => 'showArchive',
//     'month' => '2012-01',
//     'subdomain' => 'www',
//     '_route' => ...
//  )

$parameters = $matcher->match('/archive/foo');
// вызывает ResourceNotFoundException

В этом случае, маршрут сопоставляется с /archive/2012-01, так как метасимвол {month} соответствует регулярному выражению заданного метасимвола. Однако, /archive/foo не соответствует, так как "foo" не подходит метасимволу месяца.

При использовании метасимволов, они возвращаются в виде массива, при вызове match. Часть пути, с которой сопоставляется метасимвол (например, 2012-01), используется в качестве значения.

Tip

Если вы хотите сопоставить все URL, которые начинаются с определённого пути и заканчиваются произвольным суффиксом, вы можете использовать следующее определение маршрута:

1
2
3
4
5
$route = new Route(
    '/start/{suffix}',
    array('suffix' => ''),
    array('suffix' => '.*')
);

Использование префиксов

Вы можете добавлять маршруты или другие экземпляры RouteCollection к другой коллекции. Таким образом, вы можете построить древо маршрутов. Кроме того, вы можете определить префикс и значение по умолчанию для параметров, требований, опций, схем и хоста для всех маршрутов поддрева, используя методы, предоставленные классом RouteCollection:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$rootCollection = new RouteCollection();

$subCollection = new RouteCollection();
$subCollection->add(...);
$subCollection->add(...);
$subCollection->addPrefix('/prefix');
$subCollection->addDefaults(array(...));
$subCollection->addRequirements(array(...));
$subCollection->addOptions(array(...));
$subCollection->setHost('admin.example.com');
$subCollection->setMethods(array('POST'));
$subCollection->setSchemes(array('https'));

$rootCollection->addCollection($subCollection);

Установка параметров запроса

RequestContext предоставляет информацию о текущем запросе. Вы можете определить все параметры HTTP запроса с этим классом через его конструктор:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public function __construct(
    $baseUrl = '',
    $method = 'GET',
    $host = 'localhost',
    $scheme = 'http',
    $httpPort = 80,
    $httpsPort = 443,
    $path = '/',
    $queryString = ''
)

Обычно вы можете передать значения из переменной $_SERVER, чтобы наполнить RequestContext. Но если вы используете компонент HttpFoundation , вы можете использовать его класс Request, чтобы наполнить RequestContext с помощью сокращения:

1
2
3
4
use Symfony\Component\HttpFoundation\Request;

$context = new RequestContext();
$context->fromRequest(Request::createFromGlobals());

Генерирование URL

В то время как UrlMatcher, пытается найти маршрут, который соответствует заданному запросу, вы можете также построить URL из определённого маршрута:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

$routes = new RouteCollection();
$routes->add('show_post', new Route('/show/{slug}'));

$context = new RequestContext('/');

$generator = new UrlGenerator($routes, $context);

$url = $generator->generate('show_post', array(
    'slug' => 'my-blog-post',
));
// /show/my-blog-post

Note

Если вы определили схему, генерируются абсолютный URL, если схема текущего RequestContext не соответствует требованию.

Проверка существования пути

В динамично развивающихся приложениях может быть необходимо проверить существование пути перед генерации URL. В данных случаях не используйте метод getRouteCollection() потому что он пересоздаёт кеш таблицы маршрутизации и замедляет приложение.

Вместо этого попробуйте создать URL и ловите исключение RouteNotFoundException, которое выбросится если маршрут не существует:

1
2
3
4
5
6
7
8
9
use Symfony\Component\Routing\Exception\RouteNotFoundException;

// ...

try {
    $url = $generator->generate($dynamicRouteName, $parameters);
} catch (RouteNotFoundException $e) {
    // маршрут не определён...
}

Загрузка маршрутов из файла

Вы уже видели, как вы с лёгкостью можете добавлять маршруты в коллекцию прямо внутри PHP. Но вы также можете загружать маршруты из множества различных файлов.

Компонент Routing поставляется с определённым количеством классов загрузчиков, и каждый предоставляет вам возможность загружать коллекцию определения маршрутов из внешнего файла какого-либо формата. Каждый загрузчик ожидает экземпляр FileLocator, в качестве аргумента конструктора. Вы можете использовать FileLocator, чтобы определить массив путей, в которых загрузчик будет искать запрошенные файлы. Если файл найден, загрузчик возвращает RouteCollection.

Если вы используете YamlFileLoader, то определения маршрута будут выглядеть так:

1
2
3
4
5
6
7
8
# routes.yaml
route1:
    path:     /foo
    defaults: { _controller: 'MyController::fooAction' }

route2:
    path:     /foo/bar
    defaults: { _controller: 'MyController::foobarAction' }

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

1
2
3
4
5
6
7
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\YamlFileLoader;

// ищет внутри *этого* каталога
$fileLocator = new FileLocator(array(__DIR__));
$loader = new YamlFileLoader($fileLocator);
$routes = $loader->load('routes.yaml');

Кроме YamlFileLoader, существует два других загрузчика, которые работают таким же образом:

Если вы используете PhpFileLoader, то вам нужно предоставить имя PHP файла, который возвращает RouteCollection:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// RouteProvider.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;

$routes = new RouteCollection();
$routes->add(
    'route_name',
    new Route('/foo', array('_controller' => 'ExampleController'))
);
// ...

return $routes;

Маршруты, как завершители

Также существует ClosureLoader, который вызывает завершитель, и использует результат в качестве RouteCollection:

1
2
3
4
5
6
7
8
use Symfony\Component\Routing\Loader\ClosureLoader;

$closure = function () {
    return new RouteCollection();
};

$loader = new ClosureLoader();
$routes = $loader->load($closure);

Маршруты, как аннотации

Наконец, существует AnnotationDirectoryLoader и AnnotationFileLoader, для загрузки определений маршрута из аннотаций класса. Конкретные детали здесь не рассматриваются.

Note

In order to use the annotation loader, you should have installed the doctrine/annotations and doctrine/cache packages with Composer.

Tip

Annotation classes aren't loaded automatically, so you must load them using a class loader like this:

1
2
3
4
5
6
7
8
9
use Composer\Autoload\ClassLoader;
use Doctrine\Common\Annotations\AnnotationRegistry;

/** @var ClassLoader $loader */
$loader = require __DIR__.'/../vendor/autoload.php';

AnnotationRegistry::registerLoader([$loader, 'loadClass']);

return $loader;

Маршрутизавтор "всё-в-одном"

Класс Router является пакетом "всё-в-одном", для быстрого использования компонента Routing. Конструктор ожидают экземпляр загрузчика, путь к главному определению маршрута и некоторые другие настройки:

1
2
3
4
5
6
7
public function __construct(
    LoaderInterface $loader,
    $resource,
    array $options = array(),
    RequestContext $context = null,
    LoggerInterface $logger = null
);

С опцией cache_dir, вы можете включить кеширование маршрута (если вы предоставите путь), или отключить кеширование (если оно установлено, как null). Кеширование производится фоново автоматически, если вы хотите его использовать. Базовым примером класса Router будет:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$fileLocator = new FileLocator(array(__DIR__));
$requestContext = new RequestContext('/');

$router = new Router(
    new YamlFileLoader($fileLocator),
    'routes.yaml',
    array('cache_dir' => __DIR__.'/cache'),
    $requestContext
);
$router->match('/foo/bar');

Note

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

Поддержка маршрутизации Unicode

Компонент Routing поддерживает символы UTF-8 в путях маршрута и требованиях. Благодаря опции маршрута utf8 , вы можете сделать так, чтобы Symfony сопоставляла и генерировала маршрут с использованием UTF-8 символов:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\Routing\Annotation\Route;
    
    class DefaultController extends Controller
    {
        /**
         * @Route("/category/{name}", name="route1", options={"utf8": true})
         */
        public function category()
        {
            // ...
        }
    
  • YAML
    1
    2
    3
    4
    5
    route1:
        path:     /category/{name}
        defaults: { _controller: 'App\Controller\DefaultController::category' }
        options:
            utf8: true
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <?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">
    
        <route id="route1" path="/category/{name}">
            <default key="_controller">App\Controller\DefaultController::category</default>
            <option key="utf8">true</option>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $routes = new RouteCollection();
    $routes->add('route1', new Route('/category/{name}',
        array(
            '_controller' => 'App\Controller\DefaultController::category',
        ),
        array(),
        array(
            'utf8' => true,
        )
    ));
    
    // ...
    
    return $routes;
    

В этом маршруте, опция utf8 установлена как true, что заставляет Symfony рассмотреть требование . на соответствие каким-либо символам UTF-8, вместо одного символа байта. Это означает, что следующие URL будут соответствовать: /category/日本語, /category/فارسی, /category/한국어, и т.д. В случае, если вы хотели знать, эта опция также позволяет включать и сопоставлять эмоджи в URL.

Вы можете тоже включать UTF-8 строки в качестве требований маршрутизации:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\Routing\Annotation\Route;
    
    class DefaultController extends Controller
    {
        /**
         * @Route(
         *     "/category/{name}",
         *     name="route2",
         *     requirements={"default"="한국어"},
         *     options={"utf8": true}
         * )
         */
        public function default()
        {
            // ...
        }
    
  • YAML
    1
    2
    3
    4
    5
    6
    7
    route2:
        path:     /default/{default}
        defaults: { _controller: 'App\Controller\DefaultController::default' }
        requirements:
            default: "한국어"
        options:
            utf8: true
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <?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">
    
        <route id="route2" path="/default/{default}">
            <default key="_controller">App\Controller\DefaultController::default</default>
            <requirement key="default">한국어</requirement>
            <option key="utf8">true</option>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $routes = new RouteCollection();
    $routes->add('route2', new Route('/default/{default}',
        array(
            '_controller' => 'App\Controller\DefaultController::default',
        ),
        array(
            'default' => '한국어',
        ),
        array(
            'utf8' => true,
        )
    ));
    
    // ...
    
    return $routes;
    

Tip

Кроме символов UTF-8, компонент Routing также поддерживает все свойства PCRE Unicode, которые экранируют последовательности, соответствующие общим типам символов. Например, \p{Lu} соответствует любым символам верхнего регистра на любом языке, \p{Greek} соответствует любому греческому символу, \P{Han} соответствует любому символу, не включённому в скрипт Chinese Han.

Узнать больше

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