From 6d12fce4588987549c74a04f9518156f499c1f12 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 29 Jan 2026 14:12:22 +0100 Subject: [PATCH 01/19] [FrameworkBundle] Fix clearing the HttpCache store in tests --- Test/KernelTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/KernelTestCase.php b/Test/KernelTestCase.php index 3c3a1e5a0..410fac3dd 100644 --- a/Test/KernelTestCase.php +++ b/Test/KernelTestCase.php @@ -142,7 +142,7 @@ protected static function ensureKernelShutdown() $httpCacheDir = null; if ($container->has('http_cache')) { - $httpCacheDir = static::$kernel->getCacheDir().'/http_cache'; + $httpCacheDir = static::$kernel->getShareDir().'/http_cache'; } if ($container->has('services_resetter')) { From 2116b50240c37aed0ce213b10310916f57444587 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 29 Jan 2026 17:50:04 +0100 Subject: [PATCH 02/19] [FrameworkBundle] Fix accessing the test container when using KernelTestCase in non-debug mode --- Test/KernelTestCase.php | 2 +- Tests/Functional/TestServiceContainerTest.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Test/KernelTestCase.php b/Test/KernelTestCase.php index 5c337b730..5a6384b5e 100644 --- a/Test/KernelTestCase.php +++ b/Test/KernelTestCase.php @@ -86,7 +86,7 @@ protected static function bootKernel(array $options = []): KernelInterface // If the cache warmer is registered, it means that the cache has been // warmed up, so the current container is not fresh anymore. Let's // reboot a fresh one. - if (self::getContainer()->initialized('cache_warmer')) { + if ($kernel->getContainer()->initialized('cache_warmer')) { static::ensureKernelShutdown(); $kernel = static::createKernel($options); diff --git a/Tests/Functional/TestServiceContainerTest.php b/Tests/Functional/TestServiceContainerTest.php index 2b69e6aa1..25c248cad 100644 --- a/Tests/Functional/TestServiceContainerTest.php +++ b/Tests/Functional/TestServiceContainerTest.php @@ -22,9 +22,10 @@ class TestServiceContainerTest extends AbstractWebTestCase { public function testLogicExceptionIfTestConfigIsDisabled() { - $this->expectException(\LogicException::class); - static::bootKernel(['test_case' => 'TestServiceContainer', 'root_config' => 'test_disabled.yml', 'environment' => 'test_disabled']); + + $this->expectException(\LogicException::class); + static::getContainer(); } public function testThatPrivateServicesAreAvailableIfTestConfigIsEnabled() From 7855632b07fef93b7dcf9094f9ca7706a38bd6b5 Mon Sep 17 00:00:00 2001 From: "Florian \"Ori\" Neveu" Date: Fri, 16 Jan 2026 00:07:21 +0100 Subject: [PATCH 03/19] [HttpKernel] Bypass mapping construction when `RedirectController::urlRedirectAction` is triggered --- Routing/RedirectableCompiledUrlMatcher.php | 1 + Tests/Routing/RedirectableCompiledUrlMatcherTest.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Routing/RedirectableCompiledUrlMatcher.php b/Routing/RedirectableCompiledUrlMatcher.php index 609502bca..6f968adae 100644 --- a/Routing/RedirectableCompiledUrlMatcher.php +++ b/Routing/RedirectableCompiledUrlMatcher.php @@ -31,6 +31,7 @@ public function redirect(string $path, string $route, ?string $scheme = null): a 'httpPort' => $this->context->getHttpPort(), 'httpsPort' => $this->context->getHttpsPort(), '_route' => $route, + '_route_mapping' => [], ]; } } diff --git a/Tests/Routing/RedirectableCompiledUrlMatcherTest.php b/Tests/Routing/RedirectableCompiledUrlMatcherTest.php index 9a96313d0..b63cd9616 100644 --- a/Tests/Routing/RedirectableCompiledUrlMatcherTest.php +++ b/Tests/Routing/RedirectableCompiledUrlMatcherTest.php @@ -36,6 +36,7 @@ public function testRedirectWhenNoSlash() 'httpPort' => $context->getHttpPort(), 'httpsPort' => $context->getHttpsPort(), '_route' => 'foo', + '_route_mapping' => [], ], $matcher->match('/foo') ); @@ -57,6 +58,7 @@ public function testSchemeRedirect() 'httpPort' => $context->getHttpPort(), 'httpsPort' => $context->getHttpsPort(), '_route' => 'foo', + '_route_mapping' => [], ], $matcher->match('/foo') ); From 9bc3b1bd6550321304ef9b0297dca42e8baebb2b Mon Sep 17 00:00:00 2001 From: matlec Date: Fri, 30 Jan 2026 18:59:30 +0100 Subject: [PATCH 04/19] [FrameworkBundle] Add missing `useAttributeAsKey` calls --- DependencyInjection/Configuration.php | 5 +++++ Resources/config/schema/symfony-1.0.xsd | 1 + 2 files changed, 6 insertions(+) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 7fe754e3d..3d3523d47 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -494,6 +494,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void ->cannotBeEmpty() ->end() ->arrayNode('metadata') + ->useAttributeAsKey('key') ->normalizeKeys(false) ->defaultValue([]) ->example(['color' => 'blue', 'description' => 'Workflow to manage article.']) @@ -563,6 +564,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void ->end() ->end() ->arrayNode('metadata') + ->useAttributeAsKey('key') ->normalizeKeys(false) ->defaultValue([]) ->example(['color' => 'blue', 'description' => 'Workflow to manage article.']) @@ -573,6 +575,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void ->end() ->end() ->arrayNode('metadata') + ->useAttributeAsKey('key') ->normalizeKeys(false) ->defaultValue([]) ->example(['color' => 'blue', 'description' => 'Workflow to manage article.']) @@ -1186,6 +1189,7 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $e ->end() ->end() ->arrayNode('default_context') + ->useAttributeAsKey('key') ->normalizeKeys(false) ->validate() ->ifTrue(fn () => $this->debug && class_exists(JsonParser::class)) @@ -1667,6 +1671,7 @@ function ($a) { ->scalarNode('dsn')->end() ->scalarNode('serializer')->defaultNull()->info('Service id of a custom serializer to use.')->end() ->arrayNode('options') + ->useAttributeAsKey('key') ->normalizeKeys(false) ->defaultValue([]) ->prototype('variable') diff --git a/Resources/config/schema/symfony-1.0.xsd b/Resources/config/schema/symfony-1.0.xsd index 4a650af6b..586bb5e3c 100644 --- a/Resources/config/schema/symfony-1.0.xsd +++ b/Resources/config/schema/symfony-1.0.xsd @@ -486,6 +486,7 @@ + From 28e8e0fc1e141d08564da7470feda7a7c68412b3 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Fri, 30 Jan 2026 09:49:26 +0100 Subject: [PATCH 05/19] [PropertyInfo] fix compatibility with phpdocumentor/reflection-docblock 6.x --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 715aa9c2c..72b8a1b0a 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "require-dev": { "doctrine/persistence": "^1.3|^2|^3", "dragonmantank/cron-expression": "^3.1", - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "seld/jsonlint": "^1.10", "symfony/asset": "^6.4|^7.0|^8.0", "symfony/asset-mapper": "^6.4|^7.0|^8.0", @@ -81,7 +81,7 @@ }, "conflict": { "doctrine/persistence": "<1.3", - "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/asset": "<6.4", "symfony/asset-mapper": "<6.4", From bb0237bfd1e4bce131e7c4afbe7ac2ad41f39ff2 Mon Sep 17 00:00:00 2001 From: Thiago Melo Date: Sun, 1 Feb 2026 18:16:10 +0000 Subject: [PATCH 06/19] [FrameworkBundle] Fix BrowserKitAssertionsTrait compatibility with HttpBrowser --- Test/BrowserKitAssertionsTrait.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Test/BrowserKitAssertionsTrait.php b/Test/BrowserKitAssertionsTrait.php index b09045b4f..f2e3e3c14 100644 --- a/Test/BrowserKitAssertionsTrait.php +++ b/Test/BrowserKitAssertionsTrait.php @@ -16,6 +16,8 @@ use PHPUnit\Framework\Constraint\LogicalNot; use PHPUnit\Framework\ExpectationFailedException; use Symfony\Component\BrowserKit\AbstractBrowser; +use Symfony\Component\BrowserKit\Request as BrowserKitRequest; +use Symfony\Component\BrowserKit\Response as BrowserKitResponse; use Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -183,6 +185,14 @@ private static function getResponse(): Response static::fail('A client must have an HTTP Response to make assertions. Did you forget to make an HTTP request?'); } + if ($response instanceof BrowserKitResponse) { + return new Response( + $response->getContent(), + $response->getStatusCode(), + $response->getHeaders() + ); + } + return $response; } @@ -192,6 +202,18 @@ private static function getRequest(): Request static::fail('A client must have an HTTP Request to make assertions. Did you forget to make an HTTP request?'); } + if ($request instanceof BrowserKitRequest) { + return Request::create( + $request->getUri(), + $request->getMethod(), + $request->getParameters(), + $request->getCookies(), + $request->getFiles(), + $request->getServer(), + $request->getContent() + ); + } + return $request; } } From 09c315989ee8e27dddbb9ec110c745f436805655 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 6 Feb 2026 17:44:03 +0100 Subject: [PATCH 07/19] fix forward compatibility with JsonStreamer 8.1+ --- DependencyInjection/FrameworkExtension.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DependencyInjection/FrameworkExtension.php b/DependencyInjection/FrameworkExtension.php index dba3f833e..a98728259 100644 --- a/DependencyInjection/FrameworkExtension.php +++ b/DependencyInjection/FrameworkExtension.php @@ -116,6 +116,7 @@ use Symfony\Component\HttpKernel\Profiler\ProfilerStateChecker; use Symfony\Component\JsonStreamer\Attribute\JsonStreamable; use Symfony\Component\JsonStreamer\JsonStreamWriter; +use Symfony\Component\JsonStreamer\Mapping\PropertyMetadata; use Symfony\Component\JsonStreamer\StreamReaderInterface; use Symfony\Component\JsonStreamer\StreamWriterInterface; use Symfony\Component\JsonStreamer\ValueTransformer\ValueTransformerInterface; @@ -2232,6 +2233,11 @@ private function registerJsonStreamerConfiguration(array $config, ContainerBuild if (\PHP_VERSION_ID >= 80400) { $container->removeDefinition('.json_streamer.cache_warmer.lazy_ghost'); } + + // forward compatibility with "symfony/json-streamer" 8.1+ + if (!method_exists(PropertyMetadata::class, 'getNativeToStreamValueTransformer')) { + $container->getDefinition('json_streamer.stream_reader')->replaceArgument(4, []); + } } private function registerPropertyInfoConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void From ffeccd6bef28f7ff81593c19bea8c3e9b4d361e1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 6 Feb 2026 18:22:20 +0100 Subject: [PATCH 08/19] [FrameworkBundle] Fix autoconfiguring controllers using legacy Route annotations as attributes --- DependencyInjection/FrameworkExtension.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DependencyInjection/FrameworkExtension.php b/DependencyInjection/FrameworkExtension.php index dba3f833e..40a5c13e6 100644 --- a/DependencyInjection/FrameworkExtension.php +++ b/DependencyInjection/FrameworkExtension.php @@ -179,6 +179,7 @@ use Symfony\Component\RateLimiter\Storage\CacheStorage; use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer; use Symfony\Component\RemoteEvent\RemoteEvent; +use Symfony\Component\Routing\Annotation\Route as LegacyRoute; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Loader\AttributeServicesLoader; use Symfony\Component\Routing\Loader\XmlFileLoader as RoutingXmlFileLoader; @@ -793,6 +794,9 @@ public function load(array $configs, ContainerBuilder $container): void $container->registerAttributeForAutoconfiguration(Route::class, static function (ChildDefinition $definition, Route $attribute, \ReflectionClass|\ReflectionMethod $reflection): void { $definition->addTag('controller.service_arguments')->addTag('routing.controller'); }); + $container->registerAttributeForAutoconfiguration(LegacyRoute::class, static function (ChildDefinition $definition, Route $attribute, \ReflectionClass|\ReflectionMethod $reflection): void { + $definition->addTag('controller.service_arguments')->addTag('routing.controller'); + }); $container->registerAttributeForAutoconfiguration(AsRemoteEventConsumer::class, static function (ChildDefinition $definition, AsRemoteEventConsumer $attribute): void { $definition->addTag('remote_event.consumer', ['consumer' => $attribute->name]); }); From 9acdfebc3c837f1efa9860194f0e1769806af9e2 Mon Sep 17 00:00:00 2001 From: W0rma Date: Sat, 7 Feb 2026 11:14:53 +0100 Subject: [PATCH 09/19] [FrameworkBundle] express that raw strings are mapped to senders for messenger routing --- DependencyInjection/Configuration.php | 1 + 1 file changed, 1 insertion(+) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 1b34464bc..1e32389e5 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -1738,6 +1738,7 @@ function ($a) { }) ->end() ->prototype('array') + ->acceptAndWrap(['string'], 'senders') ->performNoDeepMerging() ->children() ->arrayNode('senders') From 4c59cf7fa76457e6148faa2364f9fcdf047de56e Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Mon, 9 Feb 2026 13:33:10 +0100 Subject: [PATCH 10/19] [FrameworkBundle] Fix JsonStreamer forward compatibility --- DependencyInjection/FrameworkExtension.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/DependencyInjection/FrameworkExtension.php b/DependencyInjection/FrameworkExtension.php index f943b0e85..5c9661b76 100644 --- a/DependencyInjection/FrameworkExtension.php +++ b/DependencyInjection/FrameworkExtension.php @@ -2238,9 +2238,15 @@ private function registerJsonStreamerConfiguration(array $config, ContainerBuild $container->removeDefinition('.json_streamer.cache_warmer.lazy_ghost'); } - // forward compatibility with "symfony/json-streamer" 8.1+ + // forward compatibility with "symfony/json-streamer" 8.0+ if (!method_exists(PropertyMetadata::class, 'getNativeToStreamValueTransformer')) { - $container->getDefinition('json_streamer.stream_reader')->replaceArgument(4, []); + $reader = $container->getDefinition('json_streamer.stream_reader'); + + $args = $reader->getArguments(); + unset($args[4]); + + $reader->setArguments($args); + $container->removeDefinition('.json_streamer.cache_warmer.lazy_ghost'); } } From 5b714c228145885bccb5d2742b687e5e2b1a755c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 9 Feb 2026 20:30:38 +0100 Subject: [PATCH 11/19] [FrameworkBundle] Fix referencing DI's AppReference shape in config/reference.php --- .../Compiler/PhpConfigReferenceDumpPass.php | 8 +++++--- .../Compiler/PhpConfigReferenceDumpPassTest.php | 2 +- Tests/Fixtures/reference.php | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/DependencyInjection/Compiler/PhpConfigReferenceDumpPass.php b/DependencyInjection/Compiler/PhpConfigReferenceDumpPass.php index 5e98c9de6..6ce4ab3e7 100644 --- a/DependencyInjection/Compiler/PhpConfigReferenceDumpPass.php +++ b/DependencyInjection/Compiler/PhpConfigReferenceDumpPass.php @@ -43,7 +43,11 @@ class PhpConfigReferenceDumpPass implements CompilerPassInterface {APP_TYPES} final class App { - {APP_PARAM} + /** + * @param ConfigType $config + * + * @psalm-return ConfigType + */ public static function config(array $config): array { return AppReference::config($config); @@ -168,7 +172,6 @@ public function process(ContainerBuilder $container): void '{SHAPE}' => $this->getShapeForExtensions($extensions, $container, ' '), ]), $i, 0); } - $appParam = $r->getMethod('config')->getDocComment(); $r = new \ReflectionClass(RoutesReference::class); @@ -192,7 +195,6 @@ public function process(ContainerBuilder $container): void $configReference = strtr(self::REFERENCE_TEMPLATE, [ '{APP_TYPES}' => $appTypes, - '{APP_PARAM}' => $appParam, '{ROUTES_TYPES}' => $routesTypes, '{ROUTES_PARAM}' => $r->getMethod('config')->getDocComment(), ]); diff --git a/Tests/DependencyInjection/Compiler/PhpConfigReferenceDumpPassTest.php b/Tests/DependencyInjection/Compiler/PhpConfigReferenceDumpPassTest.php index c2111b9b6..b91b5f73f 100644 --- a/Tests/DependencyInjection/Compiler/PhpConfigReferenceDumpPassTest.php +++ b/Tests/DependencyInjection/Compiler/PhpConfigReferenceDumpPassTest.php @@ -111,7 +111,7 @@ public function testProcessGeneratesExpectedReferenceFile() self::markTestIncomplete('TEST_GENERATE_FIXTURES is set'); } - $this->assertFileEquals(__DIR__.'/../../Fixtures/reference.php', $this->tempDir.'/reference.php'); + $this->assertFileMatchesFormatFile(__DIR__.'/../../Fixtures/reference.php', $this->tempDir.'/reference.php'); $this->assertEquals([new FileResource(realpath($this->tempDir).'/reference.php')], $container->getResources()); } diff --git a/Tests/Fixtures/reference.php b/Tests/Fixtures/reference.php index 5c51f2aa2..8d6279f4d 100644 --- a/Tests/Fixtures/reference.php +++ b/Tests/Fixtures/reference.php @@ -81,7 +81,7 @@ * decoration_inner_name?: string, * decoration_priority?: int, * decoration_on_invalid?: 'exception'|'ignore'|null, - * autowire?: bool, + * %Aautowire?: bool, * autoconfigure?: bool, * bind?: array, * constructor?: string, @@ -162,7 +162,7 @@ * services?: ServicesConfig, * test?: TestConfig, * }, - * ... Date: Fri, 6 Feb 2026 08:55:12 +0100 Subject: [PATCH 12/19] use PHPUnit 13 on PHP 8.4+ --- Tests/CacheWarmer/RouterCacheWarmerTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Tests/CacheWarmer/RouterCacheWarmerTest.php b/Tests/CacheWarmer/RouterCacheWarmerTest.php index 0814b5274..e984d86a9 100644 --- a/Tests/CacheWarmer/RouterCacheWarmerTest.php +++ b/Tests/CacheWarmer/RouterCacheWarmerTest.php @@ -23,14 +23,13 @@ public function testWarmUpWithWarmableInterfaceWithBuildDir() { $container = new Container(); - $routerMock = $this->createStub(testRouterInterfaceWithWarmableInterface::class); - $routerMock->method('warmUp')->willReturn([]); + $routerMock = $this->createMock(testRouterInterfaceWithWarmableInterface::class); + $routerMock->expects($this->once())->method('warmUp')->with('/tmp/cache', '/tmp/build')->willReturn([]); $container->set('router', $routerMock); $routerCacheWarmer = new RouterCacheWarmer($container); $routerCacheWarmer->warmUp('/tmp/cache', '/tmp/build'); - $routerMock->method('warmUp')->with('/tmp/cache', '/tmp/build')->willReturn([]); $this->addToAssertionCount(1); } From ee15ee82da16cb29b45eea88b8be6695f9ea779c Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 13 Feb 2026 14:45:10 +0100 Subject: [PATCH 13/19] fail gracefully when the semaphore config is used but the component is missing --- DependencyInjection/FrameworkExtension.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DependencyInjection/FrameworkExtension.php b/DependencyInjection/FrameworkExtension.php index a8ef96b65..4ac26dddf 100644 --- a/DependencyInjection/FrameworkExtension.php +++ b/DependencyInjection/FrameworkExtension.php @@ -2101,6 +2101,10 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont private function registerSemaphoreConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void { + if (!class_exists(Semaphore::class)) { + throw new LogicException('Semaphore support cannot be enabled as the Semaphore component is not installed. Try running "composer require symfony/semaphore".'); + } + $loader->load('semaphore.php'); foreach ($config['resources'] as $resourceName => $resourceStore) { From 2a427bbc0e8a0e59b5c6d97dc8ff3333b41aa62b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 15 Feb 2026 11:37:42 +0100 Subject: [PATCH 14/19] Improve the robustness of some tests --- .../SecretsDecryptToLocalCommandTest.php | 2 +- Tests/Command/SecretsListCommandTest.php | 29 +++++++------------ .../Functional/ContainerDebugCommandTest.php | 10 +++---- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/Tests/Command/SecretsDecryptToLocalCommandTest.php b/Tests/Command/SecretsDecryptToLocalCommandTest.php index 8a1c05d69..5dba11afb 100644 --- a/Tests/Command/SecretsDecryptToLocalCommandTest.php +++ b/Tests/Command/SecretsDecryptToLocalCommandTest.php @@ -68,7 +68,7 @@ public function testExistingLocalSecretsAreSkippedWithoutForce() $tester = new CommandTester(new SecretsDecryptToLocalCommand($mainVault, $localVault)); $this->assertSame(0, $tester->execute([])); - $this->assertStringContainsString('1 secret is already overridden in the local vault and will be skipped.', $tester->getDisplay()); + $this->assertMatchesRegularExpression('/1 secret is already overridden in the local vault and will be\s+skipped\./', $tester->getDisplay()); $this->assertSame('old_value', $localVault->reveal('FOO_SECRET')); } diff --git a/Tests/Command/SecretsListCommandTest.php b/Tests/Command/SecretsListCommandTest.php index 5ffeaed65..c3a0821e1 100644 --- a/Tests/Command/SecretsListCommandTest.php +++ b/Tests/Command/SecretsListCommandTest.php @@ -34,24 +34,15 @@ public function testExecute() $tester = new CommandTester($command); $this->assertSame(0, $tester->execute([])); - $expectedOutput = <<)%%" to reference a secret in a config file. - - // To reveal the secrets run %s secrets:list --reveal - - -------- -------- ------------- - Secret Value Local Value - -------- -------- ------------- - A "a" - B "b" ****** - C ****** - D ****** ****** - E ****** - -------- -------- ------------- - - // Local values override secret values. - // Use secrets:set --local to define them. - EOTXT; - $this->assertStringMatchesFormat($expectedOutput, trim(preg_replace('/ ++$/m', '', $tester->getDisplay(true)), "\n")); + $display = trim(preg_replace('/ ++$/m', '', $tester->getDisplay(true)), "\n"); + + $this->assertStringContainsString('// Use "%env()%" to reference a secret in a config file.', $display); + $this->assertMatchesRegularExpression('/\n\s*A\s+"a"\s*\n/', $display); + $this->assertMatchesRegularExpression('/\n\s*B\s+"b"\s+\*\*\*\*\*\*\s*\n/', $display); + $this->assertMatchesRegularExpression('/\n\s*C\s+\*\*\*\*\*\*\s*\n/', $display); + $this->assertMatchesRegularExpression('/\n\s*D\s+\*\*\*\*\*\*\s+\*\*\*\*\*\*\s*\n/', $display); + $this->assertMatchesRegularExpression('/\n\s*E\s+\*\*\*\*\*\*\s*\n/', $display); + $this->assertStringContainsString('// Local values override secret values.', $display); + $this->assertStringContainsString('// Use secrets:set --local to define them.', $display); } } diff --git a/Tests/Functional/ContainerDebugCommandTest.php b/Tests/Functional/ContainerDebugCommandTest.php index 95dcc36ed..fe0582c65 100644 --- a/Tests/Functional/ContainerDebugCommandTest.php +++ b/Tests/Functional/ContainerDebugCommandTest.php @@ -94,10 +94,10 @@ public function testDeprecatedServiceAndAlias() $tester = new ApplicationTester($application); $tester->run(['command' => 'debug:container', 'name' => 'deprecated', '--format' => 'txt']); - $this->assertStringContainsString('[WARNING] The "deprecated" service is deprecated since foo/bar 1.9 and will be removed in 2.0', $tester->getDisplay()); + $this->assertStringContainsString('The "deprecated" service is deprecated since foo/bar 1.9 and will be removed in 2.0', preg_replace('/\s+/', ' ', $tester->getDisplay())); $tester->run(['command' => 'debug:container', 'name' => 'deprecated_alias', '--format' => 'txt']); - $this->assertStringContainsString('[WARNING] The "deprecated_alias" alias is deprecated since foo/bar 1.9 and will be removed in 2.0', $tester->getDisplay()); + $this->assertStringContainsString('The "deprecated_alias" alias is deprecated since foo/bar 1.9 and will be removed in 2.0', preg_replace('/\s+/', ' ', $tester->getDisplay())); } public function testExcludedService() @@ -214,10 +214,10 @@ public function testGetDeprecation() file_put_contents($path, serialize([[ 'type' => 16384, 'message' => 'The "Symfony\Bundle\FrameworkBundle\Controller\Controller" class is deprecated since Symfony 4.2, use Symfony\Bundle\FrameworkBundle\Controller\AbstractController instead.', - 'file' => '/home/hamza/projet/contrib/sf/vendor/symfony/framework-bundle/Controller/Controller.php', + 'file' => '/home/hamza/project/contrib/sf/vendor/symfony/framework-bundle/Controller/Controller.php', 'line' => 17, 'trace' => [[ - 'file' => '/home/hamza/projet/contrib/sf/src/Controller/DefaultController.php', + 'file' => '/home/hamza/project/contrib/sf/src/Controller/DefaultController.php', 'line' => 9, 'function' => 'spl_autoload_call', ]], @@ -233,7 +233,7 @@ public function testGetDeprecation() $tester->assertCommandIsSuccessful(); $this->assertStringContainsString('Symfony\Bundle\FrameworkBundle\Controller\Controller', $tester->getDisplay()); - $this->assertStringContainsString('/home/hamza/projet/contrib/sf/vendor/symfony/framework-bundle/Controller/Controller.php', $tester->getDisplay()); + $this->assertStringContainsString('/home/hamza/project/contrib/sf/vendor/symfony/framework-bundle/Controller/Controller.php', $tester->getDisplay()); } public function testGetDeprecationNone() From 51b8af9e60f0af9fb2ebfa693b81c6a5393b8ec8 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 16 Feb 2026 13:42:33 +0100 Subject: [PATCH 15/19] remove usages of the deprecated any() invoked count expectation --- Tests/Command/CachePoolClearCommandTest.php | 1 - Tests/Command/CachePoolDeleteCommandTest.php | 1 - Tests/Command/CachePruneCommandTest.php | 1 - Tests/Command/RouterMatchCommandTest.php | 1 - Tests/Command/XliffLintCommandTest.php | 1 - Tests/Command/YamlLintCommandTest.php | 1 - Tests/Console/ApplicationTest.php | 2 -- Tests/KernelBrowserTest.php | 2 +- 8 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Tests/Command/CachePoolClearCommandTest.php b/Tests/Command/CachePoolClearCommandTest.php index 966669354..6c298ee44 100644 --- a/Tests/Command/CachePoolClearCommandTest.php +++ b/Tests/Command/CachePoolClearCommandTest.php @@ -51,7 +51,6 @@ private function getKernel(): MockObject&KernelInterface $kernel = $this->createMock(KernelInterface::class); $kernel - ->expects($this->any()) ->method('getContainer') ->willReturn($container); diff --git a/Tests/Command/CachePoolDeleteCommandTest.php b/Tests/Command/CachePoolDeleteCommandTest.php index 18de5773e..b9a989d8e 100644 --- a/Tests/Command/CachePoolDeleteCommandTest.php +++ b/Tests/Command/CachePoolDeleteCommandTest.php @@ -109,7 +109,6 @@ private function getKernel(): MockObject&KernelInterface $kernel = $this->createMock(KernelInterface::class); $kernel - ->expects($this->any()) ->method('getContainer') ->willReturn($container); diff --git a/Tests/Command/CachePruneCommandTest.php b/Tests/Command/CachePruneCommandTest.php index 2b6d566e3..19aafc7a0 100644 --- a/Tests/Command/CachePruneCommandTest.php +++ b/Tests/Command/CachePruneCommandTest.php @@ -92,7 +92,6 @@ private function getKernel(): MockObject&KernelInterface $kernel = $this->createMock(KernelInterface::class); $kernel - ->expects($this->any()) ->method('getContainer') ->willReturn($container); diff --git a/Tests/Command/RouterMatchCommandTest.php b/Tests/Command/RouterMatchCommandTest.php index ea0405059..d9accce1e 100644 --- a/Tests/Command/RouterMatchCommandTest.php +++ b/Tests/Command/RouterMatchCommandTest.php @@ -85,7 +85,6 @@ private function getKernel() $kernel = $this->createMock(KernelInterface::class); $kernel - ->expects($this->any()) ->method('getContainer') ->willReturn($container) ; diff --git a/Tests/Command/XliffLintCommandTest.php b/Tests/Command/XliffLintCommandTest.php index db32bc19c..73e9574a6 100644 --- a/Tests/Command/XliffLintCommandTest.php +++ b/Tests/Command/XliffLintCommandTest.php @@ -92,7 +92,6 @@ private function getKernelAwareApplicationMock() ->willReturn(new HelperSet()); $application - ->expects($this->any()) ->method('getDefinition') ->willReturn(new InputDefinition()); diff --git a/Tests/Command/YamlLintCommandTest.php b/Tests/Command/YamlLintCommandTest.php index 08f4a7526..6363fb4b2 100644 --- a/Tests/Command/YamlLintCommandTest.php +++ b/Tests/Command/YamlLintCommandTest.php @@ -140,7 +140,6 @@ private function getKernelAwareApplicationMock() ->willReturn(new HelperSet()); $application - ->expects($this->any()) ->method('getDefinition') ->willReturn(new InputDefinition()); diff --git a/Tests/Console/ApplicationTest.php b/Tests/Console/ApplicationTest.php index 9a92abd7d..66cd8de8f 100644 --- a/Tests/Console/ApplicationTest.php +++ b/Tests/Console/ApplicationTest.php @@ -297,12 +297,10 @@ private function getKernel(array $bundles, $useDispatcher = false) $kernel = $this->createMock(KernelInterface::class); $kernel->expects($this->once())->method('boot'); $kernel - ->expects($this->any()) ->method('getBundles') ->willReturn($bundles) ; $kernel - ->expects($this->any()) ->method('getContainer') ->willReturn($container) ; diff --git a/Tests/KernelBrowserTest.php b/Tests/KernelBrowserTest.php index 1e462f7d0..f8b4081c9 100644 --- a/Tests/KernelBrowserTest.php +++ b/Tests/KernelBrowserTest.php @@ -68,7 +68,7 @@ private function getKernelMock() ->disableOriginalConstructor() ->getMock(); - $mock->expects($this->any())->method('handle')->willReturn(new Response('foo')); + $mock->method('handle')->willReturn(new Response('foo')); return $mock; } From 788157da03663233d422842b65f0e02cc61e0760 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 18 Feb 2026 08:46:10 +0100 Subject: [PATCH 16/19] stop using with*() without expects() --- Tests/Command/TranslationExtractCommandTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/Command/TranslationExtractCommandTest.php b/Tests/Command/TranslationExtractCommandTest.php index cc8c8f026..d129e2cd6 100644 --- a/Tests/Command/TranslationExtractCommandTest.php +++ b/Tests/Command/TranslationExtractCommandTest.php @@ -185,8 +185,9 @@ public function testRemoveNoFillTranslationsMethod($noFillCounter, $messages) $operation = $this->createMock(MessageCatalogueInterface::class); $operation ->method('all') - ->with('messages') - ->willReturn($messages); + ->willReturnMap([ + ['messages', $messages], + ]); $operation ->expects($this->exactly($noFillCounter)) ->method('set'); From 2a426862290695e7d07bbbd7b9a530cbb9689d5d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 23 Feb 2026 09:49:12 +0100 Subject: [PATCH 17/19] [FrameworkBundle] Fix phpstan false-positive about config/reference.php --- .../Compiler/PhpConfigReferenceDumpPass.php | 13 +++++++------ Tests/Fixtures/reference.php | 5 ++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/DependencyInjection/Compiler/PhpConfigReferenceDumpPass.php b/DependencyInjection/Compiler/PhpConfigReferenceDumpPass.php index 6ce4ab3e7..a8d64ebf0 100644 --- a/DependencyInjection/Compiler/PhpConfigReferenceDumpPass.php +++ b/DependencyInjection/Compiler/PhpConfigReferenceDumpPass.php @@ -43,14 +43,13 @@ class PhpConfigReferenceDumpPass implements CompilerPassInterface {APP_TYPES} final class App { - /** - * @param ConfigType $config - * - * @psalm-return ConfigType - */ + {APP_PARAM} public static function config(array $config): array { - return AppReference::config($config); + /** @var ConfigType $config */ + $config = AppReference::config($config); + + return $config; } } @@ -172,6 +171,7 @@ public function process(ContainerBuilder $container): void '{SHAPE}' => $this->getShapeForExtensions($extensions, $container, ' '), ]), $i, 0); } + $appParam = $r->getMethod('config')->getDocComment(); $r = new \ReflectionClass(RoutesReference::class); @@ -195,6 +195,7 @@ public function process(ContainerBuilder $container): void $configReference = strtr(self::REFERENCE_TEMPLATE, [ '{APP_TYPES}' => $appTypes, + '{APP_PARAM}' => $appParam, '{ROUTES_TYPES}' => $routesTypes, '{ROUTES_PARAM}' => $r->getMethod('config')->getDocComment(), ]); diff --git a/Tests/Fixtures/reference.php b/Tests/Fixtures/reference.php index 8d6279f4d..21d7d4752 100644 --- a/Tests/Fixtures/reference.php +++ b/Tests/Fixtures/reference.php @@ -179,7 +179,10 @@ final class App */ public static function config(array $config): array { - return AppReference::config($config); + /** @var ConfigType $config */ + $config = AppReference::config($config); + + return $config; } } From a86407909767ad9271a2725e67f1f0a493cd5996 Mon Sep 17 00:00:00 2001 From: matlec Date: Sun, 7 Dec 2025 18:14:40 +0100 Subject: [PATCH 18/19] [FrameworkBundle] Make `ConfigDebugCommand` use its container to resolve env vars --- Command/ConfigDebugCommand.php | 12 +++++- Resources/config/console.php | 3 ++ .../EnvVarLoader/StatefulEnvVarLoader.php | 40 +++++++++++++++++++ .../EnvVarLoader/VaultEnvVarPrimer.php | 25 ++++++++++++ Tests/Functional/ConfigDebugCommandTest.php | 29 ++++++++++++++ Tests/Functional/app/AppKernel.php | 5 ++- Tests/Functional/app/ConfigDump/config.yml | 14 +++++++ 7 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 Tests/Functional/Bundle/TestBundle/EnvVarLoader/StatefulEnvVarLoader.php create mode 100644 Tests/Functional/Bundle/TestBundle/EnvVarLoader/VaultEnvVarPrimer.php diff --git a/Command/ConfigDebugCommand.php b/Command/ConfigDebugCommand.php index fcef7c1d6..f0bcb1786 100644 --- a/Command/ConfigDebugCommand.php +++ b/Command/ConfigDebugCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Psr\Container\ContainerInterface; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\Console\Attribute\AsCommand; @@ -39,9 +40,15 @@ #[AsCommand(name: 'debug:config', description: 'Dump the current configuration for an extension')] class ConfigDebugCommand extends AbstractConfigCommand { + public function __construct( + private ?ContainerInterface $envVarProcessors = null, + ) { + parent::__construct(); + } + protected function configure(): void { - $commentedHelpFormats = array_map(fn ($format) => \sprintf('%s', $format), $this->getAvailableFormatOptions()); + $commentedHelpFormats = array_map(static fn ($format) => \sprintf('%s', $format), $this->getAvailableFormatOptions()); $helpFormats = implode('", "', $commentedHelpFormats); $this @@ -146,6 +153,9 @@ private function compileContainer(): ContainerBuilder $method = new \ReflectionMethod($kernel, 'buildContainer'); $container = $method->invoke($kernel); + if ($this->envVarProcessors) { + $container->set('container.env_var_processors_locator', $this->envVarProcessors); + } $container->getCompiler()->compile($container); return $container; diff --git a/Resources/config/console.php b/Resources/config/console.php index 334d20426..0ca643842 100644 --- a/Resources/config/console.php +++ b/Resources/config/console.php @@ -124,6 +124,9 @@ ->tag('console.command') ->set('console.command.config_debug', ConfigDebugCommand::class) + ->args([ + service('container.env_var_processors_locator'), + ]) ->tag('console.command') ->set('console.command.config_dump_reference', ConfigDumpReferenceCommand::class) diff --git a/Tests/Functional/Bundle/TestBundle/EnvVarLoader/StatefulEnvVarLoader.php b/Tests/Functional/Bundle/TestBundle/EnvVarLoader/StatefulEnvVarLoader.php new file mode 100644 index 000000000..a86a0f8e9 --- /dev/null +++ b/Tests/Functional/Bundle/TestBundle/EnvVarLoader/StatefulEnvVarLoader.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\EnvVarLoader; + +use Symfony\Component\DependencyInjection\EnvVarLoaderInterface; + +/** + * Simulates a vault-like env var loader whose secrets are populated via static + * state. This allows tests to control what the loader returns and to verify + * that the running container's already-initialized processor (with its cached + * env var state) is used by ConfigDebugCommand instead of a freshly-built one. + */ +class StatefulEnvVarLoader implements EnvVarLoaderInterface +{ + private static array $envVars = []; + + public static function setEnvVar(string $name, string $value): void + { + self::$envVars[$name] = $value; + } + + public static function reset(): void + { + self::$envVars = []; + } + + public function loadEnvVars(): array + { + return self::$envVars; + } +} diff --git a/Tests/Functional/Bundle/TestBundle/EnvVarLoader/VaultEnvVarPrimer.php b/Tests/Functional/Bundle/TestBundle/EnvVarLoader/VaultEnvVarPrimer.php new file mode 100644 index 000000000..3aec425df --- /dev/null +++ b/Tests/Functional/Bundle/TestBundle/EnvVarLoader/VaultEnvVarPrimer.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\EnvVarLoader; + +/** + * A simple service that holds a resolved env var value. + * Used in tests to force the application container's env var processor + * to resolve and cache a vault-provided env var before the static loader + * state is cleared. + */ +class VaultEnvVarPrimer +{ + public function __construct(public readonly string $value) + { + } +} diff --git a/Tests/Functional/ConfigDebugCommandTest.php b/Tests/Functional/ConfigDebugCommandTest.php index c0ac23c5e..f1f6911c5 100644 --- a/Tests/Functional/ConfigDebugCommandTest.php +++ b/Tests/Functional/ConfigDebugCommandTest.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand; use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\EnvVarLoader\StatefulEnvVarLoader; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; @@ -274,6 +275,34 @@ public function testDumpPathDeepIntoScalar() $this->assertStringContainsString('Unable to find configuration for "framework.secret.foo"', $tester->getDisplay()); } + public function testEnvVarsResolvedFromRunningContainerProcessor() + { + // This test reproduces the scenario where an env var is only resolvable via a + // loader (e.g. SodiumVault) that is already initialized in the running container + // but cannot be re-instantiated inside the freshly built container (e.g. because + // its secrets dir or decryption key depends on another vault secret – a circular + // dependency). StatefulEnvVarLoader simulates such a vault: after its state is + // cleared, new instances return nothing, but the already-running processor + // retains the value in its $loadedVars cache. + StatefulEnvVarLoader::setEnvVar('TEST_VAULT_SECRET', 'from_loader'); + + $application = $this->createApplication(true); + + // Accessing the primer service forces the running container's EnvVarProcessor + // to call StatefulEnvVarLoader::loadEnvVars() and cache the result. + static::$kernel->getContainer()->get('test.vault_env_var_primer'); + + // Clear the loader's static state: from this point on, new StatefulEnvVarLoader + // instances (as would be created by the freshly compiled container) return []. + StatefulEnvVarLoader::reset(); + + $tester = new CommandTester($application->find('debug:config')); + $ret = $tester->execute(['name' => 'foo', '--resolve-env' => true]); + + $this->assertSame(0, $ret); + $this->assertStringContainsString('vault_test_secret: from_loader', $tester->getDisplay()); + } + private function createCommandTester(bool $debug): CommandTester { $command = $this->createApplication($debug)->find('debug:config'); diff --git a/Tests/Functional/app/AppKernel.php b/Tests/Functional/app/AppKernel.php index 5748b61cd..724dcce6f 100644 --- a/Tests/Functional/app/AppKernel.php +++ b/Tests/Functional/app/AppKernel.php @@ -119,7 +119,10 @@ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('foo'); $rootNode = $treeBuilder->getRootNode(); - $rootNode->children()->scalarNode('foo')->defaultValue('bar')->end()->end(); + $rootNode->children() + ->scalarNode('foo')->defaultValue('bar')->end() + ->scalarNode('vault_test_secret')->defaultValue('')->end() + ->end(); return $treeBuilder; } diff --git a/Tests/Functional/app/ConfigDump/config.yml b/Tests/Functional/app/ConfigDump/config.yml index 679326817..bc7336ded 100644 --- a/Tests/Functional/app/ConfigDump/config.yml +++ b/Tests/Functional/app/ConfigDump/config.yml @@ -10,8 +10,22 @@ framework: storage_factory_id: session.storage.factory.native cookie_httponly: '%env(bool:COOKIE_HTTPONLY)%' +foo: + vault_test_secret: '%env(TEST_VAULT_SECRET)%' + parameters: env(LOCALE): en env(COOKIE_HTTPONLY): '1' + env(TEST_VAULT_SECRET): '' secret: test default_config_test_foo: bar + +services: + Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\EnvVarLoader\StatefulEnvVarLoader: + tags: + - container.env_var_loader + + test.vault_env_var_primer: + class: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\EnvVarLoader\VaultEnvVarPrimer + arguments: ['%env(TEST_VAULT_SECRET)%'] + public: true From 5b5d19473f22d699811a41b01cef2462bc42b238 Mon Sep 17 00:00:00 2001 From: Barthold Bos Date: Wed, 4 Feb 2026 21:58:49 +0100 Subject: [PATCH 19/19] [Messenger] Fix re-sending failed messages to a different failure transport --- DependencyInjection/FrameworkExtension.php | 3 ++- Resources/config/messenger.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DependencyInjection/FrameworkExtension.php b/DependencyInjection/FrameworkExtension.php index 4ac26dddf..20810098a 100644 --- a/DependencyInjection/FrameworkExtension.php +++ b/DependencyInjection/FrameworkExtension.php @@ -2399,7 +2399,8 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $failureTransportsByTransportNameServiceLocator = ServiceLocatorTagPass::register($container, $failureTransportReferencesByTransportName); $container->getDefinition('messenger.failure.send_failed_message_to_failure_transport_listener') - ->replaceArgument(0, $failureTransportsByTransportNameServiceLocator); + ->replaceArgument(0, $failureTransportsByTransportNameServiceLocator) + ->replaceArgument(2, $failureTransportsByName); } else { $container->removeDefinition('messenger.failure.send_failed_message_to_failure_transport_listener'); $container->removeDefinition('console.command.messenger_failed_messages_retry'); diff --git a/Resources/config/messenger.php b/Resources/config/messenger.php index 5e4726265..fe647f2eb 100644 --- a/Resources/config/messenger.php +++ b/Resources/config/messenger.php @@ -186,6 +186,7 @@ ->args([ abstract_arg('failure transports'), service('logger')->ignoreOnInvalid(), + abstract_arg('failure transports by name'), ]) ->tag('kernel.event_subscriber') ->tag('monolog.logger', ['channel' => 'messenger'])