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')); + } +}