Всем привет! У большинства фреймворков, построенных на паттерне 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
Пишите в комментариях, пользовались ли вы уже роутингом в своих проектах и чего вам возможно не хватает при работе с ним ;-)
