From 26d353174a104c0793d49e0e5f37f16502ac6e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brian=20Sch=C3=A4ffner?= Date: Tue, 6 Oct 2026 19:05:02 +0200 Subject: [PATCH] fix: isolate compiler passes during runtime recompilation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Brian Schäffner --- CHANGELOG.md | 7 ++++ src/Kernel/ContainerCacheManager.php | 14 ++++--- tests/Kernel/CompilerPassIsolationTest.php | 45 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 tests/Kernel/CompilerPassIsolationTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c5eac5..4ecd040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html) where applicable. +## 1.1.6 — 2026-10-06 + +- Isolate native compiler-pass state when rebuilding a runtime container after + newly discovered resources change its identity. Doctrine event listeners and + other passes now populate the final container instead of retaining definitions + from the discarded first candidate. + ## 1.1.5 — 2026-10-05 - Store container metadata and discovery descriptors as atomic private JSON, so unchanged readers retain OPcache entries while CLI publications remain visible to existing FPM workers with timestamp validation disabled. Ignore and rebuild legacy PHP metadata without executing it. diff --git a/src/Kernel/ContainerCacheManager.php b/src/Kernel/ContainerCacheManager.php index 254520b..82b8f1c 100644 --- a/src/Kernel/ContainerCacheManager.php +++ b/src/Kernel/ContainerCacheManager.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; final readonly class ContainerCacheManager { @@ -289,11 +290,14 @@ private function copyCompilerPasses(ContainerBuilder $source, ContainerBuilder $ $sourcePasses = $source->getCompilerPassConfig(); $targetPasses = $target->getCompilerPassConfig(); - $targetPasses->setBeforeOptimizationPasses($sourcePasses->getBeforeOptimizationPasses()); - $targetPasses->setOptimizationPasses($sourcePasses->getOptimizationPasses()); - $targetPasses->setBeforeRemovingPasses($sourcePasses->getBeforeRemovingPasses()); - $targetPasses->setRemovingPasses($sourcePasses->getRemovingPasses()); - $targetPasses->setAfterRemovingPasses($sourcePasses->getAfterRemovingPasses()); + // Native passes can retain Definition objects from their last compilation. + // Each candidate runtime must start from the pristine builder prototypes. + $copy = static fn (CompilerPassInterface $pass): CompilerPassInterface => clone $pass; + $targetPasses->setBeforeOptimizationPasses(array_map($copy, $sourcePasses->getBeforeOptimizationPasses())); + $targetPasses->setOptimizationPasses(array_map($copy, $sourcePasses->getOptimizationPasses())); + $targetPasses->setBeforeRemovingPasses(array_map($copy, $sourcePasses->getBeforeRemovingPasses())); + $targetPasses->setRemovingPasses(array_map($copy, $sourcePasses->getRemovingPasses())); + $targetPasses->setAfterRemovingPasses(array_map($copy, $sourcePasses->getAfterRemovingPasses())); } /** @param array $metadata */ diff --git a/tests/Kernel/CompilerPassIsolationTest.php b/tests/Kernel/CompilerPassIsolationTest.php new file mode 100644 index 0000000..4536588 --- /dev/null +++ b/tests/Kernel/CompilerPassIsolationTest.php @@ -0,0 +1,45 @@ +tmpPath('compiler-state-project'); + $resource = $this->tmpPath('compiler-resource.php'); + file_put_contents($resource, 'kernel($project); + $container = $kernel->createContainer(); + $registry = new BundleRegistry(); + $files = $kernel->configureContainer($container->builder(), $container, $registry); + $pass = new class ($resource) implements CompilerPassInterface { + public int $runs = 0; + + public function __construct(private readonly string $resource) + { + } + + public function process(ContainerBuilder $container): void + { + ++$this->runs; + $container->setParameter('fixture.compiler_runs', $this->runs); + $container->addResource(new FileResource($this->resource)); + } + }; + $container->builder()->addCompilerPass($pass); + $kernel->createRuntimeContainer($container, $registry, $files); + self::assertSame(1, $container->getParameter('fixture.compiler_runs')); + self::assertSame(0, $pass->runs); + $cached = $kernel->createContainer(); + self::assertTrue($kernel->tryUseRuntimeContainer($cached, $registry)); + self::assertSame(1, $cached->getParameter('fixture.compiler_runs')); + } +}