From 340d3f997ee01324599997ed006335d07da9e671 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Fri, 14 Nov 2025 11:00:21 +0100 Subject: [PATCH 01/57] [PropertyInfo] Fix inconsistency between isWritable and getWriteInfo --- .../Extractor/ReflectionExtractor.php | 30 +++++++++++++++++++ .../Extractor/ReflectionExtractorTest.php | 3 ++ 2 files changed, 33 insertions(+) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index 379737de507c9..cb51a8e0ff25c 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -305,6 +305,7 @@ public function getWriteInfo(string $class, string $property, array $context = [ $allowAdderRemover = $context['enable_adder_remover_extraction'] ?? true; $camelized = $this->camelize($property); + $nonCamelized = ucfirst($property); $constructor = $reflClass->getConstructor(); $singulars = $this->inflector->singularize($camelized); $errors = []; @@ -347,7 +348,26 @@ public function getWriteInfo(string $class, string $property, array $context = [ } } + if ($camelized !== $nonCamelized) { + foreach ($this->mutatorPrefixes as $mutatorPrefix) { + $methodName = $mutatorPrefix.$nonCamelized; + + [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $methodName, 1); + if (!$accessible) { + $errors[] = $methodAccessibleErrors; + continue; + } + + $method = $reflClass->getMethod($methodName); + + if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) { + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic()); + } + } + } + $getsetter = lcfirst($camelized); + $getsetterNonCamelized = lcfirst($nonCamelized); if ($allowGetterSetter) { [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetter, 1); @@ -358,6 +378,16 @@ public function getWriteInfo(string $class, string $property, array $context = [ } $errors[] = $methodAccessibleErrors; + + if ($getsetter !== $getsetterNonCamelized) { + [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetterNonCamelized, 1); + if ($accessible) { + $method = $reflClass->getMethod($getsetterNonCamelized); + + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetterNonCamelized, $this->getWriteVisiblityForMethod($method), $method->isStatic()); + } + $errors[] = $methodAccessibleErrors; + } } if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) { diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index 2e73a6aedf51e..89446c93977c6 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -633,6 +633,9 @@ public static function writeMutatorProvider(): array [Php71DummyExtended2::class, 'string', false, false, '', '', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false], [Php71DummyExtended2::class, 'string', true, false, '', '', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false], [Php71DummyExtended2::class, 'baz', false, true, PropertyWriteInfo::TYPE_ADDER_AND_REMOVER, null, 'addBaz', 'removeBaz', PropertyWriteInfo::VISIBILITY_PUBLIC, false], + [SnakeCaseDummy::class, 'snake_property', false, true, PropertyWriteInfo::TYPE_METHOD, 'setSnakeProperty', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false], + [SnakeCaseDummy::class, 'snake_method', false, true, PropertyWriteInfo::TYPE_METHOD, 'setSnake_method', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false], + [SnakeCaseDummy::class, 'snake_readonly', false, false, PropertyWriteInfo::TYPE_NONE, null, null, null, null, null], ]; } From 4d91dfc4501c0b2e21caa0ea4eb8a8dacd618872 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 16 Nov 2025 11:40:25 +0100 Subject: [PATCH 02/57] Bump version to 8.1 --- src/Symfony/Component/HttpKernel/Kernel.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 765f2c731f1d2..24b039994a8c6 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -71,15 +71,15 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '8.0.0-DEV'; - public const VERSION_ID = 80000; + public const VERSION = '8.1.0-DEV'; + public const VERSION_ID = 80100; public const MAJOR_VERSION = 8; - public const MINOR_VERSION = 0; + public const MINOR_VERSION = 1; public const RELEASE_VERSION = 0; public const EXTRA_VERSION = 'DEV'; - public const END_OF_MAINTENANCE = '07/2026'; - public const END_OF_LIFE = '07/2026'; + public const END_OF_MAINTENANCE = '01/2027'; + public const END_OF_LIFE = '01/2027'; public function __construct( protected string $environment, From 69384666ff8e46bdf35bce37ccf026deaeba8891 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 6 Nov 2025 08:56:45 +0100 Subject: [PATCH 03/57] add missing Clear-Site-Data directives --- src/Symfony/Bundle/SecurityBundle/CHANGELOG.md | 5 +++++ .../SecurityBundle/DependencyInjection/MainConfiguration.php | 2 +- src/Symfony/Component/Security/Http/CHANGELOG.md | 5 +++++ .../Http/EventListener/ClearSiteDataLogoutListener.php | 2 +- .../Tests/EventListener/ClearSiteDataLogoutListenerTest.php | 1 + 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 73ba9206d744a..a92b1800eb20f 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +8.1 +--- + + * Add support for the `clientHints`, `prefetchCache`, and `prerenderCache` `ClearSite-Data` directives + 8.0 --- diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index 45476a176f69b..a8e5a6a317429 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -236,7 +236,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->beforeNormalization()->ifString()->then(static fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end() ->enumPrototype() ->values([ - '*', 'cache', 'cookies', 'storage', 'executionContexts', + '*', 'cache', 'cookies', 'storage', 'clientHints', 'executionContexts', 'prefetchCache', 'prerenderCache', ]) ->end() ->end() diff --git a/src/Symfony/Component/Security/Http/CHANGELOG.md b/src/Symfony/Component/Security/Http/CHANGELOG.md index 7ed486f313342..1b3b9835c9bdd 100644 --- a/src/Symfony/Component/Security/Http/CHANGELOG.md +++ b/src/Symfony/Component/Security/Http/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +8.1 +--- + + * Add support for the `clientHints`, `prefetchCache`, and `prerenderCache` `ClearSite-Data` directives + 8.0 --- diff --git a/src/Symfony/Component/Security/Http/EventListener/ClearSiteDataLogoutListener.php b/src/Symfony/Component/Security/Http/EventListener/ClearSiteDataLogoutListener.php index 77ca07a642835..0c72a214bc435 100644 --- a/src/Symfony/Component/Security/Http/EventListener/ClearSiteDataLogoutListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/ClearSiteDataLogoutListener.php @@ -27,7 +27,7 @@ class ClearSiteDataLogoutListener implements EventSubscriberInterface /** * @param string[] $cookieValue The value for the Clear-Site-Data header. - * Can be '*' or a subset of 'cache', 'cookies', 'storage', 'executionContexts'. + * Can be '*' or a subset of 'cache', 'cookies', 'storage', 'clientHints', 'executionContexts', 'prefetchCache', 'prerenderCache'. */ public function __construct(private readonly array $cookieValue) { diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/ClearSiteDataLogoutListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/ClearSiteDataLogoutListenerTest.php index 42ceff76444da..9cf21aa36ae26 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/ClearSiteDataLogoutListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/ClearSiteDataLogoutListenerTest.php @@ -43,5 +43,6 @@ public static function provideClearSiteDataConfig(): iterable { yield [['*'], '"*"']; yield [['cache', 'cookies', 'storage', 'executionContexts'], '"cache", "cookies", "storage", "executionContexts"']; + yield [['clientHints', 'executionContexts', 'prefetchCache', 'prerenderCache'], '"clientHints", "executionContexts", "prefetchCache", "prerenderCache"']; } } From b2e6fe2ca2cbde9250dd1b4d62d22f937a2be690 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Delhommeau Date: Thu, 13 Nov 2025 17:21:44 +0100 Subject: [PATCH 04/57] feat: add support to ipPoolId for infobip mailer transport --- .../Component/Mailer/Bridge/Infobip/CHANGELOG.md | 5 +++++ .../Component/Mailer/Bridge/Infobip/README.md | 3 ++- .../Tests/Transport/InfobipApiTransportTest.php | 13 +++++++++++-- .../Infobip/Transport/InfobipApiTransport.php | 1 + 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Mailer/Bridge/Infobip/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/Infobip/CHANGELOG.md index 403fbe93c4bd8..aeecb8624d328 100644 --- a/src/Symfony/Component/Mailer/Bridge/Infobip/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/Bridge/Infobip/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +8.1 +--- + + * Add support of `ipPoolId` option + 7.2 --- diff --git a/src/Symfony/Component/Mailer/Bridge/Infobip/README.md b/src/Symfony/Component/Mailer/Bridge/Infobip/README.md index b260c10a74fa2..edfb5e2bd3a8e 100644 --- a/src/Symfony/Component/Mailer/Bridge/Infobip/README.md +++ b/src/Symfony/Component/Mailer/Bridge/Infobip/README.md @@ -25,8 +25,9 @@ This transport supports the following custom headers: | `X-Infobip-MessageId` | string | The ID that uniquely identifies the message sent to a recipient. | | `X-Infobip-Track` | boolean | Enable or disable open and click tracking. | | `X-Infobip-TrackingUrl` | string | The URL on your callback server on which the open and click notifications will be sent. | -| `X-Infobip-TrackClicks` | boolean | Enable or disable track click feature.. | +| `X-Infobip-TrackClicks` | boolean | Enable or disable track click feature. | | `X-Infobip-TrackOpens` | boolean | Enable or disable open click feature. | +| `X-Infobip-IpPoolId` | string | The ID of the dedicated IP pool that will be used to deliver the message. | Resources --------- diff --git a/src/Symfony/Component/Mailer/Bridge/Infobip/Tests/Transport/InfobipApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Infobip/Tests/Transport/InfobipApiTransportTest.php index aa417558c6a53..1cd48855c92a1 100644 --- a/src/Symfony/Component/Mailer/Bridge/Infobip/Tests/Transport/InfobipApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Infobip/Tests/Transport/InfobipApiTransportTest.php @@ -252,7 +252,8 @@ public function testSendEmailWithHeadersShouldCalledInfobipWithTheRightParameter ->addTextHeader('X-Infobip-Track', 'false') ->addTextHeader('X-Infobip-TrackingUrl', 'https://bar.foo') ->addTextHeader('X-Infobip-TrackClicks', 'true') - ->addTextHeader('X-Infobip-TrackOpens', 'true'); + ->addTextHeader('X-Infobip-TrackOpens', 'true') + ->addTextHeader('X-Infobip-IpPoolId', 'pool-123'); $this->transport->send($email); @@ -308,6 +309,12 @@ public function testSendEmailWithHeadersShouldCalledInfobipWithTheRightParameter Content-Disposition: form-data; name="trackOpens" true + --%s + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: 8bit + Content-Disposition: form-data; name="ipPoolId" + + pool-123 --%s-- TXT, $options['body'] @@ -441,7 +448,8 @@ public function testSendEmailWithHeadersWithSuccess() ->addTextHeader('X-Infobip-Track', 'false') ->addTextHeader('X-Infobip-TrackingUrl', 'https://bar.foo') ->addTextHeader('X-Infobip-TrackClicks', 'true') - ->addTextHeader('X-Infobip-TrackOpens', 'true'); + ->addTextHeader('X-Infobip-TrackOpens', 'true') + ->addTextHeader('X-Infobip-IpPoolId', 'pool-123'); $sentMessage = $this->transport->send($email); @@ -457,6 +465,7 @@ public function testSendEmailWithHeadersWithSuccess() X-Infobip-TrackingUrl: https://bar.foo X-Infobip-TrackClicks: true X-Infobip-TrackOpens: true + X-Infobip-IpPoolId: pool-123 %a TXT, $sentMessage->toString() diff --git a/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php index b2bfcc6961098..98a64f4a65f97 100644 --- a/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php @@ -42,6 +42,7 @@ final class InfobipApiTransport extends AbstractApiTransport 'X-Infobip-TrackingUrl' => 'trackingUrl', 'X-Infobip-TrackClicks' => 'trackClicks', 'X-Infobip-TrackOpens' => 'trackOpens', + 'X-Infobip-IpPoolId' => 'ipPoolId', ]; public function __construct( From 87f5f9723f669b28be886b125b22f01cded96fe4 Mon Sep 17 00:00:00 2001 From: Mickael GOETZ Date: Wed, 7 May 2025 16:22:56 +0200 Subject: [PATCH 05/57] [Mailer][SendGrid] add support for scheduling delivery via `send_at` API parameter --- .../Component/Mailer/Bridge/Sendgrid/CHANGELOG.md | 5 +++++ .../Component/Mailer/Bridge/Sendgrid/README.md | 14 ++++++++++++++ .../Tests/Transport/SendgridApiTransportTest.php | 14 ++++++++++++++ .../Sendgrid/Transport/SendgridApiTransport.php | 12 +++++++++--- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/Sendgrid/CHANGELOG.md index 9bcfdb33600fd..7a025a0c6b198 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +8.1 +--- + + * Add support for scheduling delivery with the `send_at` API parameter via a `Send-At` date-header + 7.4 --- diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md b/src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md index 33ee90ef65b3b..bf3f1ece7f81d 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md @@ -60,6 +60,20 @@ where: - `GROUP_ID` is your Sendgrid suppression group ID - `GROUPS_TO_DISPLAY_ID` is an array of the Sendgrid suppression group IDs presented to the user +Scheduling +---------- + +When using the **API transport** (with a `sendgrid+api` DSN), you can schedule +your emails by providing a `\DateTimeInterface` object in a +`Symfony\Component\Mime\Header\DateHeader` named `Send-At`. + +```php +$email = new \Symfony\Component\Mime\Email(); +$email->getHeaders()->addDateHeader('Send-At', new \DateTimeImmutable('+3 hours')); +``` +It will be mapped to the `send_at` parameter of the `[POST] /mail/send` +[API endpoint](https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send#request-body) + Resources --------- diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php index 3f3814df7ba90..b5a624579f31d 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php @@ -313,4 +313,18 @@ public function testWithSuppressionGroup() $this->assertSame([1, 2, 3, 4, 5], $payload['asm']['groups_to_display']); } + + public function testSendAtHeader() + { + $email = new Email(); + $email->getHeaders()->addDateHeader('Send-At', new \DateTime('2025-05-07 16:00:00', new \DateTimeZone('Europe/Paris'))); + $envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]); + + $transport = new SendgridApiTransport('ACCESS_KEY'); + $method = new \ReflectionMethod(SendgridApiTransport::class, 'getPayload'); + $payload = $method->invoke($transport, $email, $envelope); + + $this->assertArrayHasKey('send_at', $payload); + $this->assertSame(1746626400, $payload['send_at']); + } } diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php index 5492d026870fc..39028b2d53c89 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php @@ -23,6 +23,7 @@ use Symfony\Component\Mailer\Transport\AbstractApiTransport; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Header\DateHeader; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -126,7 +127,12 @@ private function getPayload(Email $email, Envelope $envelope): array continue; } - if ($header instanceof TagHeader) { + if ('send-at' === $name) { + if (!$header instanceof DateHeader) { + throw new TransportException(\sprintf('The "Send-At" header must be a "%s" instance.', DateHeader::class)); + } + $payload['send_at'] = $header->getDateTime()->getTimestamp(); + } elseif ($header instanceof TagHeader) { if (10 === \count($categories)) { throw new TransportException(\sprintf('Too many "%s" instances present in the email headers. Sendgrid does not accept more than 10 categories on an email.', TagHeader::class)); } @@ -145,11 +151,11 @@ private function getPayload(Email $email, Envelope $envelope): array } } - if (\count($categories) > 0) { + if ($categories) { $payload['categories'] = $categories; } - if (\count($customArguments) > 0) { + if ($customArguments) { $personalization['custom_args'] = $customArguments; } From 896680d29732090e61d0ea47ae7c95bd0bbebd10 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Sat, 22 Nov 2025 23:59:50 +0100 Subject: [PATCH 06/57] [DependencyInjection] Deprecate invalid options when using "from_callable" --- UPGRADE-8.1.md | 7 ++++++ .../DependencyInjection/CHANGELOG.md | 5 ++++ .../Loader/YamlFileLoader.php | 4 ++++ .../Tests/Loader/YamlFileLoaderTest.php | 24 +++++++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 UPGRADE-8.1.md diff --git a/UPGRADE-8.1.md b/UPGRADE-8.1.md new file mode 100644 index 0000000000000..b978dd0690a1f --- /dev/null +++ b/UPGRADE-8.1.md @@ -0,0 +1,7 @@ +UPGRADE FROM 8.0 to 8.1 +======================= + +DependencyInjection +------------------- + + * Deprecate configuring options `alias`, `parent`, `synthetic`, `file`, `arguments`, `properties`, `configurator` or `calls` when using `from_callable` diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 1bb32f57a7781..8df4831011978 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +8.1 +--- + + * Deprecate configuring options `alias`, `parent`, `synthetic`, `file`, `arguments`, `properties`, `configurator` or `calls` when using `from_callable` + 8.0 --- diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index a0572b3a86091..785740df6db87 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -412,6 +412,10 @@ private function parseDefinition(string $id, array|string|null $service, string if (isset($service['factory'])) { throw new InvalidArgumentException(\sprintf('The configuration key "%s" is unsupported for the service "%s" when using "from_callable" in "%s".', $key, $id, $file)); } + + if (isset($service[$key])) { + trigger_deprecation('symfony/dependency-injection', '8.1', 'Configuring the "%s" key for the service "%s" when using "from_callable" is deprecated and will throw an "InvalidArgumentException" in 9.0.', $key, $id); + } } if ('Closure' !== $service['class'] ??= 'Closure') { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 35527e9bc34dd..f287cd3d04449 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Loader; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; use Symfony\Component\Config\Exception\LoaderLoadException; @@ -1217,4 +1218,27 @@ public function testStaticConstructor() $definition = $container->getDefinition('static_constructor'); $this->assertEquals((new Definition('stdClass'))->setFactory([null, 'create']), $definition); } + + #[IgnoreDeprecations] + #[DataProvider('provideForbiddenKeys')] + public function testFromCallableTriggersDeprecationOnForbiddenKeys(string $key, mixed $value) + { + $this->expectUserDeprecationMessage(\sprintf('Since symfony/dependency-injection 8.1: Configuring the "%s" key for the service "my_service" when using "from_callable" is deprecated and will throw an "InvalidArgumentException" in 9.0.', $key)); + + $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator()); + + $reflectionMethod = new \ReflectionMethod($loader, 'parseDefinition'); + $reflectionMethod->invoke($loader, 'my_service', ['from_callable' => 'strlen', $key => $value], 'config/services.yaml', []); + } + + public static function provideForbiddenKeys(): iterable + { + yield 'parent' => ['parent', 'App\\SomeParent']; + yield 'synthetic' => ['synthetic', true]; + yield 'file' => ['file', 'some_file.php']; + yield 'arguments' => ['arguments', []]; + yield 'properties' => ['properties', ['foo' => 'bar']]; + yield 'configurator' => ['configurator', 'some_configurator']; + yield 'calls' => ['calls', [['method' => 'setFoo', 'arguments' => ['bar']]]]; + } } From 0af0693f4e8a5729b37466edae504b3153871977 Mon Sep 17 00:00:00 2001 From: Mdsujansarkar Date: Fri, 28 Nov 2025 01:38:54 +0600 Subject: [PATCH 07/57] Fix typos in documentation --- src/Symfony/Contracts/Translation/Test/TranslatorTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Contracts/Translation/Test/TranslatorTest.php b/src/Symfony/Contracts/Translation/Test/TranslatorTest.php index 5342f5b82c341..c86efe17d1bb5 100644 --- a/src/Symfony/Contracts/Translation/Test/TranslatorTest.php +++ b/src/Symfony/Contracts/Translation/Test/TranslatorTest.php @@ -27,7 +27,7 @@ * * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199 * - * The goal to cover all languages is to far fetched so this test case is smaller. + * The goal to cover all languages is too far fetched so this test case is smaller. * * @author Clemens Tolboom clemens@build2be.nl */ @@ -349,7 +349,7 @@ public static function successLangcodes(): array * This both depends on a complete list trying to add above as understanding * the plural rules of the current failing languages. * - * @return array with nplural together with langcodes + * @return array With nplural together with langcodes */ public static function failingLangcodes(): array { From 71b4c7b9ce13bc7e7372af9288895c61f221e4ff Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 29 Nov 2025 10:41:47 +0100 Subject: [PATCH 08/57] add legacy group to test testing a deprecated feature We need this group in the future to properly skip the test in the high deps job once we start the development of Symfony 8.4. --- .../DependencyInjection/Tests/Loader/YamlFileLoaderTest.php | 2 ++ .../Component/Routing/Tests/Generator/UrlGeneratorTest.php | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index f287cd3d04449..9e9f3b911e4c9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Loader; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; @@ -1220,6 +1221,7 @@ public function testStaticConstructor() } #[IgnoreDeprecations] + #[Group('legacy')] #[DataProvider('provideForbiddenKeys')] public function testFromCallableTriggersDeprecationOnForbiddenKeys(string $key, mixed $value) { diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index d7dd9bb6b885d..588632a332476 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Routing\Tests\Generator; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; From d97d09cb1b7583c5a370046d0e311fd48f8ec452 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Fri, 6 Jun 2025 10:53:37 +0200 Subject: [PATCH 09/57] [FrameworkBundle] Deprecate setting `collect_serializer_data` --- UPGRADE-8.1.md | 5 +++++ src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 5 +++++ .../FrameworkBundle/DependencyInjection/Configuration.php | 6 +++++- .../Tests/DependencyInjection/Fixtures/php/profiler.php | 1 - .../Tests/DependencyInjection/Fixtures/yml/profiler.yml | 1 - .../Tests/Functional/app/config/framework.yml | 2 -- .../Tests/Functional/app/FirewallEntryPoint/config.yml | 1 - .../Tests/Functional/app/config/framework.yml | 1 - .../Tests/Functional/WebProfilerBundleKernel.php | 2 +- 9 files changed, 16 insertions(+), 8 deletions(-) diff --git a/UPGRADE-8.1.md b/UPGRADE-8.1.md index b978dd0690a1f..4aefd06c644b8 100644 --- a/UPGRADE-8.1.md +++ b/UPGRADE-8.1.md @@ -5,3 +5,8 @@ DependencyInjection ------------------- * Deprecate configuring options `alias`, `parent`, `synthetic`, `file`, `arguments`, `properties`, `configurator` or `calls` when using `from_callable` + +FrameworkBundle +--------------- + + * Deprecate setting the `framework.profiler.collect_serializer_data` config option diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index be82a64bda2a2..914ff0d1c051b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +8.1 +--- + + * Deprecate setting the `framework.profiler.collect_serializer_data` config option + 8.0 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 02f5f5bf7dce1..a01528059f1b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -361,7 +361,11 @@ private function addProfilerSection(ArrayNodeDefinition $rootNode): void ->booleanNode('only_exceptions')->defaultFalse()->end() ->booleanNode('only_main_requests')->defaultFalse()->end() ->scalarNode('dsn')->defaultValue('file:%kernel.cache_dir%/profiler')->end() - ->enumNode('collect_serializer_data')->values([true])->defaultTrue()->end() // to be @deprecated in Symfony 8.1 + ->enumNode('collect_serializer_data') + ->values([true]) + ->defaultTrue() + ->setDeprecated('symfony/framework-bundle', '8.1', 'Setting the "%path%.%node%" configuration option is deprecated. It will be removed in version 9.0.') + ->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php index 56cc6a1723fa4..60d0cdf8b54e2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php @@ -3,7 +3,6 @@ $container->loadFromExtension('framework', [ 'profiler' => [ 'enabled' => true, - 'collect_serializer_data' => true, ], 'serializer' => [ 'enabled' => true, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml index 8cc59ab8f46fe..1cfe686b1eb19 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml @@ -1,6 +1,5 @@ framework: profiler: enabled: true - collect_serializer_data: true serializer: enabled: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml index 7c0a1b2bcf3cc..17e9cba676fe6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml @@ -13,8 +13,6 @@ framework: storage_factory_id: session.storage.factory.mock_file cookie_secure: auto cookie_samesite: lax - profiler: - collect_serializer_data: true services: logger: { class: Psr\Log\NullLogger } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml index 0601c253a5c1c..14d47a95bb9be 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml @@ -14,7 +14,6 @@ framework: cookie_samesite: lax profiler: only_exceptions: false - collect_serializer_data: true services: logger: { class: Psr\Log\NullLogger } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml index 1b16f1f027444..38ea5de3c2659 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml @@ -15,7 +15,6 @@ framework: cookie_samesite: lax profiler: only_exceptions: false - collect_serializer_data: true services: logger: { class: Psr\Log\NullLogger } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php index 556befe105409..4915fdb176248 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php @@ -52,7 +52,7 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa { $config = [ 'secret' => 'foo-secret', - 'profiler' => ['only_exceptions' => false, 'collect_serializer_data' => true], + 'profiler' => ['only_exceptions' => false], 'session' => ['handler_id' => null, 'storage_factory_id' => 'session.storage.factory.mock_file', 'cookie-secure' => 'auto', 'cookie-samesite' => 'lax'], 'router' => ['utf8' => true], ]; From 624cdfe29bbcfebd2f40ea5244d825a423363346 Mon Sep 17 00:00:00 2001 From: Mohammad Eftekhari Date: Sat, 29 Nov 2025 13:25:33 +0100 Subject: [PATCH 10/57] [Messenger] Use clock in `DelayStamp` and `RedeliveryStamp` instead of native time classes and methods --- src/Symfony/Component/Messenger/Stamp/DelayStamp.php | 6 ++++-- src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Messenger/Stamp/DelayStamp.php b/src/Symfony/Component/Messenger/Stamp/DelayStamp.php index f75a2f85e40c7..1164007d15f3a 100644 --- a/src/Symfony/Component/Messenger/Stamp/DelayStamp.php +++ b/src/Symfony/Component/Messenger/Stamp/DelayStamp.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Messenger\Stamp; +use Symfony\Component\Clock\Clock; + /** * Apply this stamp to delay delivery of your message on a transport. */ @@ -31,7 +33,7 @@ public function getDelay(): int public static function delayFor(\DateInterval $interval): self { - $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); + $now = Clock::get()->withTimeZone(new \DateTimeZone('UTC'))->now(); $end = $now->add($interval); return new self(($end->getTimestamp() - $now->getTimestamp()) * 1000); @@ -39,6 +41,6 @@ public static function delayFor(\DateInterval $interval): self public static function delayUntil(\DateTimeInterface $dateTime): self { - return new self(($dateTime->getTimestamp() - time()) * 1000); + return new self(($dateTime->getTimestamp() - Clock::get()->now()->getTimestamp()) * 1000); } } diff --git a/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php b/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php index 08077e73cde5b..bd5963c3b1a5c 100644 --- a/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php +++ b/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Messenger\Stamp; +use Symfony\Component\Clock\Clock; use Symfony\Component\Messenger\Envelope; /** @@ -24,7 +25,7 @@ public function __construct( private int $retryCount, ?\DateTimeInterface $redeliveredAt = null, ) { - $this->redeliveredAt = $redeliveredAt ?? new \DateTimeImmutable(); + $this->redeliveredAt = $redeliveredAt ?? Clock::get()->now(); } public static function getRetryCountFromEnvelope(Envelope $envelope): int From 4613ccfda9920000e14c027c4f651a374c7cdcd9 Mon Sep 17 00:00:00 2001 From: Nayte Date: Sun, 30 Nov 2025 01:32:33 +0100 Subject: [PATCH 11/57] [Form] Add reset button in NavigatorFlowType ResetFlowType is now conditionally rendered when displaying NavigatorFlowType. - Added an option 'with_reset' - option is false by default to respect BC - added Test class, - modified previous tests that rendered NavigatorFlowType and ResetFlowType --- src/Symfony/Component/Form/CHANGELOG.md | 5 ++ .../Form/Flow/Type/NavigatorFlowType.php | 9 ++++ .../Fixtures/Flow/LastStepSkippedType.php | 4 +- .../Fixtures/Flow/UserSignUpNavigatorType.php | 9 +++- .../Form/Tests/Flow/FormFlowTest.php | 3 +- .../Tests/Flow/Type/NavigatorFlowTypeTest.php | 49 +++++++++++++++++++ 6 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Form/Tests/Flow/Type/NavigatorFlowTypeTest.php diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 7ac95ccc8db1e..238f7ca83e9ea 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +8.1 +--- + + * Add `ResetFlowType` button in `NavigatorFlowType` that you can display with `with_reset` option + 8.0 --- diff --git a/src/Symfony/Component/Form/Flow/Type/NavigatorFlowType.php b/src/Symfony/Component/Form/Flow/Type/NavigatorFlowType.php index 7dab23c35a4e0..3510c44da2ec6 100644 --- a/src/Symfony/Component/Form/Flow/Type/NavigatorFlowType.php +++ b/src/Symfony/Component/Form/Flow/Type/NavigatorFlowType.php @@ -27,6 +27,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder->add('previous', PreviousFlowType::class); $builder->add('next', NextFlowType::class); $builder->add('finish', FinishFlowType::class); + + if ($options['with_reset']) { + $builder->add('reset', ResetFlowType::class); + } } public function configureOptions(OptionsResolver $resolver): void @@ -36,5 +40,10 @@ public function configureOptions(OptionsResolver $resolver): void 'mapped' => false, 'priority' => -100, ]); + + $resolver->define('with_reset') + ->allowedTypes('bool') + ->default(false) + ->info('Whether to add a reset button to restart the flow from the first step'); } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Flow/LastStepSkippedType.php b/src/Symfony/Component/Form/Tests/Fixtures/Flow/LastStepSkippedType.php index a4194e755bc4f..eb7e786cfff77 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Flow/LastStepSkippedType.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/Flow/LastStepSkippedType.php @@ -24,7 +24,9 @@ public function buildFormFlow(FormFlowBuilderInterface $builder, array $options) $builder->addStep('step1', TextType::class); $builder->addStep('step2', skip: static fn () => true); - $builder->add('navigator', NavigatorFlowType::class); + $builder->add('navigator', NavigatorFlowType::class, [ + 'with_reset' => true, + ]); } public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Flow/UserSignUpNavigatorType.php b/src/Symfony/Component/Form/Tests/Fixtures/Flow/UserSignUpNavigatorType.php index ed1bc31d96590..2341423947992 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Flow/UserSignUpNavigatorType.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/Flow/UserSignUpNavigatorType.php @@ -14,8 +14,8 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Flow\Type\NavigatorFlowType; use Symfony\Component\Form\Flow\Type\NextFlowType; -use Symfony\Component\Form\Flow\Type\ResetFlowType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; class UserSignUpNavigatorType extends AbstractType { @@ -25,8 +25,13 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'clear_submission' => true, 'include_if' => ['professional'], ]); + } - $builder->add('reset', ResetFlowType::class); + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'with_reset' => true, + ]); } public function getParent(): string diff --git a/src/Symfony/Component/Form/Tests/Flow/FormFlowTest.php b/src/Symfony/Component/Form/Tests/Flow/FormFlowTest.php index 9d21b8d034d02..97d3c253e0af5 100644 --- a/src/Symfony/Component/Form/Tests/Flow/FormFlowTest.php +++ b/src/Symfony/Component/Form/Tests/Flow/FormFlowTest.php @@ -945,8 +945,9 @@ public function testLastStepSkippedMarkFlowAsFinished() self::assertTrue($flow->has('navigator')); $navigatorForm = $flow->get('navigator'); - self::assertCount(1, $navigatorForm->all()); + self::assertCount(2, $navigatorForm->all()); self::assertTrue($navigatorForm->has('next')); + self::assertTrue($navigatorForm->has('reset')); $flow->submit([ 'step1' => 'foo', diff --git a/src/Symfony/Component/Form/Tests/Flow/Type/NavigatorFlowTypeTest.php b/src/Symfony/Component/Form/Tests/Flow/Type/NavigatorFlowTypeTest.php new file mode 100644 index 0000000000000..6d919b8ebeb75 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Flow/Type/NavigatorFlowTypeTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Flow\Type; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\Flow\Type\NavigatorFlowType; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\Form\Forms; + +class NavigatorFlowTypeTest extends TestCase +{ + private FormFactoryInterface $factory; + + protected function setUp(): void + { + $this->factory = Forms::createFormFactoryBuilder()->getFormFactory(); + } + + public function testDefaultOptionsDoNotIncludeReset() + { + $form = $this->factory->create(NavigatorFlowType::class); + + self::assertTrue($form->has('previous')); + self::assertTrue($form->has('next')); + self::assertTrue($form->has('finish')); + self::assertFalse($form->has('reset')); + } + + public function testWithResetOptionAddsResetButton() + { + $form = $this->factory->create(NavigatorFlowType::class, null, [ + 'with_reset' => true, + ]); + + self::assertTrue($form->has('previous')); + self::assertTrue($form->has('next')); + self::assertTrue($form->has('finish')); + self::assertTrue($form->has('reset')); + } +} From 0ea712f520d9b6ed36cdf501ef4c8a0dd3fc23c4 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 8 Dec 2025 08:43:31 +0100 Subject: [PATCH 12/57] Update CHANGELOG for 7.4.2 --- CHANGELOG-7.4.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG-7.4.md b/CHANGELOG-7.4.md index 7b15382af31a7..80abaa8eca648 100644 --- a/CHANGELOG-7.4.md +++ b/CHANGELOG-7.4.md @@ -7,6 +7,11 @@ in 7.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v7.4.0...v7.4.1 +* 7.4.2 (2025-12-08) + + * bug #62682 [Serializer][Validator] Attribute metadata no longer requires `container.excluded` tags (HypeMC) + * bug #62685 [DependencyInjection] Fix `PriorityTaggedServiceTrait` when tag attributes are not a list (GromNaN) + * 7.4.1 (2025-12-07) * bug #62663 [HttpFoundation] Improve logic in Request::createFromGlobals() (nicolas-grekas) From 937e721f12ce10e2361ea10a190d5609fecef1c9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 8 Dec 2025 08:43:37 +0100 Subject: [PATCH 13/57] Update VERSION for 7.4.2 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 0e34cc822bc99..bd08fb7567775 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -74,12 +74,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '7.4.2-DEV'; + public const VERSION = '7.4.2'; public const VERSION_ID = 70402; public const MAJOR_VERSION = 7; public const MINOR_VERSION = 4; public const RELEASE_VERSION = 2; - public const EXTRA_VERSION = 'DEV'; + public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '11/2028'; public const END_OF_LIFE = '11/2029'; From 120a6abe9b593752a91ee06c3e0174cd519a41e0 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 8 Dec 2025 08:58:50 +0100 Subject: [PATCH 14/57] Bump Symfony version to 7.4.3 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index bd08fb7567775..84b8b7794e6f8 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -74,12 +74,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '7.4.2'; - public const VERSION_ID = 70402; + public const VERSION = '7.4.3-DEV'; + public const VERSION_ID = 70403; public const MAJOR_VERSION = 7; public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 2; - public const EXTRA_VERSION = ''; + public const RELEASE_VERSION = 3; + public const EXTRA_VERSION = 'DEV'; public const END_OF_MAINTENANCE = '11/2028'; public const END_OF_LIFE = '11/2029'; From 6597db1d0bb3d936f33f7862af1ac99cd7427152 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 8 Dec 2025 09:11:06 +0100 Subject: [PATCH 15/57] Bump Symfony version to 8.0.3 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index c60056404ecdd..48bf4e520d5d2 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -72,12 +72,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '8.0.2'; - public const VERSION_ID = 80002; + public const VERSION = '8.0.3-DEV'; + public const VERSION_ID = 80003; public const MAJOR_VERSION = 8; public const MINOR_VERSION = 0; - public const RELEASE_VERSION = 2; - public const EXTRA_VERSION = ''; + public const RELEASE_VERSION = 3; + public const EXTRA_VERSION = 'DEV'; public const END_OF_MAINTENANCE = '07/2026'; public const END_OF_LIFE = '07/2026'; From 710bbd43da4e01387bf3eb9a7d5e1c134fa8bcd4 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 8 Dec 2025 09:13:50 +0100 Subject: [PATCH 16/57] [JsonPath] Update and fix the compliance test suite --- .../Component/JsonPath/JsonCrawler.php | 30 +- .../JsonPath/Tests/Fixtures/Makefile | 2 +- .../JsonPath/Tests/Fixtures/cts.json | 335 ++++++++++++++++++ 3 files changed, 364 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/JsonPath/JsonCrawler.php b/src/Symfony/Component/JsonPath/JsonCrawler.php index 8c47cfdae5e4e..e2584ab5e33bd 100644 --- a/src/Symfony/Component/JsonPath/JsonCrawler.php +++ b/src/Symfony/Component/JsonPath/JsonCrawler.php @@ -42,6 +42,8 @@ final class JsonCrawler implements JsonCrawlerInterface 'value' => true, ]; + private const SINGULAR_ARGUMENT_FUNCTIONS = ['length', 'match', 'search']; + /** * Comparison operators and their corresponding lengths. */ @@ -780,6 +782,10 @@ private function evaluateFunction(string $name, string $args, mixed $context): m $value = $argList[0] ?? null; $nodelistSize = $nodelistSizes[0] ?? 0; + if ($nodelistSize > 1 && \in_array($name, self::SINGULAR_ARGUMENT_FUNCTIONS, true)) { + throw new JsonCrawlerException($args, \sprintf('non-singular query is not allowed as argument to "%s" function', $name)); + } + return match ($name) { 'length' => match (true) { \is_string($value) => mb_strlen($value), @@ -833,8 +839,8 @@ private function compare(mixed $left, mixed $right, string $operator): bool private function compareEquality(mixed $left, mixed $right): bool { - $leftIsNothing = $left === Nothing::Nothing; - $rightIsNothing = $right === Nothing::Nothing; + $leftIsNothing = Nothing::Nothing === $left; + $rightIsNothing = Nothing::Nothing === $right; if ( $leftIsNothing && $rightIsNothing @@ -1033,11 +1039,31 @@ private function validateFilterExpression(string $expr): void throw new JsonCrawlerException($left, 'non-singular query is not comparable'); } + $this->validateFunctionArguments($left); + $this->validateFunctionArguments($right); + return; } } } + private function validateFunctionArguments(string $expr): void + { + // is there a function call? + if (!preg_match('/^(\w+)\((.*)\)$/', trim($expr), $matches)) { + return; + } + + if (!\in_array($functionName = $matches[1], self::SINGULAR_ARGUMENT_FUNCTIONS, true)) { + return; + } + + $arg = trim($matches[2]); + if (str_starts_with($arg, '@') && $this->isNonSingularRelativeQuery($arg)) { + throw new JsonCrawlerException($arg, \sprintf('non-singular query is not allowed as argument to "%s" function', $functionName)); + } + } + /** * Transforms JSONPath regex patterns to comply with RFC 9485. * diff --git a/src/Symfony/Component/JsonPath/Tests/Fixtures/Makefile b/src/Symfony/Component/JsonPath/Tests/Fixtures/Makefile index d9b4c353f4a76..420896e9d1513 100644 --- a/src/Symfony/Component/JsonPath/Tests/Fixtures/Makefile +++ b/src/Symfony/Component/JsonPath/Tests/Fixtures/Makefile @@ -1,4 +1,4 @@ -override hash := 05f6cac786bf0cce95437e6f1adedc3186d54a71 +override hash := b9d7153e58711ad38bb8e35ece69c13f4b2f7d63 .PHONY: cts.json cts.json: diff --git a/src/Symfony/Component/JsonPath/Tests/Fixtures/cts.json b/src/Symfony/Component/JsonPath/Tests/Fixtures/cts.json index eedcdbaad55d6..1785527ed760a 100644 --- a/src/Symfony/Component/JsonPath/Tests/Fixtures/cts.json +++ b/src/Symfony/Component/JsonPath/Tests/Fixtures/cts.json @@ -565,6 +565,57 @@ "$['o'][1]['a']" ] }, + { + "name": "basic, name shorthand, true", + "selector": "$.true", + "document": { + "true": "A", + "_foo": "B" + }, + "result": [ + "A" + ], + "result_paths": [ + "$['true']" + ], + "tags": [ + "boundary" + ] + }, + { + "name": "basic, name shorthand, false", + "selector": "$.false", + "document": { + "false": "A", + "_foo": "B" + }, + "result": [ + "A" + ], + "result_paths": [ + "$['false']" + ], + "tags": [ + "boundary" + ] + }, + { + "name": "basic, name shorthand, null", + "selector": "$.null", + "document": { + "null": "A", + "_foo": "B" + }, + "result": [ + "A" + ], + "result_paths": [ + "$['null']" + ], + "tags": [ + "boundary" + ] + }, { "name": "basic, descendant segment, wildcard shorthand, array data", "selector": "$..*", @@ -3980,6 +4031,272 @@ ] ] }, + { + "name": "filter, two consecutive ands", + "selector": "$[?@.a && @.b && @.c]", + "document": [ + { + "a": 1, + "b": 2 + }, + { + "a": 1, + "c": 3 + }, + { + "b": 2, + "c": 3 + }, + { + "a": 1, + "b": 2, + "c": 3 + } + ], + "result": [ + { + "a": 1, + "b": 2, + "c": 3 + } + ], + "result_paths": [ + "$[3]" + ] + }, + { + "name": "filter, two consecutive ors", + "selector": "$[?@.a || @.b || @.c]", + "document": [ + { + "a": 1, + "b": 2 + }, + { + "a": 1, + "c": 3 + }, + { + "b": 2, + "c": 3 + }, + { + "a": 1, + "b": 2, + "c": 3 + } + ], + "result": [ + { + "a": 1, + "b": 2 + }, + { + "a": 1, + "c": 3 + }, + { + "b": 2, + "c": 3 + }, + { + "a": 1, + "b": 2, + "c": 3 + } + ], + "result_paths": [ + "$[0]", + "$[1]", + "$[2]", + "$[3]" + ] + }, + { + "name": "filter, multiple consecutive ands", + "selector": "$[?@.a && @.b && @.c && @.d && @.e]", + "document": [ + { + "a": 1, + "b": 2, + "c": 3, + "d": 4 + }, + { + "b": 2, + "c": 3, + "d": 4, + "e": 5 + }, + { + "a": 1, + "c": 3, + "e": 5 + }, + { + "a": 1, + "b": 2, + "c": 3, + "d": 4, + "e": 5 + } + ], + "result": [ + { + "a": 1, + "b": 2, + "c": 3, + "d": 4, + "e": 5 + } + ], + "result_paths": [ + "$[3]" + ] + }, + { + "name": "filter, multiple consecutive ors", + "selector": "$[?@.a || @.b || @.c || @.d || @.e]", + "document": [ + { + "a": 1, + "b": 2, + "c": 3, + "d": 4 + }, + { + "b": 2, + "c": 3, + "d": 4, + "e": 5 + }, + { + "a": 1, + "c": 3, + "e": 5 + }, + { + "a": 1, + "b": 2, + "c": 3, + "d": 4, + "e": 5 + } + ], + "result": [ + { + "a": 1, + "b": 2, + "c": 3, + "d": 4 + }, + { + "b": 2, + "c": 3, + "d": 4, + "e": 5 + }, + { + "a": 1, + "c": 3, + "e": 5 + }, + { + "a": 1, + "b": 2, + "c": 3, + "d": 4, + "e": 5 + } + ], + "result_paths": [ + "$[0]", + "$[1]", + "$[2]", + "$[3]" + ] + }, + { + "name": "filter, multiple consecutive ors and ands", + "selector": "$[?@.a && @.b && @.c || @.d || @.e]", + "document": [ + { + "a": 1 + }, + { + "e": 5 + }, + { + "a": 1, + "b": 2 + }, + { + "d": 4, + "e": 5 + }, + { + "a": 1, + "b": 2, + "c": 3 + }, + { + "c": 3, + "d": 4, + "e": 5 + }, + { + "a": 1, + "c": 3, + "e": 5 + }, + { + "a": 1, + "b": 2, + "c": 3, + "d": 4, + "e": 5 + } + ], + "result": [ + { + "e": 5 + }, + { + "d": 4, + "e": 5 + }, + { + "a": 1, + "b": 2, + "c": 3 + }, + { + "c": 3, + "d": 4, + "e": 5 + }, + { + "a": 1, + "c": 3, + "e": 5 + }, + { + "a": 1, + "b": 2, + "c": 3, + "d": 4, + "e": 5 + } + ], + "result_paths": [ + "$[1]", + "$[3]", + "$[4]", + "$[5]", + "$[6]", + "$[7]" + ] + }, { "name": "filter, and binds more tightly than or", "selector": "$[?@.a || @.b && @.c]", @@ -7733,6 +8050,24 @@ "length" ] }, + { + "name": "functions, length, non-singular query arg, multiple index selectors", + "selector": "$[?length(@[1, 2])<3]", + "invalid_selector": true, + "tags": [ + "function", + "length" + ] + }, + { + "name": "functions, length, non-singular query arg, multiple name selectors", + "selector": "$[?length(@['a', 'b'])<3]", + "invalid_selector": true, + "tags": [ + "function", + "length" + ] + }, { "name": "functions, match, found match", "selector": "$[?match(@.a, 'a.*')]", From 5ab532287e9f06d5df9b168f5c855e8e64487934 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Mon, 8 Dec 2025 11:58:19 +0100 Subject: [PATCH 17/57] only load console profiling if console is actually available this fixes a regression introduced in ea5a3ccdf66474aaa06bb95f38fc56b8e9589ec6 --- .../DependencyInjection/FrameworkExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 718937376815e..393a48cd8b107 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -646,7 +646,7 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerNotifierConfiguration($config['notifier'], $container, $loader, $this->readConfigEnabled('webhook', $container, $config['webhook'])); } - // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier, serializer being registered + // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier, serializer being registered. console is optional $this->registerProfilerConfiguration($config['profiler'], $container, $loader); if ($this->readConfigEnabled('webhook', $container, $config['webhook'])) { @@ -1078,7 +1078,7 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $ $container->getDefinition('profiler_listener') ->addArgument($config['collect_parameter']); - if (!$container->getParameter('kernel.debug') || !class_exists(CliRequest::class) || !$container->has('debug.stopwatch')) { + if (!$container->getParameter('kernel.debug') || !$this->hasConsole() || !class_exists(CliRequest::class) || !$container->has('debug.stopwatch')) { $container->removeDefinition('console_profiler_listener'); } From 68684931f1504c2e7b8caabca2e81e664d3c8db0 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 8 Dec 2025 15:42:21 +0100 Subject: [PATCH 18/57] do not render button labels if they are explicitly disabled --- .../Bridge/Twig/Resources/views/Form/form_div_layout.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index f1856f066e25f..4e2def28cb1ab 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -285,7 +285,7 @@ '%name%': name, '%id%': id, }) %} - {%- else -%} + {%- elseif 'button' not in block_prefixes or label is not same as(false) -%} {% set label = name|humanize %} {%- endif -%} {%- endif -%} From 81b9d93159acb51277ea6e1949ce0be0d9cc358b Mon Sep 17 00:00:00 2001 From: psihius Date: Sun, 5 Oct 2025 20:12:51 +0300 Subject: [PATCH 19/57] [Messenger][Doctrine] Remove batched message delete for MySQL and add a covering index for a select query --- .../Tests/Transport/ConnectionTest.php | 16 ++++++++++++ .../Bridge/Doctrine/Transport/Connection.php | 26 +------------------ 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php index d5275c9b70626..e85606dbe0735 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php @@ -610,6 +610,22 @@ public function testConfigureSchema() $connection = new Connection(['table_name' => 'queue_table'], $driverConnection); $connection->configureSchema($schema, $driverConnection, fn () => true); $this->assertTrue($schema->hasTable('queue_table')); + + // Ensure the covering index for the SELECT query exists + $table = $schema->getTable('queue_table'); + $hasCoveringIndex = false; + foreach ($table->getIndexes() as $index) { + // Doctrine DBAL 4+: use getIndexedColumns(); fallback to getColumns() for older versions + $columns = method_exists($index, 'getIndexedColumns') + ? array_map(static fn ($ic) => $ic->getColumnName()->toString(), $index->getIndexedColumns()) + : $index->getColumns(); + + if ($columns === ['queue_name', 'available_at', 'delivered_at', 'id']) { + $hasCoveringIndex = true; + break; + } + } + $this->assertTrue($hasCoveringIndex, 'Expected covering index on [queue_name, available_at, delivered_at, id] not found'); } public function testConfigureSchemaDifferentDbalConnection() diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index b99aa0929a3cf..5e275a883f33e 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -13,12 +13,10 @@ use Doctrine\DBAL\Abstraction\Result as AbstractionResult; use Doctrine\DBAL\Connection as DBALConnection; -use Doctrine\DBAL\Driver\Exception as DriverException; use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\LockMode; -use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode; @@ -163,18 +161,6 @@ public function send(string $body, array $headers, int $delay = 0): string public function get(): ?array { - if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { - try { - $this->driverConnection->delete($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59']); - } catch (TableNotFoundException $e) { - if ($this->autoSetup) { - $this->setup(); - } - } catch (DriverException $e) { - // Ignore the exception - } - } - get: $this->driverConnection->beginTransaction(); try { @@ -276,10 +262,6 @@ public function get(): ?array public function ack(string $id): bool { try { - if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { - return $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59'], ['id' => $id]) > 0; - } - return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0; } catch (DBALException $exception) { throw new TransportException($exception->getMessage(), 0, $exception); @@ -289,10 +271,6 @@ public function ack(string $id): bool public function reject(string $id): bool { try { - if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { - return $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59'], ['id' => $id]) > 0; - } - return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0; } catch (DBALException $exception) { throw new TransportException($exception->getMessage(), 0, $exception); @@ -542,9 +520,7 @@ private function addTableToSchema(Schema $schema): void $table->addColumn('delivered_at', Types::DATETIME_IMMUTABLE) ->setNotnull(false); $table->setPrimaryKey(['id']); - $table->addIndex(['queue_name']); - $table->addIndex(['available_at']); - $table->addIndex(['delivered_at']); + $table->addIndex(['queue_name', 'available_at', 'delivered_at', 'id']); // We need to create a sequence for Oracle and set the id column to get the correct nextval if ($this->driverConnection->getDatabasePlatform() instanceof OraclePlatform) { From 9dede67cba26fce88ec300132a6bd42c845b1a2c Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 9 Dec 2025 00:16:08 +0100 Subject: [PATCH 20/57] [HttpClient] Fix copy as curl for arrays with resources & unreachable host --- .../DataCollector/HttpClientDataCollector.php | 7 ++++-- .../HttpClientDataCollectorTest.php | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php b/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php index 97604284b678b..86f7f24822e9f 100644 --- a/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php +++ b/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php @@ -203,11 +203,14 @@ private function getCurlCommand(array $trace): ?string $dataArg[] = '--data-raw '.$this->escapePayload($body); } elseif (\is_array($body)) { try { - $body = explode('&', self::normalizeBody($body)); + $body = self::normalizeBody($body); } catch (TransportException) { return null; } - foreach ($body as $value) { + if (!\is_string($body)) { + return null; + } + foreach (explode('&', $body) as $value) { $dataArg[] = '--data-raw '.$this->escapePayload(urldecode($value)); } } else { diff --git a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php index c1ca5f259f963..9eb341a58f3ff 100644 --- a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php +++ b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php @@ -12,7 +12,9 @@ namespace Symfony\Component\HttpClient\Tests\DataCollector; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\CurlHttpClient; use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; +use Symfony\Component\HttpClient\Exception\TransportException; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\NativeHttpClient; use Symfony\Component\HttpClient\TraceableHttpClient; @@ -427,6 +429,27 @@ public function testItDoesNotGeneratesCurlCommandsForUploadedFiles() self::assertNull($curlCommand); } + /** + * @requires extension curl + */ + public function testGeneratingCurlCommandForArraysWithResourcesAndUnreachableHost() + { + $httpClient = new TraceableHttpClient(new CurlHttpClient()); + try { + $httpClient->request('POST', 'http://localhast:8057/', [ + 'body' => ['file' => fopen('data://text/plain,', 'r')], + ]); + } catch (TransportException) { + } + $sut = new HttpClientDataCollector(); + $sut->registerClient('http_client', $httpClient); + $sut->lateCollect(); + $collectedData = $sut->getClients(); + self::assertCount(1, $collectedData['http_client']['traces']); + $curlCommand = $collectedData['http_client']['traces'][0]['curlCommand']; + self::assertNull($curlCommand); + } + private function httpClientThatHasTracedRequests($tracedRequests): TraceableHttpClient { $httpClient = new TraceableHttpClient(new NativeHttpClient()); From 7026f9a6f28eff4c768769f39de407eff9d28a07 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 9 Dec 2025 13:13:09 +0100 Subject: [PATCH 21/57] [HttpClient] Fix PHP deprecation when using AmpHttpClient --- src/Symfony/Component/HttpClient/AmpHttpClient.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpClient/AmpHttpClient.php b/src/Symfony/Component/HttpClient/AmpHttpClient.php index 1d4745ffbe25a..9fb4f8b2d33f6 100644 --- a/src/Symfony/Component/HttpClient/AmpHttpClient.php +++ b/src/Symfony/Component/HttpClient/AmpHttpClient.php @@ -133,9 +133,9 @@ public function request(string $method, string $url, array $options = []): Respo $request->addHeader($h[0], $h[1]); } - $request->setTcpConnectTimeout(1000 * $options['timeout']); - $request->setTlsHandshakeTimeout(1000 * $options['timeout']); - $request->setTransferTimeout(1000 * $options['max_duration']); + $request->setTcpConnectTimeout(ceil(1000 * $options['timeout'])); + $request->setTlsHandshakeTimeout(ceil(1000 * $options['timeout'])); + $request->setTransferTimeout(ceil(1000 * $options['max_duration'])); if (method_exists($request, 'setInactivityTimeout')) { $request->setInactivityTimeout(0); } From db41cea64e04295911b854d578b489e7d56b9409 Mon Sep 17 00:00:00 2001 From: Fabien Villepinte Date: Tue, 9 Dec 2025 16:33:45 +0100 Subject: [PATCH 22/57] [PhpUnitBridge] Fix TestCase patching --- src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php index f62628667798b..76972606a465b 100644 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php @@ -276,6 +276,7 @@ if ($PHPUNIT_REMOVE_RETURN_TYPEHINT) { $alteredCode = preg_replace('/^ ((?:protected|public)(?: static)? function \w+\(\)): void/m', ' $1', $alteredCode); } + file_put_contents($alteredFile, $alteredCode); // Mutate Assert code $alteredCode = file_get_contents($alteredFile = './src/Framework/Assert.php'); From a8f73b36b0e393a4133c88f6e1a1defd775e6a1d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 9 Dec 2025 20:26:04 +0100 Subject: [PATCH 23/57] [DependencyInjection] Handle recursive factory reentry for shared services in PhpDumper --- .../DependencyInjection/Dumper/PhpDumper.php | 33 ++- .../Tests/Dumper/PhpDumperTest.php | 55 +++- ...y_autowire_attribute_with_intersection.php | 8 +- ...y_autowire_attribute_with_intersection.php | 8 +- .../Tests/Fixtures/php/services9_as_files.txt | 16 +- .../Tests/Fixtures/php/services9_compiled.php | 16 +- .../php/services9_inlined_factories.txt | 16 +- .../Tests/Fixtures/php/services_adawson.php | 16 +- .../php/services_almost_circular_private.php | 184 ++++++++++-- .../php/services_almost_circular_public.php | 280 +++++++++++++++--- .../Fixtures/php/services_deep_graph.php | 16 +- .../php/services_errored_definition.php | 16 +- 12 files changed, 577 insertions(+), 87 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 574745ffa31d8..3bfee605a1ae0 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -458,7 +458,7 @@ private function collectCircularReferences(string $sourceId, array $edges, array foreach ($edges as $edge) { $node = $edge->getDestNode(); $id = $node->getId(); - if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isWeak()) { + if (($sourceId === $id && !$edge->isLazy()) || !$node->getValue() instanceof Definition || $edge->isWeak()) { continue; } @@ -699,7 +699,6 @@ private function addServiceInstance(string $id, Definition $definition, bool $is $asGhostObject = false; $isProxyCandidate = $this->isProxyCandidate($definition, $asGhostObject, $id); - $instantiation = ''; $lastWitherIndex = null; foreach ($definition->getMethodCalls() as $k => $call) { @@ -708,20 +707,26 @@ private function addServiceInstance(string $id, Definition $definition, bool $is } } - if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) { - $instantiation = \sprintf('$container->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance'); - } elseif (!$isSimpleInstance) { - $instantiation = '$instance'; - } + $shouldShareInline = !$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex; + $serviceAccessor = \sprintf('$container->%s[%s]', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id)); + $return = match (true) { + $shouldShareInline && !isset($this->circularReferences[$id]) && $isSimpleInstance=> 'return '.$serviceAccessor.' = ', + $shouldShareInline && !isset($this->circularReferences[$id]) => $serviceAccessor.' = $instance = ', + $shouldShareInline || !$isSimpleInstance => '$instance = ', + default => 'return ', + }; - $return = ''; - if ($isSimpleInstance) { - $return = 'return '; - } else { - $instantiation .= ' = '; + $code = $this->addNewInstance($definition, ' '.$return, $id, $asGhostObject); + + if ($shouldShareInline && isset($this->circularReferences[$id])) { + $code .= \sprintf( + "\n if (isset(%s)) {\n return %1\$s;\n }\n\n %s%1\$s = \$instance;\n", + $serviceAccessor, + $isSimpleInstance ? 'return ' : '' + ); } - return $this->addNewInstance($definition, ' '.$return.$instantiation, $id, $asGhostObject); + return $code; } private function isTrivialInstance(Definition $definition): bool @@ -1055,7 +1060,7 @@ private function addInlineService(string $id, Definition $definition, ?Definitio $code = ''; if ($isSimpleInstance = $isRootInstance = null === $inlineDef) { - foreach ($this->serviceCalls as $targetId => [$callCount, $behavior, $byConstructor]) { + foreach ($this->serviceCalls as $targetId => [, , $byConstructor]) { if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId] && !($this->hasProxyDumper && $definition->isLazy())) { $code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 17ec142eeeaa7..ed8ad39b175df 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -921,7 +921,6 @@ public function testDedupLazyProxy() $dumper = new PhpDumper($container); - $legacy = \PHP_VERSION_ID < 80400 || !trait_exists(LazyDecoratorTrait::class) ? 'legacy_' : ''; $this->assertStringEqualsFile(self::$fixturesPath.'/php/'.$legacy.'services_dedup_lazy.php', $dumper->dump()); } @@ -1833,6 +1832,37 @@ public function testReferencingDeprecatedPublicService() $this->addToAssertionCount(1); } + public function testDecoratedFactoryServiceKeepsReentrantInstance() + { + ReentrantFactory::reset(); + + $container = new ContainerBuilder(); + $container + ->register('decorated_service', \stdClass::class) + ->setPublic(true); + $container + ->register('decorated_service.reentrant', \stdClass::class) + ->setPublic(true) + ->setDecoratedService('decorated_service') + ->setFactory([ReentrantFactory::class, 'create']) + ->setArguments([ + new ServiceLocatorArgument(['decorated_service' => new Reference('decorated_service')]), + new Reference('decorated_service.reentrant.inner'), + ]); + + $container->compile(); + + $dumper = new PhpDumper($container); + eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Reentrant_Service'])); + + $compiled = new \Symfony_DI_PhpDumper_Test_Reentrant_Service(); + + $service = $compiled->get('decorated_service'); + + $this->assertInstanceOf(\stdClass::class, ReentrantFactory::$reentrantInstance); + $this->assertSame(ReentrantFactory::$reentrantInstance, $service); + } + public function testExpressionInFactory() { $container = new ContainerBuilder(); @@ -2219,3 +2249,26 @@ public function __construct( ) { } } + +class ReentrantFactory +{ + public static ?object $reentrantInstance = null; + private static bool $shouldReenter = true; + + public static function reset(): void + { + self::$reentrantInstance = null; + self::$shouldReenter = true; + } + + public static function create(ServiceLocator $locator, object $inner): \stdClass + { + if (self::$shouldReenter) { + self::$shouldReenter = false; + self::$reentrantInstance = $locator->get('decorated_service'); + self::$shouldReenter = true; + } + + return (object) ['inner' => $inner]; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php index a4223cd64f21c..73095ef1e899d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php @@ -61,7 +61,13 @@ protected static function getFooService($container) return $container->services['foo']; } - return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer($a); + $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer($a); + + if (isset($container->services['foo'])) { + return $container->services['foo']; + } + + return $container->services['foo'] = $instance; } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/legacy_lazy_autowire_attribute_with_intersection.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/legacy_lazy_autowire_attribute_with_intersection.php index 8dc0eb50e62fb..8051d6d8d2cab 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/legacy_lazy_autowire_attribute_with_intersection.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/legacy_lazy_autowire_attribute_with_intersection.php @@ -61,7 +61,13 @@ protected static function getFooService($container) return $container->services['foo']; } - return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer($a); + $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer($a); + + if (isset($container->services['foo'])) { + return $container->services['foo']; + } + + return $container->services['foo'] = $instance; } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt index 0cebc1f09445d..b8a7edf50d71e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt @@ -117,7 +117,13 @@ class getBazService extends ProjectServiceContainer */ public static function do($container, $lazyLoad = true) { - $container->services['baz'] = $instance = new \Baz(); + $instance = new \Baz(); + + if (isset($container->services['baz'])) { + return $container->services['baz']; + } + + $container->services['baz'] = $instance; $instance->setFoo(($container->services['foo_with_inline'] ?? $container->load('getFooWithInlineService'))); @@ -339,7 +345,13 @@ class getFooWithInlineService extends ProjectServiceContainer */ public static function do($container, $lazyLoad = true) { - $container->services['foo_with_inline'] = $instance = new \Foo(); + $instance = new \Foo(); + + if (isset($container->services['foo_with_inline'])) { + return $container->services['foo_with_inline']; + } + + $container->services['foo_with_inline'] = $instance; $a = new \Bar(); $a->pub = 'pub'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index f0bfa8855a7ef..af6ce71350c42 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -162,7 +162,13 @@ protected static function getBar22Service($container) */ protected static function getBazService($container) { - $container->services['baz'] = $instance = new \Baz(); + $instance = new \Baz(); + + if (isset($container->services['baz'])) { + return $container->services['baz']; + } + + $container->services['baz'] = $instance; $instance->setFoo(($container->services['foo_with_inline'] ?? self::getFooWithInlineService($container))); @@ -310,7 +316,13 @@ protected static function getFooBarService($container) */ protected static function getFooWithInlineService($container) { - $container->services['foo_with_inline'] = $instance = new \Foo(); + $instance = new \Foo(); + + if (isset($container->services['foo_with_inline'])) { + return $container->services['foo_with_inline']; + } + + $container->services['foo_with_inline'] = $instance; $a = new \Bar(); $a->pub = 'pub'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt index 24f26c111fc70..86aa151466735 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt @@ -181,7 +181,13 @@ class ProjectServiceContainer extends Container */ protected static function getBazService($container) { - $container->services['baz'] = $instance = new \Baz(); + $instance = new \Baz(); + + if (isset($container->services['baz'])) { + return $container->services['baz']; + } + + $container->services['baz'] = $instance; $instance->setFoo(($container->services['foo_with_inline'] ?? self::getFooWithInlineService($container))); @@ -331,7 +337,13 @@ class ProjectServiceContainer extends Container */ protected static function getFooWithInlineService($container) { - $container->services['foo_with_inline'] = $instance = new \Foo(); + $instance = new \Foo(); + + if (isset($container->services['foo_with_inline'])) { + return $container->services['foo_with_inline']; + } + + $container->services['foo_with_inline'] = $instance; $a = new \Bar(); $a->pub = 'pub'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php index ae05d67a54999..7ac7a8e58eac9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php @@ -78,7 +78,13 @@ protected static function getBusService($container) */ protected static function getDbService($container) { - $container->services['App\\Db'] = $instance = new \App\Db(); + $instance = new \App\Db(); + + if (isset($container->services['App\\Db'])) { + return $container->services['App\\Db']; + } + + $container->services['App\\Db'] = $instance; $instance->schema = ($container->privates['App\\Schema'] ?? self::getSchemaService($container)); @@ -98,6 +104,12 @@ protected static function getSchemaService($container) return $container->privates['App\\Schema']; } - return $container->privates['App\\Schema'] = new \App\Schema($a); + $instance = new \App\Schema($a); + + if (isset($container->privates['App\\Schema'])) { + return $container->privates['App\\Schema']; + } + + return $container->privates['App\\Schema'] = $instance; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index 0c234ac3934c3..e473acd1259b3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -106,7 +106,13 @@ public function getRemovedIds(): array */ protected static function getBar2Service($container) { - $container->services['bar2'] = $instance = new \BarCircular(); + $instance = new \BarCircular(); + + if (isset($container->services['bar2'])) { + return $container->services['bar2']; + } + + $container->services['bar2'] = $instance; $instance->addFoobar(new \FoobarCircular(($container->services['foo2'] ?? self::getFoo2Service($container)))); @@ -154,7 +160,13 @@ protected static function getConnectionService($container) $b = new \stdClass(); - $container->services['connection'] = $instance = new \stdClass($a, $b); + $instance = new \stdClass($a, $b); + + if (isset($container->services['connection'])) { + return $container->services['connection']; + } + + $container->services['connection'] = $instance; $b->logger = ($container->services['logger'] ?? self::getLoggerService($container)); @@ -174,7 +186,13 @@ protected static function getConnection2Service($container) $b = new \stdClass(); - $container->services['connection2'] = $instance = new \stdClass($a, $b); + $instance = new \stdClass($a, $b); + + if (isset($container->services['connection2'])) { + return $container->services['connection2']; + } + + $container->services['connection2'] = $instance; $c = new \stdClass($instance); @@ -202,7 +220,13 @@ protected static function getDoctrine_EntityManagerService($container) }, 1)); $a->flag = 'ok'; - return $container->services['doctrine.entity_manager'] = \FactoryChecker::create($a); + $instance = \FactoryChecker::create($a); + + if (isset($container->services['doctrine.entity_manager'])) { + return $container->services['doctrine.entity_manager']; + } + + return $container->services['doctrine.entity_manager'] = $instance; } /** @@ -234,7 +258,13 @@ protected static function getFoo2Service($container) return $container->services['foo2']; } - return $container->services['foo2'] = new \FooCircular($a); + $instance = new \FooCircular($a); + + if (isset($container->services['foo2'])) { + return $container->services['foo2']; + } + + return $container->services['foo2'] = $instance; } /** @@ -261,7 +291,13 @@ protected static function getFoo5Service($container) */ protected static function getFoo6Service($container) { - $container->services['foo6'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['foo6'])) { + return $container->services['foo6']; + } + + $container->services['foo6'] = $instance; $instance->bar6 = ($container->privates['bar6'] ?? self::getBar6Service($container)); @@ -291,7 +327,13 @@ protected static function getFoobar4Service($container) */ protected static function getListener3Service($container) { - $container->services['listener3'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['listener3'])) { + return $container->services['listener3']; + } + + $container->services['listener3'] = $instance; $instance->manager = ($container->services['manager3'] ?? self::getManager3Service($container)); @@ -311,7 +353,13 @@ protected static function getListener4Service($container) return $container->services['listener4']; } - return $container->services['listener4'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['listener4'])) { + return $container->services['listener4']; + } + + return $container->services['listener4'] = $instance; } /** @@ -327,7 +375,13 @@ protected static function getLoggerService($container) return $container->services['logger']; } - $container->services['logger'] = $instance = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['logger'])) { + return $container->services['logger']; + } + + $container->services['logger'] = $instance; $instance->handler = new \stdClass(($container->services['manager'] ?? self::getManagerService($container))); @@ -347,7 +401,13 @@ protected static function getManagerService($container) return $container->services['manager']; } - return $container->services['manager'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['manager'])) { + return $container->services['manager']; + } + + return $container->services['manager'] = $instance; } /** @@ -363,7 +423,13 @@ protected static function getManager2Service($container) return $container->services['manager2']; } - return $container->services['manager2'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['manager2'])) { + return $container->services['manager2']; + } + + return $container->services['manager2'] = $instance; } /** @@ -379,7 +445,13 @@ protected static function getManager3Service($container, $lazyLoad = true) return $container->services['manager3']; } - return $container->services['manager3'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['manager3'])) { + return $container->services['manager3']; + } + + return $container->services['manager3'] = $instance; } /** @@ -424,7 +496,13 @@ protected static function getPAService($container) } $b = new \stdClass(); - $container->services['pA'] = $instance = new \stdClass($b, $a); + $instance = new \stdClass($b, $a); + + if (isset($container->services['pA'])) { + return $container->services['pA']; + } + + $container->services['pA'] = $instance; $b->d = ($container->privates['pD'] ?? self::getPDService($container)); @@ -460,7 +538,13 @@ protected static function getSubscriberService($container) return $container->services['subscriber']; } - return $container->services['subscriber'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['subscriber'])) { + return $container->services['subscriber']; + } + + return $container->services['subscriber'] = $instance; } /** @@ -476,7 +560,13 @@ protected static function getBar6Service($container) return $container->privates['bar6']; } - return $container->privates['bar6'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->privates['bar6'])) { + return $container->privates['bar6']; + } + + return $container->privates['bar6'] = $instance; } /** @@ -486,7 +576,13 @@ protected static function getBar6Service($container) */ protected static function getConnection3Service($container) { - $container->privates['connection3'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->privates['connection3'])) { + return $container->privates['connection3']; + } + + $container->privates['connection3'] = $instance; $instance->listener = [($container->services['listener3'] ?? self::getListener3Service($container))]; @@ -500,7 +596,13 @@ protected static function getConnection3Service($container) */ protected static function getConnection4Service($container) { - $container->privates['connection4'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->privates['connection4'])) { + return $container->privates['connection4']; + } + + $container->privates['connection4'] = $instance; $instance->listener = [($container->services['listener4'] ?? self::getListener4Service($container))]; @@ -520,7 +622,13 @@ protected static function getDoctrine_ListenerService($container) return $container->privates['doctrine.listener']; } - return $container->privates['doctrine.listener'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->privates['doctrine.listener'])) { + return $container->privates['doctrine.listener']; + } + + return $container->privates['doctrine.listener'] = $instance; } /** @@ -546,10 +654,16 @@ protected static function getLevel5Service($container) */ protected static function getMailer_TransportService($container) { - return $container->privates['mailer.transport'] = (new \FactoryCircular(new RewindableGenerator(function () use ($container) { + $instance = (new \FactoryCircular(new RewindableGenerator(function () use ($container) { yield 0 => ($container->privates['mailer.transport_factory.amazon'] ?? self::getMailer_TransportFactory_AmazonService($container)); yield 1 => self::getMailerInline_TransportFactory_AmazonService($container); }, 2)))->create(); + + if (isset($container->privates['mailer.transport'])) { + return $container->privates['mailer.transport']; + } + + return $container->privates['mailer.transport'] = $instance; } /** @@ -561,7 +675,13 @@ protected static function getMailer_TransportFactory_AmazonService($container) { $a = new \stdClass(); - $container->privates['mailer.transport_factory.amazon'] = $instance = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->privates['mailer.transport_factory.amazon'])) { + return $container->privates['mailer.transport_factory.amazon']; + } + + $container->privates['mailer.transport_factory.amazon'] = $instance; $a->handler = ($container->privates['mailer.transport'] ?? self::getMailer_TransportService($container)); @@ -604,7 +724,13 @@ protected static function getManager4Service($container, $lazyLoad = true) return $container->privates['manager4']; } - return $container->privates['manager4'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->privates['manager4'])) { + return $container->privates['manager4']; + } + + return $container->privates['manager4'] = $instance; } /** @@ -614,7 +740,13 @@ protected static function getManager4Service($container, $lazyLoad = true) */ protected static function getPCService($container, $lazyLoad = true) { - $container->privates['pC'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->privates['pC'])) { + return $container->privates['pC']; + } + + $container->privates['pC'] = $instance; $instance->d = ($container->privates['pD'] ?? self::getPDService($container)); @@ -634,6 +766,12 @@ protected static function getPDService($container) return $container->privates['pD']; } - return $container->privates['pD'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->privates['pD'])) { + return $container->privates['pD']; + } + + return $container->privates['pD'] = $instance; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index ae283e556a0da..edb2da14c3bea 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -106,7 +106,13 @@ public function getRemovedIds(): array */ protected static function getBarService($container) { - $container->services['bar'] = $instance = new \BarCircular(); + $instance = new \BarCircular(); + + if (isset($container->services['bar'])) { + return $container->services['bar']; + } + + $container->services['bar'] = $instance; $instance->addFoobar(($container->services['foobar'] ?? self::getFoobarService($container))); @@ -142,7 +148,13 @@ protected static function getBar5Service($container) return $container->services['bar5']; } - $container->services['bar5'] = $instance = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['bar5'])) { + return $container->services['bar5']; + } + + $container->services['bar5'] = $instance; $instance->foo = $a; @@ -177,7 +189,13 @@ protected static function getConnectionService($container) } $b = new \stdClass(); - $container->services['connection'] = $instance = new \stdClass($a, $b); + $instance = new \stdClass($a, $b); + + if (isset($container->services['connection'])) { + return $container->services['connection']; + } + + $container->services['connection'] = $instance; $b->logger = ($container->services['logger'] ?? self::getLoggerService($container)); @@ -198,7 +216,13 @@ protected static function getConnection2Service($container) } $b = new \stdClass(); - $container->services['connection2'] = $instance = new \stdClass($a, $b); + $instance = new \stdClass($a, $b); + + if (isset($container->services['connection2'])) { + return $container->services['connection2']; + } + + $container->services['connection2'] = $instance; $c = new \stdClass($instance); $c->handler2 = new \stdClass(($container->services['manager2'] ?? self::getManager2Service($container))); @@ -215,7 +239,13 @@ protected static function getConnection2Service($container) */ protected static function getConnection3Service($container) { - $container->services['connection3'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['connection3'])) { + return $container->services['connection3']; + } + + $container->services['connection3'] = $instance; $instance->listener = [($container->services['listener3'] ?? self::getListener3Service($container))]; @@ -229,7 +259,13 @@ protected static function getConnection3Service($container) */ protected static function getConnection4Service($container) { - $container->services['connection4'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['connection4'])) { + return $container->services['connection4']; + } + + $container->services['connection4'] = $instance; $instance->listener = [($container->services['listener4'] ?? self::getListener4Service($container))]; @@ -243,7 +279,13 @@ protected static function getConnection4Service($container) */ protected static function getDispatcherService($container, $lazyLoad = true) { - $container->services['dispatcher'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['dispatcher'])) { + return $container->services['dispatcher']; + } + + $container->services['dispatcher'] = $instance; $instance->subscriber = ($container->services['subscriber'] ?? self::getSubscriberService($container)); @@ -257,7 +299,13 @@ protected static function getDispatcherService($container, $lazyLoad = true) */ protected static function getDispatcher2Service($container, $lazyLoad = true) { - $container->services['dispatcher2'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['dispatcher2'])) { + return $container->services['dispatcher2']; + } + + $container->services['dispatcher2'] = $instance; $instance->subscriber2 = ($container->privates['subscriber2'] ?? self::getSubscriber2Service($container)); @@ -271,9 +319,15 @@ protected static function getDispatcher2Service($container, $lazyLoad = true) */ protected static function getDoctrine_EntityListenerResolverService($container) { - return $container->services['doctrine.entity_listener_resolver'] = new \stdClass(new RewindableGenerator(function () use ($container) { + $instance = new \stdClass(new RewindableGenerator(function () use ($container) { yield 0 => ($container->services['doctrine.listener'] ?? self::getDoctrine_ListenerService($container)); }, 1)); + + if (isset($container->services['doctrine.entity_listener_resolver'])) { + return $container->services['doctrine.entity_listener_resolver']; + } + + return $container->services['doctrine.entity_listener_resolver'] = $instance; } /** @@ -292,7 +346,13 @@ protected static function getDoctrine_EntityManagerService($container) $b->resolver = $a; $b->flag = 'ok'; - return $container->services['doctrine.entity_manager'] = \FactoryChecker::create($b); + $instance = \FactoryChecker::create($b); + + if (isset($container->services['doctrine.entity_manager'])) { + return $container->services['doctrine.entity_manager']; + } + + return $container->services['doctrine.entity_manager'] = $instance; } /** @@ -308,7 +368,13 @@ protected static function getDoctrine_ListenerService($container) return $container->services['doctrine.listener']; } - return $container->services['doctrine.listener'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['doctrine.listener'])) { + return $container->services['doctrine.listener']; + } + + return $container->services['doctrine.listener'] = $instance; } /** @@ -324,7 +390,13 @@ protected static function getFooService($container) return $container->services['foo']; } - return $container->services['foo'] = new \FooCircular($a); + $instance = new \FooCircular($a); + + if (isset($container->services['foo'])) { + return $container->services['foo']; + } + + return $container->services['foo'] = $instance; } /** @@ -336,7 +408,13 @@ protected static function getFoo2Service($container) { $a = new \BarCircular(); - $container->services['foo2'] = $instance = new \FooCircular($a); + $instance = new \FooCircular($a); + + if (isset($container->services['foo2'])) { + return $container->services['foo2']; + } + + $container->services['foo2'] = $instance; $a->addFoobar(($container->services['foobar2'] ?? self::getFoobar2Service($container))); @@ -368,7 +446,13 @@ protected static function getFoo4Service($container) */ protected static function getFoo5Service($container) { - $container->services['foo5'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['foo5'])) { + return $container->services['foo5']; + } + + $container->services['foo5'] = $instance; $instance->bar = ($container->services['bar5'] ?? self::getBar5Service($container)); @@ -382,7 +466,13 @@ protected static function getFoo5Service($container) */ protected static function getFoo6Service($container) { - $container->services['foo6'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['foo6'])) { + return $container->services['foo6']; + } + + $container->services['foo6'] = $instance; $instance->bar6 = ($container->privates['bar6'] ?? self::getBar6Service($container)); @@ -402,7 +492,13 @@ protected static function getFoobarService($container) return $container->services['foobar']; } - return $container->services['foobar'] = new \FoobarCircular($a); + $instance = new \FoobarCircular($a); + + if (isset($container->services['foobar'])) { + return $container->services['foobar']; + } + + return $container->services['foobar'] = $instance; } /** @@ -418,7 +514,13 @@ protected static function getFoobar2Service($container) return $container->services['foobar2']; } - return $container->services['foobar2'] = new \FoobarCircular($a); + $instance = new \FoobarCircular($a); + + if (isset($container->services['foobar2'])) { + return $container->services['foobar2']; + } + + return $container->services['foobar2'] = $instance; } /** @@ -454,7 +556,13 @@ protected static function getFoobar4Service($container) */ protected static function getListener3Service($container) { - $container->services['listener3'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['listener3'])) { + return $container->services['listener3']; + } + + $container->services['listener3'] = $instance; $instance->manager = ($container->services['manager3'] ?? self::getManager3Service($container)); @@ -474,7 +582,13 @@ protected static function getListener4Service($container) return $container->services['listener4']; } - return $container->services['listener4'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['listener4'])) { + return $container->services['listener4']; + } + + return $container->services['listener4'] = $instance; } /** @@ -490,7 +604,13 @@ protected static function getLoggerService($container) return $container->services['logger']; } - $container->services['logger'] = $instance = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['logger'])) { + return $container->services['logger']; + } + + $container->services['logger'] = $instance; $instance->handler = new \stdClass(($container->services['manager'] ?? self::getManagerService($container))); @@ -510,7 +630,13 @@ protected static function getMailer_TransportService($container) return $container->services['mailer.transport']; } - return $container->services['mailer.transport'] = $a->create(); + $instance = $a->create(); + + if (isset($container->services['mailer.transport'])) { + return $container->services['mailer.transport']; + } + + return $container->services['mailer.transport'] = $instance; } /** @@ -520,10 +646,16 @@ protected static function getMailer_TransportService($container) */ protected static function getMailer_TransportFactoryService($container) { - return $container->services['mailer.transport_factory'] = new \FactoryCircular(new RewindableGenerator(function () use ($container) { + $instance = new \FactoryCircular(new RewindableGenerator(function () use ($container) { yield 0 => ($container->services['mailer.transport_factory.amazon'] ?? self::getMailer_TransportFactory_AmazonService($container)); yield 1 => ($container->services['mailer_inline.transport_factory.amazon'] ?? self::getMailerInline_TransportFactory_AmazonService($container)); }, 2)); + + if (isset($container->services['mailer.transport_factory'])) { + return $container->services['mailer.transport_factory']; + } + + return $container->services['mailer.transport_factory'] = $instance; } /** @@ -539,7 +671,13 @@ protected static function getMailer_TransportFactory_AmazonService($container) return $container->services['mailer.transport_factory.amazon']; } - return $container->services['mailer.transport_factory.amazon'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['mailer.transport_factory.amazon'])) { + return $container->services['mailer.transport_factory.amazon']; + } + + return $container->services['mailer.transport_factory.amazon'] = $instance; } /** @@ -575,7 +713,13 @@ protected static function getManagerService($container) return $container->services['manager']; } - return $container->services['manager'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['manager'])) { + return $container->services['manager']; + } + + return $container->services['manager'] = $instance; } /** @@ -591,7 +735,13 @@ protected static function getManager2Service($container) return $container->services['manager2']; } - return $container->services['manager2'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['manager2'])) { + return $container->services['manager2']; + } + + return $container->services['manager2'] = $instance; } /** @@ -607,7 +757,13 @@ protected static function getManager3Service($container, $lazyLoad = true) return $container->services['manager3']; } - return $container->services['manager3'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['manager3'])) { + return $container->services['manager3']; + } + + return $container->services['manager3'] = $instance; } /** @@ -631,7 +787,13 @@ protected static function getMonolog_LoggerService($container) */ protected static function getMonolog_Logger2Service($container) { - $container->services['monolog.logger_2'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['monolog.logger_2'])) { + return $container->services['monolog.logger_2']; + } + + $container->services['monolog.logger_2'] = $instance; $instance->handler = ($container->services['mailer.transport'] ?? self::getMailer_TransportService($container)); @@ -684,7 +846,13 @@ protected static function getPAService($container) return $container->services['pA']; } - return $container->services['pA'] = new \stdClass($a, $b); + $instance = new \stdClass($a, $b); + + if (isset($container->services['pA'])) { + return $container->services['pA']; + } + + return $container->services['pA'] = $instance; } /** @@ -694,7 +862,13 @@ protected static function getPAService($container) */ protected static function getPBService($container) { - $container->services['pB'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['pB'])) { + return $container->services['pB']; + } + + $container->services['pB'] = $instance; $instance->d = ($container->services['pD'] ?? self::getPDService($container)); @@ -708,7 +882,13 @@ protected static function getPBService($container) */ protected static function getPCService($container, $lazyLoad = true) { - $container->services['pC'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['pC'])) { + return $container->services['pC']; + } + + $container->services['pC'] = $instance; $instance->d = ($container->services['pD'] ?? self::getPDService($container)); @@ -728,7 +908,13 @@ protected static function getPDService($container) return $container->services['pD']; } - return $container->services['pD'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['pD'])) { + return $container->services['pD']; + } + + return $container->services['pD'] = $instance; } /** @@ -760,7 +946,13 @@ protected static function getSubscriberService($container) return $container->services['subscriber']; } - return $container->services['subscriber'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->services['subscriber'])) { + return $container->services['subscriber']; + } + + return $container->services['subscriber'] = $instance; } /** @@ -776,7 +968,13 @@ protected static function getBar6Service($container) return $container->privates['bar6']; } - return $container->privates['bar6'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->privates['bar6'])) { + return $container->privates['bar6']; + } + + return $container->privates['bar6'] = $instance; } /** @@ -818,7 +1016,13 @@ protected static function getManager4Service($container, $lazyLoad = true) return $container->privates['manager4']; } - return $container->privates['manager4'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->privates['manager4'])) { + return $container->privates['manager4']; + } + + return $container->privates['manager4'] = $instance; } /** @@ -834,6 +1038,12 @@ protected static function getSubscriber2Service($container) return $container->privates['subscriber2']; } - return $container->privates['subscriber2'] = new \stdClass($a); + $instance = new \stdClass($a); + + if (isset($container->privates['subscriber2'])) { + return $container->privates['subscriber2']; + } + + return $container->privates['subscriber2'] = $instance; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php index 9c22c5f9e7ae1..04c653247fef8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php @@ -50,7 +50,13 @@ public function getRemovedIds(): array */ protected static function getBarService($container) { - $container->services['bar'] = $instance = new \stdClass(); + $instance = new \stdClass(); + + if (isset($container->services['bar'])) { + return $container->services['bar']; + } + + $container->services['bar'] = $instance; $instance->p5 = new \stdClass(($container->services['foo'] ?? self::getFooService($container))); @@ -76,6 +82,12 @@ protected static function getFooService($container) $b->p2 = $c; - return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\FooForDeepGraph($a, $b); + $instance = new \Symfony\Component\DependencyInjection\Tests\Dumper\FooForDeepGraph($a, $b); + + if (isset($container->services['foo'])) { + return $container->services['foo']; + } + + return $container->services['foo'] = $instance; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php index cc6e8cd889514..83ee087575d27 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php @@ -162,7 +162,13 @@ protected static function getBar22Service($container) */ protected static function getBazService($container) { - $container->services['baz'] = $instance = new \Baz(); + $instance = new \Baz(); + + if (isset($container->services['baz'])) { + return $container->services['baz']; + } + + $container->services['baz'] = $instance; $instance->setFoo(($container->services['foo_with_inline'] ?? self::getFooWithInlineService($container))); @@ -310,7 +316,13 @@ protected static function getFooBarService($container) */ protected static function getFooWithInlineService($container) { - $container->services['foo_with_inline'] = $instance = new \Foo(); + $instance = new \Foo(); + + if (isset($container->services['foo_with_inline'])) { + return $container->services['foo_with_inline']; + } + + $container->services['foo_with_inline'] = $instance; $a = new \Bar(); $a->pub = 'pub'; From 77fef82c3d74073f3646fda98ff2cc16648f687d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 10 Dec 2025 06:43:00 +0000 Subject: [PATCH 24/57] [DependencyInjection] Fix sharing services used only by tagged iterators --- .../Compiler/AnalyzeServiceReferencesPass.php | 14 +++- .../Compiler/ServiceReferenceGraph.php | 4 +- .../Compiler/ServiceReferenceGraphEdge.php | 9 ++- .../DependencyInjection/Dumper/PhpDumper.php | 4 +- .../AnalyzeServiceReferencesPassTest.php | 24 ++++++ .../Tests/Dumper/PhpDumperTest.php | 73 +++++++++++++++++++ .../php/services_almost_circular_private.php | 4 +- 7 files changed, 123 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index 4fea73217cec6..ea1986ce94407 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -41,6 +41,7 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass private bool $lazy; private bool $byConstructor; private bool $byFactory; + private bool $byMultiUseArgument; private array $definitions; private array $aliases; @@ -67,6 +68,7 @@ public function process(ContainerBuilder $container) $this->lazy = false; $this->byConstructor = false; $this->byFactory = false; + $this->byMultiUseArgument = false; $this->definitions = $container->getDefinitions(); $this->aliases = $container->getAliases(); @@ -89,7 +91,12 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if ($value instanceof ArgumentInterface) { $this->lazy = !$this->byFactory || !$value instanceof IteratorArgument; + $byMultiUseArgument = $this->byMultiUseArgument; + if ($value instanceof IteratorArgument) { + $this->byMultiUseArgument = true; + } parent::processValue($value->getValues()); + $this->byMultiUseArgument = $byMultiUseArgument; $this->lazy = $lazy; return $value; @@ -106,7 +113,8 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed $value, $this->lazy || ($this->hasProxyDumper && $targetDefinition?->isLazy()), ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior(), - $this->byConstructor + $this->byConstructor, + $this->byMultiUseArgument ); if ($inExpression) { @@ -117,7 +125,9 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed $targetDefinition, $value, $this->lazy || $targetDefinition?->isLazy(), - true + true, + $this->byConstructor, + $this->byMultiUseArgument ); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php index 2544cde95498e..97f9398e1b6b9 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php @@ -74,7 +74,7 @@ public function clear(): void /** * Connects 2 nodes together in the Graph. */ - public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, mixed $destValue = null, ?Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false): void + public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, mixed $destValue = null, ?Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false, bool $byMultiUseArgument = false): void { if (null === $sourceId || null === $destId) { return; @@ -82,7 +82,7 @@ public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, $sourceNode = $this->createNode($sourceId, $sourceValue); $destNode = $this->createNode($destId, $destValue); - $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak, $byConstructor); + $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak, $byConstructor, $byMultiUseArgument); $sourceNode->addOutEdge($edge); $destNode->addInEdge($edge); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php index b607164a6d6c7..1fad566668bce 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php @@ -26,8 +26,9 @@ class ServiceReferenceGraphEdge private bool $lazy; private bool $weak; private bool $byConstructor; + private bool $byMultiUseArgument; - public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, mixed $value = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false) + public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, mixed $value = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false, bool $byMultiUseArgument = false) { $this->sourceNode = $sourceNode; $this->destNode = $destNode; @@ -35,6 +36,7 @@ public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceRefere $this->lazy = $lazy; $this->weak = $weak; $this->byConstructor = $byConstructor; + $this->byMultiUseArgument = $byMultiUseArgument; } /** @@ -84,4 +86,9 @@ public function isReferencedByConstructor(): bool { return $this->byConstructor; } + + public function isFromMultiUseArgument(): bool + { + return $this->byMultiUseArgument; + } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 3bfee605a1ae0..8454c1a52b95e 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -710,7 +710,7 @@ private function addServiceInstance(string $id, Definition $definition, bool $is $shouldShareInline = !$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex; $serviceAccessor = \sprintf('$container->%s[%s]', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id)); $return = match (true) { - $shouldShareInline && !isset($this->circularReferences[$id]) && $isSimpleInstance=> 'return '.$serviceAccessor.' = ', + $shouldShareInline && !isset($this->circularReferences[$id]) && $isSimpleInstance => 'return '.$serviceAccessor.' = ', $shouldShareInline && !isset($this->circularReferences[$id]) => $serviceAccessor.' = $instance = ', $shouldShareInline || !$isSimpleInstance => '$instance = ', default => 'return ', @@ -2194,7 +2194,7 @@ private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool if (!$value = $edge->getSourceNode()->getValue()) { continue; } - if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) { + if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared() || $edge->isFromMultiUseArgument()) { return false; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php index a075b51d41ae2..5b9e52e2a5706 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; class AnalyzeServiceReferencesPassTest extends TestCase { @@ -204,6 +205,29 @@ public function testProcessDetectsFactoryReferences() $this->assertCount(1, $graph->getNode('foo')->getInEdges()); } + public function testExpressionReferenceKeepsConstructorFlag() + { + $container = new ContainerBuilder(); + $container->register('bar'); + $container + ->register('foo') + ->addArgument(new Expression('service("bar")')); + + $graph = $this->process($container); + + $edges = $graph->getNode('bar')->getInEdges(); + $exprEdge = null; + foreach ($edges as $edge) { + if ('.internal.reference_in_expression' === $edge->getSourceNode()->getId()) { + $exprEdge = $edge; + break; + } + } + + $this->assertNotNull($exprEdge, 'Expression edge should exist.'); + $this->assertTrue($exprEdge->isReferencedByConstructor()); + } + protected function process(ContainerBuilder $container) { $pass = new AnalyzeServiceReferencesPass(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index ed8ad39b175df..fa2ce8f1b8682 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -319,6 +319,38 @@ public function testDumpAsFilesWithFactoriesInlinedWithTaggedIterator() $this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/services9_inlined_factories_with_tagged_iterrator.txt', $dump); } + public function testTaggedIteratorServicesRemainSharedWhenUsedByFactory() + { + PhpDumperTest_TaggedIteratorService::reset(); + + $container = new ContainerBuilder(); + $container + ->register('tagged_service', PhpDumperTest_TaggedIteratorService::class) + ->addTag('tag1'); + $container + ->register('wrapper', PhpDumperTest_TaggedIteratorWrapper::class) + ->setFactory([new Reference('wrapper_factory'), 'create']) + ->setPublic(true); + $container + ->register('wrapper_factory', PhpDumperTest_TaggedIteratorWrapperFactory::class) + ->setArguments([new TaggedIteratorArgument('tag1')]); + + $container->compile(); + + $dumper = new PhpDumper($container); + eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Tagged_Iterator_Factory'])); + + $compiled = new \Symfony_DI_PhpDumper_Test_Tagged_Iterator_Factory(); + $wrapper = $compiled->get('wrapper'); + + $firstIteration = iterator_to_array($wrapper->getTaggedServices(), false); + $secondIteration = iterator_to_array($wrapper->getTaggedServices(), false); + + $this->assertCount(1, $firstIteration); + $this->assertSame($firstIteration, $secondIteration); + $this->assertSame(1, PhpDumperTest_TaggedIteratorService::$constructed); + } + public function testDumpAsFilesWithLazyFactoriesInlined() { $container = new ContainerBuilder(); @@ -2272,3 +2304,44 @@ public static function create(ServiceLocator $locator, object $inner): \stdClass return (object) ['inner' => $inner]; } } + +class PhpDumperTest_TaggedIteratorService +{ + public static int $constructed = 0; + + public function __construct() + { + ++self::$constructed; + } + + public static function reset(): void + { + self::$constructed = 0; + } +} + +class PhpDumperTest_TaggedIteratorWrapper +{ + public function __construct( + private iterable $taggedServices, + ) { + } + + public function getTaggedServices(): iterable + { + return $this->taggedServices; + } +} + +class PhpDumperTest_TaggedIteratorWrapperFactory +{ + public function __construct( + private iterable $taggedServices, + ) { + } + + public function create(): PhpDumperTest_TaggedIteratorWrapper + { + return new PhpDumperTest_TaggedIteratorWrapper($this->taggedServices); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index e473acd1259b3..986be4bf95da8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -656,7 +656,7 @@ protected static function getMailer_TransportService($container) { $instance = (new \FactoryCircular(new RewindableGenerator(function () use ($container) { yield 0 => ($container->privates['mailer.transport_factory.amazon'] ?? self::getMailer_TransportFactory_AmazonService($container)); - yield 1 => self::getMailerInline_TransportFactory_AmazonService($container); + yield 1 => ($container->privates['mailer_inline.transport_factory.amazon'] ?? self::getMailerInline_TransportFactory_AmazonService($container)); }, 2)))->create(); if (isset($container->privates['mailer.transport'])) { @@ -708,7 +708,7 @@ protected static function getMailerInline_TransportFactory_AmazonService($contai $a = new \stdClass(); $a->handler = ($container->privates['mailer_inline.mailer'] ?? self::getMailerInline_MailerService($container)); - return new \stdClass($a); + return $container->privates['mailer_inline.transport_factory.amazon'] = new \stdClass($a); } /** From c49575012eb259da694e0d81a937fc0542d843a9 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 10 Dec 2025 09:22:31 +0100 Subject: [PATCH 25/57] fix handling named arguments in Existence constraint --- .../Component/Validator/Constraints/Existence.php | 2 ++ .../Tests/Mapping/Loader/XmlFileLoaderTest.php | 6 ++++++ .../Tests/Mapping/Loader/YamlFileLoaderTest.php | 6 ++++++ .../Tests/Mapping/Loader/constraint-mapping.xml | 14 ++++++++++++++ .../Tests/Mapping/Loader/constraint-mapping.yml | 8 ++++++++ 5 files changed, 36 insertions(+) diff --git a/src/Symfony/Component/Validator/Constraints/Existence.php b/src/Symfony/Component/Validator/Constraints/Existence.php index a867f09e58307..8a01312426de9 100644 --- a/src/Symfony/Component/Validator/Constraints/Existence.php +++ b/src/Symfony/Component/Validator/Constraints/Existence.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\Constraints; +use Symfony\Component\Validator\Attribute\HasNamedArguments; use Symfony\Component\Validator\Constraint; /** @@ -20,6 +21,7 @@ abstract class Existence extends Composite { public array|Constraint $constraints = []; + #[HasNamedArguments] public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null) { if (!$constraints instanceof Constraint && !\is_array($constraints) || \is_array($constraints) && !array_is_list($constraints)) { diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php index 6490a8b1395eb..4a7a088f5f570 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -18,11 +18,15 @@ use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; +use Symfony\Component\Validator\Constraints\Optional; use Symfony\Component\Validator\Constraints\Range; use Symfony\Component\Validator\Constraints\Regex; +use Symfony\Component\Validator\Constraints\Required; use Symfony\Component\Validator\Constraints\Traverse; use Symfony\Component\Validator\Constraints\Type; use Symfony\Component\Validator\Exception\MappingException; @@ -83,6 +87,8 @@ public function testLoadClassMetadata() $expected->addPropertyConstraint('firstName', new Collection(fields: [ 'foo' => [new NotNull(), new Range(min: 3)], 'bar' => [new Range(min: 5)], + 'baz' => new Required([new Email()]), + 'qux' => new Optional([new NotBlank()]), ])); $expected->addPropertyConstraint('firstName', new Choice( message: 'Must be one of %choices%', diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php index 8b2077931de5f..fbd1591888638 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -19,9 +19,13 @@ use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\IsTrue; +use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; +use Symfony\Component\Validator\Constraints\Optional; use Symfony\Component\Validator\Constraints\Range; +use Symfony\Component\Validator\Constraints\Required; use Symfony\Component\Validator\Constraints\Type; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; @@ -125,6 +129,8 @@ public function testLoadClassMetadata() $expected->addPropertyConstraint('firstName', new Collection(fields: [ 'foo' => [new NotNull(), new Range(min: 3)], 'bar' => [new Range(min: 5)], + 'baz' => new Required([new Email()]), + 'qux' => new Optional([new NotBlank()]), ])); $expected->addPropertyConstraint('firstName', new Choice( message: 'Must be one of %choices%', diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml index 588833208db0a..4ece3aec548a0 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml @@ -97,6 +97,20 @@ + + + + + + + + + + diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml index e31a64189ecff..b40bfc02ad87a 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml @@ -53,6 +53,14 @@ Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity: bar: - Range: min: 5 + baz: + - Required: + constraints: + - Email: ~ + qux: + - Optional: + constraints: + - NotBlank: ~ # Constraint with options - Choice: { choices: [A, B], message: Must be one of %choices% } dummy: From 683c79f271b76eeaf0d2d22ca8ac43257fd457ab Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Tue, 9 Dec 2025 16:19:47 +0100 Subject: [PATCH 26/57] Fix unintended BC break for the exception_controller twig setting Setting this setting to `null` was the opt-in for the Symfony 5.0 behavior in TwigBundle and so was kept supported as a no-op in 5.0+ to support the migration path, without ever being deprecated. This setting was removed in 7.4.0 without deprecation, without realizing that it was part of this compat layer. This replaces this removal with a deprecation (reported as if it was introduced in 7.4 from the start) while the removal will be preserved in Symfony 8.0. --- UPGRADE-7.4.md | 5 +++++ src/Symfony/Bundle/TwigBundle/CHANGELOG.md | 5 +++++ .../DependencyInjection/Configuration.php | 19 ++++++++++++++++++- src/Symfony/Bundle/TwigBundle/composer.json | 1 + 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/UPGRADE-7.4.md b/UPGRADE-7.4.md index 4fd7c81156b3f..b04847982dc05 100644 --- a/UPGRADE-7.4.md +++ b/UPGRADE-7.4.md @@ -183,6 +183,11 @@ Translation * Deprecate `TranslatableMessage::__toString` +TwigBundle +---------- + + * Deprecate setting the `exception_controller` config to `null`. This was a legacy opt-out of a deprecation that is a no-op since Symfony 5.0. Remove that setting entirely instead. + Uid --- diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md index 40d5be350afe7..c0e44472b28c3 100644 --- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.4 +--- + +* Deprecate setting the `exception_controller` config to `null`. This was a legacy opt-out of a deprecation that is a no-op since Symfony 5.0. Remove that setting entirely instead. + 7.3 --- diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index 36a58ba935c68..629a57f3deee0 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -14,6 +14,7 @@ use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; /** @@ -33,7 +34,23 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->docUrl('https://symfony.com/doc/{version:major}.{version:minor}/reference/configuration/twig.html', 'symfony/twig-bundle') - ->end(); + ->beforeNormalization() + ->ifArray() + ->then(function ($v) { + if (!\array_key_exists('exception_controller', $v)) { + return $v; + } + + if (isset($v['exception_controller'])) { + throw new InvalidConfigurationException('Option "exception_controller" under "twig" must be null or unset, use "error_controller" under "framework" instead.'); + } + + unset($v['exception_controller']); + trigger_deprecation('symfony/twig-bundle', '7.4', 'Setting the "exception_controller" option under "twig" to null is deprecated. Omit this legacy no-op option instead.'); + + return $v; + }) + ->end(); $this->addFormThemesSection($rootNode); $this->addGlobalsSection($rootNode); diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 88bfd56eae2a7..308a084a81826 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -20,6 +20,7 @@ "composer-runtime-api": ">=2.1", "symfony/config": "^7.4|^8.0", "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/twig-bridge": "^7.3|^8.0", "symfony/http-foundation": "^6.4|^7.0|^8.0", "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", From 4158e3b37d10a463f443cae4a1481d24696adf87 Mon Sep 17 00:00:00 2001 From: Bohdan Pliachenko Date: Wed, 10 Dec 2025 11:07:54 +0200 Subject: [PATCH 27/57] fix: fix property info var tag extractor --- .../Extractor/PhpStanExtractor.php | 2 +- .../Tests/Extractor/PhpStanExtractorTest.php | 20 ++++++++++++- .../ConstructorDummyWithVarTagsDocBlock.php | 30 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php index d79a6a10a616e..b591e2920a79f 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php @@ -154,7 +154,7 @@ public function getTypes(string $class, string $property, array $context = []): public function getTypesFromConstructor(string $class, string $property): ?array { if (null === $tagDocNode = $this->getDocBlockFromConstructor($class, $property)) { - return null; + return $this->getTypes($class, $property); } $types = []; diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php index b7987668b4f8f..cd64c32013182 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php @@ -370,11 +370,29 @@ public static function constructorTypesProvider() ['date', [new Type(Type::BUILTIN_TYPE_INT)]], ['timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]], ['dateObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]], - ['dateTime', null], + ['dateTime', [new Type(Type::BUILTIN_TYPE_INT)]], ['ddd', null], ]; } + /** + * @dataProvider provideConstructorTypesWithOnlyVarTags + */ + public function testExtractConstructorTypesWithOnlyVarTags($property, ?array $type = null) + { + $this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithVarTagsDocBlock', $property)); + } + + public static function provideConstructorTypesWithOnlyVarTags() + { + yield ['date', [new Type(Type::BUILTIN_TYPE_INT)]]; + yield ['dateObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]]; + yield ['objectsArray', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy'))]]; + yield ['dateTime', null]; + yield ['mixed', null]; + yield ['timezone', null]; + } + /** * @dataProvider unionTypesProvider */ diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php new file mode 100644 index 0000000000000..57e50f4ea2f97 --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Tests\Fixtures; + + +class ConstructorDummyWithVarTagsDocBlock +{ + public function __construct( + public \DateTimeZone $timezone, + /** @var int */ + public $date, + /** @var \DateTimeInterface */ + public $dateObject, + public \DateTimeImmutable $dateTime, + public $mixed, + /** @var ConstructorDummy[] */ + public array $objectsArray, + ) + { + } +} From e2b78443c2cda4fb70473240f67c130a1cf09fc4 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 7 Dec 2025 22:55:14 +0100 Subject: [PATCH 28/57] [PropertyInfo] Fix PseudoType support in PhpDocTypeHelper --- .../Tests/Extractor/PhpDocExtractorTest.php | 15 ++++++++- .../Tests/Fixtures/PseudoTypesDummy.php | 33 +++++++++++++++++++ .../PropertyInfo/Util/PhpDocTypeHelper.php | 21 ++++++++---- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index 5aa2cd681caf3..4a362c0297fc5 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\PropertyInfo\Tests\Extractor; +use phpDocumentor\Reflection\PseudoTypes\IntMask; +use phpDocumentor\Reflection\PseudoTypes\IntMaskOf; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; @@ -428,7 +430,7 @@ public static function constructorTypesProvider() /** * @dataProvider pseudoTypesProvider */ - public function testPseudoTypes($property, array $type) + public function testPseudoTypes($property, ?array $type) { $this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\PseudoTypesDummy', $property)); } @@ -445,6 +447,17 @@ public static function pseudoTypesProvider(): array ['numericString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], ['traitString', [new Type(Type::BUILTIN_TYPE_STRING, false, null)]], ['positiveInt', [new Type(Type::BUILTIN_TYPE_INT, false, null)]], + ['true', [new Type(Type::BUILTIN_TYPE_TRUE, false, null)]], + ['false', [new Type(Type::BUILTIN_TYPE_FALSE, false, null)]], + ['valueOfStrings', null], + ['valueOfIntegers', null], + ['keyOfStrings', null], + ['keyOfIntegers', null], + ['arrayKey', null], + ['intMask', class_exists(IntMask::class) ? [new Type(Type::BUILTIN_TYPE_INT, false, null)] : null], + ['intMaskOf', class_exists(IntMaskOf::class) ? [new Type(Type::BUILTIN_TYPE_INT, false, null)] : null], + ['conditional', null], + ['offsetAccess', null], ]; } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php index 48574a1efe43e..08efe035a713c 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php @@ -45,4 +45,37 @@ class PseudoTypesDummy /** @var literal-string */ public $literalString; + + /** @var true */ + public $true; + + /** @var false */ + public $false; + + /** @var value-of */ + public $valueOfStrings; + + /** @var value-of */ + public $valueOfIntegers; + + /** @var key-of */ + public $keyOfStrings; + + /** @var key-of */ + public $keyOfIntegers; + + /** @var array-key */ + public $arrayKey; + + /** @var int-mask<1,2,4> */ + public $intMask; + + /** @var int-mask-of<1|2|4> */ + public $intMaskOf; + + /** @var (T is int ? string : int) */ + public $conditional; + + /** @var self::STRINGS['A'] */ + public $offsetAccess; } diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php index 683719ddbda41..c11618bc68a19 100644 --- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php +++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php @@ -156,21 +156,28 @@ private function createType(DocType $type, bool $nullable): ?Type return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes); } + $docType = $this->normalizeType($docType); + [$phpType, $class] = $this->getPhpTypeAndClass($docType); + + if ('array' === $docType) { + return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, null, null); + } + + if (null === $class) { + return new Type($phpType, $nullable, $class); + } + if ($type instanceof PseudoType) { if ($type->underlyingType() instanceof Integer) { return new Type(Type::BUILTIN_TYPE_INT, $nullable, null); } elseif ($type->underlyingType() instanceof String_) { return new Type(Type::BUILTIN_TYPE_STRING, $nullable, null); + } else { + // It's safer to fall back to other extractors here, as resolving pseudo types correctly is not easy at the moment + return null; } } - $docType = $this->normalizeType($docType); - [$phpType, $class] = $this->getPhpTypeAndClass($docType); - - if ('array' === $docType) { - return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, null, null); - } - return new Type($phpType, $nullable, $class); } From 9dd5cdbb970287820a4d19ff780b16b181c9d124 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 7 Dec 2025 23:00:04 +0100 Subject: [PATCH 29/57] [PropertyInfo] Fix PseudoType support in PhpDocTypeHelper --- .../Tests/Extractor/PhpDocExtractorTest.php | 13 +++++++ .../Tests/Fixtures/PseudoTypesDummy.php | 36 +++++++++++++++++++ .../PropertyInfo/Util/PhpDocTypeHelper.php | 25 ++++++++----- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index f86527ad59f01..23b7b50d0e4d6 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\PropertyInfo\Tests\Extractor; use phpDocumentor\Reflection\DocBlock; +use phpDocumentor\Reflection\PseudoTypes\IntMask; +use phpDocumentor\Reflection\PseudoTypes\IntMaskOf; use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; @@ -909,6 +911,17 @@ public static function pseudoTypeProvider(): iterable yield ['numericString', Type::string()]; yield ['traitString', Type::string()]; yield ['positiveInt', Type::int()]; + yield ['true', Type::true()]; + yield ['false', Type::false()]; + yield ['valueOfStrings', null]; + yield ['valueOfIntegers', null]; + yield ['keyOfStrings', null]; + yield ['keyOfIntegers', null]; + yield ['arrayKey', null]; + yield ['intMask', class_exists(IntMask::class) ? Type::int() : null]; + yield ['intMaskOf', class_exists(IntMaskOf::class) ? Type::int() : null]; + yield ['conditional', null]; + yield ['offsetAccess', null]; } /** diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php index 48574a1efe43e..8000f79adf984 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php @@ -16,6 +16,9 @@ */ class PseudoTypesDummy { + public const STRINGS = ['A' => 'A', 'B' => 'B']; + public const INTEGERS = [1, 2]; + /** @var class-string */ public $classString; @@ -45,4 +48,37 @@ class PseudoTypesDummy /** @var literal-string */ public $literalString; + + /** @var true */ + public $true; + + /** @var false */ + public $false; + + /** @var value-of */ + public $valueOfStrings; + + /** @var value-of */ + public $valueOfIntegers; + + /** @var key-of */ + public $keyOfStrings; + + /** @var key-of */ + public $keyOfIntegers; + + /** @var array-key */ + public $arrayKey; + + /** @var int-mask<1,2,4> */ + public $intMask; + + /** @var int-mask-of<1|2|4> */ + public $intMaskOf; + + /** @var (T is int ? string : int) */ + public $conditional; + + /** @var self::STRINGS['A'] */ + public $offsetAccess; } diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php index 519e5b60e5f5f..66cae95749199 100644 --- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php +++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php @@ -300,14 +300,6 @@ private function createType(DocType $docType): ?Type return Type::array($collectionValueType, $collectionKeyType); } - if ($docType instanceof PseudoType) { - if ($docType->underlyingType() instanceof Integer) { - return Type::int(); - } elseif ($docType->underlyingType() instanceof String_) { - return Type::string(); - } - } - $docTypeString = match ($docTypeString) { 'integer' => 'int', 'boolean' => 'bool', @@ -324,7 +316,22 @@ private function createType(DocType $docType): ?Type return Type::array(); } - return null !== $class ? Type::object($class) : Type::builtin($phpType); + if (null === $class) { + return Type::builtin($phpType); + } + + if ($docType instanceof PseudoType) { + if ($docType->underlyingType() instanceof Integer) { + return Type::int(); + } elseif ($docType->underlyingType() instanceof String_) { + return Type::string(); + } else { + // It's safer to fall back to other extractors here, as resolving pseudo types correctly is not easy at the moment + return null; + } + } + + return Type::object($class); } private function normalizeType(string $docType): string From c6ff9846af01cae0a01264366da54bc6b9b088a0 Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Tue, 9 Dec 2025 08:46:11 +0100 Subject: [PATCH 30/57] [Config] Add default generic to Configuration to TParent generic --- .../Component/Config/Definition/Builder/FloatNodeDefinition.php | 2 +- .../Config/Definition/Builder/IntegerNodeDefinition.php | 2 +- src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php | 2 +- .../Component/Config/Definition/Builder/NodeDefinition.php | 2 +- .../Config/Definition/Builder/NumericNodeDefinition.php | 2 +- .../Config/Definition/Builder/ScalarNodeDefinition.php | 2 +- .../Config/Definition/Builder/StringNodeDefinition.php | 2 +- .../Config/Definition/Builder/VariableNodeDefinition.php | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Config/Definition/Builder/FloatNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/FloatNodeDefinition.php index 2548edc320c59..7fffca787bfc1 100644 --- a/src/Symfony/Component/Config/Definition/Builder/FloatNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/FloatNodeDefinition.php @@ -16,7 +16,7 @@ /** * This class provides a fluent interface for defining a float node. * - * @template TParent of NodeParentInterface|null + * @template TParent of NodeParentInterface|null = null * * @extends NumericNodeDefinition * diff --git a/src/Symfony/Component/Config/Definition/Builder/IntegerNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/IntegerNodeDefinition.php index 0a16859e1491e..da28d5a40a220 100644 --- a/src/Symfony/Component/Config/Definition/Builder/IntegerNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/IntegerNodeDefinition.php @@ -16,7 +16,7 @@ /** * This class provides a fluent interface for defining an integer node. * - * @template TParent of NodeParentInterface|null + * @template TParent of NodeParentInterface|null = null * * @extends NumericNodeDefinition * diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php index d6cb2c77d3633..330df37c87a34 100644 --- a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php @@ -14,7 +14,7 @@ /** * This class provides a fluent interface for building a node. * - * @template TParent of (NodeDefinition&ParentNodeDefinitionInterface)|null + * @template TParent of (NodeDefinition&ParentNodeDefinitionInterface)|null = null * * @author Johannes M. Schmitt */ diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php index 594bcdd53aeb4..52d85a110924a 100644 --- a/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php @@ -19,7 +19,7 @@ /** * This class provides a fluent interface for defining a node. * - * @template TParent of NodeParentInterface|null + * @template TParent of NodeParentInterface|null = null * * @author Johannes M. Schmitt */ diff --git a/src/Symfony/Component/Config/Definition/Builder/NumericNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/NumericNodeDefinition.php index 47656ea2a96aa..6a8f5b966ee1d 100644 --- a/src/Symfony/Component/Config/Definition/Builder/NumericNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/NumericNodeDefinition.php @@ -16,7 +16,7 @@ /** * Abstract class that contains common code of integer and float node definitions. * - * @template TParent of NodeParentInterface|null + * @template TParent of NodeParentInterface|null = null * * @extends ScalarNodeDefinition * diff --git a/src/Symfony/Component/Config/Definition/Builder/ScalarNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/ScalarNodeDefinition.php index 27e4caf4c7f6d..6409aa69c5445 100644 --- a/src/Symfony/Component/Config/Definition/Builder/ScalarNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/ScalarNodeDefinition.php @@ -16,7 +16,7 @@ /** * This class provides a fluent interface for defining a node. * - * @template TParent of NodeParentInterface|null + * @template TParent of NodeParentInterface|null = null * * @extends VariableNodeDefinition * diff --git a/src/Symfony/Component/Config/Definition/Builder/StringNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/StringNodeDefinition.php index 9ff38d88dc3ad..e14f352adda25 100644 --- a/src/Symfony/Component/Config/Definition/Builder/StringNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/StringNodeDefinition.php @@ -16,7 +16,7 @@ /** * This class provides a fluent interface for defining a node. * - * @template TParent of NodeParentInterface|null + * @template TParent of NodeParentInterface|null = null * * @extends ScalarNodeDefinition * diff --git a/src/Symfony/Component/Config/Definition/Builder/VariableNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/VariableNodeDefinition.php index 6431a70a3a44e..6e6977f6d273b 100644 --- a/src/Symfony/Component/Config/Definition/Builder/VariableNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/VariableNodeDefinition.php @@ -17,7 +17,7 @@ /** * This class provides a fluent interface for defining a node. * - * @template TParent of NodeParentInterface|null + * @template TParent of NodeParentInterface|null = null * * @extends NodeDefinition * From 1f1dc46417e3cc3faf8aab093a44e903b10bfc00 Mon Sep 17 00:00:00 2001 From: Bohdan Pliachenko Date: Tue, 9 Dec 2025 19:09:23 +0200 Subject: [PATCH 31/57] [PropertyInfo] fix `@var` tag support for `PhpStanExtractor` --- .../Extractor/PhpStanExtractor.php | 4 +- .../Tests/Extractor/PhpStanExtractorTest.php | 44 ++++++++++++++++++- .../ConstructorDummyWithVarTagsDocBlock.php | 30 +++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php index 01e2bb77ef35f..0da8ca083b10a 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php @@ -182,7 +182,7 @@ public function getTypesFromConstructor(string $class, string $property): ?array trigger_deprecation('symfony/property-info', '7.3', 'The "%s()" method is deprecated, use "%s::getTypeFromConstructor()" instead.', __METHOD__, self::class); if (null === $tagDocNode = $this->getDocBlockFromConstructor($class, $property)) { - return null; + return $this->getTypes($class, $property); } $types = []; @@ -247,7 +247,7 @@ public function getTypeFromConstructor(string $class, string $property): ?Type { $declaringClass = $class; if (!$tagDocNode = $this->getDocBlockFromConstructor($declaringClass, $property)) { - return null; + return $this->getType($class, $property); } $typeContext = $this->typeContextFactory->createFromClassName($class, $declaringClass); diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php index 654bcc3a7d139..64724d4aed021 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php @@ -20,6 +20,7 @@ use Symfony\Component\PropertyInfo\Tests\Fixtures\Clazz; use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithoutDocBlock; +use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithVarTagsDocBlock; use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue; use Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback; use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; @@ -411,11 +412,31 @@ public static function provideLegacyConstructorTypes() ['date', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], ['timezone', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]], ['dateObject', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]], - ['dateTime', null], + ['dateTime', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], ['ddd', null], ]; } + #[IgnoreDeprecations] + #[Group('legacy')] + #[DataProvider('provideLegacyConstructorTypesWithOnlyVarTags')] + public function testExtractConstructorTypesWithOnlyVarTagsLegacy($property, ?array $type = null) + { + $this->expectUserDeprecationMessage('Since symfony/property-info 7.3: The "Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor::getTypesFromConstructor()" method is deprecated, use "Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor::getTypeFromConstructor()" instead.'); + + $this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithVarTagsDocBlock', $property)); + } + + public static function provideLegacyConstructorTypesWithOnlyVarTags() + { + yield ['date', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]]; + yield ['dateObject', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]]; + yield ['objectsArray', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy'))]]; + yield ['dateTime', null]; + yield ['mixed', null]; + yield ['timezone', null]; + } + #[IgnoreDeprecations] #[Group('legacy')] #[DataProvider('provideLegacyUnionTypes')] @@ -887,10 +908,29 @@ public static function constructorTypesProvider(): iterable yield ['date', Type::int()]; yield ['timezone', Type::object(\DateTimeZone::class)]; yield ['dateObject', Type::object(\DateTimeInterface::class)]; - yield ['dateTime', null]; + yield ['dateTime', Type::int()]; yield ['ddd', null]; } + #[DataProvider('constructorTypesWithOnlyVarTagsProvider')] + public function testExtractConstructorTypesWithOnlyVarTags(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getTypeFromConstructor(ConstructorDummyWithVarTagsDocBlock::class, $property)); + } + + /** + * @return iterable + */ + public static function constructorTypesWithOnlyVarTagsProvider(): iterable + { + yield ['date', Type::int()]; + yield ['dateObject', Type::object(\DateTimeInterface::class)]; + yield ['objectsArray', Type::array(Type::object(ConstructorDummy::class))]; + yield ['dateTime', null]; + yield ['mixed', null]; + yield ['timezone', null]; + } + #[DataProvider('constructorTypesOfParentClassProvider')] public function testExtractTypeFromConstructorOfParentClass(string $class, string $property, Type $type) { diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php new file mode 100644 index 0000000000000..57e50f4ea2f97 --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Tests\Fixtures; + + +class ConstructorDummyWithVarTagsDocBlock +{ + public function __construct( + public \DateTimeZone $timezone, + /** @var int */ + public $date, + /** @var \DateTimeInterface */ + public $dateObject, + public \DateTimeImmutable $dateTime, + public $mixed, + /** @var ConstructorDummy[] */ + public array $objectsArray, + ) + { + } +} From 9f80dd4f91624dd8331f3d4b8654dcb9f876ec50 Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Mon, 8 Dec 2025 20:46:12 -0300 Subject: [PATCH 32/57] [HttpClient] Fix `ScopingHttpClient` to always pass `base_uri` as `string` instead of parsed `array` --- .../Component/HttpClient/HttpClientTrait.php | 7 +++-- .../HttpClient/RetryableHttpClient.php | 2 ++ .../HttpClient/ScopingHttpClient.php | 7 +++++ .../Tests/ScopingHttpClientTest.php | 28 +++++++++++++++++-- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index 2ee2ad59f5640..5e1f1ebbc231b 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -170,13 +170,14 @@ private static function prepareRequest(?string $method, ?string $url, array $opt unset($options['auth_basic'], $options['auth_bearer']); // Parse base URI - if (\is_string($options['base_uri'])) { - $options['base_uri'] = self::parseUrl($options['base_uri']); + if (\is_string($baseUri = $options['base_uri'] ?? null)) { + $baseUri = self::parseUrl($baseUri); } + unset($options['base_uri']); // Validate and resolve URL $url = self::parseUrl($url, $options['query']); - $url = self::resolveUrl($url, $options['base_uri'], $defaultOptions['query'] ?? []); + $url = self::resolveUrl($url, $baseUri, $defaultOptions['query'] ?? []); } // Finalize normalization of options diff --git a/src/Symfony/Component/HttpClient/RetryableHttpClient.php b/src/Symfony/Component/HttpClient/RetryableHttpClient.php index 76aad263a0200..eff1f917e29fe 100644 --- a/src/Symfony/Component/HttpClient/RetryableHttpClient.php +++ b/src/Symfony/Component/HttpClient/RetryableHttpClient.php @@ -201,6 +201,8 @@ private static function shiftBaseUri(array $options, array &$baseUris): array if ($baseUris) { $baseUri = 1 < \count($baseUris) ? array_shift($baseUris) : current($baseUris); $options['base_uri'] = \is_array($baseUri) ? $baseUri[array_rand($baseUri)] : $baseUri; + } elseif (\is_array($options['base_uri'] ?? null)) { + unset($options['base_uri']); } return $options; diff --git a/src/Symfony/Component/HttpClient/ScopingHttpClient.php b/src/Symfony/Component/HttpClient/ScopingHttpClient.php index da5bdb85b200d..5cf30aef1cab3 100644 --- a/src/Symfony/Component/HttpClient/ScopingHttpClient.php +++ b/src/Symfony/Component/HttpClient/ScopingHttpClient.php @@ -56,9 +56,11 @@ public function request(string $method, string $url, array $options = []): Respo { $e = null; $url = self::parseUrl($url, $options['query'] ?? []); + $resolved = false; if (\is_string($options['base_uri'] ?? null)) { $options['base_uri'] = self::parseUrl($options['base_uri']); + $resolved = true; } try { @@ -72,10 +74,15 @@ public function request(string $method, string $url, array $options = []): Respo $options = self::mergeDefaultOptions($options, $defaultOptions, true); if (\is_string($options['base_uri'] ?? null)) { $options['base_uri'] = self::parseUrl($options['base_uri']); + $resolved = true; } $url = implode('', self::resolveUrl($url, $options['base_uri'] ?? null, $defaultOptions['query'] ?? [])); } + if ($resolved) { + unset($options['base_uri']); + } + foreach ($this->defaultOptionsByRegexp as $regexp => $defaultOptions) { if (preg_match("{{$regexp}}A", $url)) { if (null === $e || $regexp !== $this->defaultRegexp) { diff --git a/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php index 0fbda4e2a2619..ae8fdaa89d8c9 100644 --- a/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php @@ -14,6 +14,9 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\Exception\InvalidArgumentException; use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Component\HttpClient\RetryableHttpClient; use Symfony\Component\HttpClient\ScopingHttpClient; class ScopingHttpClientTest extends TestCase @@ -94,10 +97,31 @@ public function testForBaseUri() $client = ScopingHttpClient::forBaseUri(new MockHttpClient(null, null), 'http://example.com/foo'); $response = $client->request('GET', '/bar'); - $this->assertSame('http://example.com/foo', implode('', $response->getRequestOptions()['base_uri'])); $this->assertSame('http://example.com/bar', $response->getInfo('url')); $response = $client->request('GET', 'http://foo.bar/'); - $this->assertNull($response->getRequestOptions()['base_uri']); + $this->assertSame('http://foo.bar/', $response->getInfo('url')); + } + + public function testRetryableHttpClientIntegration() + { + $responses = [ + new MockResponse(info: ['http_code' => 503]), + new MockResponse(info: ['http_code' => 503]), + new MockResponse(info: ['http_code' => 503]), + new MockResponse(), + ]; + + $client = ScopingHttpClient::forBaseUri( + new RetryableHttpClient( + new MockHttpClient($responses), + new GenericRetryStrategy(delayMs: 0) + ), + 'https://foo.example.com/app/', + ); + + $response = $client->request('GET', 'santysisi'); + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('https://foo.example.com/app/santysisi', $response->getInfo('url')); } } From ffcc3418df6b3daeac8a291060b83c12f673eb59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20B=C3=B6lsterli?= Date: Wed, 10 Dec 2025 15:09:03 +0100 Subject: [PATCH 33/57] empty string condition for place name --- src/Symfony/Component/Workflow/Arc.php | 2 +- src/Symfony/Component/Workflow/Tests/ArcTest.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Workflow/Arc.php b/src/Symfony/Component/Workflow/Arc.php index 07b2b9236cfa1..073466a4c1e50 100644 --- a/src/Symfony/Component/Workflow/Arc.php +++ b/src/Symfony/Component/Workflow/Arc.php @@ -23,7 +23,7 @@ public function __construct( if ($weight < 1) { throw new \InvalidArgumentException(\sprintf('The weight must be greater than 0, %d given.', $weight)); } - if (!$place) { + if ('' === $place) { throw new \InvalidArgumentException('The place name cannot be empty.'); } } diff --git a/src/Symfony/Component/Workflow/Tests/ArcTest.php b/src/Symfony/Component/Workflow/Tests/ArcTest.php index 11e6b1b40187b..801c02a2725b9 100644 --- a/src/Symfony/Component/Workflow/Tests/ArcTest.php +++ b/src/Symfony/Component/Workflow/Tests/ArcTest.php @@ -31,4 +31,10 @@ public function testConstructorWithInvalidWeight() new Arc('not empty', 0); } + + public function testConstructorWithZeroPlaceName() + { + $arc = new Arc('0', 1); + $this->assertEquals('0', $arc->place); + } } From bba0963c4b636d7a2437194dab3671b087a4d6ff Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 10 Dec 2025 17:05:00 +0100 Subject: [PATCH 34/57] [PropertyInfo] Fix upmerge --- .../PropertyInfo/Extractor/PhpStanExtractor.php | 2 +- .../PropertyInfo/Extractor/ReflectionExtractor.php | 2 +- .../Tests/Extractor/PhpStanExtractorTest.php | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php index 8bc4e5995c680..0da8ca083b10a 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php @@ -247,7 +247,7 @@ public function getTypeFromConstructor(string $class, string $property): ?Type { $declaringClass = $class; if (!$tagDocNode = $this->getDocBlockFromConstructor($declaringClass, $property)) { - return $this->getTypes($class, $property); + return $this->getType($class, $property); } $typeContext = $this->typeContextFactory->createFromClassName($class, $declaringClass); diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index dd50d552a7e18..64ded66a537b1 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -479,7 +479,7 @@ public function getWriteInfo(string $class, string $property, array $context = [ $method = $reflClass->getMethod($methodName); if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) { - return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic()); + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisibilityForMethod($method), $method->isStatic()); } } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php index a4cddabc799ec..85e23fb8ab878 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php @@ -434,7 +434,7 @@ public static function provideLegacyConstructorTypes() ['date', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], ['timezone', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]], ['dateObject', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]], - ['dateTime', [new LegacyType(Type::BUILTIN_TYPE_INT)]], + ['dateTime', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]], ['ddd', null], ]; } @@ -451,9 +451,9 @@ public function testExtractConstructorTypesWithOnlyVarTagsLegacy($property, ?arr public static function provideLegacyConstructorTypesWithOnlyVarTags() { - yield ['date', [new LegacyType(Type::BUILTIN_TYPE_INT)]]; - yield ['dateObject', [new LegacyType(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]]; - yield ['objectsArray', [new LegacyType(Type::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(Type::BUILTIN_TYPE_INT), new LegacyType(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy'))]]; + yield ['date', [new LegacyType(LegacyType::BUILTIN_TYPE_INT)]]; + yield ['dateObject', [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]]; + yield ['objectsArray', [new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy'))]]; yield ['dateTime', null]; yield ['mixed', null]; yield ['timezone', null]; @@ -963,7 +963,7 @@ public static function constructorTypesProvider(): iterable yield ['date', Type::int()]; yield ['timezone', Type::object(\DateTimeZone::class)]; yield ['dateObject', Type::object(\DateTimeInterface::class)]; - yield ['dateTime', null]; + yield ['dateTime', Type::int()]; yield ['ddd', null]; } From 5c5a632cdcb3d8fd81a3d7631298e986d4f1f84d Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 10 Dec 2025 17:09:17 +0100 Subject: [PATCH 35/57] [PropertyInfo] Fix typo --- .../Extractor/ReflectionExtractor.php | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index cb51a8e0ff25c..9c9d65b61cdc3 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -264,14 +264,14 @@ public function getReadInfo(string $class, string $property, array $context = [] if ($reflClass->hasMethod($methodName) && $reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags && !$reflClass->getMethod($methodName)->getNumberOfRequiredParameters()) { $method = $reflClass->getMethod($methodName); - return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $methodName, $this->getReadVisiblityForMethod($method), $method->isStatic(), false); + return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $methodName, $this->getReadVisibilityForMethod($method), $method->isStatic(), false); } } if ($allowGetterSetter && $reflClass->hasMethod($getsetter) && ($reflClass->getMethod($getsetter)->getModifiers() & $this->methodReflectionFlags)) { $method = $reflClass->getMethod($getsetter); - return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic(), false); + return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisibilityForMethod($method), $method->isStatic(), false); } if ($allowMagicGet && $reflClass->hasMethod('__get') && (($r = $reflClass->getMethod('__get'))->getModifiers() & $this->methodReflectionFlags)) { @@ -279,7 +279,7 @@ public function getReadInfo(string $class, string $property, array $context = [] } if ($hasProperty && (($r = $reflClass->getProperty($property))->getModifiers() & $this->propertyReflectionFlags)) { - return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($r), $r->isStatic(), true); + return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisibilityForProperty($r), $r->isStatic(), true); } if ($allowMagicCall && $reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags)) { @@ -324,8 +324,8 @@ public function getWriteInfo(string $class, string $property, array $context = [ $removerMethod = $reflClass->getMethod($removerAccessName); $mutator = new PropertyWriteInfo(PropertyWriteInfo::TYPE_ADDER_AND_REMOVER); - $mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $adderAccessName, $this->getWriteVisiblityForMethod($adderMethod), $adderMethod->isStatic())); - $mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $removerAccessName, $this->getWriteVisiblityForMethod($removerMethod), $removerMethod->isStatic())); + $mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $adderAccessName, $this->getWriteVisibilityForMethod($adderMethod), $adderMethod->isStatic())); + $mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $removerAccessName, $this->getWriteVisibilityForMethod($removerMethod), $removerMethod->isStatic())); return $mutator; } @@ -344,7 +344,7 @@ public function getWriteInfo(string $class, string $property, array $context = [ $method = $reflClass->getMethod($methodName); if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) { - return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic()); + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisibilityForMethod($method), $method->isStatic()); } } @@ -361,7 +361,7 @@ public function getWriteInfo(string $class, string $property, array $context = [ $method = $reflClass->getMethod($methodName); if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) { - return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic()); + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisibilityForMethod($method), $method->isStatic()); } } } @@ -374,7 +374,7 @@ public function getWriteInfo(string $class, string $property, array $context = [ if ($accessible) { $method = $reflClass->getMethod($getsetter); - return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetter, $this->getWriteVisiblityForMethod($method), $method->isStatic()); + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetter, $this->getWriteVisibilityForMethod($method), $method->isStatic()); } $errors[] = $methodAccessibleErrors; @@ -384,7 +384,7 @@ public function getWriteInfo(string $class, string $property, array $context = [ if ($accessible) { $method = $reflClass->getMethod($getsetterNonCamelized); - return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetterNonCamelized, $this->getWriteVisiblityForMethod($method), $method->isStatic()); + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetterNonCamelized, $this->getWriteVisibilityForMethod($method), $method->isStatic()); } $errors[] = $methodAccessibleErrors; } @@ -393,7 +393,7 @@ public function getWriteInfo(string $class, string $property, array $context = [ if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) { $reflProperty = $reflClass->getProperty($property); if (!$reflProperty->isReadOnly()) { - return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, $this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic()); + return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, $this->getWriteVisibilityForProperty($reflProperty), $reflProperty->isStatic()); } $errors[] = [\sprintf('The property "%s" in class "%s" is a promoted readonly property.', $property, $reflClass->getName())]; @@ -838,7 +838,7 @@ private function getPropertyFlags(int $accessFlags): int return $propertyFlags; } - private function getReadVisiblityForProperty(\ReflectionProperty $reflectionProperty): string + private function getReadVisibilityForProperty(\ReflectionProperty $reflectionProperty): string { if ($reflectionProperty->isPrivate()) { return PropertyReadInfo::VISIBILITY_PRIVATE; @@ -851,7 +851,7 @@ private function getReadVisiblityForProperty(\ReflectionProperty $reflectionProp return PropertyReadInfo::VISIBILITY_PUBLIC; } - private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod): string + private function getReadVisibilityForMethod(\ReflectionMethod $reflectionMethod): string { if ($reflectionMethod->isPrivate()) { return PropertyReadInfo::VISIBILITY_PRIVATE; @@ -864,7 +864,7 @@ private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod): return PropertyReadInfo::VISIBILITY_PUBLIC; } - private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty): string + private function getWriteVisibilityForProperty(\ReflectionProperty $reflectionProperty): string { if (\PHP_VERSION_ID >= 80400) { if ($reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) { @@ -891,7 +891,7 @@ private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionPro return PropertyWriteInfo::VISIBILITY_PUBLIC; } - private function getWriteVisiblityForMethod(\ReflectionMethod $reflectionMethod): string + private function getWriteVisibilityForMethod(\ReflectionMethod $reflectionMethod): string { if ($reflectionMethod->isPrivate()) { return PropertyWriteInfo::VISIBILITY_PRIVATE; From 208a76d8475c0b28e36a977248159fe7520c633d Mon Sep 17 00:00:00 2001 From: Lctrs Date: Wed, 10 Dec 2025 21:42:40 +0100 Subject: [PATCH 36/57] [HttpClient] mark response stale when age equals freshness lifetime --- src/Symfony/Component/HttpClient/CachingHttpClient.php | 2 +- .../Component/HttpClient/Tests/CachingHttpClientTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/HttpClient/CachingHttpClient.php b/src/Symfony/Component/HttpClient/CachingHttpClient.php index c3f6061ecc736..9fe1b0498ca70 100644 --- a/src/Symfony/Component/HttpClient/CachingHttpClient.php +++ b/src/Symfony/Component/HttpClient/CachingHttpClient.php @@ -559,7 +559,7 @@ private function evaluateCacheFreshness(array $data): Freshness $now = time(); $expires = $data['expires_at']; - if (null !== $expires && $now <= $expires) { + if (null !== $expires && $now < $expires) { return Freshness::Fresh; } diff --git a/src/Symfony/Component/HttpClient/Tests/CachingHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/CachingHttpClientTest.php index 064737abb9b3c..acb785a8dba63 100644 --- a/src/Symfony/Component/HttpClient/Tests/CachingHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/CachingHttpClientTest.php @@ -172,9 +172,9 @@ public function testItDoesntServeAStaleResponse() self::assertSame(200, $response->getStatusCode()); self::assertSame('foo', $response->getContent()); - sleep(5); + sleep(4); - // After 5 seconds, the cached response is still considered valid. + // After 4 seconds, the cached response is still considered valid. $response = $client->request('GET', 'http://example.com/foo-bar'); self::assertSame(200, $response->getStatusCode()); self::assertSame('foo', $response->getContent()); @@ -954,14 +954,14 @@ public function testHeuristicFreshnessWithLastModified() self::assertSame('foo', $response->getContent()); // Heuristic: 10% of 3600s = 360s; should be fresh within this time - sleep(360); // 5 minutes + sleep(359); // 5 minutes $response = $client->request('GET', 'http://example.com/heuristic'); self::assertSame(200, $response->getStatusCode()); self::assertSame('foo', $response->getContent()); // After heuristic expires - sleep(1); // Total 361s, past 360s heuristic + sleep(2); // Total 361s, past 360s heuristic $response = $client->request('GET', 'http://example.com/heuristic'); self::assertSame(200, $response->getStatusCode()); From d47fa204daddd0ce05141356f3a6d07cc40821e0 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 11 Dec 2025 08:52:01 +0100 Subject: [PATCH 37/57] Add split.sh config for contracts --- src/Symfony/Contracts/contracts.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/Symfony/Contracts/contracts.json diff --git a/src/Symfony/Contracts/contracts.json b/src/Symfony/Contracts/contracts.json new file mode 100644 index 0000000000000..1b1a16353f239 --- /dev/null +++ b/src/Symfony/Contracts/contracts.json @@ -0,0 +1,13 @@ +{ + "subtrees": { + "cache-contracts": "Cache", + "deprecation-contracts": "Deprecation", + "event-dispatcher-contracts": "EventDispatcher", + "http-client-contracts": "HttpClient", + "service-contracts": "Service", + "translation-contracts": "Translation" + }, + "defaults": { + "git_constraint": "<1.8.2" + } +} From aa064458214777d9cbf63fc9a6ec84fdd82538d8 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 11 Dec 2025 08:53:36 +0100 Subject: [PATCH 38/57] keep test with Type class from TypeInfo component --- .../Tests/Extractor/PhpStanExtractorTest.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php index 85e23fb8ab878..2a379de3253bd 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php @@ -18,6 +18,7 @@ use Symfony\Component\PropertyInfo\Tests\Fixtures\Clazz; use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithoutDocBlock; +use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithVarTagsDocBlock; use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue; use Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback; use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; @@ -967,6 +968,27 @@ public static function constructorTypesProvider(): iterable yield ['ddd', null]; } + /** + * @dataProvider provideConstructorTypesWithOnlyVarTags + */ + public function testExtractConstructorTypesWithOnlyVarTags(string $property, ?Type $type) + { + $this->assertEquals($type, $this->extractor->getTypeFromConstructor(ConstructorDummyWithVarTagsDocBlock::class, $property)); + } + + /** + * @return iterable + */ + public static function provideConstructorTypesWithOnlyVarTags(): iterable + { + yield ['date', Type::int()]; + yield ['dateObject', Type::object(\DateTimeInterface::class)]; + yield ['objectsArray', Type::array(Type::object(ConstructorDummy::class))]; + yield ['dateTime', null]; + yield ['mixed', null]; + yield ['timezone', null]; + } + /** * @dataProvider constructorTypesOfParentClassProvider */ From dffb59db98aea82a1e41ef2190cf6f029638b3aa Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 11 Dec 2025 09:08:58 +0100 Subject: [PATCH 39/57] Fix split.sh config name --- src/Symfony/Contracts/{contracts.json => splitsh.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Symfony/Contracts/{contracts.json => splitsh.json} (100%) diff --git a/src/Symfony/Contracts/contracts.json b/src/Symfony/Contracts/splitsh.json similarity index 100% rename from src/Symfony/Contracts/contracts.json rename to src/Symfony/Contracts/splitsh.json From 291a54c60c81410e8fde63ca932c272f5d729a59 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 11 Dec 2025 09:56:47 +0100 Subject: [PATCH 40/57] Add split.sh config --- splitsh.json | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 splitsh.json diff --git a/splitsh.json b/splitsh.json new file mode 100644 index 0000000000000..eb105bde4cd16 --- /dev/null +++ b/splitsh.json @@ -0,0 +1,177 @@ +{ + "subtrees": { + "doctrine-bridge": "src/Symfony/Bridge/Doctrine", + "monolog-bridge": "src/Symfony/Bridge/Monolog", + "phpunit-bridge": "src/Symfony/Bridge/PhpUnit", + "proxy-manager-bridge": "src/Symfony/Bridge/ProxyManager", + "psr-http-message-bridge": "src/Symfony/Bridge/PsrHttpMessage", + "twig-bridge": "src/Symfony/Bridge/Twig", + "debug-bundle": "src/Symfony/Bundle/DebugBundle", + "framework-bundle": "src/Symfony/Bundle/FrameworkBundle", + "security-bundle": "src/Symfony/Bundle/SecurityBundle", + "twig-bundle": "src/Symfony/Bundle/TwigBundle", + "web-profiler-bundle": "src/Symfony/Bundle/WebProfilerBundle", + "asset": "src/Symfony/Component/Asset", + "asset-mapper": "src/Symfony/Component/AssetMapper", + "browser-kit": "src/Symfony/Component/BrowserKit", + "cache": "src/Symfony/Component/Cache", + "clock": "src/Symfony/Component/Clock", + "config": "src/Symfony/Component/Config", + "console": "src/Symfony/Component/Console", + "css-selector": "src/Symfony/Component/CssSelector", + "dependency-injection": "src/Symfony/Component/DependencyInjection", + "dom-crawler": "src/Symfony/Component/DomCrawler", + "dotenv": "src/Symfony/Component/Dotenv", + "error-handler": "src/Symfony/Component/ErrorHandler", + "event-dispatcher": "src/Symfony/Component/EventDispatcher", + "expression-language": "src/Symfony/Component/ExpressionLanguage", + "filesystem": "src/Symfony/Component/Filesystem", + "finder": "src/Symfony/Component/Finder", + "form": "src/Symfony/Component/Form", + "html-sanitizer": "src/Symfony/Component/HtmlSanitizer", + "http-client": "src/Symfony/Component/HttpClient", + "http-foundation": "src/Symfony/Component/HttpFoundation", + "http-kernel": "src/Symfony/Component/HttpKernel", + "intl": "src/Symfony/Component/Intl", + "ldap": "src/Symfony/Component/Ldap", + "lock": { + "prefixes": [{ "from": "src/Symfony/Component/Lock", "to": "", "excludes": ["Bridge"] }] + }, + "mailer": { + "prefixes": [{ "from": "src/Symfony/Component/Mailer", "to": "", "excludes": ["Bridge"] }] + }, + "amazon-mailer": "src/Symfony/Component/Mailer/Bridge/Amazon", + "brevo-mailer": "src/Symfony/Component/Mailer/Bridge/Brevo", + "google-mailer": "src/Symfony/Component/Mailer/Bridge/Google", + "infobip-mailer": "src/Symfony/Component/Mailer/Bridge/Infobip", + "mail-pace-mailer": "src/Symfony/Component/Mailer/Bridge/MailPace", + "mailchimp-mailer": "src/Symfony/Component/Mailer/Bridge/Mailchimp", + "mailer-send-mailer": "src/Symfony/Component/Mailer/Bridge/MailerSend", + "mailgun-mailer": "src/Symfony/Component/Mailer/Bridge/Mailgun", + "mailjet-mailer": "src/Symfony/Component/Mailer/Bridge/Mailjet", + "oh-my-smtp-mailer": "src/Symfony/Component/Mailer/Bridge/OhMySmtp", + "postmark-mailer": "src/Symfony/Component/Mailer/Bridge/Postmark", + "scaleway-mailer": "src/Symfony/Component/Mailer/Bridge/Scaleway", + "sendgrid-mailer": "src/Symfony/Component/Mailer/Bridge/Sendgrid", + "sendinblue-mailer": "src/Symfony/Component/Mailer/Bridge/Sendinblue", + "messenger": { + "prefixes": [{ "from": "src/Symfony/Component/Messenger", "to": "", "excludes": ["Bridge"] }] + }, + "amazon-sqs-messenger": "src/Symfony/Component/Messenger/Bridge/AmazonSqs", + "amqp-messenger": "src/Symfony/Component/Messenger/Bridge/Amqp", + "beanstalkd-messenger": "src/Symfony/Component/Messenger/Bridge/Beanstalkd", + "doctrine-messenger": "src/Symfony/Component/Messenger/Bridge/Doctrine", + "redis-messenger": "src/Symfony/Component/Messenger/Bridge/Redis", + "mime": "src/Symfony/Component/Mime", + "notifier": { + "prefixes": [{ "from": "src/Symfony/Component/Notifier", "to": "", "excludes": ["Bridge"] }] + }, + "all-my-sms-notifier": "src/Symfony/Component/Notifier/Bridge/AllMySms", + "amazon-sns-notifier": "src/Symfony/Component/Notifier/Bridge/AmazonSns", + "bandwidth-notifier": "src/Symfony/Component/Notifier/Bridge/Bandwidth", + "brevo-notifier": "src/Symfony/Component/Notifier/Bridge/Brevo", + "chatwork-notifier": "src/Symfony/Component/Notifier/Bridge/Chatwork", + "click-send-notifier": "src/Symfony/Component/Notifier/Bridge/ClickSend", + "clickatell-notifier": "src/Symfony/Component/Notifier/Bridge/Clickatell", + "contact-everyone-notifier": "src/Symfony/Component/Notifier/Bridge/ContactEveryone", + "discord-notifier": "src/Symfony/Component/Notifier/Bridge/Discord", + "engagespot-notifier": "src/Symfony/Component/Notifier/Bridge/Engagespot", + "esendex-notifier": "src/Symfony/Component/Notifier/Bridge/Esendex", + "expo-notifier": "src/Symfony/Component/Notifier/Bridge/Expo", + "fake-chat-notifier": "src/Symfony/Component/Notifier/Bridge/FakeChat", + "fake-sms-notifier": "src/Symfony/Component/Notifier/Bridge/FakeSms", + "firebase-notifier": "src/Symfony/Component/Notifier/Bridge/Firebase", + "forty-six-elks-notifier": "src/Symfony/Component/Notifier/Bridge/FortySixElks", + "free-mobile-notifier": "src/Symfony/Component/Notifier/Bridge/FreeMobile", + "gateway-api-notifier": "src/Symfony/Component/Notifier/Bridge/GatewayApi", + "gitter-notifier": "src/Symfony/Component/Notifier/Bridge/Gitter", + "go-ip-notifier": "src/Symfony/Component/Notifier/Bridge/GoIp", + "google-chat-notifier": "src/Symfony/Component/Notifier/Bridge/GoogleChat", + "infobip-notifier": "src/Symfony/Component/Notifier/Bridge/Infobip", + "iqsms-notifier": "src/Symfony/Component/Notifier/Bridge/Iqsms", + "isendpro-notifier": "src/Symfony/Component/Notifier/Bridge/Isendpro", + "kaz-info-teh-notifier": "src/Symfony/Component/Notifier/Bridge/KazInfoTeh", + "light-sms-notifier": "src/Symfony/Component/Notifier/Bridge/LightSms", + "line-notify-notifier": "src/Symfony/Component/Notifier/Bridge/LineNotify", + "linked-in-notifier": "src/Symfony/Component/Notifier/Bridge/LinkedIn", + "mailjet-notifier": "src/Symfony/Component/Notifier/Bridge/Mailjet", + "mastodon-notifier": "src/Symfony/Component/Notifier/Bridge/Mastodon", + "mattermost-notifier": "src/Symfony/Component/Notifier/Bridge/Mattermost", + "mercure-notifier": "src/Symfony/Component/Notifier/Bridge/Mercure", + "message-bird-notifier": "src/Symfony/Component/Notifier/Bridge/MessageBird", + "message-media-notifier": "src/Symfony/Component/Notifier/Bridge/MessageMedia", + "microsoft-teams-notifier": "src/Symfony/Component/Notifier/Bridge/MicrosoftTeams", + "mobyt-notifier": "src/Symfony/Component/Notifier/Bridge/Mobyt", + "novu-notifier": "src/Symfony/Component/Notifier/Bridge/Novu", + "ntfy-notifier": "src/Symfony/Component/Notifier/Bridge/Ntfy", + "octopush-notifier": "src/Symfony/Component/Notifier/Bridge/Octopush", + "one-signal-notifier": "src/Symfony/Component/Notifier/Bridge/OneSignal", + "orange-sms-notifier": "src/Symfony/Component/Notifier/Bridge/OrangeSms", + "ovh-cloud-notifier": "src/Symfony/Component/Notifier/Bridge/OvhCloud", + "pager-duty-notifier": "src/Symfony/Component/Notifier/Bridge/PagerDuty", + "plivo-notifier": "src/Symfony/Component/Notifier/Bridge/Plivo", + "pushover-notifier": "src/Symfony/Component/Notifier/Bridge/Pushover", + "redlink-notifier": "src/Symfony/Component/Notifier/Bridge/Redlink", + "ring-central-notifier": "src/Symfony/Component/Notifier/Bridge/RingCentral", + "rocket-chat-notifier": "src/Symfony/Component/Notifier/Bridge/RocketChat", + "sendberry-notifier": "src/Symfony/Component/Notifier/Bridge/Sendberry", + "sendinblue-notifier": "src/Symfony/Component/Notifier/Bridge/Sendinblue", + "simple-textin-notifier": "src/Symfony/Component/Notifier/Bridge/SimpleTextin", + "sinch-notifier": "src/Symfony/Component/Notifier/Bridge/Sinch", + "slack-notifier": "src/Symfony/Component/Notifier/Bridge/Slack", + "sms77-notifier": "src/Symfony/Component/Notifier/Bridge/Sms77", + "sms-biuras-notifier": "src/Symfony/Component/Notifier/Bridge/SmsBiuras", + "sms-factor-notifier": "src/Symfony/Component/Notifier/Bridge/SmsFactor", + "smsapi-notifier": "src/Symfony/Component/Notifier/Bridge/Smsapi", + "smsc-notifier": "src/Symfony/Component/Notifier/Bridge/Smsc", + "smsmode-notifier": "src/Symfony/Component/Notifier/Bridge/Smsmode", + "spot-hit-notifier": "src/Symfony/Component/Notifier/Bridge/SpotHit", + "telegram-notifier": "src/Symfony/Component/Notifier/Bridge/Telegram", + "telnyx-notifier": "src/Symfony/Component/Notifier/Bridge/Telnyx", + "termii-notifier": "src/Symfony/Component/Notifier/Bridge/Termii", + "turbo-sms-notifier": "src/Symfony/Component/Notifier/Bridge/TurboSms", + "twilio-notifier": "src/Symfony/Component/Notifier/Bridge/Twilio", + "twitter-notifier": "src/Symfony/Component/Notifier/Bridge/Twitter", + "vonage-notifier": "src/Symfony/Component/Notifier/Bridge/Vonage", + "yunpian-notifier": "src/Symfony/Component/Notifier/Bridge/Yunpian", + "zendesk-notifier": "src/Symfony/Component/Notifier/Bridge/Zendesk", + "zulip-notifier": "src/Symfony/Component/Notifier/Bridge/Zulip", + "options-resolver": "src/Symfony/Component/OptionsResolver", + "password-hasher": "src/Symfony/Component/PasswordHasher", + "process": "src/Symfony/Component/Process", + "property-access": "src/Symfony/Component/PropertyAccess", + "property-info": "src/Symfony/Component/PropertyInfo", + "rate-limiter": "src/Symfony/Component/RateLimiter", + "remote-event": "src/Symfony/Component/RemoteEvent", + "routing": "src/Symfony/Component/Routing", + "runtime": "src/Symfony/Component/Runtime", + "scheduler": "src/Symfony/Component/Scheduler", + "security-core": "src/Symfony/Component/Security/Core", + "security-csrf": "src/Symfony/Component/Security/Csrf", + "security-http": "src/Symfony/Component/Security/Http", + "semaphore": "src/Symfony/Component/Semaphore", + "serializer": "src/Symfony/Component/Serializer", + "stopwatch": "src/Symfony/Component/Stopwatch", + "string": "src/Symfony/Component/String", + "templating": "src/Symfony/Component/Templating", + "translation": { + "prefixes": [{ "from": "src/Symfony/Component/Translation", "to": "", "excludes": ["Bridge"] }] + }, + "crowdin-translation-provider": "src/Symfony/Component/Translation/Bridge/Crowdin", + "loco-translation-provider": "src/Symfony/Component/Translation/Bridge/Loco", + "lokalise-translation-provider": "src/Symfony/Component/Translation/Bridge/Lokalise", + "phrase-translation-provider": "src/Symfony/Component/Translation/Bridge/Phrase", + "uid": "src/Symfony/Component/Uid", + "validator": "src/Symfony/Component/Validator", + "var-dumper": "src/Symfony/Component/VarDumper", + "var-exporter": "src/Symfony/Component/VarExporter", + "web-link": "src/Symfony/Component/WebLink", + "webhook": "src/Symfony/Component/Webhook", + "workflow": "src/Symfony/Component/Workflow", + "yaml": "src/Symfony/Component/Yaml", + "contracts": "src/Symfony/Contracts" + }, + "defaults": { + "git_constraint": "<1.8.2" + } +} From bb2e6f9cb6e7942b7bb983a519f040e870bc73d9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 11 Dec 2025 09:57:50 +0100 Subject: [PATCH 41/57] Update splitsh.json --- splitsh.json | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/splitsh.json b/splitsh.json index eb105bde4cd16..4094b63c612b5 100644 --- a/splitsh.json +++ b/splitsh.json @@ -3,7 +3,6 @@ "doctrine-bridge": "src/Symfony/Bridge/Doctrine", "monolog-bridge": "src/Symfony/Bridge/Monolog", "phpunit-bridge": "src/Symfony/Bridge/PhpUnit", - "proxy-manager-bridge": "src/Symfony/Bridge/ProxyManager", "psr-http-message-bridge": "src/Symfony/Bridge/PsrHttpMessage", "twig-bridge": "src/Symfony/Bridge/Twig", "debug-bundle": "src/Symfony/Bundle/DebugBundle", @@ -22,6 +21,7 @@ "dependency-injection": "src/Symfony/Component/DependencyInjection", "dom-crawler": "src/Symfony/Component/DomCrawler", "dotenv": "src/Symfony/Component/Dotenv", + "emoji": "src/Symfony/Component/Emoji", "error-handler": "src/Symfony/Component/ErrorHandler", "event-dispatcher": "src/Symfony/Component/EventDispatcher", "expression-language": "src/Symfony/Component/ExpressionLanguage", @@ -33,6 +33,8 @@ "http-foundation": "src/Symfony/Component/HttpFoundation", "http-kernel": "src/Symfony/Component/HttpKernel", "intl": "src/Symfony/Component/Intl", + "json-path": "src/Symfony/Component/JsonPath", + "json-streamer": "src/Symfony/Component/JsonStreamer", "ldap": "src/Symfony/Component/Ldap", "lock": { "prefixes": [{ "from": "src/Symfony/Component/Lock", "to": "", "excludes": ["Bridge"] }] @@ -40,7 +42,9 @@ "mailer": { "prefixes": [{ "from": "src/Symfony/Component/Mailer", "to": "", "excludes": ["Bridge"] }] }, + "aha-send-mailer": "src/Symfony/Component/Mailer/Bridge/AhaSend", "amazon-mailer": "src/Symfony/Component/Mailer/Bridge/Amazon", + "azure-mailer": "src/Symfony/Component/Mailer/Bridge/Azure", "brevo-mailer": "src/Symfony/Component/Mailer/Bridge/Brevo", "google-mailer": "src/Symfony/Component/Mailer/Bridge/Google", "infobip-mailer": "src/Symfony/Component/Mailer/Bridge/Infobip", @@ -49,11 +53,14 @@ "mailer-send-mailer": "src/Symfony/Component/Mailer/Bridge/MailerSend", "mailgun-mailer": "src/Symfony/Component/Mailer/Bridge/Mailgun", "mailjet-mailer": "src/Symfony/Component/Mailer/Bridge/Mailjet", - "oh-my-smtp-mailer": "src/Symfony/Component/Mailer/Bridge/OhMySmtp", + "mailomat-mailer": "src/Symfony/Component/Mailer/Bridge/Mailomat", + "mailtrap-mailer": "src/Symfony/Component/Mailer/Bridge/Mailtrap", + "postal-mailer": "src/Symfony/Component/Mailer/Bridge/Postal", "postmark-mailer": "src/Symfony/Component/Mailer/Bridge/Postmark", + "resend-mailer": "src/Symfony/Component/Mailer/Bridge/Resend", "scaleway-mailer": "src/Symfony/Component/Mailer/Bridge/Scaleway", "sendgrid-mailer": "src/Symfony/Component/Mailer/Bridge/Sendgrid", - "sendinblue-mailer": "src/Symfony/Component/Mailer/Bridge/Sendinblue", + "sweego-mailer": "src/Symfony/Component/Mailer/Bridge/Sweego", "messenger": { "prefixes": [{ "from": "src/Symfony/Component/Messenger", "to": "", "excludes": ["Bridge"] }] }, @@ -69,6 +76,7 @@ "all-my-sms-notifier": "src/Symfony/Component/Notifier/Bridge/AllMySms", "amazon-sns-notifier": "src/Symfony/Component/Notifier/Bridge/AmazonSns", "bandwidth-notifier": "src/Symfony/Component/Notifier/Bridge/Bandwidth", + "bluesky-notifier": "src/Symfony/Component/Notifier/Bridge/Bluesky", "brevo-notifier": "src/Symfony/Component/Notifier/Bridge/Brevo", "chatwork-notifier": "src/Symfony/Component/Notifier/Bridge/Chatwork", "click-send-notifier": "src/Symfony/Component/Notifier/Bridge/ClickSend", @@ -84,18 +92,21 @@ "forty-six-elks-notifier": "src/Symfony/Component/Notifier/Bridge/FortySixElks", "free-mobile-notifier": "src/Symfony/Component/Notifier/Bridge/FreeMobile", "gateway-api-notifier": "src/Symfony/Component/Notifier/Bridge/GatewayApi", - "gitter-notifier": "src/Symfony/Component/Notifier/Bridge/Gitter", "go-ip-notifier": "src/Symfony/Component/Notifier/Bridge/GoIp", "google-chat-notifier": "src/Symfony/Component/Notifier/Bridge/GoogleChat", "infobip-notifier": "src/Symfony/Component/Notifier/Bridge/Infobip", "iqsms-notifier": "src/Symfony/Component/Notifier/Bridge/Iqsms", "isendpro-notifier": "src/Symfony/Component/Notifier/Bridge/Isendpro", + "joli-notif-notifier": "src/Symfony/Component/Notifier/Bridge/JoliNotif", "kaz-info-teh-notifier": "src/Symfony/Component/Notifier/Bridge/KazInfoTeh", "light-sms-notifier": "src/Symfony/Component/Notifier/Bridge/LightSms", + "line-bot-notifier": "src/Symfony/Component/Notifier/Bridge/LineBot", "line-notify-notifier": "src/Symfony/Component/Notifier/Bridge/LineNotify", "linked-in-notifier": "src/Symfony/Component/Notifier/Bridge/LinkedIn", + "lox24-notifier": "src/Symfony/Component/Notifier/Bridge/Lox24", "mailjet-notifier": "src/Symfony/Component/Notifier/Bridge/Mailjet", "mastodon-notifier": "src/Symfony/Component/Notifier/Bridge/Mastodon", + "matrix-notifier": "src/Symfony/Component/Notifier/Bridge/Matrix", "mattermost-notifier": "src/Symfony/Component/Notifier/Bridge/Mattermost", "mercure-notifier": "src/Symfony/Component/Notifier/Bridge/Mercure", "message-bird-notifier": "src/Symfony/Component/Notifier/Bridge/MessageBird", @@ -110,32 +121,41 @@ "ovh-cloud-notifier": "src/Symfony/Component/Notifier/Bridge/OvhCloud", "pager-duty-notifier": "src/Symfony/Component/Notifier/Bridge/PagerDuty", "plivo-notifier": "src/Symfony/Component/Notifier/Bridge/Plivo", + "primotexto-notifier": "src/Symfony/Component/Notifier/Bridge/Primotexto", "pushover-notifier": "src/Symfony/Component/Notifier/Bridge/Pushover", + "pushy-notifier": "src/Symfony/Component/Notifier/Bridge/Pushy", "redlink-notifier": "src/Symfony/Component/Notifier/Bridge/Redlink", "ring-central-notifier": "src/Symfony/Component/Notifier/Bridge/RingCentral", "rocket-chat-notifier": "src/Symfony/Component/Notifier/Bridge/RocketChat", "sendberry-notifier": "src/Symfony/Component/Notifier/Bridge/Sendberry", - "sendinblue-notifier": "src/Symfony/Component/Notifier/Bridge/Sendinblue", + "sevenio-notifier": "src/Symfony/Component/Notifier/Bridge/Sevenio", "simple-textin-notifier": "src/Symfony/Component/Notifier/Bridge/SimpleTextin", "sinch-notifier": "src/Symfony/Component/Notifier/Bridge/Sinch", + "sipgate-notifier": "src/Symfony/Component/Notifier/Bridge/Sipgate", "slack-notifier": "src/Symfony/Component/Notifier/Bridge/Slack", "sms77-notifier": "src/Symfony/Component/Notifier/Bridge/Sms77", "sms-biuras-notifier": "src/Symfony/Component/Notifier/Bridge/SmsBiuras", "sms-factor-notifier": "src/Symfony/Component/Notifier/Bridge/SmsFactor", + "sms-sluzba-notifier": "src/Symfony/Component/Notifier/Bridge/SmsSluzba", "smsapi-notifier": "src/Symfony/Component/Notifier/Bridge/Smsapi", + "smsbox-notifier": "src/Symfony/Component/Notifier/Bridge/Smsbox", "smsc-notifier": "src/Symfony/Component/Notifier/Bridge/Smsc", + "smsense-notifier": "src/Symfony/Component/Notifier/Bridge/Smsense", "smsmode-notifier": "src/Symfony/Component/Notifier/Bridge/Smsmode", "spot-hit-notifier": "src/Symfony/Component/Notifier/Bridge/SpotHit", + "sweego-notifier": "src/Symfony/Component/Notifier/Bridge/Sweego", "telegram-notifier": "src/Symfony/Component/Notifier/Bridge/Telegram", "telnyx-notifier": "src/Symfony/Component/Notifier/Bridge/Telnyx", "termii-notifier": "src/Symfony/Component/Notifier/Bridge/Termii", "turbo-sms-notifier": "src/Symfony/Component/Notifier/Bridge/TurboSms", "twilio-notifier": "src/Symfony/Component/Notifier/Bridge/Twilio", "twitter-notifier": "src/Symfony/Component/Notifier/Bridge/Twitter", + "unifonic-notifier": "src/Symfony/Component/Notifier/Bridge/Unifonic", "vonage-notifier": "src/Symfony/Component/Notifier/Bridge/Vonage", "yunpian-notifier": "src/Symfony/Component/Notifier/Bridge/Yunpian", "zendesk-notifier": "src/Symfony/Component/Notifier/Bridge/Zendesk", "zulip-notifier": "src/Symfony/Component/Notifier/Bridge/Zulip", + "object-mapper": "src/Symfony/Component/ObjectMapper", "options-resolver": "src/Symfony/Component/OptionsResolver", "password-hasher": "src/Symfony/Component/PasswordHasher", "process": "src/Symfony/Component/Process", @@ -153,7 +173,6 @@ "serializer": "src/Symfony/Component/Serializer", "stopwatch": "src/Symfony/Component/Stopwatch", "string": "src/Symfony/Component/String", - "templating": "src/Symfony/Component/Templating", "translation": { "prefixes": [{ "from": "src/Symfony/Component/Translation", "to": "", "excludes": ["Bridge"] }] }, @@ -161,6 +180,7 @@ "loco-translation-provider": "src/Symfony/Component/Translation/Bridge/Loco", "lokalise-translation-provider": "src/Symfony/Component/Translation/Bridge/Lokalise", "phrase-translation-provider": "src/Symfony/Component/Translation/Bridge/Phrase", + "type-info": "src/Symfony/Component/TypeInfo", "uid": "src/Symfony/Component/Uid", "validator": "src/Symfony/Component/Validator", "var-dumper": "src/Symfony/Component/VarDumper", From 65c7163352ae7985e96ab2b1168889d9468f8707 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 11 Dec 2025 10:04:38 +0100 Subject: [PATCH 42/57] Update splitsh.json --- splitsh.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/splitsh.json b/splitsh.json index 4094b63c612b5..defad786dacc5 100644 --- a/splitsh.json +++ b/splitsh.json @@ -39,6 +39,7 @@ "lock": { "prefixes": [{ "from": "src/Symfony/Component/Lock", "to": "", "excludes": ["Bridge"] }] }, + "dynamo-db-lock": "src/Symfony/Component/Lock/Bridge/DynamoDb", "mailer": { "prefixes": [{ "from": "src/Symfony/Component/Mailer", "to": "", "excludes": ["Bridge"] }] }, @@ -55,6 +56,7 @@ "mailjet-mailer": "src/Symfony/Component/Mailer/Bridge/Mailjet", "mailomat-mailer": "src/Symfony/Component/Mailer/Bridge/Mailomat", "mailtrap-mailer": "src/Symfony/Component/Mailer/Bridge/Mailtrap", + "microsoft-graph-mailer": "src/Symfony/Component/Mailer/Bridge/MicrosoftGraph", "postal-mailer": "src/Symfony/Component/Mailer/Bridge/Postal", "postmark-mailer": "src/Symfony/Component/Mailer/Bridge/Postmark", "resend-mailer": "src/Symfony/Component/Mailer/Bridge/Resend", From 7e174f9ee7e255f7ba110a629a33bb5659cf33eb Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 11 Dec 2025 10:06:08 +0100 Subject: [PATCH 43/57] Update splitsh.json --- splitsh.json | 1 - 1 file changed, 1 deletion(-) diff --git a/splitsh.json b/splitsh.json index defad786dacc5..232189f349bc0 100644 --- a/splitsh.json +++ b/splitsh.json @@ -135,7 +135,6 @@ "sinch-notifier": "src/Symfony/Component/Notifier/Bridge/Sinch", "sipgate-notifier": "src/Symfony/Component/Notifier/Bridge/Sipgate", "slack-notifier": "src/Symfony/Component/Notifier/Bridge/Slack", - "sms77-notifier": "src/Symfony/Component/Notifier/Bridge/Sms77", "sms-biuras-notifier": "src/Symfony/Component/Notifier/Bridge/SmsBiuras", "sms-factor-notifier": "src/Symfony/Component/Notifier/Bridge/SmsFactor", "sms-sluzba-notifier": "src/Symfony/Component/Notifier/Bridge/SmsSluzba", From 4d992d563873cffdff9e94b365332331de9cdf77 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 11 Dec 2025 09:02:20 +0100 Subject: [PATCH 44/57] add unit test job for PHP 8.6 --- .github/workflows/unit-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 792d3c7ebae6a..fe0a5d20349c4 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -34,7 +34,7 @@ jobs: - php: '8.3' - php: '8.4' - php: '8.5' - #mode: experimental + - php: '8.6' fail-fast: false runs-on: ubuntu-24.04 @@ -181,7 +181,7 @@ jobs: echo -e "\n\\e[32mOK\\e[0m $title\\n\\n::endgroup::" fi - [[ "${{ matrix.mode }}" = experimental ]] || (exit $ok) + exit $ok } export -f _run_tests From 6cf24f3a15f492a4b5332a5f9e5037798364d93a Mon Sep 17 00:00:00 2001 From: Quentin Schuler Date: Thu, 11 Dec 2025 11:50:54 +0100 Subject: [PATCH 45/57] Fix the generated PHP code when using array shape. --- .../JsonStreamer/Read/PhpGenerator.php | 29 +++++++++++-------- .../Fixtures/stream_reader/array_shape.php | 8 +++++ .../stream_reader/array_shape.stream.php | 17 +++++++++++ .../stream_reader/nullable_backed_enum.php | 2 +- .../nullable_backed_enum.stream.php | 2 +- .../stream_reader/nullable_object.php | 2 +- .../stream_reader/nullable_object.stream.php | 2 +- .../stream_reader/nullable_object_dict.php | 2 +- .../nullable_object_dict.stream.php | 2 +- .../stream_reader/nullable_object_list.php | 2 +- .../nullable_object_list.stream.php | 2 +- .../object_with_nullable_properties.php | 2 +- ...object_with_nullable_properties.stream.php | 2 +- .../stream_reader/object_with_union.php | 2 +- .../object_with_union.stream.php | 2 +- .../Tests/Fixtures/stream_reader/union.php | 2 +- .../Fixtures/stream_reader/union.stream.php | 2 +- .../Tests/Read/StreamReaderGeneratorTest.php | 2 ++ 18 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/array_shape.php create mode 100644 src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/array_shape.stream.php diff --git a/src/Symfony/Component/JsonStreamer/Read/PhpGenerator.php b/src/Symfony/Component/JsonStreamer/Read/PhpGenerator.php index 8aa6f6ebfd025..aa89d4f0f4c6f 100644 --- a/src/Symfony/Component/JsonStreamer/Read/PhpGenerator.php +++ b/src/Symfony/Component/JsonStreamer/Read/PhpGenerator.php @@ -58,7 +58,7 @@ public function generate(DataModelNodeInterface $dataModel, bool $decodeFromStre .$providers .($this->canBeDecodedWithJsonDecode($dataModel, $decodeFromStream) ? $this->line(' return \\'.Decoder::class.'::decodeStream($stream, 0, null);', $context) - : $this->line(' return $providers[\''.$dataModel->getIdentifier().'\']($stream, 0, null);', $context)) + : $this->line(' return $providers['.$this->quote($dataModel->getIdentifier()).']($stream, 0, null);', $context)) .$this->line('};', $context); } @@ -71,7 +71,7 @@ public function generate(DataModelNodeInterface $dataModel, bool $decodeFromStre .$providers .($this->canBeDecodedWithJsonDecode($dataModel, $decodeFromStream) ? $this->line(' return \\'.Decoder::class.'::decodeString((string) $string);', $context) - : $this->line(' return $providers[\''.$dataModel->getIdentifier().'\'](\\'.Decoder::class.'::decodeString((string) $string));', $context)) + : $this->line(' return $providers['.$this->quote($dataModel->getIdentifier()).'](\\'.Decoder::class.'::decodeString((string) $string));', $context)) .$this->line('};', $context); } @@ -94,7 +94,7 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro $accessor = $decodeFromStream ? '\\'.Decoder::class.'::decodeStream($stream, $offset, $length)' : '$data'; $arguments = $decodeFromStream ? '$stream, $offset, $length' : '$data'; - return $this->line("\$providers['".$node->getIdentifier()."'] = static function ($arguments) {", $context) + return $this->line('$providers['.$this->quote($node->getIdentifier())."] = static function ($arguments) {", $context) .$this->line(' return '.$this->generateValueFormat($node, $accessor).';', $context) .$this->line('};', $context); } @@ -109,20 +109,20 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro $arguments = $decodeFromStream ? '$stream, $offset, $length' : '$data'; - $php .= $this->line("\$providers['".$node->getIdentifier()."'] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context); + $php .= $this->line('$providers['.$this->quote($node->getIdentifier())."] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context); ++$context['indentation_level']; $php .= $decodeFromStream ? $this->line('$data = \\'.Decoder::class.'::decodeStream($stream, $offset, $length);', $context) : ''; foreach ($node->getNodes() as $n) { - $value = $this->canBeDecodedWithJsonDecode($n, $decodeFromStream) ? $this->generateValueFormat($n, '$data') : '$providers[\''.$n->getIdentifier().'\']($data)'; + $value = $this->canBeDecodedWithJsonDecode($n, $decodeFromStream) ? $this->generateValueFormat($n, '$data') : '$providers['.$this->quote($n->getIdentifier()).']($data)'; $php .= $this->line('if ('.$this->generateCompositeNodeItemCondition($n, '$data').') {', $context) .$this->line(" return $value;", $context) .$this->line('}', $context); } - $php .= $this->line('throw new \\'.UnexpectedValueException::class.'(\\sprintf(\'Unexpected "%s" value for "'.$node->getIdentifier().'".\', \\get_debug_type($data)));', $context); + $php .= $this->line('throw new \\'.UnexpectedValueException::class.'(\\sprintf(\'Unexpected "%s" value for "%s".\', \\get_debug_type($data), '.$this->quote($node->getIdentifier()).'));', $context); --$context['indentation_level']; @@ -132,7 +132,7 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro if ($node instanceof CollectionNode) { $arguments = $decodeFromStream ? '$stream, $offset, $length' : '$data'; - $php = $this->line("\$providers['".$node->getIdentifier()."'] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context); + $php = $this->line('$providers['.$this->quote($node->getIdentifier())."] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context); ++$context['indentation_level']; @@ -146,11 +146,11 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro if ($decodeFromStream) { $php .= $this->canBeDecodedWithJsonDecode($node->getItemNode(), $decodeFromStream) ? $this->line(' yield $k => '.$this->generateValueFormat($node->getItemNode(), '\\'.Decoder::class.'::decodeStream($stream, $v[0], $v[1]);'), $context) - : $this->line(' yield $k => $providers[\''.$node->getItemNode()->getIdentifier().'\']($stream, $v[0], $v[1]);', $context); + : $this->line(' yield $k => $providers['.$this->quote($node->getItemNode()->getIdentifier()).']($stream, $v[0], $v[1]);', $context); } else { $php .= $this->canBeDecodedWithJsonDecode($node->getItemNode(), $decodeFromStream) ? $this->line(' yield $k => $v;', $context) - : $this->line(' yield $k => $providers[\''.$node->getItemNode()->getIdentifier().'\']($v);', $context); + : $this->line(' yield $k => $providers['.$this->quote($node->getItemNode()->getIdentifier()).']($v);', $context); } $php .= $this->line(' }', $context) @@ -175,7 +175,7 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro $arguments = $decodeFromStream ? '$stream, $offset, $length' : '$data'; - $php = $this->line("\$providers['".$node->getIdentifier()."'] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context); + $php = $this->line('$providers['.$this->quote($node->getIdentifier())."] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context); ++$context['indentation_level']; @@ -189,7 +189,7 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro foreach ($node->getProperties() as $streamedName => $property) { $propertyValuePhp = $this->canBeDecodedWithJsonDecode($property['value'], $decodeFromStream) ? $this->generateValueFormat($property['value'], '\\'.Decoder::class.'::decodeStream($stream, $v[0], $v[1])') - : '$providers[\''.$property['value']->getIdentifier().'\']($stream, $v[0], $v[1])'; + : '$providers['.$this->quote($property['value']->getIdentifier()).']($stream, $v[0], $v[1])'; $php .= $this->line(" '$streamedName' => \$object->".$property['name'].' = '.$property['accessor']($propertyValuePhp).',', $context); } @@ -204,7 +204,7 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro foreach ($node->getProperties() as $streamedName => $property) { $propertyValuePhp = $this->canBeDecodedWithJsonDecode($property['value'], $decodeFromStream) ? "\$data['$streamedName'] ?? '_symfony_missing_value'" - : "\\array_key_exists('$streamedName', \$data) ? \$providers['".$property['value']->getIdentifier()."'](\$data['$streamedName']) : '_symfony_missing_value'"; + : "\\array_key_exists('$streamedName', \$data) ? \$providers[".$this->quote($property['value']->getIdentifier())."](\$data['$streamedName']) : '_symfony_missing_value'"; $propertiesValuePhp .= "$separator'".$property['name']."' => ".$property['accessor']($propertyValuePhp); $separator = ', '; } @@ -305,6 +305,11 @@ private function line(string $line, array $context): string return str_repeat(' ', $context['indentation_level']).$line."\n"; } + private function quote(string $identifier): string + { + return \sprintf("'%s'", addcslashes($identifier, "'")); + } + /** * Determines if the $node can be decoded using a simple "json_decode". */ diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/array_shape.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/array_shape.php new file mode 100644 index 0000000000000..f0d75ddba23a7 --- /dev/null +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/array_shape.php @@ -0,0 +1,8 @@ + $v) { + yield $k => \Symfony\Component\JsonStreamer\Read\Decoder::decodeStream($stream, $v[0], $v[1]); + } + }; + return \iterator_to_array($iterable($stream, $data)); + }; + return $providers['array{\'id\': int, \'name\': string}']($stream, 0, null); +}; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.php index fc286791e9f57..1fdf92f9ad238 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.php @@ -14,7 +14,7 @@ if (null === $data) { return null; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null')); }; return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string)); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.stream.php index 1fe954ebf6fd3..eb7c1950d070f 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.stream.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.stream.php @@ -15,7 +15,7 @@ if (null === $data) { return null; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null')); }; return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null']($stream, 0, null); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.php index 28ff2c0828061..e1f7c50822d1f 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.php @@ -16,7 +16,7 @@ if (null === $data) { return null; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null')); }; return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string)); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.stream.php index ee8a34a2f8b8a..8056586d690fa 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.stream.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.stream.php @@ -24,7 +24,7 @@ if (null === $data) { return null; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null')); }; return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null']($stream, 0, null); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.php index 83f8387bde755..22a67ce0679c6 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.php @@ -24,7 +24,7 @@ if (null === $data) { return null; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "array|null".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'array|null')); }; return $providers['array|null'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string)); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.stream.php index c3bf621fed4d9..1b3fe01c609cb 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.stream.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.stream.php @@ -33,7 +33,7 @@ if (null === $data) { return null; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "array|null".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'array|null')); }; return $providers['array|null']($stream, 0, null); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.php index 65cdae459555b..75c5fb7841614 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.php @@ -24,7 +24,7 @@ if (null === $data) { return null; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "list|null".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'list|null')); }; return $providers['list|null'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string)); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.stream.php index 9c035367616a3..b8af34b638acc 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.stream.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.stream.php @@ -33,7 +33,7 @@ if (null === $data) { return null; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "list|null".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'list|null')); }; return $providers['list|null']($stream, 0, null); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.php index 91923525f1d32..0f0f3ab6fd00c 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.php @@ -19,7 +19,7 @@ if (null === $data) { return null; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null')); }; return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNullableProperties'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string)); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.stream.php index c05e0f05d84cf..e7c47d31b5342 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.stream.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.stream.php @@ -27,7 +27,7 @@ if (null === $data) { return null; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null')); }; return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNullableProperties']($stream, 0, null); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.php index 9c64bdb6aa368..7f63358d3e215 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.php @@ -22,7 +22,7 @@ if (\is_string($data)) { return $data; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null|string".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null|string')); }; return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithUnionProperties'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string)); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.stream.php index 1ccf17a7b0bf2..cc6574e1ea17d 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.stream.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.stream.php @@ -29,7 +29,7 @@ if (\is_string($data)) { return $data; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null|string".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null|string')); }; return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithUnionProperties']($stream, 0, null); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.php index ad118d3567dd8..09d6599e7ee82 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.php @@ -30,7 +30,7 @@ if (\is_int($data)) { return $data; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list')); }; return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string)); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.stream.php index ebbc18bc2d2c9..7f4a37b7acd86 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.stream.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.stream.php @@ -39,7 +39,7 @@ if (\is_int($data)) { return $data; } - throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list".', \get_debug_type($data))); + throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list')); }; return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list']($stream, 0, null); }; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Read/StreamReaderGeneratorTest.php b/src/Symfony/Component/JsonStreamer/Tests/Read/StreamReaderGeneratorTest.php index 71b29edeabcb1..5bcd4b8c04da3 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Read/StreamReaderGeneratorTest.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Read/StreamReaderGeneratorTest.php @@ -111,6 +111,8 @@ public static function generatedStreamReaderDataProvider(): iterable yield ['object_list', Type::list(Type::object(ClassicDummy::class))]; yield ['nullable_object_list', Type::nullable(Type::list(Type::object(ClassicDummy::class)))]; + yield ['array_shape', Type::arrayShape(['id' => Type::int(), 'name' => Type::string()])]; + yield ['dict', Type::dict()]; yield ['object_dict', Type::dict(Type::object(ClassicDummy::class))]; yield ['nullable_object_dict', Type::nullable(Type::dict(Type::object(ClassicDummy::class)))]; From 014ed2633c65ec2a88c1e775a3c1a8d99fae7012 Mon Sep 17 00:00:00 2001 From: aaa2000 Date: Thu, 11 Dec 2025 15:30:47 +0100 Subject: [PATCH 46/57] [Serializer] Fix php 8.5 warning unexpected NAN value was coerced to string --- .../Serializer/Encoder/XmlEncoder.php | 2 +- .../Tests/Encoder/XmlEncoderTest.php | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php index 33fdf748da755..840b36d65fcf1 100644 --- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php @@ -464,7 +464,7 @@ private function selectNodeType(\DOMNode $node, mixed $val, string $format, arra return $this->selectNodeType($node, $this->serializer->normalize($val, $format, $context), $format, $context); } elseif (is_numeric($val)) { - return $this->appendText($node, (string) $val); + return $this->appendText($node, is_nan($val) ? 'NAN' : (string) $val); } elseif (\is_string($val) && $this->needsCdataWrapping($val, $context)) { return $this->appendCData($node, $val); } elseif (\is_string($val)) { diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php index 9fddde23dbcdf..21212e847271b 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php @@ -954,6 +954,26 @@ public function testEncodeWithoutComment() $this->assertEquals($expected, $encoder->encode($data, 'xml')); } + public function testEncodeNan() + { + $value = \NAN; + + $expected = ''."\n". + 'NAN'."\n"; + + $this->assertEquals($expected, $this->encoder->encode($value, 'xml')); + } + + public function testEncodeInfinite() + { + $value = \INF; + + $expected = ''."\n". + 'INF'."\n"; + + $this->assertEquals($expected, $this->encoder->encode($value, 'xml')); + } + private function createXmlEncoderWithEnvelopeNormalizer(): XmlEncoder { $normalizers = [ From 1bf0541e71e106b110cd449653f44f3dc2fd27b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20H=C3=B6rner?= Date: Thu, 11 Dec 2025 19:01:19 +0100 Subject: [PATCH 47/57] [Routing] Do not renumber query parameters with numeric key --- src/Symfony/Component/Routing/Generator/UrlGenerator.php | 2 +- .../Component/Routing/Tests/Generator/UrlGeneratorTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index d82b91898194a..4c5ae1c9c1beb 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -272,7 +272,7 @@ protected function doGenerate(array $variables, array $defaults, array $requirem // add a query string if needed $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, fn ($a, $b) => $a == $b ? 0 : 1); - $extra = array_merge($extra, $queryParameters); + $extra = array_replace($extra, $queryParameters); array_walk_recursive($extra, $caster = static function (&$v) use (&$caster) { if (\is_object($v)) { diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index e4c6d0c662021..9abbef7d9f166 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -149,6 +149,7 @@ public static function valuesProvider(): array 'stdClass in nested stdClass' => ['?foo%5Bnested%5D%5Bbaz%5D=bar', 'foo', $nestedStdClass], 'non stringable object' => ['', 'foo', new NonStringableObject()], 'non stringable object but has public property' => ['?foo%5Bfoo%5D=property', 'foo', new NonStringableObjectWithPublicProperty()], + 'numeric key' => ['?123=foo', '123', 'foo'], ]; } From 88761760abb81e53b6579d5b3ba4eb24e9532fd5 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 11 Dec 2025 15:36:26 +0100 Subject: [PATCH 48/57] do not use PHPUnit mock objects without configured expectations --- .../Bridge/Twig/Test/FormLayoutTestCase.php | 4 +- .../Test/Traits/RuntimeLoaderProvider.php | 11 ++-- .../Bridge/Twig/Tests/AppVariableTest.php | 61 +++++++++---------- .../ErrorRenderer/TwigErrorRendererTest.php | 3 +- .../TemplateAttributeListenerTest.php | 9 ++- .../Extension/AbstractDivLayoutTestCase.php | 2 +- .../Extension/AbstractLayoutTestCase.php | 6 +- .../Extension/AbstractTableLayoutTestCase.php | 2 +- .../Tests/Extension/DumpExtensionTest.php | 5 +- .../FormExtensionBootstrap3LayoutTest.php | 7 +-- .../FormExtensionBootstrap4LayoutTest.php | 7 +-- .../FormExtensionBootstrap5LayoutTest.php | 7 +-- .../Extension/FormExtensionDivLayoutTest.php | 4 +- .../Extension/HttpKernelExtensionTest.php | 20 +++--- .../Extension/ImportMapExtensionTest.php | 17 +++--- .../Tests/Extension/RoutingExtensionTest.php | 6 +- .../Extension/SerializerExtensionTest.php | 15 ++--- .../Extension/StopwatchExtensionTest.php | 4 +- .../Bridge/Twig/Tests/Node/DumpNodeTest.php | 10 +-- .../Bridge/Twig/Tests/Node/FormThemeTest.php | 6 +- .../Node/SearchAndRenderBlockNodeTest.php | 22 +++---- .../Bridge/Twig/Tests/Node/TransNodeTest.php | 4 +- ...ranslationDefaultDomainNodeVisitorTest.php | 6 +- .../TranslationNodeVisitorTest.php | 4 +- .../TokenParser/FormThemeTokenParserTest.php | 4 +- .../Tests/Translation/TwigExtractorTest.php | 14 ++--- 26 files changed, 122 insertions(+), 138 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php b/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php index 0c719efc0134b..1fcdcfb2b43a7 100644 --- a/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php @@ -17,7 +17,7 @@ use Symfony\Component\Form\FormRendererInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\Test\FormIntegrationTestCase; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManager; use Twig\Environment; use Twig\Loader\FilesystemLoader; @@ -44,7 +44,7 @@ protected function setUp(): void } $rendererEngine = new TwigRendererEngine($this->getThemes(), $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); + $this->renderer = new FormRenderer($rendererEngine, new CsrfTokenManager()); $this->registerTwigRuntimeLoader($environment, $this->renderer); } diff --git a/src/Symfony/Bridge/Twig/Test/Traits/RuntimeLoaderProvider.php b/src/Symfony/Bridge/Twig/Test/Traits/RuntimeLoaderProvider.php index 52f84a7d8f23b..aa856cf377c41 100644 --- a/src/Symfony/Bridge/Twig/Test/Traits/RuntimeLoaderProvider.php +++ b/src/Symfony/Bridge/Twig/Test/Traits/RuntimeLoaderProvider.php @@ -11,18 +11,17 @@ namespace Symfony\Bridge\Twig\Test\Traits; +use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\Form\FormRenderer; use Twig\Environment; -use Twig\RuntimeLoader\RuntimeLoaderInterface; +use Twig\RuntimeLoader\ContainerRuntimeLoader; trait RuntimeLoaderProvider { protected function registerTwigRuntimeLoader(Environment $environment, FormRenderer $renderer) { - $loader = $this->createMock(RuntimeLoaderInterface::class); - $loader->expects($this->any())->method('load')->willReturnMap([ - ['Symfony\Component\Form\FormRenderer', $renderer], - ]); - $environment->addRuntimeLoader($loader); + $environment->addRuntimeLoader(new ContainerRuntimeLoader(new ServiceLocator([ + FormRenderer::class => fn () => $renderer, + ]))); } } diff --git a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php index beed252e96573..5ae93e92aebe8 100644 --- a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php +++ b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php @@ -15,11 +15,12 @@ use Symfony\Bridge\Twig\AppVariable; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; use Symfony\Component\HttpFoundation\Session\Session; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Translation\LocaleSwitcher; class AppVariableTest extends TestCase @@ -61,9 +62,9 @@ public function testEnvironment() */ public function testGetSession() { - $request = $this->createMock(Request::class); - $request->method('hasSession')->willReturn(true); - $request->method('getSession')->willReturn($session = new Session()); + $session = new Session(); + $request = new Request(); + $request->setSession($session); $this->setRequestStack($request); @@ -86,28 +87,25 @@ public function testGetRequest() public function testGetToken() { - $tokenStorage = $this->createMock(TokenStorageInterface::class); + $tokenStorage = new TokenStorage(); $this->appVariable->setTokenStorage($tokenStorage); - $token = $this->createMock(TokenInterface::class); - $tokenStorage->method('getToken')->willReturn($token); + $token = new NullToken(); + $tokenStorage->setToken($token); $this->assertEquals($token, $this->appVariable->getToken()); } public function testGetUser() { - $this->setTokenStorage($user = $this->createMock(UserInterface::class)); + $this->setTokenStorage($user = new InMemoryUser('john', 'password')); $this->assertEquals($user, $this->appVariable->getUser()); } public function testGetLocale() { - $localeSwitcher = $this->createMock(LocaleSwitcher::class); - $this->appVariable->setLocaleSwitcher($localeSwitcher); - - $localeSwitcher->method('getLocale')->willReturn('fr'); + $this->appVariable->setLocaleSwitcher(new LocaleSwitcher('fr', [])); self::assertEquals('fr', $this->appVariable->getLocale()); } @@ -121,16 +119,14 @@ public function testGetEnabledLocales() public function testGetTokenWithNoToken() { - $tokenStorage = $this->createMock(TokenStorageInterface::class); - $this->appVariable->setTokenStorage($tokenStorage); + $this->appVariable->setTokenStorage(new TokenStorage()); $this->assertNull($this->appVariable->getToken()); } public function testGetUserWithNoToken() { - $tokenStorage = $this->createMock(TokenStorageInterface::class); - $this->appVariable->setTokenStorage($tokenStorage); + $this->appVariable->setTokenStorage(new TokenStorage()); $this->assertNull($this->appVariable->getUser()); } @@ -301,13 +297,11 @@ protected function setRequestStack($request) protected function setTokenStorage($user) { - $tokenStorage = $this->createMock(TokenStorageInterface::class); + $tokenStorage = new TokenStorage(); $this->appVariable->setTokenStorage($tokenStorage); - $token = $this->createMock(TokenInterface::class); - $tokenStorage->method('getToken')->willReturn($token); - - $token->method('getUser')->willReturn($user); + $token = new UsernamePasswordToken($user, 'main'); + $tokenStorage->setToken($token); } private function setFlashMessages($sessionHasStarted = true) @@ -317,16 +311,19 @@ private function setFlashMessages($sessionHasStarted = true) 'warning' => ['Warning #1 message'], 'error' => ['Error #1 message', 'Error #2 message'], ]; - $flashBag = new FlashBag(); - $flashBag->initialize($flashMessages); - $session = $this->createMock(Session::class); - $session->method('isStarted')->willReturn($sessionHasStarted); - $session->method('getFlashBag')->willReturn($flashBag); + $storage = new MockArraySessionStorage(); + $storage->setSessionData([ + '_symfony_flashes' => $flashMessages, + ]); + $session = new Session($storage); + + if ($sessionHasStarted) { + $session->start(); + } - $request = $this->createMock(Request::class); - $request->method('hasSession')->willReturn(true); - $request->method('getSession')->willReturn($session); + $request = new Request(); + $request->setSession($session); $this->setRequestStack($request); return $flashMessages; diff --git a/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php b/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php index 9febc61e61887..6837a84ca819a 100644 --- a/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php @@ -25,7 +25,6 @@ public function testFallbackToNativeRendererIfDebugOn() { $exception = new \Exception(); - $twig = $this->createMock(Environment::class); $nativeRenderer = $this->createMock(HtmlErrorRenderer::class); $nativeRenderer ->expects($this->once()) @@ -33,7 +32,7 @@ public function testFallbackToNativeRendererIfDebugOn() ->with($exception) ; - (new TwigErrorRenderer($twig, $nativeRenderer, true))->render(new \Exception()); + (new TwigErrorRenderer(new Environment(new ArrayLoader()), $nativeRenderer, true))->render(new \Exception()); } public function testFallbackToNativeRendererIfCustomTemplateNotFound() diff --git a/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php b/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php index e1fb7f9575902..da9676885a57c 100644 --- a/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php +++ b/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php @@ -21,6 +21,7 @@ use Symfony\Component\HttpKernel\Event\ViewEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Twig\Environment; +use Twig\Loader\ArrayLoader; class TemplateAttributeListenerTest extends TestCase { @@ -43,7 +44,7 @@ public function testAttribute() ; $request = new Request(); - $kernel = $this->createMock(HttpKernelInterface::class); + $kernel = $this->createStub(HttpKernelInterface::class); $controllerArgumentsEvent = new ControllerArgumentsEvent($kernel, [new TemplateAttributeController(), 'foo'], ['Bar'], $request, null); $listener = new TemplateAttributeListener($twig); @@ -68,9 +69,11 @@ public function testAttribute() public function testForm() { $request = new Request(); - $kernel = $this->createMock(HttpKernelInterface::class); + $kernel = $this->createStub(HttpKernelInterface::class); $controllerArgumentsEvent = new ControllerArgumentsEvent($kernel, [new TemplateAttributeController(), 'foo'], [], $request, null); - $listener = new TemplateAttributeListener($this->createMock(Environment::class)); + $listener = new TemplateAttributeListener(new Environment(new ArrayLoader([ + 'templates/foo.html.twig' => '', + ]))); $form = $this->createMock(FormInterface::class); $form->expects($this->once())->method('createView'); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php index d4ecfce05a2e1..ed0af60a2ce7c 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php @@ -471,7 +471,7 @@ public function testNestedFormError() public function testCsrf() { - $this->csrfTokenManager->expects($this->any()) + $this->csrfTokenManager ->method('getToken') ->willReturn(new CsrfToken('token_id', 'foo&bar')); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php index 25fea73b94a57..12d0c809b1747 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Twig\Tests\Extension; -use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\Stub; use Symfony\Bridge\Twig\Test\FormLayoutTestCase; use Symfony\Component\Form\Extension\Core\Type\PercentType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -25,7 +25,7 @@ abstract class AbstractLayoutTestCase extends FormLayoutTestCase { - protected MockObject&CsrfTokenManagerInterface $csrfTokenManager; + protected Stub&CsrfTokenManagerInterface $csrfTokenManager; protected array $testableFeatures = []; private string $defaultLocale; @@ -39,7 +39,7 @@ protected function setUp(): void $this->defaultLocale = \Locale::getDefault(); \Locale::setDefault('en'); - $this->csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class); + $this->csrfTokenManager = $this->createStub(CsrfTokenManagerInterface::class); parent::setUp(); } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php index c3cdb08e547e8..4147a9e3b6cf4 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php @@ -337,7 +337,7 @@ public function testNestedFormError() public function testCsrf() { - $this->csrfTokenManager->expects($this->any()) + $this->csrfTokenManager ->method('getToken') ->willReturn(new CsrfToken('token_id', 'foo&bar')); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php index 8fe455e5d5706..2ade85b1ea8a5 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php @@ -18,7 +18,6 @@ use Symfony\Component\VarDumper\VarDumper; use Twig\Environment; use Twig\Loader\ArrayLoader; -use Twig\Loader\LoaderInterface; class DumpExtensionTest extends TestCase { @@ -68,7 +67,7 @@ public static function getDumpTags() public function testDump($context, $args, $expectedOutput, $debug = true) { $extension = new DumpExtension(new VarCloner()); - $twig = new Environment($this->createMock(LoaderInterface::class), [ + $twig = new Environment(new ArrayLoader(), [ 'debug' => $debug, 'cache' => false, 'optimizations' => 0, @@ -125,7 +124,7 @@ public function testCustomDumper() '' ); $extension = new DumpExtension(new VarCloner(), $dumper); - $twig = new Environment($this->createMock(LoaderInterface::class), [ + $twig = new Environment(new ArrayLoader(), [ 'debug' => true, 'cache' => false, 'optimizations' => 0, diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php index f658a9338b398..f186d03f14192 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php @@ -16,7 +16,7 @@ use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManager; use Twig\Environment; use Twig\Loader\FilesystemLoader; @@ -60,7 +60,7 @@ public function testMoneyWidgetInIso() 'bootstrap_3_layout.html.twig', 'custom_widgets.html.twig', ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); + $this->renderer = new FormRenderer($rendererEngine, new CsrfTokenManager()); $this->registerTwigRuntimeLoader($environment, $this->renderer); $view = $this->factory @@ -72,8 +72,7 @@ public function testMoneyWidgetInIso()
-HTML - , trim($this->renderWidget($view))); +HTML, trim($this->renderWidget($view))); } protected function getTemplatePaths(): array diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php index 95e72c9abad97..9dacf37250d19 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php @@ -16,7 +16,7 @@ use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManager; use Twig\Environment; use Twig\Loader\FilesystemLoader; @@ -65,7 +65,7 @@ public function testMoneyWidgetInIso() 'bootstrap_4_layout.html.twig', 'custom_widgets.html.twig', ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); + $this->renderer = new FormRenderer($rendererEngine, new CsrfTokenManager()); $this->registerTwigRuntimeLoader($environment, $this->renderer); $view = $this->factory @@ -77,8 +77,7 @@ public function testMoneyWidgetInIso()
-HTML - , trim($this->renderWidget($view))); +HTML, trim($this->renderWidget($view))); } protected function getTemplatePaths(): array diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php index a96b39d9cb2d7..819de841164d9 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php @@ -18,7 +18,7 @@ use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\MoneyType; use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManager; use Twig\Environment; use Twig\Loader\FilesystemLoader; @@ -67,7 +67,7 @@ public function testMoneyWidgetInIso() 'bootstrap_5_layout.html.twig', 'custom_widgets.html.twig', ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->getMockBuilder(CsrfTokenManagerInterface::class)->getMock()); + $this->renderer = new FormRenderer($rendererEngine, new CsrfTokenManager()); $this->registerTwigRuntimeLoader($environment, $this->renderer); $view = $this->factory @@ -76,8 +76,7 @@ public function testMoneyWidgetInIso() self::assertSame(<<<'HTML'
-HTML - , trim($this->renderWidget($view))); +HTML, trim($this->renderWidget($view))); } protected function getTemplatePaths(): array diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index 2b12fb6d3f3bd..c10b20e8e8ad9 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -18,7 +18,7 @@ use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Form\FormView; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManager; use Twig\Environment; use Twig\Loader\FilesystemLoader; @@ -148,7 +148,7 @@ public function testMoneyWidgetInIso() 'form_div_layout.html.twig', 'custom_widgets.html.twig', ], $environment); - $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class)); + $this->renderer = new FormRenderer($rendererEngine, new CsrfTokenManager()); $this->registerTwigRuntimeLoader($environment, $this->renderer); $view = $this->factory diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php index fdb222b929412..c0f7f06cb0a9f 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Twig\Extension\HttpKernelExtension; use Symfony\Bridge\Twig\Extension\HttpKernelRuntime; use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; +use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; @@ -24,7 +25,7 @@ use Symfony\Component\HttpKernel\Fragment\FragmentUriGenerator; use Twig\Environment; use Twig\Loader\ArrayLoader; -use Twig\RuntimeLoader\RuntimeLoaderInterface; +use Twig\RuntimeLoader\ContainerRuntimeLoader; class HttpKernelExtensionTest extends TestCase { @@ -70,15 +71,13 @@ public function testGenerateFragmentUri() $loader = new ArrayLoader([ 'index' => \sprintf(<< true, 'cache' => false]); $twig->addExtension(new HttpKernelExtension()); - $loader = $this->createMock(RuntimeLoaderInterface::class); - $loader->expects($this->any())->method('load')->willReturnMap([ - [HttpKernelRuntime::class, $kernelRuntime], - ]); + $loader = new ContainerRuntimeLoader(new ServiceLocator([ + HttpKernelRuntime::class => fn () => $kernelRuntime, + ])); $twig->addRuntimeLoader($loader); $this->assertMatchesRegularExpression('#/_fragment\?_hash=.+&_path=template%3Dfoo.html.twig%26_format%3Dhtml%26_locale%3Den%26_controller%3DSymfony%255CBundle%255CFrameworkBundle%255CController%255CTemplateController%253A%253AtemplateAction$#', $twig->render('index')); @@ -109,10 +108,9 @@ protected function renderTemplate(FragmentHandler $renderer, $template = '{{ ren $twig = new Environment($loader, ['debug' => true, 'cache' => false]); $twig->addExtension(new HttpKernelExtension()); - $loader = $this->createMock(RuntimeLoaderInterface::class); - $loader->expects($this->any())->method('load')->willReturnMap([ - ['Symfony\Bridge\Twig\Extension\HttpKernelRuntime', new HttpKernelRuntime($renderer)], - ]); + $loader = new ContainerRuntimeLoader(new ServiceLocator([ + HttpKernelRuntime::class => fn () => new HttpKernelRuntime($renderer), + ])); $twig->addRuntimeLoader($loader); return $twig->render('index'); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php index 26a572e1954f5..61a292dc55c27 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php @@ -9,15 +9,16 @@ * file that was distributed with this source code. */ -namespace Extension; +namespace Symfony\Bridge\Twig\Tests\Extension; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\ImportMapExtension; use Symfony\Bridge\Twig\Extension\ImportMapRuntime; use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer; +use Symfony\Component\DependencyInjection\ServiceLocator; use Twig\Environment; use Twig\Loader\ArrayLoader; -use Twig\RuntimeLoader\RuntimeLoaderInterface; +use Twig\RuntimeLoader\ContainerRuntimeLoader; class ImportMapExtensionTest extends TestCase { @@ -35,14 +36,10 @@ public function testItRendersTheImportmap() ->willReturn($expected); $runtime = new ImportMapRuntime($importMapRenderer); - $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); - $mockRuntimeLoader - ->method('load') - ->willReturnMap([ - [ImportMapRuntime::class, $runtime], - ]) - ; - $twig->addRuntimeLoader($mockRuntimeLoader); + $runtimeLoader = new ContainerRuntimeLoader(new ServiceLocator([ + ImportMapRuntime::class => fn () => $runtime, + ])); + $twig->addRuntimeLoader($runtimeLoader); $this->assertSame($expected, $twig->render('template')); } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php index 742a74f325b91..7b1d411e5811b 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php @@ -15,7 +15,7 @@ use Symfony\Bridge\Twig\Extension\RoutingExtension; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Twig\Environment; -use Twig\Loader\LoaderInterface; +use Twig\Loader\ArrayLoader; use Twig\Node\Expression\FilterExpression; use Twig\Source; @@ -26,8 +26,8 @@ class RoutingExtensionTest extends TestCase */ public function testEscaping($template, $mustBeEscaped) { - $twig = new Environment($this->createMock(LoaderInterface::class), ['debug' => true, 'cache' => false, 'autoescape' => 'html', 'optimizations' => 0]); - $twig->addExtension(new RoutingExtension($this->createMock(UrlGeneratorInterface::class))); + $twig = new Environment(new ArrayLoader(), ['debug' => true, 'cache' => false, 'autoescape' => 'html', 'optimizations' => 0]); + $twig->addExtension(new RoutingExtension($this->createStub(UrlGeneratorInterface::class))); $nodes = $twig->parse($twig->tokenize(new Source($template, ''))); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php index 610030cec5a9f..a7f1b49fe118a 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Twig\Extension\SerializerExtension; use Symfony\Bridge\Twig\Extension\SerializerRuntime; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\SerializerModelFixture; +use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\YamlEncoder; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; @@ -23,7 +24,7 @@ use Symfony\Component\Serializer\Serializer; use Twig\Environment; use Twig\Loader\ArrayLoader; -use Twig\RuntimeLoader\RuntimeLoaderInterface; +use Twig\RuntimeLoader\ContainerRuntimeLoader; /** * @author Jesse Rushlow @@ -52,17 +53,13 @@ private function getTwig(string $template): Environment $meta = new ClassMetadataFactory(new AttributeLoader()); $runtime = new SerializerRuntime(new Serializer([new ObjectNormalizer($meta)], [new JsonEncoder(), new YamlEncoder()])); - $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); - $mockRuntimeLoader - ->method('load') - ->willReturnMap([ - ['Symfony\Bridge\Twig\Extension\SerializerRuntime', $runtime], - ]) - ; + $runtimeLoader = new ContainerRuntimeLoader(new ServiceLocator([ + SerializerRuntime::class => fn () => $runtime, + ])); $twig = new Environment(new ArrayLoader(['template' => $template])); $twig->addExtension(new SerializerExtension()); - $twig->addRuntimeLoader($mockRuntimeLoader); + $twig->addRuntimeLoader($runtimeLoader); return $twig; } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php index d7ff03d72ff98..1555c3773684e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php @@ -77,7 +77,7 @@ protected function getStopwatch($events = []) $expectedName->evaluate($name); $this->assertSame($expectedCategory, $category); - return $this->createMock(StopwatchEvent::class); + return new StopwatchEvent('1.0'); }) ; @@ -88,7 +88,7 @@ protected function getStopwatch($events = []) [$expectedName] = array_shift($expectedStopCalls); $expectedName->evaluate($name); - return $this->createMock(StopwatchEvent::class); + return new StopwatchEvent('1.0'); }) ; diff --git a/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php index 6d584c89b44b3..ba9b42b694eef 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php @@ -15,7 +15,7 @@ use Symfony\Bridge\Twig\Node\DumpNode; use Twig\Compiler; use Twig\Environment; -use Twig\Loader\LoaderInterface; +use Twig\Loader\ArrayLoader; use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\Node; @@ -27,7 +27,7 @@ public function testNoVar() { $node = new DumpNode('bar', null, 7); - $env = new Environment($this->createMock(LoaderInterface::class)); + $env = new Environment(new ArrayLoader()); $compiler = new Compiler($env); $expected = <<<'EOTXT' @@ -51,7 +51,7 @@ public function testIndented() { $node = new DumpNode('bar', null, 7); - $env = new Environment($this->createMock(LoaderInterface::class)); + $env = new Environment(new ArrayLoader()); $compiler = new Compiler($env); $expected = <<<'EOTXT' @@ -85,7 +85,7 @@ public function testOneVar() $node = new DumpNode('bar', $vars, 7); - $env = new Environment($this->createMock(LoaderInterface::class)); + $env = new Environment(new ArrayLoader()); $compiler = new Compiler($env); $expected = <<<'EOTXT' @@ -117,7 +117,7 @@ public function testMultiVars() $node = new DumpNode('bar', $vars, 7); - $env = new Environment($this->createMock(LoaderInterface::class)); + $env = new Environment(new ArrayLoader()); $compiler = new Compiler($env); $expected = <<<'EOTXT' diff --git a/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php index 132927d88ffe8..b975cf00c9936 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php @@ -18,7 +18,7 @@ use Symfony\Component\Form\FormRendererEngineInterface; use Twig\Compiler; use Twig\Environment; -use Twig\Loader\LoaderInterface; +use Twig\Loader\ArrayLoader; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\NameExpression; @@ -64,8 +64,8 @@ public function testCompile() $node = new FormThemeNode($form, $resources, 0); - $environment = new Environment($this->createMock(LoaderInterface::class)); - $formRenderer = new FormRenderer($this->createMock(FormRendererEngineInterface::class)); + $environment = new Environment(new ArrayLoader()); + $formRenderer = new FormRenderer($this->createStub(FormRendererEngineInterface::class)); $this->registerTwigRuntimeLoader($environment, $formRenderer); $compiler = new Compiler($environment); diff --git a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php index b79449c159319..50039f4440520 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -17,7 +17,7 @@ use Twig\Compiler; use Twig\Environment; use Twig\Extension\CoreExtension; -use Twig\Loader\LoaderInterface; +use Twig\Loader\ArrayLoader; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConditionalExpression; use Twig\Node\Expression\ConstantExpression; @@ -48,7 +48,7 @@ public function testCompileWidget() $node = new SearchAndRenderBlockNode('form_widget', $arguments, 0); } - $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); + $compiler = new Compiler(new Environment(new ArrayLoader())); $this->assertEquals( \sprintf( @@ -85,7 +85,7 @@ public function testCompileWidgetWithVariables() $node = new SearchAndRenderBlockNode('form_widget', $arguments, 0); } - $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); + $compiler = new Compiler(new Environment(new ArrayLoader())); $this->assertEquals( \sprintf( @@ -116,7 +116,7 @@ public function testCompileLabelWithLabel() $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); } - $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); + $compiler = new Compiler(new Environment(new ArrayLoader())); $this->assertEquals( \sprintf( @@ -147,7 +147,7 @@ public function testCompileLabelWithNullLabel() $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); } - $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); + $compiler = new Compiler(new Environment(new ArrayLoader())); // "label" => null must not be included in the output! // Otherwise the default label is overwritten with null. @@ -180,7 +180,7 @@ public function testCompileLabelWithEmptyStringLabel() $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); } - $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); + $compiler = new Compiler(new Environment(new ArrayLoader())); // "label" => null must not be included in the output! // Otherwise the default label is overwritten with null. @@ -211,7 +211,7 @@ public function testCompileLabelWithDefaultLabel() $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); } - $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); + $compiler = new Compiler(new Environment(new ArrayLoader())); $this->assertEquals( \sprintf( @@ -250,7 +250,7 @@ public function testCompileLabelWithAttributes() $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); } - $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); + $compiler = new Compiler(new Environment(new ArrayLoader())); // "label" => null must not be included in the output! // Otherwise the default label is overwritten with null. @@ -296,7 +296,7 @@ public function testCompileLabelWithLabelAndAttributes() $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); } - $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); + $compiler = new Compiler(new Environment(new ArrayLoader())); $this->assertEquals( \sprintf( @@ -343,7 +343,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNull() $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); } - $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); + $compiler = new Compiler(new Environment(new ArrayLoader())); // "label" => null must not be included in the output! // Otherwise the default label is overwritten with null. @@ -412,7 +412,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); } - $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); + $compiler = new Compiler(new Environment(new ArrayLoader())); // "label" => null must not be included in the output! // Otherwise the default label is overwritten with null. diff --git a/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php index 73d03ddd403dd..fc6bfb35072d8 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php @@ -16,7 +16,7 @@ use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Environment; -use Twig\Loader\LoaderInterface; +use Twig\Loader\ArrayLoader; use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\TextNode; @@ -32,7 +32,7 @@ public function testCompileStrict() $vars = class_exists(ContextVariable::class) ? new ContextVariable('foo', 0) : new NameExpression('foo', 0); $node = new TransNode($body, null, null, $vars); - $env = new Environment($this->createMock(LoaderInterface::class), ['strict_variables' => true]); + $env = new Environment(new ArrayLoader(), ['strict_variables' => true]); $compiler = new Compiler($env); $this->assertEquals( diff --git a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php index 40063c6b7817f..3735200421eb7 100644 --- a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php @@ -15,7 +15,7 @@ use Symfony\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor; use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; use Twig\Environment; -use Twig\Loader\LoaderInterface; +use Twig\Loader\ArrayLoader; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Node; @@ -27,7 +27,7 @@ class TranslationDefaultDomainNodeVisitorTest extends TestCase /** @dataProvider getDefaultDomainAssignmentTestData */ public function testDefaultDomainAssignment(Node $node) { - $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]); + $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]); $visitor = new TranslationDefaultDomainNodeVisitor(); // visit trans_default_domain tag @@ -53,7 +53,7 @@ public function testDefaultDomainAssignment(Node $node) /** @dataProvider getDefaultDomainAssignmentTestData */ public function testNewModuleWithoutDefaultDomainTag(Node $node) { - $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]); + $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]); $visitor = new TranslationDefaultDomainNodeVisitor(); // visit trans_default_domain tag diff --git a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php index 6dbd0d273d9fc..2bd2dee71c7f0 100644 --- a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php @@ -15,7 +15,7 @@ use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Environment; -use Twig\Loader\LoaderInterface; +use Twig\Loader\ArrayLoader; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; @@ -30,7 +30,7 @@ class TranslationNodeVisitorTest extends TestCase /** @dataProvider getMessagesExtractionTestData */ public function testMessagesExtraction(Node $node, array $expectedMessages) { - $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]); + $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]); $visitor = new TranslationNodeVisitor(); $visitor->enable(); $visitor->enterNode($node, $env); diff --git a/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php b/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php index 02b6597cf4f57..a46e2424b8bc0 100644 --- a/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php +++ b/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php @@ -16,7 +16,7 @@ use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Environment; -use Twig\Loader\LoaderInterface; +use Twig\Loader\ArrayLoader; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\NameExpression; @@ -31,7 +31,7 @@ class FormThemeTokenParserTest extends TestCase */ public function testCompile($source, $expected) { - $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]); + $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]); $env->addTokenParser(new FormThemeTokenParser()); $source = new Source($source, ''); $stream = $env->tokenize($source); diff --git a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php index f9ae8c348e0fb..82653b81a790d 100644 --- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php @@ -14,11 +14,10 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Translation\TwigExtractor; +use Symfony\Component\Translation\IdentityTranslator; use Symfony\Component\Translation\MessageCatalogue; -use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; use Twig\Loader\ArrayLoader; -use Twig\Loader\LoaderInterface; class TwigExtractorTest extends TestCase { @@ -29,14 +28,13 @@ class TwigExtractorTest extends TestCase */ public function testExtract($template, $messages) { - $loader = $this->createMock(LoaderInterface::class); - $twig = new Environment($loader, [ + $twig = new Environment(new ArrayLoader(), [ 'strict_variables' => true, 'debug' => true, 'cache' => false, 'autoescape' => false, ]); - $twig->addExtension(new TranslationExtension($this->createMock(TranslatorInterface::class))); + $twig->addExtension(new TranslationExtension(new IdentityTranslator())); $extractor = new TwigExtractor($twig); $extractor->setPrefix('prefix'); @@ -99,8 +97,8 @@ public static function getExtractData() */ public function testExtractSyntaxError($resources, array $messages) { - $twig = new Environment($this->createMock(LoaderInterface::class)); - $twig->addExtension(new TranslationExtension($this->createMock(TranslatorInterface::class))); + $twig = new Environment(new ArrayLoader()); + $twig->addExtension(new TranslationExtension(new IdentityTranslator())); $extractor = new TwigExtractor($twig); $catalogue = new MessageCatalogue('en'); @@ -129,7 +127,7 @@ public function testExtractWithFiles($resource) 'cache' => false, 'autoescape' => false, ]); - $twig->addExtension(new TranslationExtension($this->createMock(TranslatorInterface::class))); + $twig->addExtension(new TranslationExtension(new IdentityTranslator())); $extractor = new TwigExtractor($twig); $catalogue = new MessageCatalogue('en'); From 6b5f7d981af75557eed63dbd314665bc1cd5f2f6 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Fri, 12 Dec 2025 07:15:38 +0100 Subject: [PATCH 49/57] [DependencyInjection] Fix `#[AutowireCallable]` sometimes incorrectly inlined --- .../Compiler/InlineServiceDefinitionsPass.php | 7 +- .../Tests/Dumper/PhpDumperTest.php | 40 +++++++++ .../Fixtures/includes/autowiring_classes.php | 28 ++++++ ...autowire_callable_with_service_locator.php | 85 +++++++++++++++++++ .../php/callable_adapter_consumer.php | 2 +- 5 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_callable_with_service_locator.php diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index 9a6c4c530eb8f..660bf510fdc03 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -31,6 +31,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass private array $notInlinedIds = []; private array $inlinedIds = []; private array $notInlinableIds = []; + private array $autowireInline = []; private ?ServiceReferenceGraph $graph = null; public function __construct( @@ -86,7 +87,9 @@ public function process(ContainerBuilder $container): void $remainingInlinedIds[$id] = $id; } else { $container->removeDefinition($id); - $analyzedContainer->removeDefinition($id); + if (!isset($this->autowireInline[$id])) { + $analyzedContainer->removeDefinition($id); + } } } } while ($this->inlinedIds && $this->analyzingPass); @@ -169,6 +172,8 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed private function isInlineableDefinition(string $id, Definition $definition): bool { if (str_starts_with($id, '.autowire_inline.')) { + $this->autowireInline[$id] = true; + return true; } if ($definition->hasErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic() || $definition->hasTag('container.do_not_inline')) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 4ea5aba847b21..44322d7583e9e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -50,6 +50,9 @@ use Symfony\Component\DependencyInjection\Tests\Compiler\Foo; use Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid; use Symfony\Component\DependencyInjection\Tests\Compiler\IInterface; +use Symfony\Component\DependencyInjection\Tests\Compiler\Listener1; +use Symfony\Component\DependencyInjection\Tests\Compiler\Listener2; +use Symfony\Component\DependencyInjection\Tests\Compiler\ListenerResolver; use Symfony\Component\DependencyInjection\Tests\Compiler\MyCallable; use Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory; use Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService; @@ -2176,6 +2179,43 @@ public function testInlineAdapterConsumer() $this->assertNotSame($fooService->factoredFromServiceWithParam, $barService->factoredFromServiceWithParam); } + public function testAutowireCallableWithServiceLocator() + { + $containerBuilder = new ContainerBuilder(); + + $containerBuilder->register(MyInlineService::class, MyInlineService::class); + $containerBuilder->register(\stdClass::class, \stdClass::class); + + $containerBuilder->register(Listener1::class, Listener1::class) + ->setAutowired(true); + + $containerBuilder->register(Listener2::class, Listener2::class) + ->setAutowired(true); + + $containerBuilder->register(ListenerResolver::class, ListenerResolver::class) + ->addArgument(new ServiceLocatorArgument([ + Listener1::class => new TypedReference(Listener1::class, Listener1::class), + Listener2::class => new TypedReference(Listener2::class, Listener2::class), + ])) + ->setPublic(true); + + $containerBuilder->compile(); + + $dumper = new PhpDumper($containerBuilder); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/autowire_callable_with_service_locator.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_AutowireCallable_With_ServiceLocator'])); + + require self::$fixturesPath.'/php/autowire_callable_with_service_locator.php'; + + $container = new \Symfony_DI_PhpDumper_Test_AutowireCallable_With_ServiceLocator(); + + $listenerResolver = $container->get(ListenerResolver::class); + $this->assertTrue($listenerResolver->container->has(Listener1::class)); + $this->assertInstanceOf(\Closure::class, $listenerResolver->container->get(Listener1::class)->closure); + $this->assertTrue($listenerResolver->container->has(Listener2::class)); + $this->assertInstanceOf(\Closure::class, $listenerResolver->container->get(Listener2::class)->closure); + } + /** * @dataProvider getStripCommentsCodes */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php index 73d641f5466f2..72d9050afec0f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -2,8 +2,10 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; +use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; use Symfony\Contracts\Service\Attribute\Required; require __DIR__.'/uniontype_classes.php'; @@ -577,3 +579,29 @@ public function getDependency(): BaseLazyProxyClass return $this->dep; } } + +class Listener1 +{ + public function __construct( + #[AutowireCallable(service: MyInlineService::class, method: 'someMethod1')] + public \Closure $closure, + ) { + } +} + +class Listener2 +{ + public function __construct( + #[AutowireCallable(service: MyInlineService::class, method: 'someMethod2')] + public \Closure $closure, + public \stdClass $someOtherService, + ) { + } +} + +class ListenerResolver +{ + public function __construct(public ContainerInterface $container) + { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_callable_with_service_locator.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_callable_with_service_locator.php new file mode 100644 index 0000000000000..4d82a7f5ef777 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_callable_with_service_locator.php @@ -0,0 +1,85 @@ +services = $this->privates = []; + $this->methodMap = [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\ListenerResolver' => 'getListenerResolverService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + public function getRemovedIds(): array + { + return [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1' => true, + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2' => true, + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\MyInlineService' => true, + 'stdClass' => true, + ]; + } + + /** + * Gets the public 'Symfony\Component\DependencyInjection\Tests\Compiler\ListenerResolver' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\ListenerResolver + */ + protected static function getListenerResolverService($container) + { + return $container->services['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\ListenerResolver'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\ListenerResolver(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1' => ['privates', 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1', 'getListener1Service', false], + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2' => ['privates', 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2', 'getListener2Service', false], + ], [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1' => 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1', + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2' => 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2', + ])); + } + + /** + * Gets the private 'Symfony\Component\DependencyInjection\Tests\Compiler\Listener1' shared autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Listener1 + */ + protected static function getListener1Service($container) + { + return $container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\Listener1(($container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\MyInlineService'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService())->someMethod1(...)); + } + + /** + * Gets the private 'Symfony\Component\DependencyInjection\Tests\Compiler\Listener2' shared autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Listener2 + */ + protected static function getListener2Service($container) + { + return $container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\Listener2(($container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\MyInlineService'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService())->someMethod2(...), new \stdClass()); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/callable_adapter_consumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/callable_adapter_consumer.php index ccd8d2e0bf63b..216dca434e489 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/callable_adapter_consumer.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/callable_adapter_consumer.php @@ -50,6 +50,6 @@ public function getRemovedIds(): array */ protected static function getBarService($container) { - return $container->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\CallableAdapterConsumer(new class(fn () => new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } }); + return $container->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\CallableAdapterConsumer(new class(fn () => (new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } }); } } From 494fa91ef2b185b7356ab121266e9efea71dc407 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 8 Dec 2025 10:52:13 +0100 Subject: [PATCH 50/57] do not use PHPUnit mock objects without configured expectations --- .../Component/Mailer/Test/TransportFactoryTestCase.php | 9 ++++++--- src/Symfony/Component/Mailer/Tests/MailerTest.php | 6 +++--- .../Mailer/Tests/Transport/Smtp/SmtpTransportTest.php | 4 ++-- .../Component/Mailer/Tests/Transport/TransportsTest.php | 7 ++++--- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php b/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php index 5156086ae4ba1..65ecce77d7e17 100644 --- a/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php +++ b/src/Symfony/Component/Mailer/Test/TransportFactoryTestCase.php @@ -13,6 +13,9 @@ use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Mailer\Exception\IncompleteDsnException; use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; use Symfony\Component\Mailer\Transport\Dsn; @@ -102,16 +105,16 @@ public function testIncompleteDsnException(Dsn $dsn) protected function getDispatcher(): EventDispatcherInterface { - return $this->dispatcher ??= $this->createMock(EventDispatcherInterface::class); + return $this->dispatcher ??= new EventDispatcher(); } protected function getClient(): HttpClientInterface { - return $this->client ??= $this->createMock(HttpClientInterface::class); + return $this->client ??= new MockHttpClient(); } protected function getLogger(): LoggerInterface { - return $this->logger ??= $this->createMock(LoggerInterface::class); + return $this->logger ??= new NullLogger(); } } diff --git a/src/Symfony/Component/Mailer/Tests/MailerTest.php b/src/Symfony/Component/Mailer/Tests/MailerTest.php index e873caab8c3b2..0f1a4c356a630 100644 --- a/src/Symfony/Component/Mailer/Tests/MailerTest.php +++ b/src/Symfony/Component/Mailer/Tests/MailerTest.php @@ -21,8 +21,8 @@ use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\AbstractTransport; use Symfony\Component\Mailer\Transport\NullTransport; -use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Stamp\StampInterface; use Symfony\Component\Mime\Address; @@ -35,7 +35,7 @@ public function testSendingRawMessages() { $this->expectException(LogicException::class); - $transport = new Mailer($this->createMock(TransportInterface::class), $this->createMock(MessageBusInterface::class), $this->createMock(EventDispatcherInterface::class)); + $transport = new Mailer(new DummyTransport('localhost'), new MessageBus(), new EventDispatcher()); $transport->send(new RawMessage('Some raw email message')); } @@ -54,7 +54,7 @@ public function dispatch($message, array $stamps = []): Envelope } }; - $stamp = $this->createMock(StampInterface::class); + $stamp = new class implements StampInterface {}; $dispatcher = $this->createMock(EventDispatcherInterface::class); $dispatcher->expects($this->once()) diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php index ad643490e242b..89c6b7b02290a 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php @@ -143,8 +143,8 @@ public function testWriteEncodedRecipientAndSenderAddresses() public function testMessageIdFromServerIsEmbeddedInSentMessageEvent() { $calls = 0; - $eventDispatcher = $this->createMock(EventDispatcherInterface::class); - $eventDispatcher->expects($this->any()) + $eventDispatcher = $this->createStub(EventDispatcherInterface::class); + $eventDispatcher ->method('dispatch') ->with($this->callback(static function ($event) use (&$calls): bool { ++$calls; diff --git a/src/Symfony/Component/Mailer/Tests/Transport/TransportsTest.php b/src/Symfony/Component/Mailer/Tests/Transport/TransportsTest.php index e50db434ffc0c..2e725c90ae64f 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/TransportsTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/TransportsTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mailer\Exception\InvalidArgumentException; +use Symfony\Component\Mailer\Tests\DummyTransport; use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Mailer\Transport\Transports; use Symfony\Component\Mime\Header\Headers; @@ -53,8 +54,8 @@ public function testOverrideTransport() public function testTransportDoesNotExist() { $transport = new Transports([ - 'foo' => $this->createMock(TransportInterface::class), - 'bar' => $this->createMock(TransportInterface::class), + 'foo' => new DummyTransport('localhost'), + 'bar' => new DummyTransport('localhost'), ]); $headers = (new Headers())->addTextHeader('X-Transport', 'foobar'); @@ -69,7 +70,7 @@ public function testTransportRestoredAfterFailure() { $exception = new \Exception(); - $fooTransport = $this->createMock(TransportInterface::class); + $fooTransport = $this->createStub(TransportInterface::class); $fooTransport->method('send') ->willThrowException($exception); From 08b7cdba8b825ed37aa2c17bd2cc385ef105ed8d Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 12 Dec 2025 08:51:57 +0100 Subject: [PATCH 51/57] [BrowserKit] Allow Cookie expiration to be an int --- src/Symfony/Component/BrowserKit/Cookie.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/BrowserKit/Cookie.php b/src/Symfony/Component/BrowserKit/Cookie.php index d801950482a6b..91a2eb20f109f 100644 --- a/src/Symfony/Component/BrowserKit/Cookie.php +++ b/src/Symfony/Component/BrowserKit/Cookie.php @@ -48,17 +48,17 @@ class Cookie /** * Sets a cookie. * - * @param string $name The cookie name - * @param string|null $value The value of the cookie - * @param string|null $expires The time the cookie expires - * @param string|null $path The path on the server in which the cookie will be available on - * @param string $domain The domain that the cookie is available - * @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client - * @param bool $httponly The cookie httponly flag - * @param bool $encodedValue Whether the value is encoded or not - * @param string|null $samesite The cookie samesite attribute + * @param string $name The cookie name + * @param string|null $value The value of the cookie + * @param string|int|null $expires The time the cookie expires + * @param string|null $path The path on the server in which the cookie will be available on + * @param string $domain The domain that the cookie is available + * @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client + * @param bool $httponly The cookie httponly flag + * @param bool $encodedValue Whether the value is encoded or not + * @param string|null $samesite The cookie samesite attribute */ - public function __construct(string $name, ?string $value, ?string $expires = null, ?string $path = null, string $domain = '', bool $secure = false, bool $httponly = true, bool $encodedValue = false, ?string $samesite = null) + public function __construct(string $name, ?string $value, string|int|null $expires = null, ?string $path = null, string $domain = '', bool $secure = false, bool $httponly = true, bool $encodedValue = false, ?string $samesite = null) { if ($encodedValue) { $this->rawValue = $value ?? ''; From 91d0e4c70ef1676850eeb221fabdb0ea5fbbcc2a Mon Sep 17 00:00:00 2001 From: Siebe Vanden Eynden Date: Fri, 12 Dec 2025 17:26:26 +0100 Subject: [PATCH 52/57] bug #62763 [Console] Escape the description passed to the lazy command so the container can be build without wanting to replace the %not-a-param% to parameters --- .../AddConsoleCommandPass.php | 5 ++-- .../AddConsoleCommandPassTest.php | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php index 562627f4b6114..c9efdf3449417 100644 --- a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php +++ b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php @@ -135,10 +135,11 @@ public function process(ContainerBuilder $container): void } if ($description) { - $definition->addMethodCall('setDescription', [str_replace('%', '%%', $description)]); + $escapedDescription = str_replace('%', '%%', $description); + $definition->addMethodCall('setDescription', [$escapedDescription]); $container->register('.'.$id.'.lazy', LazyCommand::class) - ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]); + ->setArguments([$commandName, $aliases, $escapedDescription, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]); $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy'); } diff --git a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php index 9ac660100ea0d..393dfacf298b2 100644 --- a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php +++ b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php @@ -327,6 +327,25 @@ public function testProcessInvokableCommand() self::assertSame('The %command.name% command help content.', $command->getHelp()); } + public function testProcessCommandWithDescriptionWithpercentageSigns() + { + $container = new ContainerBuilder(); + $container + ->register( + 'description_with_percentage_signs_command', + DescriptionWithPercentageSignsCommand::class, + ) + ->addTag('console.command') + ; + $pass = new AddConsoleCommandPass(); + $pass->process($container); + + $command = $container->get('console.command_loader')->get('description-percentage-signs'); + + self::assertTrue($container->has('description_with_percentage_signs_command.command')); + self::assertSame('Just testing %percentage-signs%', $command->getDescription()); + } + public function testProcessInvokableSignalableCommand() { $container = new ContainerBuilder(); @@ -384,6 +403,14 @@ public function __invoke(): void } } +#[AsCommand(name: 'description-percentage-signs', description: 'Just testing %percentage-signs%')] +class DescriptionWithPercentageSignsCommand +{ + public function __invoke(): void + { + } +} + #[AsCommand(name: 'invokable-signalable', description: 'Just testing', help: 'The %command.name% help content.')] class InvokableSignalableCommand implements SignalableCommandInterface { From 9fba12e12b5688693b37245e50af32b4c0eb66e8 Mon Sep 17 00:00:00 2001 From: Patrick Janzen Date: Fri, 12 Dec 2025 08:55:19 +0100 Subject: [PATCH 53/57] [FIX] Comments of horizontal/vertical were mixed up --- src/Symfony/Component/Console/Helper/TableStyle.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Console/Helper/TableStyle.php b/src/Symfony/Component/Console/Helper/TableStyle.php index be956c109edf5..b0c94deb77573 100644 --- a/src/Symfony/Component/Console/Helper/TableStyle.php +++ b/src/Symfony/Component/Console/Helper/TableStyle.php @@ -77,10 +77,11 @@ public function getPaddingChar(): string * * * ╔═══════════════╤══════════════════════════╤══════════════════╗ - * 1 ISBN 2 Title │ Author ║ - * ╠═══════════════╪══════════════════════════╪══════════════════╣ + * ║ ISBN │ Title │ Author ║ + * ╠═══════1═══════╪══════════════════════════╪══════════════════╣ * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ + * ╟───────2───────┼──────────────────────────┼──────────────────╢ * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ * ╚═══════════════╧══════════════════════════╧══════════════════╝ @@ -101,11 +102,10 @@ public function setHorizontalBorderChars(string $outside, ?string $inside = null * * * ╔═══════════════╤══════════════════════════╤══════════════════╗ - * ║ ISBN │ Title │ Author ║ - * ╠═══════1═══════╪══════════════════════════╪══════════════════╣ + * 1 ISBN 2 Title │ Author ║ + * ╠═══════════════╪══════════════════════════╪══════════════════╣ * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║ * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║ - * ╟───────2───────┼──────────────────────────┼──────────────────╢ * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║ * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║ * ╚═══════════════╧══════════════════════════╧══════════════════╝ From 2414cbf37892eb74b9e7b1a996cfe0cd3b1adf49 Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Fri, 12 Dec 2025 02:13:49 +0100 Subject: [PATCH 54/57] Cleanup psalm config --- psalm.xml | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/psalm.xml b/psalm.xml index 86491b32709c7..671836ba0cd4e 100644 --- a/psalm.xml +++ b/psalm.xml @@ -26,22 +26,10 @@ - - - - - - + + - - - - - - - - - - - From c98b2760c3ab56b173b3ba987faa1436b3d5b25c Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Fri, 12 Dec 2025 11:44:18 +0100 Subject: [PATCH 55/57] chore: PHP CS Fixer and protected_to_private --- .php-cs-fixer.dist.php | 2 +- src/Symfony/Component/Cache/CacheItem.php | 2 ++ .../Component/Notifier/Bridge/Engagespot/EngagespotOptions.php | 2 +- .../Component/Notifier/Bridge/Redlink/RedlinkOptions.php | 2 +- src/Symfony/Component/RateLimiter/RateLimiterFactory.php | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index a40bc2038ee9a..5ae092541d710 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -31,7 +31,7 @@ '@Symfony' => true, '@Symfony:risky' => true, 'phpdoc_var_annotation_correct_order' => true, - 'protected_to_private' => false, + 'protected_to_private' => true, 'no_superfluous_phpdoc_tags' => [ 'remove_inheritdoc' => true, 'allow_unused_params' => true, // for future-ready params, to be replaced with https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7377 diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php index 4ce8e0cd1948a..1aa5bcdced6f0 100644 --- a/src/Symfony/Component/Cache/CacheItem.php +++ b/src/Symfony/Component/Cache/CacheItem.php @@ -197,3 +197,5 @@ private function unpack(): bool return true; } } + +// @php-cs-fixer-ignore protected_to_private Friend-level scope access relies on protected properties diff --git a/src/Symfony/Component/Notifier/Bridge/Engagespot/EngagespotOptions.php b/src/Symfony/Component/Notifier/Bridge/Engagespot/EngagespotOptions.php index 54137d5651666..da57404e18556 100644 --- a/src/Symfony/Component/Notifier/Bridge/Engagespot/EngagespotOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Engagespot/EngagespotOptions.php @@ -20,7 +20,7 @@ */ final class EngagespotOptions implements MessageOptionsInterface { - protected $options; + private $options; public function __construct(array $options = []) { diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkOptions.php b/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkOptions.php index c15d5c9639a95..815f7812c3eb3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkOptions.php +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkOptions.php @@ -18,7 +18,7 @@ */ final class RedlinkOptions implements MessageOptionsInterface { - public function __construct(protected array $options = []) + public function __construct(private array $options = []) { } diff --git a/src/Symfony/Component/RateLimiter/RateLimiterFactory.php b/src/Symfony/Component/RateLimiter/RateLimiterFactory.php index fceebc5ddf22a..05f61d71ef940 100644 --- a/src/Symfony/Component/RateLimiter/RateLimiterFactory.php +++ b/src/Symfony/Component/RateLimiter/RateLimiterFactory.php @@ -55,7 +55,7 @@ public function create(?string $key = null): LimiterInterface }; } - protected static function configureOptions(OptionsResolver $options): void + private static function configureOptions(OptionsResolver $options): void { $intervalNormalizer = static function (Options $options, string $interval): \DateInterval { // Create DateTimeImmutable from unix timesatmp, so the default timezone is ignored and we don't need to From 36bc6a467eae40b70cf6f3d5439bc1e69429ddab Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 6 Dec 2025 15:48:05 +0100 Subject: [PATCH 56/57] do not use PHPUnit mock objects without configured expectations --- .../Test/ConstraintValidatorTestCase.php | 57 +++++++++++-------- .../Tests/Command/DebugCommandTest.php | 5 +- .../Constraints/ExpressionValidatorTest.php | 2 +- .../NotCompromisedPasswordValidatorTest.php | 50 +++++----------- .../ValidatorDataCollectorTest.php | 12 ++-- .../LazyLoadingMetadataFactoryTest.php | 6 +- .../Tests/Mapping/Loader/FilesLoaderTest.php | 3 +- .../Tests/Mapping/Loader/LoaderChainTest.php | 16 +++--- .../Mapping/Loader/PropertyInfoLoaderTest.php | 6 +- .../Validator/TraceableValidatorTest.php | 16 +++--- .../Validator/Tests/ValidatorBuilderTest.php | 12 ++-- 11 files changed, 84 insertions(+), 101 deletions(-) diff --git a/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php b/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php index 573210b62410a..0b88525d5b3e2 100644 --- a/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php +++ b/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php @@ -123,10 +123,10 @@ protected function restoreDefaultTimezone() protected function createContext() { - $translator = $this->createMock(TranslatorInterface::class); - $translator->expects($this->any())->method('trans')->willReturnArgument(0); - $validator = $this->createMock(ValidatorInterface::class); - $validator->expects($this->any()) + $translator = $this->createStub(TranslatorInterface::class); + $translator->method('trans')->willReturnArgument(0); + $validator = $this->createStub(ValidatorInterface::class); + $validator ->method('validate') ->willReturnCallback(fn () => $this->expectedViolations[$this->call++] ?? new ConstraintViolationList()); @@ -135,36 +135,47 @@ protected function createContext() $context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath); $context->setConstraint($this->constraint); - $contextualValidatorMockBuilder = $this->getMockBuilder(AssertingContextualValidator::class) - ->setConstructorArgs([$context]); - $contextualValidatorMethods = [ - 'atPath', - 'validate', - 'validateProperty', - 'validatePropertyValue', - 'getViolations', - ]; - - $contextualValidatorMockBuilder->onlyMethods($contextualValidatorMethods); - $contextualValidator = $contextualValidatorMockBuilder->getMock(); - $contextualValidator->expects($this->any()) + if (method_exists($this, 'getStubBuilder')) { + $contextualValidator = self::getStubBuilder(AssertingContextualValidator::class) + ->setConstructorArgs([$context]) + ->onlyMethods([ + 'atPath', + 'validate', + 'validateProperty', + 'validatePropertyValue', + 'getViolations', + ]) + ->getStub(); + } else { + $contextualValidator = $this->getMockBuilder(AssertingContextualValidator::class) + ->setConstructorArgs([$context]) + ->onlyMethods([ + 'atPath', + 'validate', + 'validateProperty', + 'validatePropertyValue', + 'getViolations', + ]) + ->getMock(); + } + + $contextualValidator ->method('atPath') ->willReturnCallback(fn ($path) => $contextualValidator->doAtPath($path)); - $contextualValidator->expects($this->any()) + $contextualValidator ->method('validate') ->willReturnCallback(fn ($value, $constraints = null, $groups = null) => $contextualValidator->doValidate($value, $constraints, $groups)); - $contextualValidator->expects($this->any()) + $contextualValidator ->method('validateProperty') ->willReturnCallback(fn ($object, $propertyName, $groups = null) => $contextualValidator->validateProperty($object, $propertyName, $groups)); - $contextualValidator->expects($this->any()) + $contextualValidator ->method('validatePropertyValue') ->willReturnCallback(fn ($objectOrClass, $propertyName, $value, $groups = null) => $contextualValidator->doValidatePropertyValue($objectOrClass, $propertyName, $value, $groups)); - $contextualValidator->expects($this->any()) + $contextualValidator ->method('getViolations') ->willReturnCallback(fn () => $contextualValidator->doGetViolations()); - $validator->expects($this->any()) + $validator ->method('inContext') - ->with($context) ->willReturn($contextualValidator); return $context; diff --git a/src/Symfony/Component/Validator/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Validator/Tests/Command/DebugCommandTest.php index 660905eba27da..7cc436607a2d3 100644 --- a/src/Symfony/Component/Validator/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Validator/Tests/Command/DebugCommandTest.php @@ -15,7 +15,6 @@ use Symfony\Component\Console\Tester\CommandTester; use Symfony\Component\Validator\Command\DebugCommand; use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; -use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; use Symfony\Component\Validator\Mapping\Loader\AttributeLoader; use Symfony\Component\Validator\Tests\Dummy\DummyClassOne; @@ -180,9 +179,7 @@ public function testOutputWithPathArgument() public function testOutputWithInvalidClassArgument() { - $validator = $this->createMock(MetadataFactoryInterface::class); - - $command = new DebugCommand($validator); + $command = new DebugCommand(new LazyLoadingMetadataFactory()); $tester = new CommandTester($command); $tester->execute(['class' => 'App\\NotFoundResource'], ['decorated' => false]); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php index c237c793f0cbc..a0e2bd6ac0e22 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php @@ -258,7 +258,7 @@ public function testExpressionLanguageUsage() 'expression' => 'false', ]); - $expressionLanguage = $this->createMock(ExpressionLanguage::class); + $expressionLanguage = $this->createStub(ExpressionLanguage::class); $used = false; diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php index 253529444d237..4c34d1ec5d110 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Validator\Tests\Constraints; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\Validator\Constraints\Luhn; use Symfony\Component\Validator\Constraints\NotCompromisedPassword; use Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator; @@ -18,9 +20,7 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; -use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; -use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Kévin Dunglas @@ -204,7 +204,6 @@ public function testInvalidValue() public function testApiError() { $this->expectException(ExceptionInterface::class); - $this->expectExceptionMessage('Problem contacting the Have I been Pwned API.'); $this->validator->validate(self::PASSWORD_TRIGGERING_AN_ERROR, new NotCompromisedPassword()); } @@ -213,8 +212,9 @@ public function testApiError() */ public function testApiErrorSkipped(NotCompromisedPassword $constraint) { + self::expectNotToPerformAssertions(); + $this->validator->validate(self::PASSWORD_TRIGGERING_AN_ERROR, $constraint); - $this->assertTrue(true); // No exception have been thrown } public static function provideErrorSkippingConstraints(): iterable @@ -225,44 +225,20 @@ public static function provideErrorSkippingConstraints(): iterable private function createHttpClientStub(?string $returnValue = null): HttpClientInterface { - $httpClientStub = $this->createMock(HttpClientInterface::class); - $httpClientStub->method('request')->willReturnCallback( - function (string $method, string $url) use ($returnValue): ResponseInterface { - if (self::PASSWORD_TRIGGERING_AN_ERROR_RANGE_URL === $url) { - throw new class('Problem contacting the Have I been Pwned API.') extends \Exception implements ServerExceptionInterface { - public function getResponse(): ResponseInterface - { - throw new \RuntimeException('Not implemented'); - } - }; - } - - $responseStub = $this->createMock(ResponseInterface::class); - $responseStub - ->method('getContent') - ->willReturn($returnValue ?? implode("\r\n", self::RETURN)); - - return $responseStub; + return new MockHttpClient(function ($method, $url) use ($returnValue) { + if (self::PASSWORD_TRIGGERING_AN_ERROR_RANGE_URL !== $url) { + return new MockResponse($returnValue ?? implode("\r\n", self::RETURN)); } - ); - - return $httpClientStub; + }); } private function createHttpClientStubCustomEndpoint($expectedEndpoint): HttpClientInterface { - $httpClientStub = $this->createMock(HttpClientInterface::class); - $httpClientStub->method('request')->with('GET', $expectedEndpoint)->willReturnCallback( - function (string $method, string $url): ResponseInterface { - $responseStub = $this->createMock(ResponseInterface::class); - $responseStub - ->method('getContent') - ->willReturn(implode("\r\n", self::RETURN)); - - return $responseStub; - } - ); + return new MockHttpClient(function ($method, $url) use ($expectedEndpoint) { + $this->assertSame('GET', $method); + $this->assertSame($expectedEndpoint, $url); - return $httpClientStub; + return new MockResponse(implode("\r\n", self::RETURN)); + }); } } diff --git a/src/Symfony/Component/Validator/Tests/DataCollector/ValidatorDataCollectorTest.php b/src/Symfony/Component/Validator/Tests/DataCollector/ValidatorDataCollectorTest.php index 29fd4b38d151f..5bae9b6db835e 100644 --- a/src/Symfony/Component/Validator/Tests/DataCollector/ValidatorDataCollectorTest.php +++ b/src/Symfony/Component/Validator/Tests/DataCollector/ValidatorDataCollectorTest.php @@ -22,14 +22,14 @@ class ValidatorDataCollectorTest extends TestCase { public function testCollectsValidatorCalls() { - $originalValidator = $this->createMock(ValidatorInterface::class); + $originalValidator = $this->createStub(ValidatorInterface::class); $validator = new TraceableValidator($originalValidator); $collector = new ValidatorDataCollector($validator); $violations = new ConstraintViolationList([ - $this->createMock(ConstraintViolation::class), - $this->createMock(ConstraintViolation::class), + $this->createStub(ConstraintViolation::class), + $this->createStub(ConstraintViolation::class), ]); $originalValidator->method('validate')->willReturn($violations); @@ -52,14 +52,14 @@ public function testCollectsValidatorCalls() public function testReset() { - $originalValidator = $this->createMock(ValidatorInterface::class); + $originalValidator = $this->createStub(ValidatorInterface::class); $validator = new TraceableValidator($originalValidator); $collector = new ValidatorDataCollector($validator); $violations = new ConstraintViolationList([ - $this->createMock(ConstraintViolation::class), - $this->createMock(ConstraintViolation::class), + $this->createStub(ConstraintViolation::class), + $this->createStub(ConstraintViolation::class), ]); $originalValidator->method('validate')->willReturn($violations); diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php index 3d10506aea337..6a7d02edfb246 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php @@ -20,6 +20,7 @@ use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; +use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity; use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\EntityParent; @@ -111,9 +112,8 @@ public function testNonClassNameStringValues() { $this->expectException(NoSuchMetadataException::class); $testedValue = 'error@example.com'; - $loader = $this->createMock(LoaderInterface::class); $cache = $this->createMock(CacheItemPoolInterface::class); - $factory = new LazyLoadingMetadataFactory($loader, $cache); + $factory = new LazyLoadingMetadataFactory(new StaticMethodLoader(), $cache); $cache ->expects($this->never()) ->method('getItem'); @@ -137,7 +137,7 @@ public function testMetadataCacheWithRuntimeConstraint() public function testGroupsFromParent() { - $reader = new \Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader(); + $reader = new StaticMethodLoader(); $factory = new LazyLoadingMetadataFactory($reader); $metadata = $factory->getMetadataFor('Symfony\Component\Validator\Tests\Fixtures\EntityStaticCarTurbo'); $groups = []; diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php index f2e84130cb72c..392b476e165b3 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; +use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; use Symfony\Component\Validator\Tests\Fixtures\FilesLoader; use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity; @@ -21,7 +22,7 @@ class FilesLoaderTest extends TestCase { public function testCallsGetFileLoaderInstanceForeachPath() { - $loader = $this->getFilesLoader($this->createMock(LoaderInterface::class)); + $loader = $this->getFilesLoader(new StaticMethodLoader()); $this->assertEquals(4, $loader->getTimesCalled()); } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php index 9ebe33ec1f59e..1d0138ec04e9e 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php @@ -44,13 +44,13 @@ public function testReturnsTrueIfAnyLoaderReturnedTrue() { $metadata = new ClassMetadata('\stdClass'); - $loader1 = $this->createMock(LoaderInterface::class); - $loader1->expects($this->any()) + $loader1 = $this->createStub(LoaderInterface::class); + $loader1 ->method('loadClassMetadata') ->willReturn(true); - $loader2 = $this->createMock(LoaderInterface::class); - $loader2->expects($this->any()) + $loader2 = $this->createStub(LoaderInterface::class); + $loader2 ->method('loadClassMetadata') ->willReturn(false); @@ -66,13 +66,13 @@ public function testReturnsFalseIfNoLoaderReturnedTrue() { $metadata = new ClassMetadata('\stdClass'); - $loader1 = $this->createMock(LoaderInterface::class); - $loader1->expects($this->any()) + $loader1 = $this->createStub(LoaderInterface::class); + $loader1 ->method('loadClassMetadata') ->willReturn(false); - $loader2 = $this->createMock(LoaderInterface::class); - $loader2->expects($this->any()) + $loader2 = $this->createStub(LoaderInterface::class); + $loader2 ->method('loadClassMetadata') ->willReturn(false); diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php index f6a9e84110e60..0ebc17e319e3b 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php @@ -35,7 +35,7 @@ class PropertyInfoLoaderTest extends TestCase { public function testLoadClassMetadata() { - $propertyInfoStub = $this->createMock(PropertyInfoExtractorInterface::class); + $propertyInfoStub = $this->createStub(PropertyInfoExtractorInterface::class); $propertyInfoStub ->method('getProperties') ->willReturn([ @@ -190,7 +190,7 @@ public function testLoadClassMetadata() */ public function testClassValidator(bool $expected, ?string $classValidatorRegexp = null) { - $propertyInfoStub = $this->createMock(PropertyInfoExtractorInterface::class); + $propertyInfoStub = $this->createStub(PropertyInfoExtractorInterface::class); $propertyInfoStub ->method('getProperties') ->willReturn(['string']) @@ -218,7 +218,7 @@ public static function regexpProvider(): array public function testClassNoAutoMapping() { - $propertyInfoStub = $this->createMock(PropertyInfoExtractorInterface::class); + $propertyInfoStub = $this->createStub(PropertyInfoExtractorInterface::class); $propertyInfoStub ->method('getProperties') ->willReturn(['string', 'autoMappingExplicitlyEnabled']) diff --git a/src/Symfony/Component/Validator/Tests/Validator/TraceableValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/TraceableValidatorTest.php index ae728b19cb6c0..799d4b98a3061 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/TraceableValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/TraceableValidatorTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Validator\Tests\Validator; use PHPUnit\Framework\TestCase; -use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\Context\ExecutionContextInterface; @@ -27,15 +27,15 @@ public function testValidate() { $originalValidator = $this->createMock(ValidatorInterface::class); $violations = new ConstraintViolationList([ - $this->createMock(ConstraintViolation::class), - $this->createMock(ConstraintViolation::class), + $this->createStub(ConstraintViolation::class), + $this->createStub(ConstraintViolation::class), ]); $originalValidator->expects($this->exactly(2))->method('validate')->willReturn($violations); $validator = new TraceableValidator($originalValidator); $object = new \stdClass(); - $constraints = [$this->createMock(Constraint::class)]; + $constraints = [new NotNull()]; $groups = ['Default', 'Create']; $validator->validate($object, $constraints, $groups); @@ -74,16 +74,16 @@ public function testForwardsToOriginalValidator() $expects = fn ($method) => $originalValidator->expects($this->once())->method($method); - $expects('getMetadataFor')->willReturn($expected = $this->createMock(MetadataInterface::class)); + $expects('getMetadataFor')->willReturn($expected = $this->createStub(MetadataInterface::class)); $this->assertSame($expected, $validator->getMetadataFor('value'), 'returns original validator getMetadataFor() result'); $expects('hasMetadataFor')->willReturn($expected = false); $this->assertSame($expected, $validator->hasMetadataFor('value'), 'returns original validator hasMetadataFor() result'); - $expects('inContext')->willReturn($expected = $this->createMock(ContextualValidatorInterface::class)); - $this->assertSame($expected, $validator->inContext($this->createMock(ExecutionContextInterface::class)), 'returns original validator inContext() result'); + $expects('inContext')->willReturn($expected = $this->createStub(ContextualValidatorInterface::class)); + $this->assertSame($expected, $validator->inContext($this->createStub(ExecutionContextInterface::class)), 'returns original validator inContext() result'); - $expects('startContext')->willReturn($expected = $this->createMock(ContextualValidatorInterface::class)); + $expects('startContext')->willReturn($expected = $this->createStub(ContextualValidatorInterface::class)); $this->assertSame($expected, $validator->startContext(), 'returns original validator startContext() result'); $expects('validate')->willReturn($expected = new ConstraintViolationList()); diff --git a/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php b/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php index 94b5c97da5441..48bc9ed660942 100644 --- a/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php +++ b/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php @@ -16,12 +16,12 @@ use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemPoolInterface; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; +use Symfony\Component\Translation\IdentityTranslator; use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; use Symfony\Component\Validator\ObjectInitializerInterface; use Symfony\Component\Validator\Validator\RecursiveValidator; use Symfony\Component\Validator\ValidatorBuilder; -use Symfony\Contracts\Translation\TranslatorInterface; class ValidatorBuilderTest extends TestCase { @@ -37,7 +37,7 @@ protected function setUp(): void public function testAddObjectInitializer() { $this->assertSame($this->builder, $this->builder->addObjectInitializer( - $this->createMock(ObjectInitializerInterface::class) + $this->createStub(ObjectInitializerInterface::class) )); } @@ -143,21 +143,19 @@ public function testDisableAttributeMapping() public function testSetMappingCache() { - $this->assertSame($this->builder, $this->builder->setMappingCache($this->createMock(CacheItemPoolInterface::class))); + $this->assertSame($this->builder, $this->builder->setMappingCache($this->createStub(CacheItemPoolInterface::class))); } public function testSetConstraintValidatorFactory() { $this->assertSame($this->builder, $this->builder->setConstraintValidatorFactory( - $this->createMock(ConstraintValidatorFactoryInterface::class)) + $this->createStub(ConstraintValidatorFactoryInterface::class)) ); } public function testSetTranslator() { - $this->assertSame($this->builder, $this->builder->setTranslator( - $this->createMock(TranslatorInterface::class)) - ); + $this->assertSame($this->builder, $this->builder->setTranslator(new IdentityTranslator())); } public function testSetTranslationDomain() From e6d890d547e2bdc022ebe4eb984ed207b4733cc1 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 14 Dec 2025 13:45:53 +0100 Subject: [PATCH 57/57] PHP CS Fixer: generic 8.1 cleanup --- .../DependencyInjection/Configuration.php | 1 - .../Component/BrowserKit/AbstractBrowser.php | 2 ++ .../Cache/Traits/Relay/Relay20Trait.php | 2 +- .../Traits/Relay/RelayCluster20Trait.php | 2 +- .../Component/Cache/Traits/ValueWrapper.php | 3 +++ .../Tests/Helper/QuestionHelperTest.php | 2 +- .../Tests/Compiler/AutowirePassTest.php | 1 - .../Tests/ContainerBuilderTest.php | 1 - .../Tests/DebugClassLoaderTest.php | 4 ++- src/Symfony/Component/Form/AbstractType.php | 2 -- .../EventListener/CacheAttributeListener.php | 2 +- .../HttpKernel/HttpKernelBrowser.php | 2 +- .../HttpKernel/Tests/HttpCache/StoreTest.php | 12 ++++----- .../Component/HttpKernel/Tests/KernelTest.php | 2 +- .../Tests/Write/StreamWriterGeneratorTest.php | 2 +- .../FailedMessagesRetryCommandTest.php | 2 +- .../Tests/Extractor/PhpDocExtractorTest.php | 3 --- .../Routing/Tests/RequestContextTest.php | 2 +- .../Runtime/Internal/ComposerPlugin.php | 4 ++- .../AccessToken/Oidc/OidcTokenGenerator.php | 25 ++++++++++--------- .../Bridge/Lokalise/LokaliseProvider.php | 2 +- src/Symfony/Component/Uid/BinaryUtil.php | 2 ++ .../Validator/Constraints/UrlValidator.php | 2 +- .../Constraints/TimezoneValidatorTest.php | 2 +- .../Tests/Caster/ReflectionCasterTest.php | 2 +- 25 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index e085d5d7df32a..c148fe59dc7ba 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -14,7 +14,6 @@ use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; -use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface; /** diff --git a/src/Symfony/Component/BrowserKit/AbstractBrowser.php b/src/Symfony/Component/BrowserKit/AbstractBrowser.php index 6d26b86987449..559365db89c7d 100644 --- a/src/Symfony/Component/BrowserKit/AbstractBrowser.php +++ b/src/Symfony/Component/BrowserKit/AbstractBrowser.php @@ -673,3 +673,5 @@ private function extractHost(string $uri): ?string return $host; } } + +// @php-cs-fixer-ignore error_suppression This file is explicitly expected to not silence each of trigger_error calls diff --git a/src/Symfony/Component/Cache/Traits/Relay/Relay20Trait.php b/src/Symfony/Component/Cache/Traits/Relay/Relay20Trait.php index 47930d5da3706..f8f818282c571 100644 --- a/src/Symfony/Component/Cache/Traits/Relay/Relay20Trait.php +++ b/src/Symfony/Component/Cache/Traits/Relay/Relay20Trait.php @@ -27,7 +27,7 @@ public function delex($key, $options = null): \Relay\Relay|false|int return $this->initializeLazyObject()->delex(...\func_get_args()); } - public function digest($key): \Relay\Relay|false|null|string + public function digest($key): \Relay\Relay|false|string|null { return $this->initializeLazyObject()->digest(...\func_get_args()); } diff --git a/src/Symfony/Component/Cache/Traits/Relay/RelayCluster20Trait.php b/src/Symfony/Component/Cache/Traits/Relay/RelayCluster20Trait.php index a196f103dc55f..cefb8af28a897 100644 --- a/src/Symfony/Component/Cache/Traits/Relay/RelayCluster20Trait.php +++ b/src/Symfony/Component/Cache/Traits/Relay/RelayCluster20Trait.php @@ -27,7 +27,7 @@ public function delex($key, $options = null): \Relay\Cluster|false|int return $this->initializeLazyObject()->delex(...\func_get_args()); } - public function digest($key): \Relay\Cluster|false|null|string + public function digest($key): \Relay\Cluster|false|string|null { return $this->initializeLazyObject()->digest(...\func_get_args()); } diff --git a/src/Symfony/Component/Cache/Traits/ValueWrapper.php b/src/Symfony/Component/Cache/Traits/ValueWrapper.php index 718a23d391efe..a97c8571c2125 100644 --- a/src/Symfony/Component/Cache/Traits/ValueWrapper.php +++ b/src/Symfony/Component/Cache/Traits/ValueWrapper.php @@ -79,3 +79,6 @@ public function __unserialize(array $data): void $this->metadata = $metadata; } } + +// @php-cs-fixer-ignore long_to_shorthand_operator To prevent false positive causing "Cannot use assign-op operators with string offsets" error +// @php-cs-fixer-ignore psr_autoloading This class is explicitly having short, special name diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 8d492c564a1a4..774ffc7032d69 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -970,7 +970,7 @@ public function testExitCommandOnInputSIGINT(string $mode) } $p = new Process( - ['php', dirname(__DIR__).'/Fixtures/application_test_sigint.php', $mode], + ['php', \dirname(__DIR__).'/Fixtures/application_test_sigint.php', $mode], timeout: 2, // the process will auto shutdown if not killed by SIGINT, to prevent blocking ); $p->setPty(true); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 6d23d45df4055..49c9caef2296f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index c801e0e89b637..3b021f7427c6b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -16,7 +16,6 @@ require_once __DIR__.'/Fixtures/includes/ProjectExtension.php'; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; diff --git a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php index b57530391d9c3..0f391020974e4 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php @@ -120,7 +120,7 @@ public function testDeprecatedSuper(string $class, string $super, string $type) { set_error_handler(fn () => false); $e = error_reporting(0); - trigger_error('', E_USER_DEPRECATED); + trigger_error('', \E_USER_DEPRECATED); class_exists('Test\\'.__NAMESPACE__.'\\'.$class, true); @@ -568,3 +568,5 @@ public function ownAbstractBaseMethod() { } } } } + +// @php-cs-fixer-ignore error_suppression This file is explicitly expected to not silence each of trigger_error calls diff --git a/src/Symfony/Component/Form/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php index 3716eb1fb92c1..74548bc6024d4 100644 --- a/src/Symfony/Component/Form/AbstractType.php +++ b/src/Symfony/Component/Form/AbstractType.php @@ -37,8 +37,6 @@ public function buildView(FormView $view, FormInterface $form, array $options): { } - /** - */ public function finishView(FormView $view, FormInterface $form, array $options): void { } diff --git a/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php b/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php index 9dd2abda846d5..0046e4a7be6a7 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php @@ -123,7 +123,7 @@ public function onKernelResponse(ResponseEvent $event): void // Check if the response has a Vary header that should be considered, ignoring cases where // it's only 'Accept-Language' and the request has the '_vary_by_language' attribute $hasVary = ['Accept-Language'] === $response->getVary() ? !$request->attributes->get('_vary_by_language') : $response->hasVary(); - //Check if cache-control directive was set manually in cacheControl (not auto computed) + // Check if cache-control directive was set manually in cacheControl (not auto computed) $hasCacheControlDirective = new class($response->headers) extends HeaderBag { public function __construct(private parent $headerBag) { diff --git a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php index 4422bfcdd3e54..06bc420d1c33d 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php +++ b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php @@ -184,7 +184,7 @@ protected function filterResponse(object $response): DomResponse ob_start(static function ($chunk) use (&$content) { $content .= $chunk; - return ''; + return ''; }); try { diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php index e8bffba48165a..76af83b0b87d6 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php @@ -350,7 +350,7 @@ public function testLoadsBodyEval() /** * Basic case when the second header has a different value. - * Both responses should be cached + * Both responses should be cached. */ public function testWriteWithMultipleVaryAndCachedAllResponse() { @@ -376,7 +376,7 @@ public function testWriteWithMultipleVaryAndCachedAllResponse() /** * Basic case when the second header has the same value on both requests. - * The last response should be cached + * The last response should be cached. */ public function testWriteWithMultipleVaryAndCachedLastResponse() { @@ -402,7 +402,7 @@ public function testWriteWithMultipleVaryAndCachedLastResponse() /** * Case when a vary value has been removed. - * Both responses should be cached + * Both responses should be cached. */ public function testWriteWithChangingVary() { @@ -425,11 +425,11 @@ public function testWriteWithChangingVary() /** * Case when a vary value has been removed and headers of the new vary list are the same. - * The last response should be cached + * The last response should be cached. */ public function testWriteWithRemoveVaryAndAllHeadersOnTheList() { - $req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar',]); + $req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar']); $content = str_repeat('a', 24).'b'.str_repeat('a', 24); $res1 = new Response($content, 200, ['vary' => ['Foo', 'bar', 'foobar'], 'X-Body-Eval' => 'SSI']); $this->store->write($req1, $res1); @@ -448,7 +448,7 @@ public function testWriteWithRemoveVaryAndAllHeadersOnTheList() /** * Case when a vary value has been added and headers of the new vary list are the same. - * The last response should be cached + * The last response should be cached. */ public function testWriteWithAddingVaryAndAllHeadersOnTheList() { diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index 60043491cb762..09bdd06f48235 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -17,8 +17,8 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\Request; diff --git a/src/Symfony/Component/JsonStreamer/Tests/Write/StreamWriterGeneratorTest.php b/src/Symfony/Component/JsonStreamer/Tests/Write/StreamWriterGeneratorTest.php index 8fd7fb3f4b412..bbc3a8fa2b3ee 100644 --- a/src/Symfony/Component/JsonStreamer/Tests/Write/StreamWriterGeneratorTest.php +++ b/src/Symfony/Component/JsonStreamer/Tests/Write/StreamWriterGeneratorTest.php @@ -24,8 +24,8 @@ use Symfony\Component\JsonStreamer\Tests\Fixtures\Mapping\SyntheticPropertyMetadataLoader; use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy; use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithArray; -use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithList; use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithDollarNamedProperties; +use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithList; use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes; use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedArray; use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedDictDummies; diff --git a/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesRetryCommandTest.php b/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesRetryCommandTest.php index cbcdf34706f4b..4392495cf8703 100644 --- a/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesRetryCommandTest.php +++ b/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesRetryCommandTest.php @@ -226,7 +226,7 @@ public function testCompleteIdWithSpecifiedTransport() $this->assertSame(['2ab50dfa1fbf', '78c2da843723'], $suggestions); } - public function testSuccessMessageGoesToStdout() + public function testSuccessMessageGoesToStdout() { $envelope = new Envelope(new \stdClass(), [new TransportMessageIdStamp('some_id')]); $receiver = $this->createMock(ListableReceiverInterface::class); diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index 3c76f7a9bf9a4..f3e13ebf1d15a 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -15,8 +15,6 @@ use phpDocumentor\Reflection\PseudoTypes\IntMask; use phpDocumentor\Reflection\PseudoTypes\IntMaskOf; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\Group; -use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy; @@ -31,7 +29,6 @@ use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsedInTrait; use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait; use Symfony\Component\TypeInfo\Type; -use Symfony\Component\TypeInfo\Type\NullableType; /** * @author Kévin Dunglas diff --git a/src/Symfony/Component/Routing/Tests/RequestContextTest.php b/src/Symfony/Component/Routing/Tests/RequestContextTest.php index f40045f78919d..83c2293ceaf3e 100644 --- a/src/Symfony/Component/Routing/Tests/RequestContextTest.php +++ b/src/Symfony/Component/Routing/Tests/RequestContextTest.php @@ -47,7 +47,7 @@ public function testConstruct() public function testConstructParametersBcLayer() { - $requestContext = new class() extends RequestContext { + $requestContext = new class extends RequestContext { public function __construct() { $this->setParameters(['foo' => 'bar']); diff --git a/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php b/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php index 92b1f3a37c714..b4cd25b8a33f6 100644 --- a/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php +++ b/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php @@ -82,7 +82,7 @@ public function updateAutoloadFile(): void $projectDir = substr($projectDir, 3); } - // the hack about __DIR__ is required because composer pre-processes plugins + // the hack about __DIR__ is required because Composer pre-processes plugins if (!$nestingLevel) { $projectDir = '__'.'DIR__.'.var_export('/'.$projectDir, true); } else { @@ -118,3 +118,5 @@ public static function getSubscribedEvents(): array ]; } } + +// @php-cs-fixer-ignore no_useless_concat_operator Disable to not override hack about __DIR__ and Composer pre-processes plugins diff --git a/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenGenerator.php b/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenGenerator.php index eaba079f68350..4f64f9880921d 100644 --- a/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenGenerator.php +++ b/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenGenerator.php @@ -44,19 +44,19 @@ public function generate(string $userIdentifier, ?string $algorithmAlias = null, $now = $this->clock->now(); $payload = [ $this->claim => $userIdentifier, - 'iat' => $now->getTimestamp(), # https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6 - 'aud' => $this->audience, # https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3 - 'iss' => $this->getIssuer($issuer), # https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1 + 'iat' => $now->getTimestamp(), // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6 + 'aud' => $this->audience, // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3 + 'iss' => $this->getIssuer($issuer), // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1 ]; if ($ttl) { if (0 > $ttl) { throw new \InvalidArgumentException('Time to live must be a positive integer.'); } - $payload['exp'] = $now->add(new \DateInterval("PT{$ttl}S"))->getTimestamp(); # https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4 + $payload['exp'] = $now->add(new \DateInterval("PT{$ttl}S"))->getTimestamp(); // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4 } if ($notBefore) { - $payload['nbf'] = $notBefore->getTimestamp(); # https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5 + $payload['nbf'] = $notBefore->getTimestamp(); // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5 } $jws = $jwsBuilder @@ -74,13 +74,14 @@ private function getAlgorithm(?string $alias): Algorithm { if ($alias) { if (!$this->algorithmManager->has($alias)) { - throw new \InvalidArgumentException(sprintf('"%s" is not a valid algorithm. Available algorithms: "%s".', $alias, implode('", "', $this->algorithmManager->list()))); + throw new \InvalidArgumentException(\sprintf('"%s" is not a valid algorithm. Available algorithms: "%s".', $alias, implode('", "', $this->algorithmManager->list()))); } + return $this->algorithmManager->get($alias); } - if (1 !== count($list = $this->algorithmManager->list())) { - throw new \InvalidArgumentException(sprintf('Please choose an algorithm. Available algorithms: "%s".', implode('", "', $list))); + if (1 !== \count($list = $this->algorithmManager->list())) { + throw new \InvalidArgumentException(\sprintf('Please choose an algorithm. Available algorithms: "%s".', implode('", "', $list))); } return $this->algorithmManager->get($list[0]); @@ -89,15 +90,15 @@ private function getAlgorithm(?string $alias): Algorithm private function getIssuer(?string $issuer): string { if ($issuer) { - if (!in_array($issuer, $this->issuers, true)) { - throw new \InvalidArgumentException(sprintf('"%s" is not a valid issuer. Available issuers: "%s".', $issuer, implode('", "', $this->issuers))); + if (!\in_array($issuer, $this->issuers, true)) { + throw new \InvalidArgumentException(\sprintf('"%s" is not a valid issuer. Available issuers: "%s".', $issuer, implode('", "', $this->issuers))); } return $issuer; } - if (1 !== count($this->issuers)) { - throw new \InvalidArgumentException(sprintf('Please choose an issuer. Available issuers: "%s".', implode('", "', $this->issuers))); + if (1 !== \count($this->issuers)) { + throw new \InvalidArgumentException(\sprintf('Please choose an issuer. Available issuers: "%s".', implode('", "', $this->issuers))); } return $this->issuers[0]; diff --git a/src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php b/src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php index 1d68494c66740..18ab959238fa0 100644 --- a/src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php +++ b/src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php @@ -237,7 +237,7 @@ private function exportFilesAsync(array $locales, array $domains): array return $this->getZipContents($extractPath); } finally { - if (is_resource($h)) { + if (\is_resource($h)) { fclose($h); } @unlink($zipFile); diff --git a/src/Symfony/Component/Uid/BinaryUtil.php b/src/Symfony/Component/Uid/BinaryUtil.php index 7d1e524e5e43e..0c16b9414b19f 100644 --- a/src/Symfony/Component/Uid/BinaryUtil.php +++ b/src/Symfony/Component/Uid/BinaryUtil.php @@ -187,3 +187,5 @@ public static function dateTimeToHex(\DateTimeInterface $time): string return bin2hex($time); } } + +// @php-cs-fixer-ignore long_to_shorthand_operator To prevent false positive causing "Cannot use assign-op operators with string offsets" error diff --git a/src/Symfony/Component/Validator/Constraints/UrlValidator.php b/src/Symfony/Component/Validator/Constraints/UrlValidator.php index ad17f5d25c110..eefdfc434f9d0 100644 --- a/src/Symfony/Component/Validator/Constraints/UrlValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UrlValidator.php @@ -83,7 +83,7 @@ public function validate(mixed $value, Constraint $constraint): void } $pattern = $constraint->relativeProtocol ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN; - $pattern = sprintf($pattern, $protocols); + $pattern = \sprintf($pattern, $protocols); if (!preg_match($pattern, $value)) { $this->context->buildViolation($constraint->message) diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php index 879cee69f3706..f7b4d82e03218 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php @@ -260,7 +260,7 @@ public function testDeprecatedTimezonesAreValidWithBC(string $timezone) { // Skip test if the timezone is not available in the current timezone database if (!\in_array($timezone, \DateTimeZone::listIdentifiers(\DateTimeZone::ALL_WITH_BC), true)) { - $this->markTestSkipped(sprintf('Timezone "%s" is not available in the current timezone database', $timezone)); + $this->markTestSkipped(\sprintf('Timezone "%s" is not available in the current timezone database', $timezone)); } $constraint = new Timezone(\DateTimeZone::ALL_WITH_BC); diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php index 0f12f14f393b3..dd35bfefa734c 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php @@ -651,7 +651,7 @@ public function testReflectionClassConstantWithAttribute() public function testReflectionParameterWithAttribute() { $var = new \ReflectionParameter([LotsOfAttributes::class, 'someMethod'], 'someParameter'); - + $this->assertDumpMatchesFormat(<<