Skip to content

Commit aa0b219

Browse files
committed
Кэширование контейнера
1 parent fe27782 commit aa0b219

File tree

6 files changed

+273
-10
lines changed

6 files changed

+273
-10
lines changed

.settings.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
return [
44
'parameters' => [
55
'value' => [
6+
'cache_path' => '/bitrix/cache/s1/proklung.redis', // Путь к закешированному контейнеру
7+
'compile_container_envs' => ['dev', 'prod'], // Окружения при которых компилировать контейнер
8+
'container.dumper.inline_factories' => false, // Дампить контейнер как одиночные файлы
69
],
710
'readonly' => false,
811
],

install/version.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

33
$arModuleVersion = [
4-
'VERSION' => '1.0.0',
5-
'VERSION_DATE' => '2021-07-13'
4+
'VERSION' => '1.0.1',
5+
'VERSION_DATE' => '2021-07-14'
66
];

lang/ru/install/index.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
$MESS['REDIS_MODULE_NAME'] = 'Очереди на Redis';
4-
$MESS['REDIS_MODULE_DESCRIPTION'] = 'Очереди на Redis';
3+
$MESS['REDIS_MODULE_NAME'] = 'Очереди на Redis (и не только)';
4+
$MESS['REDIS_MODULE_DESCRIPTION'] = 'Очереди на Redis (и не только)';
55
$MESS['REDIS_MODULE_PARTNER_NAME'] = 'ProklUng';
66
$MESS['REDIS_MODULE_PARTNER_URI'] = '';

lib/DI/CompilerContainer.php

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<?php
2+
3+
namespace Proklung\Redis\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\Redis\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/DI/services.php

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

55
use Bitrix\Main\Config\Configuration;
6+
use Closure;
67
use Enqueue\Consumption\Extension\ReplyExtension;
78
use Enqueue\Consumption\Extension\SignalExtension;
89
use Proklung\Redis\DI\Extensions\ResetServicesExtension;
@@ -28,6 +29,7 @@
2829
use Symfony\Component\Config\Definition\Processor;
2930
use Symfony\Component\Config\FileLocator;
3031
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
32+
use Symfony\Component\DependencyInjection\Container;
3133
use Symfony\Component\DependencyInjection\ContainerBuilder;
3234
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
3335
use Symfony\Component\DependencyInjection\Reference;
@@ -61,20 +63,38 @@ class Services
6163
*/
6264
private $services;
6365

66+
/**
67+
* @var string $environment
68+
*/
69+
private $environment;
70+
6471
/**
6572
* @var boolean $booted Загружена ли уже конструкция.
6673
*/
6774
private static $booted = false;
6875

76+
/**
77+
* @var boolean $debug Режим отладки.
78+
*/
79+
private $debug;
80+
6981
/**
7082
* Services constructor.
7183
*/
7284
public function __construct()
7385
{
86+
$this->debug = (bool)$_ENV['DEBUG'] ?? true;
87+
$this->environment = $this->debug ? 'dev' : 'prod';
88+
7489
$this->config = Configuration::getInstance()->get('proklung.redis') ?? ['enqueue' => []];
7590
$this->parameters = Configuration::getInstance('proklung.redis')->get('parameters') ?? [];
7691
$this->services = Configuration::getInstance('proklung.redis')->get('services') ?? [];
7792

93+
// Инициализация параметров контейнера.
94+
$this->parameters['cache_path'] = $this->parameters['cache_path'] ?? '/bitrix/cache/proklung.redis';
95+
$this->parameters['container.dumper.inline_factories'] = $this->parameters['container.dumper.inline_factories'] ?? false;
96+
$this->parameters['compile_container_envs'] = (array)$this->parameters['compile_container_envs'];
97+
7898
$this->container = new ContainerBuilder();
7999
$adapter = new BitrixSettingsDiAdapter();
80100

@@ -86,10 +106,10 @@ public function __construct()
86106
/**
87107
* Загрузка и инициализация контейнера.
88108
*
89-
* @return ContainerBuilder
109+
* @return Container
90110
* @throws Exception
91111
*/
92-
public static function boot() : ContainerBuilder
112+
public static function boot() : Container
93113
{
94114
$self = new static();
95115

@@ -104,10 +124,10 @@ public static function boot() : ContainerBuilder
104124
/**
105125
* Alias boot для читаемости.
106126
*
107-
* @return ContainerBuilder
127+
* @return Container
108128
* @throws Exception
109129
*/
110-
public static function getInstance() : ContainerBuilder
130+
public static function getInstance() : Container
111131
{
112132
return static::boot();
113133
}
@@ -129,6 +149,32 @@ public static function setBoot(bool $booted) : void
129149
* @throws Exception
130150
*/
131151
public function load() : void
152+
{
153+
$compilerContainer = new CompilerContainer();
154+
155+
// Кэшировать контейнер?
156+
if (!in_array($this->environment, $this->parameters['compile_container_envs'], true)) {
157+
$this->initContainer();
158+
return;
159+
}
160+
161+
$this->container = $compilerContainer->cacheContainer(
162+
$this->container,
163+
$_SERVER['DOCUMENT_ROOT'] . $this->parameters['cache_path'],
164+
'container.php',
165+
$this->environment,
166+
$this->debug,
167+
Closure::fromCallable([$this, 'initContainer'])
168+
);
169+
}
170+
171+
/**
172+
* Инициализация контейнера.
173+
*
174+
* @return void
175+
* @throws Exception
176+
*/
177+
public function initContainer() : void
132178
{
133179
$this->container->setParameter('kernel.debug', $_ENV['DEBUG'] ?? true);
134180
$loader = new YamlFileLoader($this->container, new FileLocator(__DIR__ . '/../../configs'));
@@ -227,9 +273,9 @@ public function load() : void
227273
/**
228274
* Экземпляр контейнера.
229275
*
230-
* @return ContainerBuilder
276+
* @return Container
231277
*/
232-
public function getContainer(): ContainerBuilder
278+
public function getContainer(): Container
233279
{
234280
return $this->container;
235281
}

readme.MD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ $container = $provider->boot();
8989
$producer = $container->get('enqueue.client.default.lazy_producer');
9090

9191
$producer->sendEvent('bitrix-redis', 'REDDIS');
92+
9293
```
9394
### Consumers
9495

@@ -123,6 +124,9 @@ class FooRedisProcessor implements Processor, TopicSubscriberInterface
123124
return [
124125
'parameters' => [
125126
'value' => [
127+
'cache_path' => '/bitrix/cache/s1/proklung.redis', // Путь к закешированному контейнеру
128+
'compile_container_envs' => ['dev', 'prod'], // Окружения при которых компилировать контейнер
129+
'container.dumper.inline_factories' => false, // Дампить контейнер как одиночные файлы
126130
],
127131
'readonly' => false,
128132
],
@@ -141,6 +145,17 @@ return [
141145

142146
В целом модуль следует канве оригинального бандла. Основное отличие - способ конфигурирования сервисов (не Yaml, а битриксовые
143147
массивные конфиги).
148+
149+
### Кэширование контейнера
150+
151+
Параметр `cache_path` - путь, куда ляжет скомпилированный контейнер. Если не задано, то по умолчанию `/bitrix/cache/s1/proklung.redis`.
152+
153+
Предполагается, что в системе так или иначе установлена переменная среды `DEBUG` в массиве `$_ENV`. Если нет, то по умолчанию
154+
полагается, что среда "отладочная".
155+
156+
Параметр (массив) `compile_container_envs` указывает окружения, при которых необходимо кэшировать контейнер.
157+
158+
Пока простая логика: `$_ENV["DEBUG"] === true` => окружение `dev`, иначе `prod`.
144159

145160
## CLI
146161

0 commit comments

Comments
 (0)