From aa0b219225d5b9a0c75aad02229fe080e3747b65 Mon Sep 17 00:00:00 2001 From: ProklUng Date: Wed, 14 Jul 2021 15:38:53 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D1=8D=D1=88=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=B9?= =?UTF-8?q?=D0=BD=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .settings.php | 3 + install/version.php | 4 +- lang/ru/install/index.php | 4 +- lib/DI/CompilerContainer.php | 199 +++++++++++++++++++++++++++++++++++ lib/DI/services.php | 58 ++++++++-- readme.MD | 15 +++ 6 files changed, 273 insertions(+), 10 deletions(-) create mode 100644 lib/DI/CompilerContainer.php diff --git a/.settings.php b/.settings.php index 1781269..1aec457 100644 --- a/.settings.php +++ b/.settings.php @@ -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, ], diff --git a/install/version.php b/install/version.php index 6e8aa7d..9348a66 100644 --- a/install/version.php +++ b/install/version.php @@ -1,6 +1,6 @@ '1.0.0', - 'VERSION_DATE' => '2021-07-13' + 'VERSION' => '1.0.1', + 'VERSION_DATE' => '2021-07-14' ]; diff --git a/lang/ru/install/index.php b/lang/ru/install/index.php index e82bbe6..976c8a1 100644 --- a/lang/ru/install/index.php +++ b/lang/ru/install/index.php @@ -1,6 +1,6 @@ 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() + ); + } +} \ No newline at end of file diff --git a/lib/DI/services.php b/lib/DI/services.php index 6401d26..43d6fc3 100644 --- a/lib/DI/services.php +++ b/lib/DI/services.php @@ -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; @@ -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; @@ -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(); @@ -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(); @@ -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(); } @@ -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')); @@ -227,9 +273,9 @@ public function load() : void /** * Экземпляр контейнера. * - * @return ContainerBuilder + * @return Container */ - public function getContainer(): ContainerBuilder + public function getContainer(): Container { return $this->container; } diff --git a/readme.MD b/readme.MD index 2551efd..e00b9e5 100644 --- a/readme.MD +++ b/readme.MD @@ -89,6 +89,7 @@ $container = $provider->boot(); $producer = $container->get('enqueue.client.default.lazy_producer'); $producer->sendEvent('bitrix-redis', 'REDDIS'); + ``` ### Consumers @@ -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, ], @@ -141,6 +145,17 @@ return [ В целом модуль следует канве оригинального бандла. Основное отличие - способ конфигурирования сервисов (не Yaml, а битриксовые массивные конфиги). + +### Кэширование контейнера + +Параметр `cache_path` - путь, куда ляжет скомпилированный контейнер. Если не задано, то по умолчанию `/bitrix/cache/s1/proklung.redis`. + +Предполагается, что в системе так или иначе установлена переменная среды `DEBUG` в массиве `$_ENV`. Если нет, то по умолчанию + полагается, что среда "отладочная". + +Параметр (массив) `compile_container_envs` указывает окружения, при которых необходимо кэшировать контейнер. + +Пока простая логика: `$_ENV["DEBUG"] === true` => окружение `dev`, иначе `prod`. ## CLI