Skip to content

Commit 2cb6662

Browse files
committed
Кэширование внутреннего контейнера
1 parent 30575f8 commit 2cb6662

File tree

5 files changed

+273
-0
lines changed

5 files changed

+273
-0
lines changed

.settings.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
'rabbitmq.logged.channel.class' => '',
2424
'rabbitmq.parts_holder.class' => 'Proklung\RabbitMq\RabbitMq\AmqpPartsHolder',
2525
'rabbitmq.fallback.class' => 'Proklung\RabbitMq\RabbitMq\Fallback',
26+
// Внутренние параметры модуля
27+
'cache_path' => '/bitrix/cache/s1/proklung.rabbitmq', // Путь к закешированному контейнеру
28+
'compile_container_envs' => ['dev', 'prod'], // Окружения при которых компилировать контейнер
29+
'container.dumper.inline_factories' => false, // Дампить контейнер как одиночные файлы
2630
],
2731
'readonly' => false,
2832
],

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,32 @@
1414
- Адаптирована работа с `RPC_Server` и `RPC_Clients`
1515
- Адаптирована работа с `Anon consumer`
1616
- Адаптирована работа с `Batch consumer`
17+
- Внутренний контейнер кэшируется
18+
19+
`.settings.php` модуля:
20+
21+
```php
22+
return [
23+
'parameters' => [
24+
'value' => [
25+
26+
'cache_path' => '/bitrix/cache/s1/proklung.rabbitmq', // Путь к закешированному контейнеру
27+
'compile_container_envs' => ['dev', 'prod'], // Окружения при которых компилировать контейнер
28+
'container.dumper.inline_factories' => false, // Дампить контейнер как одиночные файлы
29+
],
30+
'readonly' => false,
31+
]
32+
];
33+
```
34+
35+
Параметр `cache_path` - путь, куда ляжет скомпилированный контейнер. Если не задано, то по умолчанию `/bitrix/cache/s1/proklung.rabbitmq`.
36+
37+
Предполагается, что в системе так или иначе установлена переменная среды `DEBUG` в массиве `$_ENV`. Если нет, то по умолчанию
38+
полагается, что среда "отладочная".
39+
40+
Параметр (массив) `compile_container_envs` указывает окружения, при которых необходимо кэшировать контейнер.
41+
42+
Пока простая логика: `$_ENV["DEBUG"] === true` => окружение `dev`, иначе `prod`.
1743

1844
# Оригинальное readme.MD с некоторыми корректировками
1945

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"require": {
2828
"php": ">=7.1 || ^8.0",
2929
"symfony/dependency-injection": "^4.0 || ^5.0",
30+
"symfony/config": "^4.0 || ^5.0",
3031
"composer/installers": "~1",
3132
"php-amqplib/php-amqplib": "^2.12.1 || ^3",
3233
"psr/log": "^1.1.3",
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<?php
2+
3+
namespace Proklung\RabbitMq\Integration\DI;
4+
5+
use InvalidArgumentException;
6+
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
7+
use Symfony\Component\Config\ConfigCache;
8+
use Symfony\Component\DependencyInjection\Container;
9+
use Symfony\Component\DependencyInjection\ContainerBuilder;
10+
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
11+
use Symfony\Component\Filesystem\Filesystem;
12+
use Throwable;
13+
14+
/**
15+
* Class CompilerContainer
16+
* @package Proklung\RabbitMq\Integration\DI
17+
*
18+
* @since 14.07.2021
19+
*/
20+
class CompilerContainer
21+
{
22+
/**
23+
* @param ContainerBuilder $container Контейнер.
24+
* @param string $cacheDirectory Директория кэша.
25+
* @param string $filename Файл кэша.
26+
* @param string $environment Окружение.
27+
* @param boolean $debug Режим отладки.
28+
* @param callable $initializerContainer Инициализатор контейнера.
29+
*
30+
* @return Container
31+
*/
32+
public function cacheContainer(
33+
ContainerBuilder $container,
34+
string $cacheDirectory,
35+
string $filename,
36+
string $environment,
37+
bool $debug,
38+
callable $initializerContainer
39+
) : Container {
40+
$this->createCacheDirectory($cacheDirectory);
41+
42+
$compiledContainerFile = $cacheDirectory . '/' . $filename;
43+
44+
$containerConfigCache = new ConfigCache($compiledContainerFile, true);
45+
46+
// Класс скомпилированного контейнера.
47+
$classCompiledContainerName = $this->getContainerClass($environment, $debug) . md5($filename);
48+
49+
if (!$containerConfigCache->isFresh()) {
50+
// Загрузить, инициализировать и скомпилировать контейнер.
51+
$newContainer = $initializerContainer();
52+
53+
// Блокировка на предмет конкурентных запросов.
54+
$lockFile = $cacheDirectory . '/container.lock';
55+
56+
// Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
57+
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
58+
59+
$lock = false;
60+
try {
61+
if ($lock = fopen($lockFile, 'w')) {
62+
flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock);
63+
if (!flock($lock, $wouldBlock ? \LOCK_SH : \LOCK_EX)) {
64+
fclose($lock);
65+
@unlink($lockFile);
66+
$lock = null;
67+
}
68+
} else {
69+
// Если в файл контейнера уже что-то пишется, то вернем свежую копию контейнера.
70+
flock($lock, \LOCK_UN);
71+
fclose($lock);
72+
@unlink($lockFile);
73+
74+
return $newContainer;
75+
}
76+
} catch (Throwable $e) {
77+
} finally {
78+
error_reporting($errorLevel);
79+
}
80+
81+
$this->dumpContainer($containerConfigCache, $container, $classCompiledContainerName, $debug);
82+
83+
if ($lock) {
84+
flock($lock, \LOCK_UN);
85+
fclose($lock);
86+
@unlink($lockFile);
87+
}
88+
}
89+
90+
// Подключение скомпилированного контейнера.
91+
/** @noinspection PhpIncludeInspection */
92+
require_once $compiledContainerFile;
93+
94+
$classCompiledContainerName = '\\'.$classCompiledContainerName;
95+
96+
return new $classCompiledContainerName();
97+
}
98+
99+
/**
100+
* Если надо создать директорию для компилированного контейнера.
101+
*
102+
* @param string $dir
103+
*
104+
* @return void
105+
*/
106+
private function createCacheDirectory(string $dir) : void
107+
{
108+
$filesystem = new Filesystem();
109+
110+
if (!$filesystem->exists($dir)) {
111+
$filesystem->mkdir($dir);
112+
}
113+
}
114+
115+
/**
116+
* Gets the container class.
117+
*
118+
* @param string $env
119+
* @param boolean $debug
120+
*
121+
* @return string The container class.
122+
*/
123+
private function getContainerClass(string $env, bool $debug) : string
124+
{
125+
$class = static::class;
126+
$class = false !== strpos($class, "@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class))
127+
: $class;
128+
$class = str_replace('\\', '_', $class).ucfirst($env).($debug ? 'Debug' : '').'Container';
129+
130+
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) {
131+
throw new InvalidArgumentException(
132+
sprintf('The environment "%s" contains invalid characters, it can only contain characters allowed in PHP class names.', $this->environment)
133+
);
134+
}
135+
136+
return $class;
137+
}
138+
139+
/**
140+
* Dumps the service container to PHP code in the cache.
141+
*
142+
* @param ConfigCache $cache Кэш.
143+
* @param ContainerBuilder $container Контейнер.
144+
* @param string $class The name of the class to generate.
145+
* @param boolean $debug Отладка.
146+
*
147+
* @return void
148+
*/
149+
private function dumpContainer(ConfigCache $cache, ContainerBuilder $container, string $class, bool $debug) : void
150+
{
151+
// Опция - дампить как файлы. По умолчанию - нет.
152+
$asFiles = false;
153+
if ($container->hasParameter('container.dumper.inline_factories')) {
154+
$asFiles = $container->getParameter('container.dumper.inline_factories');
155+
}
156+
157+
$dumper = new PhpDumper($container);
158+
if (class_exists(\ProxyManager\Configuration::class) && class_exists(ProxyDumper::class)) {
159+
$dumper->setProxyDumper(new ProxyDumper());
160+
}
161+
162+
$content = $dumper->dump(
163+
[
164+
'class' => $class,
165+
'file' => $cache->getPath(),
166+
'as_files' => $asFiles,
167+
'debug' => $debug,
168+
'build_time' => $container->hasParameter('kernel.container_build_time')
169+
? $container->getParameter('kernel.container_build_time') : time(),
170+
'preload_classes' => [],
171+
]
172+
);
173+
174+
// Если as_files = true.
175+
if (is_array($content)) {
176+
$rootCode = array_pop($content);
177+
$dir = \dirname($cache->getPath()).'/';
178+
179+
$filesystem = new Filesystem();
180+
181+
foreach ($content as $file => $code) {
182+
$filesystem->dumpFile($dir.$file, $code);
183+
@chmod($dir.$file, 0666 & ~umask());
184+
}
185+
186+
$legacyFile = \dirname($dir.key($content)).'.legacy';
187+
if (is_file($legacyFile)) {
188+
@unlink($legacyFile);
189+
}
190+
191+
$content = $rootCode;
192+
}
193+
194+
$cache->write(
195+
$content, // @phpstan-ignore-line
196+
$container->getResources()
197+
);
198+
}
199+
}

lib/Integration/DI/Services.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Proklung\RabbitMq\Integration\DI;
44

55
use Bitrix\Main\Config\Configuration;
6+
use Closure;
67
use Exception;
78
use Proklung\RabbitMQ\RabbitMq\Consumer;
89
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -43,20 +44,38 @@ class Services
4344
*/
4445
private $container;
4546

47+
/**
48+
* @var string $environment
49+
*/
50+
private $environment;
51+
4652
/**
4753
* @var boolean $booted Загружена ли уже конструкция.
4854
*/
4955
private static $booted = false;
5056

57+
/**
58+
* @var boolean $debug Режим отладки.
59+
*/
60+
private $debug;
61+
5162
/**
5263
* Services constructor.
5364
*/
5465
public function __construct()
5566
{
67+
$this->debug = (bool)$_ENV['DEBUG'] ?? true;
68+
$this->environment = $this->debug ? 'dev' : 'prod';
69+
5670
$this->config = Configuration::getInstance()->get('rabbitmq') ?? [];
5771
$this->parameters = Configuration::getInstance('proklung.rabbitmq')->get('parameters') ?? [];
5872
$this->services = Configuration::getInstance('proklung.rabbitmq')->get('services') ?? [];
5973

74+
// Инициализация параметров контейнера.
75+
$this->parameters['cache_path'] = $this->parameters['cache_path'] ?? '/bitrix/cache/proklung.rabbitmq';
76+
$this->parameters['container.dumper.inline_factories'] = $this->parameters['container.dumper.inline_factories'] ?? false;
77+
$this->parameters['compile_container_envs'] = (array)$this->parameters['compile_container_envs'];
78+
6079
$this->container = new ContainerBuilder();
6180
$adapter = new BitrixSettingsDiAdapter();
6281

@@ -109,6 +128,30 @@ public static function setBoot(bool $booted) : void
109128
* @throws Exception
110129
*/
111130
public function load() : void
131+
{
132+
$compilerContainer = new CompilerContainer();
133+
134+
// Кэшировать контейнер?
135+
if (!in_array($this->environment, $this->parameters['compile_container_envs'], true)) {
136+
$this->initContainer();
137+
return;
138+
}
139+
140+
$this->container = $compilerContainer->cacheContainer(
141+
$this->container,
142+
$_SERVER['DOCUMENT_ROOT'] . $this->parameters['cache_path'],
143+
'container.php',
144+
$this->environment,
145+
$this->debug,
146+
Closure::fromCallable([$this, 'initContainer'])
147+
);
148+
}
149+
150+
/**
151+
* @return void
152+
* @throws Exception
153+
*/
154+
public function initContainer() : void
112155
{
113156
$this->loadConnections();
114157
$this->loadBindings();

0 commit comments

Comments
 (0)