Routing
Подробное описание модернизации функционала маршрутизации Битрикс. Переход от нативного способа, к работе через функционал атрибутов php.
Атрибуты PHP: https://www.php.net/manual/ru/language.attributes.overview.php
Роутинг в Битрикс: https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&CHAPTER_ID=013764&LESSON_PATH=3913.3516.5062.13764
Контроллеры в Битрикс: https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&CHAPTER_ID=03750&LESSON_PATH=3913.3516.5062.3750
Функционал роутинга доступен в модуле main
начиная с версии 21.400.0.
Для пользовательских модулей использование собственных роутов в папке модуля на данный момент не предусмотрено.
SetUp
Описание настройки конфигурации работы с функционалом роутинга Битрикс.
Копируем файл .settings.php
в директорию /local
и добавляем в возвращаемый массив следующую структуру:
'routing' => [
'value' => [
'config' => [
'web.php',
'api.php'
]
]
],
С версии 24.100.0
главного модуля файлы настроек .settings.php
и .settings_extra.php
могут быть размещены в папке /local
, а файл dbconn.php
— в папке /local/php_interface
.
Создаем файлы web.php
и api.php
в директории /local/routes
со следующим содержанием:
<?php declare(strict_types=1);
use Bitrix\Main\Routing\RoutingConfigurator;
return function (RoutingConfigurator $routes) {
};
Файлы с конфигурацией маршрутов по умолчанию располагаются в папках /bitrix/routes/
и /local/routes/
.
Помещаем кастомизированный файл роут обработчика в директорию /local:
//todo: добавить код файла
Для запуска новой системы роутинга нужно перенаправить обработку 404 ошибок на файл routing_index.php
в файле .htaccess
:
#RewriteCond %{REQUEST_FILENAME} !/bitrix/urlrewrite.php$
#RewriteRule ^(.*)$ /bitrix/urlrewrite.php [L]
RewriteCond %{REQUEST_FILENAME} !/local/routing_index.php$
RewriteRule ^(.*)$ /local/routing_index.php [L]
Life Cycle
Детально описание жизненного цикла модернизированного скрипта обработчика маршрутов.
Начало php скрипта
<?php declare(strict_types=1);
Инициализируется логика основного модуля системы
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/start.php';
\Bitrix\Main\Application::getDocumentRoot()
-- Класса приложение не существует до момента инициализации ядра, по этой причине нет возможности использовать нативные методы ядра Битрикс. Получение значении директивы корня сервера исполняемый директории выполняется через нативный php $_SERVER['DOCUMENT_ROOT']
$application = \Bitrix\Main\Application::getInstance();
$application->initializeExtendedKernel([
'get' => $_GET,
'post' => $_POST,
'files' => $_FILES,
'cookie' => $_COOKIE,
'server' => $_SERVER,
'env' => $_ENV
]);
Подключается файл пролога, так как он используется в нативном скрипте в 3-х из 4-х случаев и содержит определение автозагруженных классов и классов зарегистрированных модулей, а также автоматически подключает файлы init.php
require_once \Bitrix\Main\Application::getDocumentRoot() . '/bitrix/modules/main/include/prolog_before.php';
// todo: ReflectionClass & ReflectionMethod
Создаются объекты конфигуратора маршрутов и маршрутизатора, далее добавляются в HTTP приложение Битрикс
$routes = new \Bitrix\Main\Routing\RoutingConfigurator();
$router = new \Bitrix\Main\Routing\Router();
$routes->setRouter($router);
$application->setRouter($router);
Объявляется переменная массив для хранения путей файлов с конфигурацией маршрутов
$files = [];
Добавляются пути к пользовательским файлам с маршрутами из файла конфигурации .settings.php
/** @var array{config: array{string} } $routingConfig
* config => [ 0 => 'web.php', ... ];
*/
$routingConfig = \Bitrix\Main\Config\Configuration::getInstance()->get('routing');
if (!empty($routingConfig['config']))
{
/** @var string[] $fileNames [ 'web.php', 'api.php' ] */
$fileNames = $routingConfig['config'];
foreach ($fileNames as $fileName)
{
foreach (['local', 'bitrix'] as $vendor)
{
if (file_exists(\Bitrix\Main\Application::getDocumentRoot() .'/'.$vendor.'/routes/'.basename($fileName)))
{
// expect: SITE_DIR/local/routes/web.php
// expect: SITE_DIR/local/routes/api.php
$files[] = \Bitrix\Main\Application::getDocumentRoot() .'/'.$vendor.'/routes/'.basename($fileName);
}
}
}
}
Добавляются системные маршруты
if (file_exists(\Bitrix\Main\Application::getDocumentRoot() . '/bitrix/routes/web_bitrix.php'))
{
//expect: файл отсутствует на сервере
$files[] = \Bitrix\Main\Application::getDocumentRoot() . '/bitrix/routes/web_bitrix.php';
}
Устанавливаются конфигурации из файлов маршрутов
foreach ($files as $file)
{
// return function (RoutingConfigurator $routes) функция обертка с конфигом роутов
// {closure} - замыкание из файлов с роутингом
$callback = include $file;
// устанавливаются конфигурации \RoutingConfigurations, в $routes
$callback($routes);
}
Регистрируются маршруты
// \RoutingConfigurations превращаются в роуты $routes => \Bitrix\Main\Routing\Route[]
$router->releaseRoutes();
Кэшируются маршруты файлов и конфигурация объекта роутера
\Bitrix\Main\Routing\CompileCache::handle($files, $router);
Выполняется поиск совпадения текущего маршрута из \HttpRequest
в маршрутах роутера
// match request
$request = \Bitrix\Main\Context::getCurrent()->getRequest();
// маршрут \Route или пустота (void)
$route = $router->match($request);
Если совпадение найдено
Поток исполнения определяет один из вариантов типа роут-обработчика:
PublicPageController
- обработчик для совместимости нативных страниц php с подключенной бизнес логикой, например компонент и html + js + php код.\Closure
- функция замыкания, любая функция на исполнение, которую возвращает обработчик.array
- массив с названием класса и наименование Action метода, который будет вызван на исполнение. Пример:[\Umsoft\Example::class, 'foo']
string
- строка с наименованием класса и метода для запуска. Пример:'\Umsoft\Example:foo'
- Любой другой тип:
// любой другой тип контроллера, выкидываем исключение
throw new \Bitrix\Main\SystemException(sprintf(
'Unknown controller `%s`', $controller
));
Код исполнения:
//region Если роутер найден
if ($route !== null)
{
// устанавливаем маршрут в инстанс приложения Битрикс
$application->setCurrentRoute($route);
// copy route parameters to the request
if ($route->getParametersValues())
{
foreach ($route->getParametersValues()->getValues() as $name => $value)
{
//если есть параметры GET запроса ?, устнавиваются в глобальные переменные.
// $name - параметр, $value - значение
$_GET[$name] = $value;
$_REQUEST[$name] = $value;
}
}
$_SERVER["REAL_FILE_PATH"] = '/bitrix/routing_index.php';
// получаем функцию-контроллер
// callable , имя функции в роутере, в файле роута, пример [':class', 'method'];
$controller = $route->getController();
// тип контроллера - Публичная страница
if ($controller instanceof \Bitrix\Main\Routing\Controllers\PublicPageController)
{
require_once \Bitrix\Main\Application::getDocumentRoot() . '/bitrix/modules/main/classes/general/virtual_io.php';
$_SERVER["REAL_FILE_PATH"] = $controller->getPath();
/** @var string $path Путь к исполняемому файлу */
$path = \Bitrix\Main\Application::getDocumentRoot() . $controller->getPath();
$physicalFileName = (new \Bitrix\Main\IO\File(
path: $path
))->getPhysicalPath();
if(!is_string($physicalFileName))
throw new \Bitrix\Main\IO\InvalidPathException($path);
require_once $physicalFileName;
die;
}
// тип контроллера - Функция/замыкание
elseif ($controller instanceof \Closure)
{
$binder = \Bitrix\Main\Engine\AutoWire\Binder::buildForFunction($controller);
// pass current route
$binder->appendAutoWiredParameter(new \Bitrix\Main\Engine\AutoWire\Parameter(
\Bitrix\Main\Routing\Route::class,
fn () => $route
));
// pass request
$binder->appendAutoWiredParameter(new \Bitrix\Main\Engine\AutoWire\Parameter(
\Bitrix\Main\HttpRequest::class,
fn () => \Bitrix\Main\Context::getCurrent()->getRequest()
));
// pass named parameters
$binder->setSourcesParametersToMap([
$route->getParametersValues()->getValues()
]);
// init kernel
require_once \Bitrix\Main\Application::getDocumentRoot() . '/bitrix/modules/main/include/prolog_before.php';
// call
$result = $binder->invoke();
// send response
if ($result !== null)
{
if ($result instanceof \Bitrix\Main\HttpResponse)
{
// ready response
$response = $result;
}
elseif (is_array($result))
{
// json
$response = new \Bitrix\Main\Engine\Response\Json($result);
}
else
{
// string
$response = new \Bitrix\Main\HttpResponse();
$response->setContent($result);
}
$application->getContext()->setResponse($response);
$response->send();
}
// terminate app
$application->terminate(0);
}
// тип котроллера - Массив (классический контроллер)
elseif (is_array($controller))
{
// подключаем ядро-пролог
require_once \Bitrix\Main\Application::getDocumentRoot() . '/bitrix/modules/main/include/prolog_before.php';
// classic controller
// [':class', 'method'] = <= [ 0 => ':class' , 1 => 'method' ]
[$controllerClass, $actionName] = $controller;
/**
* ! пытается вычислить модуль из пространства имен класса и проверяет подключен ли он
* @see https://dev.1c-bitrix.ru/api_d7/bitrix/main/loader/autoload.php
* \Bitrix\Main\Loader::requireClass($controllerClass);
*/
//region Модификации в код
/**
* ? дополняю базовый код
*/
try{
\Bitrix\Main\Loader::requireClass($controllerClass);
}catch(\Bitrix\Main\LoaderException $e){
// пытаемся запустить контроллер без регистрации модуля
//\Bitrix\Main\Loader::registerAutoLoadClasses(null, [
// 'Umsoft\Controller\BaseController' => '/local/php_interface/lib/Umsoft/Controllers/BaseController.php',
// 'Umsoft\Controller\MainController' => '/local/php_interface/lib/Umsoft/Controllers/MainController.php',
//]);
// todo: удалить
//require_once \Bitrix\Main\Application::getDocumentRoot() . '/local/php_interface/init.php';
\Bitrix\Main\Loader::autoLoad($controllerClass);
}
//endregion
// проверяем extend'ит ли класс, класс котроллера
if(is_subclass_of($controllerClass, \Bitrix\Main\Engine\Controller::class)){
// проверяем содержит ли имя метода постфикс Action
if (substr($actionName, -6) === 'Action')
{
$actionName = substr($actionName, 0, -6);
}
// запускаем котроллер на исполнение
$application->runController($controllerClass, $actionName);
}
}
// тип контроллера - Строка
elseif (is_string($controller))
{
require_once \Bitrix\Main\Application::getDocumentRoot() . '/bitrix/modules/main/include/prolog_before.php';
// actually action could be attached to a few controllers
// but what if action was made for the one controller only
// then it could be used in routing
$actionClass = $controller;
\Bitrix\Main\Loader::requireClass($actionClass);
if (is_subclass_of($controller, \Bitrix\Main\Engine\Action::class))
{
if (is_subclass_of($actionClass, \Bitrix\Main\Engine\Contract\RoutableAction::class))
{
/** @var \Bitrix\Main\Engine\Contract\RoutableAction $actionClass */
$controllerClass = $actionClass::getControllerClass();
$actionName = $actionClass::getDefaultName();
/** @var \Bitrix\Main\HttpApplication $app */
$app = \Bitrix\Main\Application::getInstance();
$app->runController($controllerClass, $actionName);
}
else
{
throw new \Bitrix\Main\SystemException(sprintf(
'Action `%s` should implement %s interface for being called in routing',
$actionClass, \Bitrix\Main\Engine\Contract\RoutableAction::class
));
}
}
}
// любой другой тип контроллера, выкидываем исключение
throw new \Bitrix\Main\SystemException(sprintf(
'Unknown controller `%s`', $controller
));
}
//endregion
Если совпадение по маршруту не найдено (uri не зарегистрирован как роут-обработчик) поток исполнения передается в стандартный файл urlrewrite.php
Последняя строчка скрипта
// если маршрут не зарегистрирован как роут-обработчик,
// подключаем стандартный файл маршрутов urlrewrite;
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/urlrewrite.php';