Всем привет! У большинства фреймворков, построенных на паттерне 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

Пишите в комментариях, пользовались ли вы уже роутингом в своих проектах и чего вам возможно не хватает при работе с ним ;-)

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Используете роутинг?
43.48%да, использую10
47.83%нет, работаю по старинке urlrewrite.php11
8.7%нет, не знал про него2
Проголосовали 23 пользователя. Воздержались 2 пользователя.