Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions packages/guides-cli/src/DependencyInjection/ContainerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<mixed> $config */
private function registerExtension(ExtensionInterface $extension, array $config): void
{
Expand Down