composer.json:
"repositories": [
{
"type": "git",
"url": "https://github.com/proklung/bitrix.core.symfony"
}
]
composer require proklung/bitrix-core-symfony
В init.php
:
use Prokl\ServiceProvider\ServiceProvider;
$serviceProvider = new ServiceProvider('local/configs/services.yaml');
Для обеспечения "преемственности" (похожести) с оригиналом можно задать путь к файлу конфигурации (скажем, bundles.php
)
бандлов вторым (необязательным) параметром конструктора.
Предполагается, что переменные среды к моменту инициализации контейнера уже загружены тем или иным способом.
Значимые переменные среды:
-
DEBUG
(булево значение - режим отладки),APP_DEBUG
- алиасDEBUG
, но с большим приоритетом (если одновременно присустствуютDEBUG
иAPP_DEBUG
, то в дело пойдет значениеAPP_DEBUG
). -
APP_ENV
- код окружения. Если код не задан, то будет проинтерпретировано значениеDEBUG
в смысле - если в режиме отладки, то окружениеdev
, иначеprod
.
Если переменные среды не заданы, то с помощью класса Prokl\ServiceProvider\LoadEnvironment
их можно загрузить.
Скажем, в init.php
, перед инициализацией контейнера:
// Параметр конструктора - путь, где лежат файлы .env
$loader = new \Prokl\ServiceProvider\LoadEnvironment($_SERVER['DOCUMENT_ROOT'] . '/../..');
$loader->load(); // Загрузка $_ENV
$loader->process(); // Обработка переменных
- Опция
compile.container
в подтягиваемом конфиге - компилировать ли контейнер в файл. Если не задана, то "нет, не компилировать". Имеет смысл для окружения, не равного "dev". Т.е. опция управляет дампированием контейнера на проде.
Место, где хранятся дампы контейнеров: <значение переменной контейнера kernel.cache_dir>/<SITE_ID>/containers
Определяются классом AppKernel
. По умолчанию:
- путь к кэшу (
kernel.cache_dir
) -/bitrix/cache
- путь к логам (
kernel.logs_dir
) -'/../../logs'
(два уровня выше DOCUMENT_ROOT - особенности используемой сборки Битрикс)
Чтобы это изменить нужно отнаследоваться от класса AppKernel
и переопределить несколько переменных:
use Prokl\ServiceProvider\Services\AppKernel;
class MyKernel extends AppKernel
{
protected $cacheDir = '/bitrix/cache/mycache';
protected $logDir = '/logs-saver';
}
(второй вариант - отнаследоваться от AppKernel
и переопределить методы getCacheDir
и getLogDir
).
Изменить через наследование класс ядра:
class MyServiceProvider extends ServiceProvider
{
protected $kernelServiceClass = MyKernel::class;
protected $cacheDir = '/bitrix/cache/mycache';
}
Второй вариант - отнаследоваться от ServiceProvider
и заменить метод getPathCacheDirectory
своей логикой.
Файл конфигурации - /config/standalone_bundles.php
. Этот путь можно изменить через конструктор.
Папка, где лежат конфигурации - /local/configs
. Конфигурации бандлов - /local/configs/packages
.
Согласно концепции Symfony все сервисы (в идеале) должны быть приватными и инжектиться. Но в кастомном случае
часто нужно получать их через хелпер-сервис-локатор. Для превращения нужных сервисов в публичные предлагается
такое решение. В общем разделе параметров контейнера появилась опция publicable_services
:
parameters:
publicable_services:
- 'snc_redis.default'
После компиляции контейнера приватный сервис snc_redis.default
станет публичным.
Отдельные контейнеры - со своим конфигом, полностью изолированные (для модулей и т.п.).
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Prokl\ServiceProvider\Micro\AbstractStandaloneServiceProvider;
use Prokl\ServiceProvider\Micro\ExampleAppKernel;
class ExampleMicroServiceProvider extends AbstractStandaloneServiceProvider
{
/**
* @var ContainerBuilder $containerBuilder Контейнер.
*/
protected static $containerBuilder;
/**
* @var string $pathBundlesConfig Путь к конфигурации бандлов.
*/
protected $pathBundlesConfig = '/src/Micro/example.config/standalone_bundles.php';
/**
* @var string $configDir Папка, где лежат конфиги.
*/
protected $configDir = '/src/Micro/example.config/example.config/example.yaml';
/**
* @var string $kernelServiceClass Класс, реализующий сервис kernel.
* Нужен для того, чтобы экземпляры контейнеров в kernel сервисе не перемешивались.
*/
protected $kernelServiceClass = ExampleAppKernel::class;
}
Пример класса ExampleAppKernel
:
use Prokl\ServiceProvider\Micro\AbstractKernel;
class ExampleAppKernel extends AbstractKernel
{
protected static $kernelContainer;
}
Где надо - инициализация:
$micro = new ExampleMicroServiceProvider('src/SymfonyDI/Micro/example.config/example.yaml');
Хэлпер container
заточен под работу с микро-сервис-провайдерами:
var_dump(container($micro)->getParameter('example'));
Чтобы сервис запустился автоматически после инициализации контейнера, он должен быть помечен тэгом service.bootstrap
.
app.options:
class: Prokl\Services\AppOptions
arguments: ['%kernel.environment%', '@parameter_bag']
tags: ['service.bootstrap']
Поддерживается приоритет запуска. Тогда надо так:
app.options:
class: Local\Services\AppOptions
arguments: ['%kernel.environment%', '@parameter_bag']
tags:
- { name: 'service.bootstrap', priority: 100 }
Сервис с приоритетом 100 запустится раньше сервиса с приоритетом 200.
Тэг: bitrix.events.init
.
event
- название события.method
- метод-обработчик в сервисеmodule
- модуль событияsort
- сортировка
admin_entity_edit.event_init:
class: Local\Bitrix\PsModuleInitializer
tags:
- { name: bitrix.events.init, module: ps.d7, event: onGetEntityList, method: registerEntities, sort: 0 }
Тэг twig.extension
.
service.twig.parameter:
class: Prokl\Bundles\ParameterBundle\Twig\ParameterExtension
public: true
arguments:
- '@service.parameter'
tags:
- { name: twig.extension }
Автоматом регистрируются сервисы:
service_container
(и alias) - сервис-контейнер целикомapp.request
- конвертор глобалов в Request- синонимы сервиса
kernel
delegated_container_manipulator
- манипулятор делегированными контейнерами.bitrix.request.instance
- Экземпляр битриксового Requestbitrix.response.instance
- Экземпляр битриксового Responsebitrix.request
- Symfony Request, полученный из битриксовогоbitrix.request.psr7
- Битриксовый Request, приведенный к PSR-7bitrix.response
- Symfony Response, полученный из битриксовогоbitrix.response.psr7
- Битриксовый Response, приведенный к PSR-7psr17.http_factory
- HttpFactory стандарта PSR-17psr18.http_client
- Http client стандарта PSR-18
container()
- отдает экземпляр контейнера (выступает в роли сервис-локатора):
$kernel = container()->get('kernel');
delegatedContainer()
- отдает экземпляр манипулятора (реализующего интерфейсSymfony\Component\DependencyInjection\ContainerInterface
) делегированными контейнерами.
$moduleService = delegatedContainer()->get('my_module_id.service');
В контейнере делегированный контейнер помечается тэгом delegated.container
(их может быть сколь угодно много):
module_notifier_container:
class: Symfony\Component\DependencyInjection\ContainerInterface
factory: ['Proklung\Notifier\DI\Services', 'getInstance']
tags:
- { name: 'delegated.container' }
Делегированный контейнер - автономный контейнер, сформированные в модуле, плагине и тому подобных местах.
Автоматом подтягиваются в контейнер сервисы из битриксового сервис-локатора. Формат,
секция services
из /bitrix/.settings.php
и /bitrix/.settings_extra.php
. Также загрузчик пробегает
по списку установленных модулей и подцепляет их тоже.
Для отдельных сервис-контейнеров (отнаследованных от AbstractStandaloneServiceProvider
) такая загрузка
не производится.
Если эта фича не нужна, то нужно отнаследоваться от ServiceProvider
и заглушить метод loadBitrixServiceLocatorConfigs
.
class MyServiceProvider extends ServiceProvider
{
/**
* {@inheritDoc}
*/
protected function loadBitrixServiceLocatorConfigs(DelegatingLoader $loader) : void
{
}
}
Адаптер-импортер настроек битриксового сервис-локатора (.settings.php
) в симфонический контейнер.
importParameters(ContainerInterface $container, array $settings, ?string $section = null)
- импорт параметров.section
- если задано, то параметры лягут в именованную секцию параметров контейнера.importServices(ContainerInterface $container, array $services)
- импорт сервисов. Формат
Считываются конфиги в Битриксе как-то так:
use Bitrix\Main\Config\Configuration;
$this->config = Configuration::getInstance()->get('my_config') ?? [];
// Из модуля
$this->parameters = Configuration::getInstance('my.module')->get('parameters') ?? [];
$this->services = Configuration::getInstance('my.module')->get('services') ?? [];
С версии 21.400.0
(от 16.07.2021) главного модуля в Битриксе появился сносный роутер.
Чтобы использовать в этом контексте контейнер нужно:
- В файле описания маршрутов (например,
/local/routes/web.php
) в самом верху подключить ядро.
Это важно, т.к. без этого сервис-провайдер завалится на стадии подключения файла с роутами; они подключаются раньше инициализации ядра.
И, если эту проблему еще можно решить, отключив проверку классов сервисов на существование, то запускающиеся автоматом сервисы по тэгу
service.bootstrap
обломятся стопроцентно.
use Local\ExampleBitrixActionController;
use Prokl\ServiceProvider\ServiceProvider;
use Bitrix\Main\Routing\Controllers\PublicPageController;
use Bitrix\Main\Routing\RoutingConfigurator;
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
$container = ServiceProvider::instance();
return function (RoutingConfigurator $routes) use ($container) {
$routes->get('/countries3/{country}/', [$container->get(ExampleBitrixActionController::class), 'cacheAction'])
->default('country', 'Russia')
->name('first_bitrix_route')
;
$routes->get('/', new PublicPageController('/index.php')); // Старый роут на статике.
};
Класс битриксового контроллера (ExampleBitrixActionController
) с заточкой под DI:
namespace Local;
use Bitrix\Main\Engine\Contract\RoutableAction;
use Bitrix\Main\Engine\Controller;
use Symfony\Component\HttpKernel\KernelInterface;
class ExampleBitrixActionController extends Controller implements RoutableAction
{
/**
* @var KernelInterface $kernel
*/
private $kernel;
public function __construct(KernelInterface $kernel)
{
$this->kernel = $kernel;
parent::__construct();
}
/**
* @return string|Controller
*/
public static function getControllerClass() {
return ExampleBitrixActionController::class;
}
/**
* @return string
*/
public static function getDefaultName() {
return 'testingAction';
}
public function cacheAction(string $country)
{
return ['cacheDir' => $this->kernel->getCacheDir(), 'country' => $country];
}
public function configureActions()
{
return [
'cache' => [
'prefilters' => [], 'postfilters' => [],
],
];
}
}
Описывается сервисом так:
Local\ExampleBitrixActionController:
arguments: ['@kernel']
Ничего революционного, но так можно получить нормально-сконфигурированный класс контроллера, со всякими зависимостями и т.п.