Skip to content

Commit 38b08dc

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 5820131 commit 38b08dc

File tree

1 file changed

+73
-2
lines changed

1 file changed

+73
-2
lines changed

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

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,35 @@
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;
26+
use Symfony\Component\DependencyInjection\Exception\RuntimeException as DIRuntimeException;
2527
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
2628

29+
use function array_keys;
2730
use function array_merge;
31+
use function assert;
2832
use function class_exists;
33+
use function file_exists;
34+
use function file_put_contents;
35+
use function function_exists;
2936
use function getcwd;
3037
use function implode;
3138
use function is_a;
39+
use function is_dir;
40+
use function md5;
41+
use function mkdir;
42+
use function opcache_invalidate;
3243
use function rtrim;
44+
use function serialize;
3345
use function sprintf;
3446
use function strrchr;
3547
use function substr;
3648

3749
final class ContainerFactory
3850
{
51+
private const CACHE_DIR = '/tmp/guides-container-cache';
52+
private const CACHE_CLASS = 'CachedGuidesContainer';
53+
3954
private readonly ContainerBuilder $container;
4055
private readonly XmlFileLoader $configLoader;
4156

@@ -78,16 +93,72 @@ public function addConfigFile(string $filePath): void
7893

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

111+
// Build container
112+
$this->processConfig();
83113
$this->container->setParameter('vendor_dir', $vendorDir);
84114
$this->container->setParameter('working_directory', rtrim(getcwd(), '/'));
85-
86115
$this->container->compile(true);
87116

117+
// Try to cache the compiled container (may fail if container has object parameters)
118+
try {
119+
$this->cacheContainer($cacheFile, $cacheClass);
120+
} catch (DIRuntimeException) {
121+
// Container cannot be cached (has object/resource parameters), continue without caching
122+
}
123+
88124
return $this->container;
89125
}
90126

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

0 commit comments

Comments
 (0)