Всем привет! У большинства фреймворков, построенных на паттерне MVC, отсутствуют физические страницы, содержащие в себе логику обработки и детали отображения страницы. Все это держится на плечах контроллера, а роутинг уже сопоставляет используемый URL и необходимый экшен контроллера. Считается, что использование физических страниц для отображения контента — по большой части прерогатива CMS, но на самом деле это заблуждение. В данной статье рассмотрим механику работы роутинга в Битрикс.
Контроллер
Рассматривать работу будем на примере блога, всем понятного и знакомого примера.
У нас есть контроллер постов:
class PostController extends \Bitrix\Main\Engine\Controller
{
public function listAction()
{
return 'список постов';
}
public function viewAction(string $code)
{
return 'детальная страница поста ' . $code;
}
public function createAction()
{
return 'добавление';
}
public function updateAction(string $code)
{
return 'обновление поста ' . $code;
}
public function deleteAction(string $code)
{
return 'удаление поста ' . $code;
}
protected function getDefaultPreFilters()
{
# отключаем все префильтры, чтобы нам удобно было тестировать роутинг
# в продакшен контроллере не забудьте указать нужные вам ограничения ;)
return [];
}
}
Реализация методов нам в данном случае не важна. Давайте прикрутим к нему роутинг.
Включаем роутинг
Чтобы включить роутинг, перенаправьте обработку несуществующих файлов на "routing_index.php".
Для Apache измените файл ".htaccess" в корне сайта.
# старая конфигурация через urlrewrite.php
#RewriteCond %{REQUEST_FILENAME} !/bitrix/urlrewrite.php$
#RewriteRule ^(.*)$ /bitrix/urlrewrite.php [L]
# новые правила для роутинга
RewriteCond %{REQUEST_FILENAME} !/bitrix/routing_index.php$
RewriteRule ^(.*)$ /bitrix/routing_index.php [L]
Для Nginx измените конфигурацию. В секции обработки php добавьте строку:
try_files $uri $uri/ /bitrix/routing_index.php;
В Docker-окружении роутинг включен по умолчанию. Детали про Docker вы можете найти в документации.
Теперь нам нужно в конфигурации сайта "/bitrix/.settings.php" добавить секцию с роутингом:
'routing' => [
'value' => [
'config' => ['web.php'],
],
'readonly' => true,
],
И, наконец, нам необходимо добавить файл с правилами роутинга "local/routes/web.php":
<?php
use Bitrix\Main\Routing\RoutingConfigurator;
return static function (RoutingConfigurator $routes) {
// …
};
Добавляем правила
Теперь мы можем добавить правила для отображения экшенов нашего контроллера:
<?php
use Bitrix\Main\Routing\RoutingConfigurator;
return static function (RoutingConfigurator $routes) {
$routes->any('/blog/post/', [PostController::class, 'list']);
$routes->any('/blog/post/{code}', [PostController::class, 'view']);
$routes->any('/blog/post/create/', [PostController::class, 'create']);
$routes->any('/blog/post/{code}/update/', [PostController::class, 'update']);
$routes->any('/blog/post/{code}/delete/', [PostController::class, 'delete']);
};
После чего можем перейти в браузер по ссылкам "/blog/post/" или "/blog/post/create/" и увидеть результат работы контроллера.
Обработчиком маршрута может быть не только контроллер, подробнее о типах обработчиков рассказываем в документации.
HTTP-методы
При использовании метода RoutingConfigurator::any маршрут будет обрабатывать любые HTTP-методы. Для того чтобы привязать конкретный маршрут к HTTP-методу, можно использовать соответствующие методы:
<?php
use Bitrix\Main\Routing\RoutingConfigurator;
return static function (RoutingConfigurator $routes) {
$routes->get('/blog/post/', [PostController::class, 'list']);
$routes->get('/blog/post/{code}/', [PostController::class, 'view']);
$routes->post('/blog/post/', [PostController::class, 'create']);
$routes->put('/blog/post/{code}/', [PostController::class, 'update']);
$routes->patch('/blog/post/{code}/', [PostController::class, 'update']);
$routes->delete('/blog/post/{code}/', [PostController::class, 'delete']);
};
При такой конфигурации, чтобы добавить запись, нам необходимо будет отправить POST-запрос. В случае простого перехода (метод GET) по адресу "/blog/post" будет отображен список постов.
Важно отметить, что одинаковые маршруты могут быть использованы для разных HTTP-методов!
Параметры маршрута
Выше в примерах мы использовали внутри маршрута строку {code}, которая является параметром маршрута. Параметры маршрута — это динамические части URL, которые принимают различные значения.
Для примера выше при переходе по адресу "/blog/post/my-first-article" в переменной $code будет строка "my-first-article", которая затем попадёт в метод контроллера Post::viewAction.
По умолчанию параметры используют паттерн [^/]+. Шаблон "/blog/post/{code}/" преобразуется в строку с регулярным выражением /blog/post/(?<code>\[^/\]+)/.
Если нужно свое регулярное выражение, укажите его методом where:
$routes
->get('/blog/post/{code}', [PostController::class, 'view'])
->where('code', '[\w\d\-]+')
;
Теперь маршрут будет сопоставляться по регулярному выражению /blog/post/(?<code>[\w\d\-]+).
Параметры маршрута поддерживают и значения по умолчанию. Представим что у нас мультиязычный блог и в маршрутах можно указать параметр языка для перевода. Чтобы не указывать его постоянно, мы можем задать ему значение по умолчанию:
$routes
->get('/blog/post/{code}/translate/{lang}', [PostController::class, 'translate'])
->default('lang', 'en')
;
При переходе на "/blog/post/my-first-article/translate/" параметр "lang" получит значение "en".
При переходе на "/blog/post/my-first-article/translate/de" параметр "lang" будет "de".
Таким же образом можно задать параметры, которые не участвуют в формировании адреса, но доступны в обработчике:
$routes
->get('/blog/post/{code}', static function(string $code, string $lang) {
// ...
})
->default('lang', 'en')
;
Генерация URL
Чтобы не задавать вручную используемые в роутинге маршруты, можно их генерировать через объект роутера. Но прежде чем это сделать, нужно задать нашим маршрутам имя с помощью метода name:
$routes
->get('/blog/post/{code}', [PostController::class, 'view'])
->name('blog.post.view')
;
Теперь с помощью объекта роутера, мы можем сгенерировать URL для нашей страницы:
$url = \Bitrix\Main\Application::getInstance()->getRouter()->route(
// имя маршрута
'blog.post.view',
[
// параметры для подстановки
'code' => 'my-first-article',
]
);
Переменная $url будет содержать "/blog/post/my-first-article". Дополнительные параметры, которые не входят в маршрут, можно добавить в строку запроса:
$url = \Bitrix\Main\Application::getInstance()->getRouter()->route('blog.post.view', [
'code' => 'my-first-article',
'utm_source' => 'ads123',
]);
Результат: "/blog/post/my-first-article?utm_source=ads123".
Генерация URL дает возможность менять маршрут без переписывания логики приложения. Например, изменим конфигурацию маршрута:
$routes
->get('/blog/post-{code}/', [PostController::class, 'view'])
->name('blog.post.view')
;
Тогда тот же код генерации будет создавать новый URL автоматически:
$url = \Bitrix\Main\Application::getInstance()->getRouter()->route('blog.post.view', [
'code' => 'my-first-article',
]);
Переменная $url будет содержать "/blog/post-my-first-article/".
Группировка маршрутов
Напоследок обсудим, как можно оптимизировать наш роутинг с помощью группировки. Группы маршрутов объединяют несколько маршрутов с общими характеристиками. Это помогает избежать дублирования кода. Общие настройки можно изменить в одном месте.
На текущий момент у нас есть маршруты:
return static function (RoutingConfigurator $routes) {
$routes->get('/blog/post/', [PostController::class, 'list']);
$routes->get('/blog/post/{code}/', [PostController::class, 'view']);
$routes->post('/blog/post/', [PostController::class, 'create']);
$routes->put('/blog/post/{code}/', [PostController::class, 'update']);
$routes->patch('/blog/post/{code}/', [PostController::class, 'update']);
$routes->delete('/blog/post/{code}/', [PostController::class, 'delete']);
};
Для начала объединим их в группу, перед последующими манипуляциями:
$routes
->group(function(RoutingConfigurator $routes) {
$routes->post('/blog/post/', [PostController::class, 'create']);
$routes->get('/blog/post/{code}', [PostController::class, 'view']);
$routes->put('/blog/post/{code}', [PostController::class, 'update']);
$routes->patch('/blog/post/{code}', [PostController::class, 'update']);
$routes->delete('/blog/post/{code}', [PostController::class, 'delete']);
})
;
Для уменьшения шаблонов URL добавим префиксы с помощью метода prefix:
$routes
->prefix('blog/post')
->group(static function(RoutingConfigurator $routes) {
$routes->get('', [PostController::class, 'list']); // будет /blog/post/
$routes->post('', [PostController::class, 'create']); // будет /blog/post/
$routes->get('{code}', [PostController::class, 'view']);
$routes->put('{code}', [PostController::class, 'update']);
$routes->patch('{code}', [PostController::class, 'update']);
$routes->delete('{code}', [PostController::class, 'delete']);
})
;
Подобным образом можем оптимизировать и имена маршрутов с помощью метода name:
$routes
->name('blog.post.')
->prefix('blog/post')
->group(static function(RoutingConfigurator $routes) {
$routes->get('', [PostController::class, 'list'])->name('list');
$routes->post('', [PostController::class, 'create'])->name('create');
$routes->get('{code}', [PostController::class, 'view'])->name('view');
$routes->put('{code}', [PostController::class, 'update'])->name('update');
$routes->patch('{code}', [PostController::class, 'update'])->name('update');
$routes->delete('{code}', [PostController::class, 'delete'])->name('delete');
})
;
Помимо описанного функционала группировка также позволяет оптимизировать и параметры маршрутов, подробнее об этом вы можете почитать в документации.
Заключение
Как можно заметить, механизм роутинга в Битрикс довольно прост и наверняка будет привычен для разработчиков, работавших с другими фреймворками.
Стоит уточнить, что в статье описан именно новый роутинг, т.к. до этого в CMS использовался файл urlrewrite.php для маршрутизации по физическим страницам сайта. Узнать подробнее как мигрировать со старого роутинга на новый, вы можете в этом в этом разделе.
Всю актуальную информацию по фреймворку и работе с продуктом вы найдете в нашей документации https://docs.1c-bitrix.ru
Пишите в комментариях, пользовались ли вы уже роутингом в своих проектах и чего вам возможно не хватает при работе с ним ;-)
