From 340d3f997ee01324599997ed006335d07da9e671 Mon Sep 17 00:00:00 2001 From: Younes ENNAJI Date: Fri, 14 Nov 2025 11:00:21 +0100 Subject: [PATCH 01/42] [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/42] 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/42] 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/42] 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/42] [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/42] [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/42] 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/42] 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/42] [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/42] [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/42] [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/42] 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/42] 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/42] 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/42] 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/42] [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/42] 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/42] 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 9dede67cba26fce88ec300132a6bd42c845b1a2c Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 9 Dec 2025 00:16:08 +0100 Subject: [PATCH 19/42] [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 20/42] [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 21/42] [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 22/42] [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 23/42] [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 24/42] 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 25/42] 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 26/42] 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 27/42] [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 28/42] [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 29/42] [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 30/42] [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 31/42] [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 32/42] 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 33/42] [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 34/42] [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 35/42] [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 36/42] 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 37/42] 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 38/42] 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 39/42] 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 40/42] 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 41/42] 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 42/42] 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",