¶Введение
Маршрутизация позволяет регистрировать пути и обработчики для них, что в совокупности предоставляет возможность создавать страницы различной сложности.
Маршрутизация состоит из двух компонентов:
- Роут (маршрут) — содержит информацию о будущей странице, её путь, какой контроллер использовать, заголовок, требования и т.д.
- Контроллер — объект, отвечающий за формирование результата по запрошенному маршруту.
Примерная схема работы маршрутов и контроллеров:
¶Маршруты
Маршрут — описание какого-то URL-пути для сайта с требованиями по его обработке.
Маршруты задаются в специальном файле *.routing.yml, который должен находиться в корне модуля рядом с *.info.yml. Если ваш модуль имеет название foo, в таком случае файл будет называться foo.routing.yml.
В данном файле вы объявляете все необходимые вашему модулю маршруты и описываете их при помощи YAML формата. Например:
foo.content:
path: '/my-custom-page'
defaults:
_controller: '\Drupal\foo\Controller\FooController::content'
_title: 'Hello World'
requirements:
_permission: 'access content'
Данный пример объясняет Drupal, что маршрут с названием foo.content должен быть обработан по адресу /my-custom-page. Когда пользователь обратится по данному маршруту, для него будет произведена проверка, имеет ли он доступ access content. Если не имеет, то пользователь получит ответ HTTP 403 "Недостаточно прав", если имеет, тогда результат, который возвращает контроллер. В качестве заголовка страницы будет использована строка "Hello World".
¶Свойства маршрутов
Маршруты, объявляемые в *.routing.yml могут содержать следующие свойства:
-
path: (обязательно) Путь маршрута, должен начинаться со слеша
path: '/example'. Понимает динамически части, заключенные в фигурные скобки (например:path: '/foo/{argument}/bar'). -
defaults:
-
Обязательный параметр. Должен быть один из перечисленных:
-
_controller: Значение
типа callable (callback-функция):
-
Class:method: В формате
\Drupal\[module name]\Controller\[ClassName]::[method], например_controller: '\Drupal\foo\Controller\FooController::build'.В данном случае Drupal вызовет метод
build()классаFooControllerиз неймспейса\Drupal\foo\Controller. -
Сервис: Позволяет формировать значение из сервиса. Значение задаётся в формате
mymodule.service_name:[method], гдеmymodule.service_name- название сервиса, а[method]- название метода сервиса, который необходимо вызвать. Например_controller: 'mymodule.service_name:build'.
-
-
_form: Класс, реализующий
Drupal\Core\Form\FormInterface. -
_entity_view: Значение в формате
[entity_type].[view_mode]. Маршрут найдёт сущность в пути и отрендерит её в указанном формате отображения. Например_entity_view: node.teaser. -
_entity_list: Значение в формате
[entity_type]. Выводит список сущностей указанного типа при помощиEntityListControllerданной сущности. Например_entity_list: userвыведет список всех пользователей на сайте. -
_entity_form: Значение в формате
[entity_type].[form_view_mode]. Выведет форму редактирования сущности указанного типа, в определенном формате отображения формы. Например_entity_form: node.defaultвыведет форму редактирования материала в стандартном режиме отображения формы. -
_route: Название маршрута, чьим алиасом вы хотите сделать данный маршрут.
Например
_route: views.files.page_1.
-
_controller: Значение
типа callable (callback-функция):
-
Опциональные параметры.
-
_title: Заголовок страницы на английском языке. Данное значение будет обработано
t(). -
_title_arguments: Дополнительные аргументы, передаваемые с заголовком в
t(). -
_title_context: Контекст, передаваемый с заголовком в
t(). - _title_callback: Значение типа callable, которое возвращает значение для заголовка. В данном случае, перевод значения ложится на callback-функцию.
-
_title: Заголовок страницы на английском языке. Данное значение будет обработано
-
Обязательный параметр. Должен быть один из перечисленных:
-
methods: (опционально) В дополнение к пути, вы можете ограничить типы запросов, на который будет реагировать
маршрут. Значение должно быть в виде массива с перечислением всех допустимых типов запроса.
Например
methods: [GET, POST, UPDATE, DELETE]. -
requirements: (обязательно) Определяет, какие условия должны быть удовлетворены, для того чтобы получить доступ к
маршруту. Должно быть одно или более значений (все они должны пройти успешно).
-
_permission: Строковое значение прав доступа, например
_permission: 'access content'. Может принимать несколько значений разделяемых запятой,(логический оператор OR) ( например:_permissions: 'access content,access user profiles) или плюсом+(логический оператор AND) ( например:_permissions: 'acess content+access user'). -
_role: Строковое значение роли, например
_role: 'administrator'. Вы также можете использовать указание нескольких ролей, разделяя их запятой,(логический оператор OR), или плюсом+(логический оператор AND). Рекомендуется использовать_permission, вместо_role, так как на различных сайтах роли могут иметь различные настройки и смыслы. -
_access: Установите значение равное
'TRUE', чтобы не иметь никаких проверок и результат отдавался при любых условиях. -
_entity_access: Значение в формате
[entity_type].[operation]. Доступ будет предоставлен только если пользователь проходит проверки определенных прав, конкретного типа сущности. Например_entity_access: menu.edit. -
_format: Производит проверку типа запроса. Например, указав
_format: json, маршрут будет обрабатываться, только если указать?_format=jsonпри обращении по пути маршрута. -
_content_type_format: Производит проверку по значению заголовка запроса
Content-Type. Например, если зададите_content_type_format: json, то маршрут отработает только если при запросе был передан заголовокContent-Type: application/json. -
_custom_access: Позволяет задать метод, который будет вызван для проверки прав доступа.
Например
_custom_access: '\Drupal\foo\Controller\FooController::access' -
_module_dependencies: Производит проверку по модулю. Вы также можете использовать указание нескольких модулей,
разделяя их запятой
,(логический оператор OR), или плюсом+(логический оператор AND). Если указанный модуль отключен, то маршрут будет недоступен. Если модуль имеет зависимости, они также автоматически подразумевают проверку, поэтому нет никакого смысла указывать все зависимости. -
_csrf_token: Значение равное
'TRUE'произведет проверкуX-CSRF-Tokenиз заголовка запроса, для проверки, является ли запрос валидным. Используйте, для того чтобы убедиться что запрос валидный и не изменен. - Любой другой существующий Access Check сервис. Для более подробной информации обратитесь к разделу контроля доступа маршрутов.
-
_permission: Строковое значение прав доступа, например
-
options: (опционально) Дополнительные настройки, с которыми маршрут должен взаимодействовать. Распространенные:
-
_admin_route: Помечает данный маршрут как часть административного интерфейса. Для всех маршрутов начинающихся
с
/admin, данное значение автоматически устанавливается в'TRUE'. -
_auth: Доступные способы авторизации для маршрута. По умолчанию доступны все варианты авторизации с
значением
global. Данное значение ограничивает варианты авторизации только для указанных. Например_auth: ['basic_auth', 'cookie']. Разработчики могут объявлять свои собственные способы авторизации при помощи Authentication API. -
_maintenance_access: Значение равное
'TRUE'позволяет отрабатывать данному маршруту в режиме обслуживания. -
no_cache: Значение равное
'TRUE'отключает кеширование ответа по этому маршруту. - parameters: Используются для передачи данных контроллеру. Для более подробной информации обратитесь к разделу «параметры маршрутов».
-
_admin_route: Помечает данный маршрут как часть административного интерфейса. Для всех маршрутов начинающихся
с
¶Контроллеры
Контроллер — обрабатывает входящие запросы конкретного маршрута.
Drupal использует компонент Symfony - HTTP Kernel, который получает запрос и опрашивает другие системы в надежде получить от них результат, который необходимо вернуть пользователю. Результатом является один из объектов, наследующих Symfony\Component\HttpFoundation\Response или непосредственно данный объект. В случае с Drupal, мы также имеем возможность вернуть render array в качестве результата. Drupal обнаружит его, проведет все необходимые обработки и операции с ним, а затем вернет корректный ответ при помощи Response.
Например, если мы хотим создать контроллер для примера из маршрутизации выше, то результат может быть примерно таким:
<?php
namespace Drupal\foo\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* An foo controller.
*/
class FooController extends ControllerBase {
/**
* Returns a render-able array for a test page.
*/
public function content() {
$build = [
'#markup' => $this->t('Hello World!'),
];
return $build;
}
}В результате чего, Drupal вернет в качестве контента страницы строку "Hello World" на языке пользователя.
¶Параметры маршрутов
Параметр маршрута, также известный как slug — это переменная в пути маршрута, которая передаётся в качестве аргумента контроллеру.
Переменная маршрута заключаются в фигурные скобки {…} и должна иметь уникальное имя в пределах маршрута.
¶Примитивные (сырые) параметры
Пример маршрута с объявленным параметром:
blog.page:
path: '/blog/{slug}'
defaults:
_controller: '\Drupal\blog\Controller\BlogController::page'В примере выше параметром маршрута является {slug}. Значение {slug} будет передаваться контроллеру в качестве аргумента. Параметр {slug} является «сырым» и будет передан контроллеру «как есть» в виде строки.
Для того чтобы принять значение параметра в контроллере, в методе, отвечающим за подготовку ответа, вы должны объявить одноимённый параметр для метода ::page().
public function page(string $slug) { }
Обратившись по пути /blog/foo, $slug получит значение foo.
Вы можете объявлять сколько угодно параметров для маршрута, но каждый из них должен использоваться единожды.
blog.page:
path: '/blog/{foo}/{bar}/example-{baz}'
defaults:
_controller: '\Drupal\blog\Controller\BlogController::page'
¶Параметры с валидацией
Иногда может потребоваться, чтобы параметр удовлетворял определённым требованиям, например, являлся строкой или числом.
Для того чтобы указать какие параметры допустимы для маршрута, используется свойство requirements, где в качестве ключа указывается название параметра, а в качестве значения — регулярное выражение PHP, которому должно соответствовать значение параметра.
Если маршрут будет найден, но валидация не будет пройдена, будет предпринята попытка найти следующий марширут с аналогичным путём. Если аналогичных маршрутов больше нет, или все они не пройдут проверку, будет возвращён ответст HTTP 403.
Таким образом вы можете зарегистрировать несколько маршрутов с одинаковым путём, но разными обработчиками и требованиями для разных значений параметров.
blog.list:
path: '/blog/{page}'
defaults:
_controller: '\Drupal\blog\Controller\BlogController::list'
requirements:
# '\d+' — регулярное выражение «одно или более число».
slug: \d+
blog.page:
path: '/blog/{slug}'
defaults:
_controller: '\Drupal\blog\Controller\BlogController::page'В примере выше показано, что если обратиться по пути /blog/* передав в качестве параметра число, то будет вызван BlogController::list(), а если валидация на число не пройдёт, то будет вызван BlogController::page().
Для простых проверок, требования могут быть указаны прямо в параметре пути используя следующий синтаксис: {parameter_name<requirements>}. Это позволяет сделать маршрут более компактным, но уменьшает его читаемость.
blog.list:
path: '/blog/{page<\d+>}'
defaults:
_controller: '\Drupal\blog\Controller\BlogController::list'
¶Опциональные параметры
В примере выше, для маршрута blog.list мы указали путь /blog/{page}. Если пользователь обратится по пути /blog/1, он будет корректно обработан, но если обратиться по пути /blog, то проверка не пройдёт, ведь значение не будет передано.
Вы можете объявить третий маршрут /blog, который будет обрабатывать этот сценарий, но скорее всего, там будет тоже самое что и при обращении к /blog/1.
Для таких случаев, вы можете указать значение по умолчанию, которое будет использоваться если параметр не передан. Для этого используется раздел defaults маршрута.
blog.list:
path: '/blog/{page}'
defaults:
_controller: '\Drupal\blog\Controller\BlogController::list'
page: 1
requirements:
slug: \d+Теперь, если пользователь посетит /blog, то будет найден маршрут blog.list, а в качестве $page будет передано значение по умолчанию — 1.
Как и в случае с валидацией параметров, значение по умолчанию может быть указано сразу в пути, для эттого используется следующий синтаксис: {parameter_name?default_value}. Этот синтаксис совместим с валидацией, поэтому вы можете использовать их одновременно:
blog.list:
path: '/blog/{page<\d+>?1}'
defaults:
_controller: '\Drupal\blog\Controller\BlogController::list'
¶Конвертирование параметров
Очень часто, значение в параметре является идентификатором какого-то значения хранимого в системе. Для того чтобы каждый раз не описывать логику загрузки данных, используются конвертеры параметров.
Каждому параметру можно указать какой-либо явный тип, а на основе этого типа и переданного значения, конвертеры параметров предоставляют конечный результат. Данные требования описываются в свойстве options.parameters.
Самое распространённое применение в Drupal — конвертация ID сущности в саму сущность, то есть, её загрузку.
example.node_converter:
path: '/example/{node}'
defaults:
_controller: '\Drupal\example\Controller\NodeConverterController::build'
options:
parameters:
node:
type: entity:nodeВ примере выше, при обращении по адресу /example/1, будет загружена сущность node с ID равным 1 и передана в качестве аргумента методу контроллера. В данном случае, метод уже должен ожидать конечный тип от используемого конвертера параметров, вместо сырого значения:
public function build(?NodeInterface $node) {}Конвертер в примере выше является универсальным для любой сущности Drupal и имеет синтаксис entity:[entity_type_id].
¶Магические параметры
Перечисленные ниже специальные объекты могут передаваться методу контроллера. Чтобы их получать, в методе контроллера должны быть объявлены параметры с соответствующим типом:
-
\Symfony\Component\HttpFoundation\Request: необработанный объект запроса Symfony. Обычно используется если контроллеру необходимо получить значения параметров запроса. Глобальная переменная$_GETдоступна в методе$request->query(возвращаетParameterBag, не массив), информация о сессии может быть получена с помощью метода$request->getSession()(возвращаетSessionInterface). -
\Drupal\Core\Routing\RouteMatchInterface: в переменную будет записана информациясоответствия маршрутов(данные о результате выполнения процесса роутинга). Например, чтобы получить объект текущего маршрута, можно использовать метод$route_match->getRouteObject(). -
\Psr\Http\Message\ServerRequestInterface: Необработанный запрос, представленный с использованием формата PSR-7 ServerRequest.
¶Параметры для маршрутов с формой
Если в качестве обработчика для маршрута используется форма, то параметры будут переданы в ::buildForm() после основных.
foo.user_form:
path: '/example/{foo}/{bar}'
defaults:
foo: 'default_foo_value'
_form: '\Drupal\foo\Form\ExampleForm'
public function buildForm(array $form, FormStateInterface $form_state, string $foo, string $bar = '') { }Важно: каждому параметру маршрата формы необходимо указывать значение по умолчанию либо на уровне объявления метода (
{bar}), либо на уровне маршрута ({foo}).
¶Контроль доступа
Каждый маршрут в Drupal должен иметь, по крайней мере одну, проверку доступа. Данные требования указываются в requirements разделе маршрута.
В данном разделе маршрута указываются какие Access Check сервисы будут использованы для проверки доступа. Вы можете использовать как стандартные, предоставляемые с ядром, проверки доступа, так и создавать свои собственные.
¶Использование _custom_access
Данная проверка доступа подразумевает что вы напишите собственную логику для маршрута. Вы должны указать в качестве значения полный путь, включая неймспейс, до объекта и его метода, который будет вызван для проверки прав доступа к конкретному маршруту.
Пример использования:
foo.content:
path: '/my-custom-page'
defaults:
_controller: '\Drupal\foo\Controller\FooController::content'
_title: 'Hello World'
requirements:
_custom_acess: '\Drupal\foo\Controller\FooController::access'В качестве результата данный метод должен возвращать экземпляр объекта Drupal\Core\Access\AccessResultInterface.
Пример проверки прав доступа:
<?php
namespace Drupal\foo\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
/**
* An foo controller.
*/
class FooController extends ControllerBase {
/**
* Returns a render-able array for a test page.
*/
public function content() {
$build = [
'#markup' => $this->t('Hello World!'),
];
return $build;
}
/**
* Returns access result for controller.
*/
public function access(AccountInterface $account) {
return AccessResult::allowedIf(
$account->hasPermission('do example things')
&& $account->hasPermission('do other things')
);
}
}
¶Написание Access Check сервиса
Разработчики могут описывать свои access check сервисы. Например _custom_access выше тоже является access check сервисом.
Отличие access check от _custom_access в том, что собственный сервис вы можете применять к любым маршрутам. Он должен быть максимально универсальным в пределах тех маршрутов, к которым предназначен, тогда как в _custom_access, скорее всего, вы напишите индивидуальную проверку доступа, которая касается только данного маршрута и больше никакого.
Access check является сервисом и поддерживает все его возможности. Объекты, используемые для access check сервисов рекомендуется создавать в src/Access.
Пример объекта с проверкой доступа:
<?php
namespace Drupal\foo\Access;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
/**
* Checks access for displaying configuration translation page.
*/
class FooAccessCheck implements AccessInterface {
/**
* A custom access check.
*
* @param \Drupal\Core\Session\AccountInterface $account
* Run access checks for this account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(AccountInterface $account) {
// Check permissions and combine that with any custom access checking needed. Pass forward
// parameters from the route and/or request as needed.
return ($account->hasPermission('do example things') && $this->someOtherCustomCondition()) ? AccessResult::allowed() : AccessResult::forbidden();
}
}Метод должен возвращаться экземпляр объекта Drupal\Core\Access\AccessResultInterface в качестве результата проверки прав доступа.
В качестве аргументов метод проверки доступа принимает:
- Первым делом все динамические части маршрута
{name}. Они должны идти в том же порядке что и вpathи теми же названиями. - Любой из данных объектов в любом порядке:
-
\Symfony\Component\HttpFoundation\Request $request. -
\Symfony\Component\Routing\Route $route -
\Drupal\Core\Routing\RouteMatch $route_match -
\Drupal\Core\Session\AccountInterface $account
-
Далее вам необходимо объявить данный объект как сервис, например:
services:
foo.access_checker:
class: Drupal\foo\Access\FooAccessCheck
tags:
- { name: access_check, applies_to: _foo_access_check }После чего вы можете применять его к любым маршрутам на сайте:
foo.content:
path: '/my-custom-page'
defaults:
_controller: '\Drupal\foo\Controller\FooController::content'
_title: 'Hello World'
requirements:
_foo_access_check: 'TRUE'
¶Ссылки
- Using parameters in routes (англ.), Drupal.org
- Routing (англ.), Symfony
- Drupal 8 Hello World: Пишем свой первый модуль, Niklan, 2014