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

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

Комментарии (13)


  1. ispitsyn
    22.12.2025 16:59

    Где примеры с middleware?


    1. mbezvodinskikh
      22.12.2025 16:59

      В документации ))))))


      1. ispitsyn
        22.12.2025 16:59

        В статье намного больше смысла чем в Вашем комментарии.
        Не поленился ещё раз проверить, если статья про роутинг то и вопрос про middleware в роуторе, а не итерпритации(ActionFilter) в контроллере.


        1. mbezvodinskikh
          22.12.2025 16:59

          Во-первых, в статье нет смысла, так как это просто копи-паст документации с элементами комментариев.

          Во-вторых, очень жаль что вы не поняли стёб.

          В-третьих, если вам нужно использовать middleware в контексте роута, а документации нет - открываете исходный код и построчно вникаете в логику (именно так люди и делают, когда отсутствует документация). Здесь middleware это повторение laravel, поэтому если вы с ним работали, для вас не составит труда его использовать.


    1. rpsv Автор
      22.12.2025 16:59

      В данный момент они не используется и вся логика привычных middleware отдана на откуп экшен фильтрам.


  1. mbezvodinskikh
    22.12.2025 16:59

    Интересно вы конечно делаете (пытаетесь скрестить битрикс, ларавель и симфони). Статья ни о чем - вроде бы «переходите на новый роуминг и отказываетесь от urlrewrite.php», но ни слова как это подружить с компонентами, с Эрмитажем, да хоть просто со страницами. Ни слова что добавили урезанный до невозможности view(). Да, все это есть в документации, тогда для чего бессмысленная статья? Просто напишите еще один пост как скормили ИИ весь свой код чтобы она за вас написала документацию. Так ждал Битрикс Framework 2.0, но о нем ни слуху не духу. В общем, разработчики на битрикс, хотите что-то придумать и сделать обратную совместимость со старым роумингом, но в тоже время полностью отказаться от него - копайте кишки ядра (это возможно, у меня получилось, но костыли вам обеспечены)


    1. rpsv Автор
      22.12.2025 16:59

      Интересно вы конечно делаете (пытаетесь скрестить битрикс, ларавель и симфони).

      Не обижайте Yii , он тоже хороший доннор для скрещивания :)

      но ни слова как это подружить с компонентами, с Эрмитажем, да хоть просто со страницами. Ни слова что добавили урезанный до невозможности view(). Да, все это есть в документации, тогда для чего бессмысленная статья?

      Статья про роутинг, а не про "всё на свете". Не переживайте, про работу с контроллерами статья будет.

      (это возможно, у меня получилось, но костыли вам обеспечены)

      Нельзя скрывать такое творчество от общественности, с нетерпеньем жду от вас статью с деталями ;)


      1. mbezvodinskikh
        22.12.2025 16:59

        Нельзя скрывать такое творчество от общественности, с нетерпеньем жду от вас статью с деталями ;)

        Не времени чтобы писать статьи)) Вот костыльная реализация, которую можно доработать при желании. Но суть думаю понятна.

        ->get('/blog/{page}/', function (Route $route) {
        
                    $res = \Bitrix\Main\UrlRewriter::getList(
                        SITE_ID,
                        ['QUERY' => '/blog/'],
                        ['SORT' => 'ASC']
                    )[0];
        
                    $path = $res['PATH'];
                    $uri = new \Bitrix\Main\Web\Uri('/blog/');
                    parse_str($uri->getQuery(), $queryParams);
        
                    $GLOBALS['PAGEN_1'] = $route->getParameterValue('page');
        
                    if ($queryParams) {
                        $_SERVER["QUERY_STRING"] = Bitrix\Main\Web\Uri::urnEncode($uri->getQuery(), false);
                        foreach ($queryParams as $name => $value) {
                            $route->getParametersValues()->set($name, $value);
                            $_REQUEST[$name] = $value;
                            $_GET[$name] = $value;
                        }
                    }
        
                    $_SERVER["REAL_FILE_PATH"] = $path;
        
                    include_once $_SERVER['DOCUMENT_ROOT'] . $path;
                })


        1. rpsv Автор
          22.12.2025 16:59

          А чем вас стандартный PublicPageController не устроил? :)


          1. mbezvodinskikh
            22.12.2025 16:59

            class PublicPageController
            {
            	protected $path;
            
            	public function __construct($path)
            	{
            		$this->path = $path;
            	}
            
            	public function __invoke()
            	{
            	}
            
            	/**
            	 * @return mixed
            	 */
            	public function getPath()
            	{
            		return $this->path;
            	}
            }

            Серьезно ?:)))))) Думаю отвечать не нужно чем не устроил - код говорит за меня)


            1. rpsv Автор
              22.12.2025 16:59

              Ну, а вы не думали посмотреть как он используется? ;)


              1. mbezvodinskikh
                22.12.2025 16:59

                Конечно я смотрел) но логика урезанная. И это конечно сильно - если это PublicPageController то делай магию. Моя магия хотя бы работает в отличии от PublicPageController.


  1. mbezvodinskikh
    22.12.2025 16:59

    если статья про роутинг то и вопрос про middleware в роуторе, а не итерпритации(ActionFilter) в контроллере.

    @ispitsyn оставлю это здесь

    Middleware === ActionFilter :)
    Middleware === ActionFilter :)