Skip to content
Merged
Show file tree
Hide file tree
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 9 additions & 5 deletions src/Kernel/ContainerCacheManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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<string, mixed> $metadata */
Expand Down
45 changes: 45 additions & 0 deletions tests/Kernel/CompilerPassIsolationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace SymPress\Kernel\Tests\Kernel;

use SymPress\Kernel\Bundle\BundleRegistry;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\Resource\FileResource;

final class CompilerPassIsolationTest extends KernelTestCase
{
public function testCompilerPassStateIsIsolatedAcrossResourceRecompilation(): void
{
$project = $this->tmpPath('compiler-state-project');
$resource = $this->tmpPath('compiler-resource.php');
file_put_contents($resource, '<?php // discovered while compiling');
$kernel = $this->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'));
}
}
Loading