Skip to content

Commit 22ad429

Browse files
committed
perf: add DI container caching for faster CLI startup
Cache the compiled Symfony DI container to /tmp/guides-container-cache/. On subsequent runs, loads pre-compiled PHP class instead of rebuilding the entire container (config parsing, service registration, compilation). Cache key includes vendor dir, working dir, extensions, and configs to ensure cache invalidation when configuration changes. See https://cybottm.github.io/render-guides/ for benchmark data.
1 parent 8acd95a commit 22ad429

1 file changed

Lines changed: 68 additions & 2 deletions

File tree

packages/guides-cli/src/DependencyInjection/ContainerFactory.php

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,34 @@
2222
use Symfony\Component\Config\FileLocator;
2323
use Symfony\Component\DependencyInjection\Container;
2424
use Symfony\Component\DependencyInjection\ContainerBuilder;
25+
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
2526
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
2627

28+
use function array_keys;
2729
use function array_merge;
30+
use function assert;
2831
use function class_exists;
32+
use function file_exists;
33+
use function file_put_contents;
34+
use function function_exists;
2935
use function getcwd;
3036
use function implode;
3137
use function is_a;
38+
use function is_dir;
39+
use function md5;
40+
use function mkdir;
41+
use function opcache_invalidate;
3242
use function rtrim;
43+
use function serialize;
3344
use function sprintf;
3445
use function strrchr;
3546
use function substr;
3647

3748
final class ContainerFactory
3849
{
50+
private const CACHE_DIR = '/tmp/guides-container-cache';
51+
private const CACHE_CLASS = 'CachedGuidesContainer';
52+
3953
private readonly ContainerBuilder $container;
4054
private readonly XmlFileLoader $configLoader;
4155

@@ -78,16 +92,68 @@ public function addConfigFile(string $filePath): void
7892

7993
public function create(string $vendorDir): Container
8094
{
81-
$this->processConfig();
95+
$cacheKey = $this->generateCacheKey($vendorDir);
96+
$cacheFile = self::CACHE_DIR . '/' . self::CACHE_CLASS . '_' . $cacheKey . '.php';
97+
$cacheClass = self::CACHE_CLASS . '_' . $cacheKey;
98+
99+
// Try to load cached container
100+
if (file_exists($cacheFile)) {
101+
require_once $cacheFile;
102+
if (class_exists($cacheClass, false)) {
103+
$container = new $cacheClass();
104+
assert($container instanceof Container);
105+
106+
return $container;
107+
}
108+
}
82109

110+
// Build container
111+
$this->processConfig();
83112
$this->container->setParameter('vendor_dir', $vendorDir);
84113
$this->container->setParameter('working_directory', rtrim(getcwd(), '/'));
85-
86114
$this->container->compile(true);
87115

116+
// Cache the compiled container
117+
$this->cacheContainer($cacheFile, $cacheClass);
118+
88119
return $this->container;
89120
}
90121

122+
private function generateCacheKey(string $vendorDir): string
123+
{
124+
$workingDir = getcwd();
125+
$configData = [
126+
'vendor_dir' => $vendorDir,
127+
'working_dir' => $workingDir !== false ? rtrim($workingDir, '/') : '',
128+
'extensions' => array_keys($this->registeredExtensions),
129+
'configs' => serialize($this->configs),
130+
];
131+
132+
return substr(md5(serialize($configData)), 0, 12);
133+
}
134+
135+
private function cacheContainer(string $cacheFile, string $cacheClass): void
136+
{
137+
if (!is_dir(self::CACHE_DIR)) {
138+
@mkdir(self::CACHE_DIR, 0755, true);
139+
}
140+
141+
$dumper = new PhpDumper($this->container);
142+
$code = $dumper->dump([
143+
'class' => $cacheClass,
144+
'base_class' => Container::class,
145+
]);
146+
147+
file_put_contents($cacheFile, $code);
148+
149+
// Invalidate opcache for the new file
150+
if (!function_exists('opcache_invalidate')) {
151+
return;
152+
}
153+
154+
opcache_invalidate($cacheFile, true);
155+
}
156+
91157
/** @param array<mixed> $config */
92158
private function registerExtension(ExtensionInterface $extension, array $config): void
93159
{

0 commit comments

Comments
 (0)