diff --git a/packages/guides-cli/src/DependencyInjection/ContainerFactory.php b/packages/guides-cli/src/DependencyInjection/ContainerFactory.php index 866639dd1..630d20851 100644 --- a/packages/guides-cli/src/DependencyInjection/ContainerFactory.php +++ b/packages/guides-cli/src/DependencyInjection/ContainerFactory.php @@ -22,20 +22,35 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\Exception\RuntimeException as DIRuntimeException; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use function array_keys; use function array_merge; +use function assert; use function class_exists; +use function file_exists; +use function file_put_contents; +use function function_exists; use function getcwd; use function implode; use function is_a; +use function is_dir; +use function md5; +use function mkdir; +use function opcache_invalidate; use function rtrim; +use function serialize; use function sprintf; use function strrchr; use function substr; final class ContainerFactory { + private const CACHE_DIR = '/tmp/guides-container-cache'; + private const CACHE_CLASS = 'CachedGuidesContainer'; + private readonly ContainerBuilder $container; private readonly XmlFileLoader $configLoader; @@ -78,16 +93,72 @@ public function addConfigFile(string $filePath): void public function create(string $vendorDir): Container { - $this->processConfig(); + $cacheKey = $this->generateCacheKey($vendorDir); + $cacheFile = self::CACHE_DIR . '/' . self::CACHE_CLASS . '_' . $cacheKey . '.php'; + $cacheClass = self::CACHE_CLASS . '_' . $cacheKey; + + // Try to load cached container + if (file_exists($cacheFile)) { + require_once $cacheFile; + if (class_exists($cacheClass, false)) { + $container = new $cacheClass(); + assert($container instanceof Container); + + return $container; + } + } + // Build container + $this->processConfig(); $this->container->setParameter('vendor_dir', $vendorDir); $this->container->setParameter('working_directory', rtrim(getcwd(), '/')); - $this->container->compile(true); + // Try to cache the compiled container (may fail if container has object parameters) + try { + $this->cacheContainer($cacheFile, $cacheClass); + } catch (DIRuntimeException) { + // Container cannot be cached (has object/resource parameters), continue without caching + } + return $this->container; } + private function generateCacheKey(string $vendorDir): string + { + $workingDir = getcwd(); + $configData = [ + 'vendor_dir' => $vendorDir, + 'working_dir' => $workingDir !== false ? rtrim($workingDir, '/') : '', + 'extensions' => array_keys($this->registeredExtensions), + 'configs' => serialize($this->configs), + ]; + + return substr(md5(serialize($configData)), 0, 12); + } + + private function cacheContainer(string $cacheFile, string $cacheClass): void + { + if (!is_dir(self::CACHE_DIR)) { + @mkdir(self::CACHE_DIR, 0755, true); + } + + $dumper = new PhpDumper($this->container); + $code = $dumper->dump([ + 'class' => $cacheClass, + 'base_class' => Container::class, + ]); + + file_put_contents($cacheFile, $code); + + // Invalidate opcache for the new file + if (!function_exists('opcache_invalidate')) { + return; + } + + opcache_invalidate($cacheFile, true); + } + /** @param array $config */ private function registerExtension(ExtensionInterface $extension, array $config): void {