Skip to content

Commit

Permalink
Кэширование контейнера
Browse files Browse the repository at this point in the history
  • Loading branch information
ProklUng committed Jul 14, 2021
1 parent fe27782 commit aa0b219
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
return [
'parameters' => [
'value' => [
'cache_path' => '/bitrix/cache/s1/proklung.redis', // Путь к закешированному контейнеру
'compile_container_envs' => ['dev', 'prod'], // Окружения при которых компилировать контейнер
'container.dumper.inline_factories' => false, // Дампить контейнер как одиночные файлы
],
'readonly' => false,
],
Expand Down
4 changes: 2 additions & 2 deletions install/version.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

$arModuleVersion = [
'VERSION' => '1.0.0',
'VERSION_DATE' => '2021-07-13'
'VERSION' => '1.0.1',
'VERSION_DATE' => '2021-07-14'
];
4 changes: 2 additions & 2 deletions lang/ru/install/index.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

$MESS['REDIS_MODULE_NAME'] = 'Очереди на Redis';
$MESS['REDIS_MODULE_DESCRIPTION'] = 'Очереди на Redis';
$MESS['REDIS_MODULE_NAME'] = 'Очереди на Redis (и не только)';
$MESS['REDIS_MODULE_DESCRIPTION'] = 'Очереди на Redis (и не только)';
$MESS['REDIS_MODULE_PARTNER_NAME'] = 'ProklUng';
$MESS['REDIS_MODULE_PARTNER_URI'] = '';
199 changes: 199 additions & 0 deletions lib/DI/CompilerContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<?php

namespace Proklung\Redis\DI;

use InvalidArgumentException;
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\Filesystem\Filesystem;
use Throwable;

/**
* Class CompilerContainer
* @package Proklung\Redis\DI
*
* @since 14.07.2021
*/
class CompilerContainer
{
/**
* @param ContainerBuilder $container Контейнер.
* @param string $cacheDirectory Директория кэша.
* @param string $filename Файл кэша.
* @param string $environment Окружение.
* @param boolean $debug Режим отладки.
* @param callable $initializerContainer Инициализатор контейнера.
*
* @return Container
*/
public function cacheContainer(
ContainerBuilder $container,
string $cacheDirectory,
string $filename,
string $environment,
bool $debug,
callable $initializerContainer
) : Container {
$this->createCacheDirectory($cacheDirectory);

$compiledContainerFile = $cacheDirectory . '/' . $filename;

$containerConfigCache = new ConfigCache($compiledContainerFile, true);

// Класс скомпилированного контейнера.
$classCompiledContainerName = $this->getContainerClass($environment, $debug) . md5($filename);

if (!$containerConfigCache->isFresh()) {
// Загрузить, инициализировать и скомпилировать контейнер.
$newContainer = $initializerContainer();

// Блокировка на предмет конкурентных запросов.
$lockFile = $cacheDirectory . '/container.lock';

// Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);

$lock = false;
try {
if ($lock = fopen($lockFile, 'w')) {
flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock);
if (!flock($lock, $wouldBlock ? \LOCK_SH : \LOCK_EX)) {
fclose($lock);
@unlink($lockFile);
$lock = null;
}
} else {
// Если в файл контейнера уже что-то пишется, то вернем свежую копию контейнера.
flock($lock, \LOCK_UN);
fclose($lock);
@unlink($lockFile);

return $newContainer;
}
} catch (Throwable $e) {
} finally {
error_reporting($errorLevel);
}

$this->dumpContainer($containerConfigCache, $container, $classCompiledContainerName, $debug);

if ($lock) {
flock($lock, \LOCK_UN);
fclose($lock);
@unlink($lockFile);
}
}

// Подключение скомпилированного контейнера.
/** @noinspection PhpIncludeInspection */
require_once $compiledContainerFile;

$classCompiledContainerName = '\\'.$classCompiledContainerName;

return new $classCompiledContainerName();
}

/**
* Если надо создать директорию для компилированного контейнера.
*
* @param string $dir
*
* @return void
*/
private function createCacheDirectory(string $dir) : void
{
$filesystem = new Filesystem();

if (!$filesystem->exists($dir)) {
$filesystem->mkdir($dir);
}
}

/**
* Gets the container class.
*
* @param string $env
* @param boolean $debug
*
* @return string The container class.
*/
private function getContainerClass(string $env, bool $debug) : string
{
$class = static::class;
$class = false !== strpos($class, "@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class))
: $class;
$class = str_replace('\\', '_', $class).ucfirst($env).($debug ? 'Debug' : '').'Container';

if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) {
throw new InvalidArgumentException(
sprintf('The environment "%s" contains invalid characters, it can only contain characters allowed in PHP class names.', $this->environment)
);
}

return $class;
}

/**
* Dumps the service container to PHP code in the cache.
*
* @param ConfigCache $cache Кэш.
* @param ContainerBuilder $container Контейнер.
* @param string $class The name of the class to generate.
* @param boolean $debug Отладка.
*
* @return void
*/
private function dumpContainer(ConfigCache $cache, ContainerBuilder $container, string $class, bool $debug) : void
{
// Опция - дампить как файлы. По умолчанию - нет.
$asFiles = false;
if ($container->hasParameter('container.dumper.inline_factories')) {
$asFiles = $container->getParameter('container.dumper.inline_factories');
}

$dumper = new PhpDumper($container);
if (class_exists(\ProxyManager\Configuration::class) && class_exists(ProxyDumper::class)) {
$dumper->setProxyDumper(new ProxyDumper());
}

$content = $dumper->dump(
[
'class' => $class,
'file' => $cache->getPath(),
'as_files' => $asFiles,
'debug' => $debug,
'build_time' => $container->hasParameter('kernel.container_build_time')
? $container->getParameter('kernel.container_build_time') : time(),
'preload_classes' => [],
]
);

// Если as_files = true.
if (is_array($content)) {
$rootCode = array_pop($content);
$dir = \dirname($cache->getPath()).'/';

$filesystem = new Filesystem();

foreach ($content as $file => $code) {
$filesystem->dumpFile($dir.$file, $code);
@chmod($dir.$file, 0666 & ~umask());
}

$legacyFile = \dirname($dir.key($content)).'.legacy';
if (is_file($legacyFile)) {
@unlink($legacyFile);
}

$content = $rootCode;
}

$cache->write(
$content, // @phpstan-ignore-line
$container->getResources()
);
}
}
58 changes: 52 additions & 6 deletions lib/DI/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Proklung\Redis\DI;

use Bitrix\Main\Config\Configuration;
use Closure;
use Enqueue\Consumption\Extension\ReplyExtension;
use Enqueue\Consumption\Extension\SignalExtension;
use Proklung\Redis\DI\Extensions\ResetServicesExtension;
Expand All @@ -28,6 +29,7 @@
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
Expand Down Expand Up @@ -61,20 +63,38 @@ class Services
*/
private $services;

/**
* @var string $environment
*/
private $environment;

/**
* @var boolean $booted Загружена ли уже конструкция.
*/
private static $booted = false;

/**
* @var boolean $debug Режим отладки.
*/
private $debug;

/**
* Services constructor.
*/
public function __construct()
{
$this->debug = (bool)$_ENV['DEBUG'] ?? true;
$this->environment = $this->debug ? 'dev' : 'prod';

$this->config = Configuration::getInstance()->get('proklung.redis') ?? ['enqueue' => []];
$this->parameters = Configuration::getInstance('proklung.redis')->get('parameters') ?? [];
$this->services = Configuration::getInstance('proklung.redis')->get('services') ?? [];

// Инициализация параметров контейнера.
$this->parameters['cache_path'] = $this->parameters['cache_path'] ?? '/bitrix/cache/proklung.redis';
$this->parameters['container.dumper.inline_factories'] = $this->parameters['container.dumper.inline_factories'] ?? false;
$this->parameters['compile_container_envs'] = (array)$this->parameters['compile_container_envs'];

$this->container = new ContainerBuilder();
$adapter = new BitrixSettingsDiAdapter();

Expand All @@ -86,10 +106,10 @@ public function __construct()
/**
* Загрузка и инициализация контейнера.
*
* @return ContainerBuilder
* @return Container
* @throws Exception
*/
public static function boot() : ContainerBuilder
public static function boot() : Container
{
$self = new static();

Expand All @@ -104,10 +124,10 @@ public static function boot() : ContainerBuilder
/**
* Alias boot для читаемости.
*
* @return ContainerBuilder
* @return Container
* @throws Exception
*/
public static function getInstance() : ContainerBuilder
public static function getInstance() : Container
{
return static::boot();
}
Expand All @@ -129,6 +149,32 @@ public static function setBoot(bool $booted) : void
* @throws Exception
*/
public function load() : void
{
$compilerContainer = new CompilerContainer();

// Кэшировать контейнер?
if (!in_array($this->environment, $this->parameters['compile_container_envs'], true)) {
$this->initContainer();
return;
}

$this->container = $compilerContainer->cacheContainer(
$this->container,
$_SERVER['DOCUMENT_ROOT'] . $this->parameters['cache_path'],
'container.php',
$this->environment,
$this->debug,
Closure::fromCallable([$this, 'initContainer'])
);
}

/**
* Инициализация контейнера.
*
* @return void
* @throws Exception
*/
public function initContainer() : void
{
$this->container->setParameter('kernel.debug', $_ENV['DEBUG'] ?? true);
$loader = new YamlFileLoader($this->container, new FileLocator(__DIR__ . '/../../configs'));
Expand Down Expand Up @@ -227,9 +273,9 @@ public function load() : void
/**
* Экземпляр контейнера.
*
* @return ContainerBuilder
* @return Container
*/
public function getContainer(): ContainerBuilder
public function getContainer(): Container
{
return $this->container;
}
Expand Down
15 changes: 15 additions & 0 deletions readme.MD
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ $container = $provider->boot();
$producer = $container->get('enqueue.client.default.lazy_producer');

$producer->sendEvent('bitrix-redis', 'REDDIS');

```
### Consumers

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

В целом модуль следует канве оригинального бандла. Основное отличие - способ конфигурирования сервисов (не Yaml, а битриксовые
массивные конфиги).

### Кэширование контейнера

Параметр `cache_path` - путь, куда ляжет скомпилированный контейнер. Если не задано, то по умолчанию `/bitrix/cache/s1/proklung.redis`.

Предполагается, что в системе так или иначе установлена переменная среды `DEBUG` в массиве `$_ENV`. Если нет, то по умолчанию
полагается, что среда "отладочная".

Параметр (массив) `compile_container_envs` указывает окружения, при которых необходимо кэшировать контейнер.

Пока простая логика: `$_ENV["DEBUG"] === true` => окружение `dev`, иначе `prod`.

## CLI

Expand Down

0 comments on commit aa0b219

Please sign in to comment.