Symfony. Routing — компонент маршрутизации.

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

Установка

Вы можете установить компонент двумя способами:

Затем, подключить автозагрузчик vendor/autoload.php, который предоставляет Composer. Иначе, ваше приложение не сможет найти необходимые для компонента классы.

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

Для развертывания системы маршрутизации, вам понадобится три класса:

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

Простой пример. Предполагается, что вы уже сконфигурировали автолоадер, для загрузки классов компонента Routing.

  1. use Symfony\Component\Routing\Matcher\UrlMatcher;
  2. use Symfony\Component\Routing\RequestContext;
  3. use Symfony\Component\Routing\RouteCollection;
  4. use Symfony\Component\Routing\Route;
  5. $route = new Route(‘/foo’, array(‘_controller’ => ‘MyController’));
  6. $routes = new RouteCollection();
  7. $routes>add(‘route_name’, $route);
  8. $context = new RequestContext(‘/’);
  9. $matcher = new UrlMatcher($routes, $context);
  10. $parameters = $matcher>match(‘/foo’);
  11. // array(‘_controller’ => ‘MyController’, ‘_route’ => ‘route_name’)

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

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

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

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

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

Определение (описание) маршрутов.

Полное описание маршрута содержит до семи составляющих.

  1. URL запроса. Он сравнивается с URL, переданным в RequestContext, и может содержать именованные плейсхолдеры (например, {placeholders}) для сравнения динамических частей урла.
  2. Массив значений по умолчанию. Некоторые данные, которые будут возвращены для найденного роута.
  3. Массив с обязательными параметрами. Проверяет значения (обычно часть URL) по шаблону, на базе регулярных выражений.
  4. Массив с настройками. В нем внутренние параметры для роута. Используется редко.
  5. Имя хоста. Сравнивается со значением из запроса. Смотрите статью «поиск маршрутов по хосту».
  6. Массив со схемами. Определяет конкретную HTTP схему: http или https.
  7. Массив с методами. Содержит конкретные методы HTTP запросов (HEAD, GET, POST, … )

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

  1. $route = new Route(
  2. ‘/archive/{month}’, // path
  3. array(‘_controller’ => ‘showArchive’), // default values
  4. // requirements
  5. array(‘month’ => ‘[0-9]{4}-[0-9]{2}’, ‘subdomain’ => ‘www|m’),
  6. array(), // options
  7. ‘{subdomain}.example.com’, // host
  8. array(), // schemes
  9. array() // methods
  10. );
  11. // …
  12. $parameters = $matcher>match(‘/archive/2012-01’);
  13. // array(
  14. // ‘_controller’ => ‘showArchive’,
  15. // ‘month’ => ‘2012-01’,
  16. // ‘subdomain’ => ‘www’,
  17. // ‘_route’ => …
  18. // )
  19. $parameters = $matcher>match(‘/archive/foo’);
  20. // throws ResourceNotFoundException

В данном примере, найден маршрут /archive/2012-01, потому что регулярное выражение шаблона совпало с запросом. Для /archive/foo совпадений не нашлось, исходя из той же логики.

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

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

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

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

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

  1. $rootCollection = new RouteCollection();
  2. $subCollection = new RouteCollection();
  3. $subCollection>add();
  4. $subCollection>add();
  5. $subCollection>addPrefix(‘/prefix’);
  6. $subCollection>addDefaults(array());
  7. $subCollection>addRequirements(array());
  8. $subCollection>addOptions(array());
  9. $subCollection>setHost(‘admin.example.com’);
  10. $subCollection>setMethods(array(‘POST’));
  11. $subCollection>setSchemes(array(‘https’));
  12. $rootCollection>addCollection($subCollection);

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

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

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

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

  1. use Symfony\Component\HttpFoundation\Request;
  2. $context = new RequestContext();
  3. $context>fromRequest(Request::createFromGlobals());

Более подробная информация о компоненте HttpFoundation.

Генерация URL на базе роута.

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

  1. use Symfony\Component\Routing\Generator\UrlGenerator;
  2. use Symfony\Component\Routing\RequestContext;
  3. use Symfony\Component\Routing\Route;
  4. use Symfony\Component\Routing\RouteCollection;
  5. $routes = new RouteCollection();
  6. $routes>add(‘show_post’, new Route(‘/show/{slug}’));
  7. $context = new RequestContext(‘/’);
  8. $generator = new UrlGenerator($routes, $context);
  9. $url = $generator>generate(‘show_post’, array(
  10. ‘slug’ => ‘my-blog-post’,
  11. ));
  12. // /show/my-blog-post

Если была указана схема (http, https), то будет сгенерирован абсолютный URL, но лишь в том случае если схема текущего запроса в RequestContext отличается.

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

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

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

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

# routes.yml
route1:
    path:     /foo
    defaults: { _controller: 'MyController::fooAction' }

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

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

  1. use Symfony\Component\Config\FileLocator;
  2. use Symfony\Component\Routing\Loader\YamlFileLoader;
  3. // look inside *this* directory
  4. $locator = new FileLocator(array(__DIR__));
  5. $loader = new YamlFileLoader($locator);
  6. $collection = $loader>load(‘routes.yml’);

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

  • XmlFileLoader
  • PhpFileLoader

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

  1. // RouteProvider.php
  2. use Symfony\Component\Routing\RouteCollection;
  3. use Symfony\Component\Routing\Route;
  4. $collection = new RouteCollection();
  5. $collection>add(
  6. ‘route_name’,
  7. new Route(‘/foo’, array(‘_controller’ => ‘ExampleController’))
  8. );
  9. // …
  10. return $collection;

Описание маршрутов через замыкания (Closures)

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

  1. use Symfony\Component\Routing\Loader\ClosureLoader;
  2. $closure = function () {
  3. return new RouteCollection();
  4. };
  5. $loader = new ClosureLoader();
  6. $collection = $loader>load($closure);

Описание маршрутов через аннотации

Последний, но не менее интересный тип – AnnotationDirectoryLoader и AnnotationFileLoader, для загрузки маршрутов из аннотаций классов. Описание указанных загрузчиков отсутствует в данной статье.

Универсальный класс Route

Route, является универсальным классом, который позволяет удобно использовать компонент Routing. Его конструктор предполагает в качестве аргументов — экземпляр загрузчика, путь к файлу с маршрутами, и еще некоторые параметры:

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

С помощью опции cache_dir, включается кэширование маршрутов (в том случае если передан путь), или выключается (если он установлен в null). Кэширование происходит автоматически и в «фоновом режиме». Пример кода, в данном случае, будет таким:

  1. $locator = new FileLocator(array(__DIR__));
  2. $requestContext = new RequestContext(‘/’);
  3. $router = new Router(
  4. new YamlFileLoader($locator),
  5. ‘routes.yml’,
  6. array(‘cache_dir’ => __DIR__.‘/cache’),
  7. $requestContext
  8. );
  9. $router>match(‘/foo/bar’);

При использовании кэширования, компонент Routing компилирует классы и сохраняет их в cache_dir. Ваш скрипт должен обладать правами записи в эту директорию.

Поддержка юникода (UTF-8)

Начиная с Symfony 3.2 включена поддержка UTF-8 для маршрутов и обязательных параметров.

Благодаря параметру utf8, компонент Routing поддерживает соответствующую кодировку:

  1. // app/config/routing.php
  2. use Symfony\Component\Routing\RouteCollection;
  3. use Symfony\Component\Routing\Route;
  4. $collection = new RouteCollection();
  5. $collection>add(‘route1’, new Route(‘/category/{name}’,
  6. array(
  7. ‘_controller’ => ‘AppBundle:Default:category’,
  8. ),
  9. array(),
  10. array(
  11. ‘utf8’ => true,
  12. )
  13. );
  14. // …
  15. return $collection;

В примере, параметр utf8 установлен в значение true, что сообщает Symfony считать «.» любым символом UTF-8, вместо однобайтовых. То есть, запрос /category/日本語 будут совпадать с маршрутом. Если интересно, при включенном параметре, так же поддерживаются символы эмоций в URL.

Аналогичным образом можно использовать UTF-8 в шаблонах:

  1. // app/config/routing.php
  2. use Symfony\Component\Routing\RouteCollection;
  3. use Symfony\Component\Routing\Route;
  4. $collection = new RouteCollection();
  5. $collection>add(‘route2’, new Route(‘/default/{default}’,
  6. array(
  7. ‘_controller’ => ‘AppBundle:Default:default’,
  8. ),
  9. array(
  10. ‘default’ => ‘한국어’,
  11. ),
  12. array(
  13. ‘utf8’ => true,
  14. )
  15. );
  16. // …
  17. return $collection;
В дополнении к символам UTF-8, компонент поддерживает все PCRE свойства. Например, \p{Lu} соответствует любому заглавному символу в любом языке; \p{Greek} — любому греческому символу; \P{Han} – символ, отличный от Chinese Han
В Symfony 3.2, допустимо не указывать параметр utf8 явным образом. Если Symfony «видит» символ UTF-8, поддержка включается автоматически. Однако, описанное поведение объявлено deprecated, и в версии 4.0 придется указывать явно.