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.8 — 2026-10-09

- Require maintained Symfony Routing and YAML 8.1 or newer, excluding the vulnerable 8.0 releases covered by the May 2026 advisories.
- Require Symfony DependencyInjection 8.1.8 or newer so environment placeholders reused after an earlier extension discards them remain resolvable in compiled containers.
- Verify environment resolution through container creation and cached-container reload with two extensions sharing an overridden environment value.
- Document the native Symfony 8.2 compile-time environment log and add a synthetic availability probe that outputs names and configuration paths only. Stable dependencies remain unchanged; the feature requires the future upstream API.

## 1.1.7 — 2026-10-06

- Isolate native compiler-pass state when rebuilding a runtime container after
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Then it adds the Symfony patterns that pay off in larger codebases:
- PHP `^8.5`
- Composer
- WordPress
- Symfony DependencyInjection `^8.1`, Config, Console, Filesystem, Routing,
- Symfony DependencyInjection `^8.1.8`, Config, Console, Filesystem, Routing,
Service Contracts, EventDispatcher, Clock, ExpressionLanguage, and Yaml
components

Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@
"symfony/config": "^8.0",
"symfony/console": "^8.0",
"symfony/clock": "^8.0",
"symfony/dependency-injection": "^8.1",
"symfony/dependency-injection": "^8.1.8",
"symfony/event-dispatcher": "^8.0",
"symfony/expression-language": "^8.0",
"symfony/filesystem": "^8.0",
"symfony/http-foundation": "^8.1",
"symfony/http-kernel": "^8.1",
"symfony/routing": "^8.0",
"symfony/routing": "^8.1",
"symfony/service-contracts": "^3.6",
"symfony/yaml": "^8.0"
"symfony/yaml": "^8.1"
},
"require-dev": {
"sympress/qa": "^0.1.2"
Expand Down
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ It boots exactly one global site-level container, discovers active MU plugins,
plugins, and themes as bundles, and imports their `Resources/config/`
directories into that same container.

The current kernel targets Symfony DependencyInjection `^8.1`. It keeps the
The current kernel targets Symfony DependencyInjection `^8.1.8`. It keeps the
WordPress-specific pieces small: package discovery, runtime hydration, hook
registration, and WP-CLI bridging. Bundle lifecycle, configurable extensions,
service locators, tagged iterators, resettable services, and most attributes use
the original Symfony component behavior.

## Contents

- [Native compile-time environment diagnostics](compile-time-environment.md)

- `boot-and-bundles.md`
- `services-and-autowiring.md`
- `dependency-injection.md`
Expand Down
41 changes: 41 additions & 0 deletions docs/compile-time-environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Native compile-time environment diagnostics

Symfony 8.2's `resolvesAtCompileTime()` configuration node API and native
`MergeExtensionConfigurationPass` log explain which environment variables were
inlined and which option or extension requested them. These APIs are not yet
stable as of 2026-10-09. Kernel continues to require stable DI `^8.1.8` and does
not introduce a development dependency or copy the upstream implementation.

The existing Kernel `debug:container --env-vars` command lists remaining
`%env(...)%` references. It does not identify values consumed at compile time.
With Framework Bundle, the framework command of the same name uses Symfony's
native descriptors. They are distinct implementations; no second descriptor or
compile-time resolver is introduced here.

For a configured `ContainerBuilder` in an explicit development build, read
`$builder->getCompiler()->getLog()` after compilation. Select only the native
`Inlined env var` records and output their variable name and option/extension
identifier. Do not print the entire compiler log or resolved parameter values:
other compiler passes can log arbitrary application data.

```php
foreach ($builder->getCompiler()->getLog() as $line) {
if (preg_match('/Inlined env var "%env\(([A-Za-z0-9_:]+)\)%" (into option|while loading extension) "([A-Za-z0-9_.-]+)"\./', $line, $matches) === 1) {
printf("%s\t%s=%s\n", $matches[1], $matches[2], $matches[3]);
}
}
```

`php tools/check-compile-time-env.php` runs a synthetic native configuration probe
and prints only the variable and configuration path. On stable 8.1 it exits with
code 2 and an explicit unavailable-API message. The probe has been verified with
the upstream 8.2 Config/DI source; it does not certify a stable 8.2 release.

Kernel currently does not mark its own configuration nodes with
`resolvesAtCompileTime()`. These records therefore become relevant when a
consumer's extension opts into the new native API. Existing cached PHP containers
do not retain a complete compiler log; inspect an explicit fresh build, rather
than rebuilding during normal requests just to collect diagnostics.

Sources: [API rename](https://github.com/symfony/symfony/pull/66642),
[native compiler log](https://github.com/symfony/symfony/pull/66717).
4 changes: 2 additions & 2 deletions docs/dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ build, configure, dump, and run the container:
"symfony/config": "^8.0",
"symfony/console": "^8.0",
"symfony/clock": "^8.0",
"symfony/dependency-injection": "^8.1",
"symfony/dependency-injection": "^8.1.8",
"symfony/event-dispatcher": "^8.0",
"symfony/expression-language": "^8.0",
"symfony/filesystem": "^8.0",
"symfony/service-contracts": "^3.6",
"symfony/yaml": "^8.0"
"symfony/yaml": "^8.1"
}
}
```
Expand Down
31 changes: 31 additions & 0 deletions tests/Kernel/DiscardedEnvExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace SymPress\Kernel\Tests\Kernel;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;

final class DiscardedEnvExtension extends Extension
{
public function getAlias(): string
{
return 'discarded';
}

public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new class implements ConfigurationInterface {
public function getConfigTreeBuilder(): TreeBuilder
{
$tree = new TreeBuilder('discarded');
$tree->getRootNode()->children()->scalarNode('value')->end()->end();
return $tree;
}
};
$this->processConfiguration($configuration, $configs);
}
}
46 changes: 46 additions & 0 deletions tests/Kernel/EnvPlaceholderCompatibilityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace SymPress\Kernel\Tests\Kernel;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;

final class EnvPlaceholderCompatibilityTest extends KernelTestCase
{
public function testEnvironmentDiscardedByOneExtensionResolvesInTheNextAndAfterCacheReload(): void
{
$projectDir = $this->tmpPath('reused-extension-env');
$bundleDir = $this->tmpPath('reused-extension-bundle');
mkdir($bundleDir, 0700, true);
file_put_contents($bundleDir . '/composer.json', '{}');
$registry = $this->registry($bundleDir);
$kernel = $this->kernel($projectDir);
$container = $kernel->createContainer();
$container->builder()->registerExtension(new DiscardedEnvExtension());
$container->builder()->registerExtension(new class extends Extension {
public function getAlias(): string
{
return 'reused';
}

public function load(array $configs, ContainerBuilder $container): void
{
$container->setParameter('reused.value', $configs[0]['value']);
}
});
$container->builder()->prependExtensionConfig('discarded', ['value' => 'overridden']);
$container->builder()->prependExtensionConfig('discarded', ['value' => '%env(APP_RUNTIME_MODE)%']);
$container->builder()->prependExtensionConfig('reused', ['value' => '%env(APP_RUNTIME_MODE)%']);
$_ENV['APP_RUNTIME_MODE'] = 'fixture-value';

$kernel->createRuntimeContainer($container, $registry, []);
self::assertSame('fixture-value', $container->getParameter('reused.value'));

$cachedKernel = $this->kernel($projectDir);
$cached = $cachedKernel->createContainer();
self::assertTrue($cachedKernel->tryUseRuntimeContainer($cached, $registry));
self::assertSame('fixture-value', $cached->getParameter('reused.value'));
}
}
64 changes: 64 additions & 0 deletions tools/check-compile-time-env.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;

require dirname(__DIR__) . '/vendor/autoload.php';

if (!method_exists(NodeDefinition::class, 'resolvesAtCompileTime')) {
fwrite(STDERR, "Native compile-time configuration diagnostics require Symfony Config/DI 8.2, which is not yet stable.\n");
exit(2);
}

$container = new ContainerBuilder();
$container->registerExtension(new class extends Extension {
public function getAlias(): string
{
return 'fixture';
}

public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new class implements ConfigurationInterface {
public function getConfigTreeBuilder(): TreeBuilder
{
$tree = new TreeBuilder('fixture');
$node = $tree->getRootNode()->children()->booleanNode('enabled');
$node->resolvesAtCompileTime();
return $tree;
}
};
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('fixture.enabled', $config['enabled']);
}
});
$container->loadFromExtension('fixture', ['enabled' => '%env(bool:SYMPRESS_TEST_FEATURE)%']);
$previous = $_ENV['SYMPRESS_TEST_FEATURE'] ?? null;
$_ENV['SYMPRESS_TEST_FEATURE'] = '1';
try {
(new MergeExtensionConfigurationPass())->process($container);
if ($container->getParameter('fixture.enabled') !== true) {
throw new RuntimeException('Native compile-time resolution was not applied.');
}
foreach ($container->getCompiler()->getLog() as $line) {
if (preg_match('/Inlined env var "%env\(([A-Za-z0-9_:]+)\)%" into option "([A-Za-z0-9_.-]+)"\./', $line, $matches) === 1) {
if ($matches[1] === 'bool:SYMPRESS_TEST_FEATURE' && $matches[2] === 'fixture.enabled') {
echo $matches[1] . "\toption=" . $matches[2] . "\n";
exit(0);
}
}
}
throw new RuntimeException('The native compiler log did not identify the inlined variable and configuration path.');
} finally {
if ($previous === null) {
unset($_ENV['SYMPRESS_TEST_FEATURE']);
} else {
$_ENV['SYMPRESS_TEST_FEATURE'] = $previous;
}
}
Loading