Skip to content

Commit

Permalink
Конфигурирование нативных роутов Битрикса через Yaml файл
Browse files Browse the repository at this point in the history
  • Loading branch information
ProklUng committed Jul 24, 2021
1 parent 1661ed0 commit 1de2766
Show file tree
Hide file tree
Showing 4 changed files with 296 additions and 3 deletions.
25 changes: 25 additions & 0 deletions DependencyInjection/SymfonyRouterExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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');
}
}
23 changes: 23 additions & 0 deletions Resources/config/native_routes.yaml
Original file line number Diff line number Diff line change
@@ -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']
161 changes: 161 additions & 0 deletions Services/Utils/BitrixRouteConvertor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

namespace Prokl\BitrixSymfonyRouterBundle\Services\Utils;

use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Routing\RoutingConfigurator;
use LogicException;
use RuntimeException;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

/**
* Class BitrixRouteConvertor
* @package Prokl\BitrixSymfonyRouterBundle\Services\Utils
*
* @since 23.07.2021
*/
class BitrixRouteConvertor
{
use ContainerAwareTrait;

/**
* @var RouteCollection $routeCollection Коллекция роутов.
*/
private $routeCollection;

/**
* BitrixRouteConvertor constructor.
*
* @param RouteCollection $routeCollection Коллекция роутов.
*/
public function __construct(RouteCollection $routeCollection)
{
$this->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)
);
}
}
90 changes: 87 additions & 3 deletions readme.MD
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,97 @@

## Параметры

## Параметры

Файл `symfony_router.yaml` в конфигурации бандлов:

- `enabled` - Использовать бандл. По умолчанию `true`.
- `controller_annotations_path` - массив с путями к контроллерам, использующим аннотации.
- `router_cache_path` - путь к кэшу аннотаций (вида `%kernel.cache.dir%/routes`). По умолчанию `null`.
Если задан, то роуты будут кэшироваться.
- `router_config_file` - путь к файлу с конфигурацией роутов. По умолчанию `local/configs/routes.yaml`. Файл может быть в любом поддерживаемом Symfony формате - Yaml, PHP, XML и т.д.
- `router_check_exists_controller` - проверять на существование классы-контроллеры. По умолчанию `false`.
- `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`, то соответствующие сервисы будут
удалены из бандла на стадии компиляции.

0 comments on commit 1de2766

Please sign in to comment.