¶Введение
Маршрутизация позволяет регистрировать пути и обработчики для них, что в совокупности предоставляет возможность создавать страницы различной сложности.
Маршрутизация состоит из двух компонентов:
- Роут (маршрут) — содержит информацию о будущей странице, её путь, какой контроллер использовать, заголовок, требования и т.д.
- Контроллер — объект, отвечающий за формирование результата по запрошенному маршруту.
Примерная схема работы маршрутов и контроллеров:
¶Маршруты
Маршрут — описание какого-то 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 9 использует компонент 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'
¶Изменения в релизах
-
Drupal 9.2.0 (02.06.2021): Параметр
_entity_bundles
помечен устаревшим.
¶Ссылки
- Using parameters in routes (англ.), Drupal.org
- Routing (англ.), Symfony
- Drupal 8 Hello World: Пишем свой первый модуль, Niklan, 2014