From 1de276624cf049351b8bbe70e90dac70bf935dfe Mon Sep 17 00:00:00 2001 From: ProklUng Date: Sat, 24 Jul 2021 18:07:52 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83?= =?UTF-8?q?=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D0=B2=D0=BD=D1=8B=D1=85=20=D1=80=D0=BE=D1=83?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=20=D0=91=D0=B8=D1=82=D1=80=D0=B8=D0=BA=D1=81?= =?UTF-8?q?=D0=B0=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20Yaml=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SymfonyRouterExtension.php | 25 +++ Resources/config/native_routes.yaml | 23 +++ Services/Utils/BitrixRouteConvertor.php | 161 ++++++++++++++++++ readme.MD | 90 +++++++++- 4 files changed, 296 insertions(+), 3 deletions(-) create mode 100644 Resources/config/native_routes.yaml create mode 100644 Services/Utils/BitrixRouteConvertor.php diff --git a/DependencyInjection/SymfonyRouterExtension.php b/DependencyInjection/SymfonyRouterExtension.php index a8992c2..b0f3a1f 100644 --- a/DependencyInjection/SymfonyRouterExtension.php +++ b/DependencyInjection/SymfonyRouterExtension.php @@ -3,6 +3,7 @@ namespace Prokl\BitrixSymfonyRouterBundle\DependencyInjection; use Exception; +use Bitrix\Main\ModuleManager; use Prokl\BitrixSymfonyRouterBundle\Services\Facades\RunController; use Prokl\BitrixSymfonyRouterBundle\Services\Facades\RunRoute; use Prokl\FacadeBundle\Services\AbstractFacade; @@ -39,6 +40,12 @@ public function load(array $configs, ContainerBuilder $container) : void $loader->load('services.yaml'); + // Дела с нативным битриксовым роутером - только для главного + // модуля версии 21.400.0 и старше. + if ($this->checkRequirements()) { + $loader->load('native_routes.yaml'); + } + // Если не задействован FacadeBundle, то удалить фасады. if (!class_exists(AbstractFacade::class)) { $container->removeDefinition(RunRoute::class); @@ -166,6 +173,24 @@ private function registerRouterConfiguration( 'setConfigCacheFactory', [new Reference('config_cache_factory')] ); + + if ($this->checkRequirements()) { + $nativrRouterDefinition = $container->getDefinition('bitrix_native_routes.router'); + $nativrRouterDefinition->addMethodCall( + 'setConfigCacheFactory', + [new Reference('config_cache_factory')] + ); + } } } + + /** + * Проверка на нужную версию главного модуля. + * + * @return boolean + */ + private function checkRequirements(): bool + { + return CheckVersion(ModuleManager::getVersion('main'), '21.400.0'); + } } diff --git a/Resources/config/native_routes.yaml b/Resources/config/native_routes.yaml new file mode 100644 index 0000000..74baf49 --- /dev/null +++ b/Resources/config/native_routes.yaml @@ -0,0 +1,23 @@ +services: + # конфигурация по умолчанию в *этом* файле + _defaults: + autowire: true + autoconfigure: true + public: true + + bitrix_native_routes.router: + class: Symfony\Component\Routing\Router + arguments: + - '@routing.loader' + - '%kernel.project_dir%/local/configs/bitrix_routes.yaml' + - cache_dir: '%router.cache.path%' + debug: '%kernel.debug%' + generator_class: Symfony\Component\Routing\Generator\CompiledUrlGenerator + generator_dumper_class: Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper + matcher_class: Symfony\Bundle\FrameworkBundle\Routing\RedirectableCompiledUrlMatcher + matcher_dumper_class: Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper + - '@request.context' + + bitrix_native_routes.routes.collection: + class: Symfony\Component\Routing\RouteCollection + factory: ['@bitrix_native_routes.router', 'getRouteCollection'] diff --git a/Services/Utils/BitrixRouteConvertor.php b/Services/Utils/BitrixRouteConvertor.php new file mode 100644 index 0000000..62023d9 --- /dev/null +++ b/Services/Utils/BitrixRouteConvertor.php @@ -0,0 +1,161 @@ +routeCollection = $routeCollection; + } + + /** + * Конвертировать все роуты в битриксовые. + * + * @param RoutingConfigurator $routes Битриксовые роуты. + * + * @return void + */ + public function convertRoutes(RoutingConfigurator $routes) : void + { + $allRoutes = $this->routeCollection->all(); + + foreach ($allRoutes as $name => $route) { + $this->convertRoute($name, $route, $routes); + } + } + + /** + * Конвертировать симфонический роут в битриксовый. + * + * @param string $name Название роута. + * @param Route $route Роут. + * @param RoutingConfigurator $routes Битриксовые роуты. + * + * @return void + * @throws RuntimeException | LogicException Когда что-то не так с классами контроллера. + * + * @internal + * Контроллеры __invoke не поддерживаются. + * Контроллер должен наследоваться от Bitrix\Main\Engine\Controller. + * Если не задан контейнер, то класс контроллера инстанцируется обычным образом. + * В методе контроллера обязательно должно содержаться Action (битриксовое требование). + */ + public function convertRoute(string $name, Route $route, RoutingConfigurator $routes) : void + { + $methods = []; + foreach ((array)$route->getMethods() as $method) { + $methods = array_merge($methods, explode('|', $method)); + } + + $path = $route->getPath(); + $controller = $this->parseControllerString($name, $route->getDefault('_controller')); + + if (!is_subclass_of($controller[0], Controller::class)) { + throw new RuntimeException( + sprintf('Controller %s must extend \Bitrix\Main\Engine\Controller class.', $controller[0]) + ); + } + + // Достаю из контейнера, только если он задан. + if ($this->container !== null) { + if (!$this->container->has($controller[0])) { + throw new RuntimeException( + sprintf('Controller %s not registered as service', $controller[0]) + ); + } + + $service = $this->container->get($controller[0]); + } else { + if (!class_exists($controller[0])) { + throw new RuntimeException( + sprintf('Class %s not exist.', $controller[0]) + ); + } + $service = new $controller[0]; + } + + $processedRoute = $routes->any($path, [$service, $controller[1]]); + + $processedRoute = $processedRoute->methods($methods) + ->name($name); + + foreach ($route->getRequirements() as $reqParam => $reqValue) { + $processedRoute = $processedRoute->where($reqParam, $reqValue); + } + + foreach ($route->getDefaults() as $defaultParam => $defaultValue) { + if (stripos($defaultParam, '_') !== false) { + continue; + } + $processedRoute = $processedRoute->default($defaultParam, $defaultValue); + } + } + + /** + * Парсинг строки _controller. + * + * @param string $name Название роута. + * @param array|string $controller Контроллер. + * + * @return array + * @throws LogicException + */ + private function parseControllerString(string $name, $controller) : array + { + $argument = $controller; + if (is_string($controller)) { + if (strpos($controller, '::') !== false) { + $controller = explode('::', $controller, 2); + if (strpos($controller[1], 'Action') === false) { + // В методе контроллера обязательно должно содержаться Action + // (особенность битриксовых контроллеров) + throw new LogicException( + sprintf( + 'Route %s. Action %s name of controller must contain Action.', + $name, + $argument + ) + ); + } + + return $controller; + } else { + // Invoked controller (не поддерживается). + throw new LogicException( + sprintf('Route %s. Invokable controller %s not supporting.', $name, $controller) + ); + } + } + + throw new LogicException( + sprintf('Route %s. Invalid _controller param.', $name) + ); + } +} \ No newline at end of file diff --git a/readme.MD b/readme.MD index c965406..5ce0d93 100644 --- a/readme.MD +++ b/readme.MD @@ -13,8 +13,6 @@ ## Параметры -## Параметры - Файл `symfony_router.yaml` в конфигурации бандлов: - `enabled` - Использовать бандл. По умолчанию `true`. @@ -22,4 +20,90 @@ - `router_cache_path` - путь к кэшу аннотаций (вида `%kernel.cache.dir%/routes`). По умолчанию `null`. Если задан, то роуты будут кэшироваться. - `router_config_file` - путь к файлу с конфигурацией роутов. По умолчанию `local/configs/routes.yaml`. Файл может быть в любом поддерживаемом Symfony формате - Yaml, PHP, XML и т.д. -- `router_check_exists_controller` - проверять на существование классы-контроллеры. По умолчанию `false`. \ No newline at end of file +- `router_check_exists_controller` - проверять на существование классы-контроллеры. По умолчанию `false`. + +## Конфигурирование нативных роутов Битрикса через Yaml файл + +С версии `21.400.0` (от 16.07.2021) главного модуля в Битриксе появился [сносный](https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&CHAPTER_ID=013764&LESSON_PATH=3913.3516.5062.13764) роутер. + +#### Зачем? + +Чтобы использовать привычный способ конфигурирования роутов через Yaml. + +#### Использование + +Файл описания маршрутов (например, `/local/routes/web.php`): + +```php +use Prokl\ServiceProvider\ServiceProvider; +use Bitrix\Main\Routing\Controllers\PublicPageController; +use Prokl\BitrixSymfonyRouterBundle\Services\Agnostic\RoutesLoader; +use Prokl\BitrixSymfonyRouterBundle\Services\Utils\BitrixRouteConvertor; +use Bitrix\Main\Routing\RoutingConfigurator; + +// Не обязательно. Смотри ниже. +$container = ServiceProvider::instance(); + +$agnosticRouter = new RoutesLoader( + $_SERVER['DOCUMENT_ROOT'] . '/local/configs/bitrix_routes.yaml', // Конфиг роутов + $_SERVER['DOCUMENT_ROOT'] . '/bitrix/cache/routes', // Кэш; если null - без кэширования. + $_ENV['DEBUG'] +); +$routeCollection = $agnosticRouter->getRoutes(); + +$routeConvertor = new BitrixRouteConvertor($routeCollection); +// Не обязательно. Без контейнера контроллеры будут инстанцироваться через new, +// а не через контейнер. Но тогда уже без разрешения зависимостей. +$routeConvertor->setContainer($container); + +return function (RoutingConfigurator $routes) use ($container, $routeConvertor, $routeCollection) { + + $routeConvertor->convertRoutes($routes); + + $routes->get('/', new PublicPageController('/index.php')); + +}; +``` + +В случае, если контейнер подключается, то возможна краткая форма: + +```php +use Prokl\ServiceProvider\ServiceProvider; +use Bitrix\Main\Routing\Controllers\PublicPageController; +use Prokl\BitrixSymfonyRouterBundle\Services\Utils\BitrixRouteConvertor; +use Bitrix\Main\Routing\RoutingConfigurator; + +// Не обязательно. Смотри ниже. +$container = ServiceProvider::instance(); + +$routeCollection = $container->get('bitrix_native_routes.routes.collection'); + +$routeConvertor = new BitrixRouteConvertor($routeCollection); +// Не обязательно. Без контейнера контроллеры будут инстанцироваться через new, +// а не через контейнер. Но тогда уже без разрешения зависимостей. +$routeConvertor->setContainer($container); + +return function (RoutingConfigurator $routes) use ($container, $routeConvertor, $routeCollection) { + + $routeConvertor->convertRoutes($routes); + + $routes->get('/', new PublicPageController('/index.php')); + +}; +``` + +Пример файла с конфигурацией роутов (обычный yaml файл с роутами для Symfony): + +```yaml +first_bitrix_route: + path: /countries/{country}/ + controller: 'Local\ExampleBitrixActionController::cacheAction' + methods: GET|POST + requirements: + country: '\d+' + defaults: + country: 'Russia' +``` + +Если установлен Битрикс с версией модуля младше `21.400.0`, то соответствующие сервисы будут +удалены из бандла на стадии компиляции. \ No newline at end of file