diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index 0df7764a3ed29..575f27685e010 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -215,10 +215,14 @@ jobs:
# sudo rm -rf .phpunit
# [ -d .phpunit.bak ] && mv .phpunit.bak .phpunit
- - uses: marceloprado/has-changed-path@v1.0.1
+ - name: Check for changes in translation files
id: changed-translation-files
- with:
- paths: src/**/Resources/translations/*.xlf
+ run: |
+ if git diff --quiet HEAD~1 HEAD -- 'src/**/Resources/translations/*.xlf'; then
+ echo "{changed}={true}" >> $GITHUB_OUTPUT
+ else
+ echo "{changed}={false}" >> $GITHUB_OUTPUT
+ fi
- name: Check Translation Status
if: steps.changed-translation-files.outputs.changed == 'true'
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index 33b87c42af2de..f90caa71d3d8a 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -32,6 +32,7 @@ jobs:
- php: '8.2'
mode: low-deps
- php: '8.3'
+ - php: '8.4'
#mode: experimental
fail-fast: false
@@ -59,7 +60,7 @@ jobs:
git config --global init.defaultBranch main
git config --global advice.detachedHead false
- (php --ri relay 2>&1 > /dev/null) || sudo rm /etc/php/*/cli/conf.d/20-relay.ini
+ (php --ri relay 2>&1 > /dev/null) || sudo rm -f /etc/php/*/cli/conf.d/20-relay.ini
COMPOSER_HOME="$(composer config home)"
([ -d "$COMPOSER_HOME" ] || mkdir "$COMPOSER_HOME") && cp .github/composer-config.json "$COMPOSER_HOME/config.json"
diff --git a/CHANGELOG-6.4.md b/CHANGELOG-6.4.md
index 723085c67be52..2e0f70ad13e6a 100644
--- a/CHANGELOG-6.4.md
+++ b/CHANGELOG-6.4.md
@@ -7,6 +7,41 @@ in 6.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/v6.4.0...v6.4.1
+* 6.4.6 (2024-04-03)
+
+ * bug #54400 [HttpClient] stop all server processes after tests have run (xabbuh)
+ * bug #54435 [Console] respect multi-byte characters when rendering vertical-style tables (xabbuh)
+ * bug #54419 Fix TypeError on ProgressBar (Fan2Shrek)
+ * bug #54425 [TwigBridge] Remove whitespaces from block form_help output (rosier)
+ * bug #53969 [Mailer] include message id provided by the MTA when dispatching the `SentMessageEvent` (xabbuh)
+ * bug #54315 [Serializer] Fixed BackedEnumNormalizer priority for translatable enum (IndraGunawan)
+ * bug #54372 [Config] Fix `YamlReferenceDumper` handling of array examples (MatTheCat)
+ * bug #54362 [Filesystem] preserve the file modification time when mirroring directories (xabbuh)
+ * bug #54121 [Messenger] Catch TableNotFoundException in MySQL delete (acbramley)
+ * bug #54271 [DoctrineBridge] Fix deprecation warning with ORM 3 when guessing field lengths (eltharin)
+ * bug #54306 Throw TransformationFailedException when there is a null bytes injection (sormes)
+ * bug #54148 [Serializer] Fix object normalizer when properties has the same name as their accessor (NeilPeyssard)
+ * bug #54305 [Cache][Lock] Identify missing table in pgsql correctly and address failing integration tests (arifszn)
+ * bug #54199 [Mailer] [Brevo] Check that tags is present in payload before calling setTags (palgalik)
+ * bug #54292 [FrameworkBundle] Fix mailer config with XML (lyrixx)
+ * bug #54298 [Filesystem] Fix str_contains deprecation (NeilPeyssard)
+ * bug #54248 [Security] Correctly initialize the voter property (aschempp)
+ * bug #54273 [DependencyInjection] fix XmlDumper when a tag contains also a 'name' property (lyrixx)
+ * bug #54191 [Validator] add missing invalid extension error entry (xabbuh)
+ * bug #54194 [PropertyAccess] Fix checking for missing properties (nicolas-grekas)
+ * bug #54201 [Lock] Check the correct SQLSTATE error code for MySQL (edomato)
+ * bug #54252 [Lock] compatiblity with redis cluster 7 (bastnic)
+ * bug #54124 [Messenger] trigger retry logic when message is a redelivery (nikophil)
+ * bug #54254 [HttpKernel] Fix creating `ReflectionMethod` with only one argument (alexandre-daubois)
+ * bug #54219 [Validator] Allow BICs’ first four characters to be digits (MatTheCat)
+ * bug #54239 [Mailer] Fix sendmail transport not handling failure (aboks)
+ * bug #54207 [HttpClient] Lazily initialize CurlClientState (arjenm)
+ * bug #53865 [Workflow]Fix Marking when it must contains more than one tokens (lyrixx)
+ * bug #54137 [Validator] UniqueValidator - normalize before reducing keys (Brajk19)
+ * bug #54187 [FrameworkBundle] Fix PHP 8.4 deprecation on `ReflectionMethod` (alexandre-daubois)
+ * bug #54167 [Messenger] [Amqp] Handle AMQPConnectionException when publishing a message. (jwage)
+ * bug #54146 [HttpClient] Preserve float in JsonMockResponse (Jibbarth)
+
* 6.4.5 (2024-03-04)
* bug #54113 [AssetMapper] Throw exception in Javascript compiler when PCRE error (smnandre)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index c4a195380c0a3..f676651321bef 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -80,11 +80,11 @@ The Symfony Connect username in parenthesis allows to get more information
- Mathieu Santostefano (welcomattic)
- Alexander Schranz (alexander-schranz)
- Vasilij Duško (staff)
+ - Simon André (simonandre)
- Sarah Khalil (saro0h)
- Laurent VOULLEMIER (lvo)
- Konstantin Kudryashov (everzet)
- Tomasz Kowalczyk (thunderer)
- - Simon André (simonandre)
- Guilhem N (guilhemn)
- Bilal Amarni (bamarni)
- Eriksen Costa
@@ -97,10 +97,10 @@ The Symfony Connect username in parenthesis allows to get more information
- David Buchmann (dbu)
- Andrej Hudec (pulzarraider)
- Jáchym Toušek (enumag)
+ - Ruud Kamphuis (ruudk)
- Christian Raue
- Eric Clemmons (ericclemmons)
- Denis (yethee)
- - Ruud Kamphuis (ruudk)
- Michel Weimerskirch (mweimerskirch)
- Issei Murasawa (issei_m)
- Douglas Greenshields (shieldo)
@@ -129,8 +129,8 @@ The Symfony Connect username in parenthesis allows to get more information
- Bart van den Burg (burgov)
- Vasilij Dusko | CREATION
- Jordan Alliot (jalliot)
- - John Wards (johnwards)
- Phil E. Taylor (philetaylor)
+ - John Wards (johnwards)
- Théo FIDRY
- Antoine Hérault (herzult)
- Konstantin.Myakshin
@@ -177,6 +177,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Maxime Helias (maxhelias)
- Robert Schönthal (digitalkaoz)
- Smaine Milianni (ismail1432)
+ - Michael Babker (mbabker)
- François-Xavier de Guillebon (de-gui_f)
- Maximilian Beckers (maxbeckers)
- noniagriconomie
@@ -188,7 +189,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Jhonny Lidfors (jhonne)
- Juti Noppornpitak (shiroyuki)
- Gregor Harlan (gharlan)
- - Michael Babker (mbabker)
- Anthony MARTIN
- Sebastian Hörl (blogsh)
- Tigran Azatyan (tigranazatyan)
@@ -231,6 +231,7 @@ The Symfony Connect username in parenthesis allows to get more information
- George Mponos (gmponos)
- Richard Shank (iampersistent)
- Thomas Landauer (thomas-landauer)
+ - Roland Franssen :)
- Romain Monteil (ker0x)
- Sergey (upyx)
- Marco Pivetta (ocramius)
@@ -257,7 +258,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Artur Kotyrba
- Wouter J
- Tyson Andre
- - Roland Franssen :)
- GDIBass
- Samuel NELA (snela)
- Vincent AUBERT (vincent)
@@ -733,6 +733,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Vadim Borodavko (javer)
- Tavo Nieves J (tavoniievez)
- Luc Vieillescazes (iamluc)
+ - Stiven Llupa (sllupa)
- Erik Saunier (snickers)
- François Dume (franek)
- Jerzy Lekowski (jlekowski)
@@ -1043,7 +1044,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Florian Pfitzer (marmelatze)
- Ivan Grigoriev (greedyivan)
- Johann Saunier (prophet777)
- - Stiven Llupa (sllupa)
- Kevin SCHNEKENBURGER
- Fabien Salles (blacked)
- Andreas Erhard (andaris)
@@ -2117,6 +2117,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Sébastien HOUZÉ
- Mbechezi Nawo
- wivaku
+ - Markus Reinhold
- Jingyu Wang
- steveYeah
- Asrorbek (asrorbek)
@@ -2523,6 +2524,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Roy de Vos Burchart
- John Stevenson
- everyx
+ - Richard Heine
- Francisco Facioni (fran6co)
- Stanislav Gamaiunov (happyproff)
- Iwan van Staveren (istaveren)
@@ -3193,6 +3195,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Daniele Cesarini (ijanki)
- Ismail Asci (ismailasci)
- Jeffrey Moelands (jeffreymoelands)
+ - Jakub Caban (lustmored)
- Ondřej Mirtes (mirtes)
- Paulius Jarmalavičius (pjarmalavicius)
- Ramon Ornelas (ramonornela)
@@ -3615,6 +3618,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Konrad
- Kovacs Nicolas
- eminjk
+ - Gálik Pál
- craigmarvelley
- Stano Turza
- Antoine Leblanc
diff --git a/composer.json b/composer.json
index 0091db8b543c7..eae0d1cafe807 100644
--- a/composer.json
+++ b/composer.json
@@ -137,7 +137,7 @@
"doctrine/orm": "^2.15|^3",
"dragonmantank/cron-expression": "^3.1",
"egulias/email-validator": "^2.1.10|^3.1|^4",
- "guzzlehttp/promises": "^1.4",
+ "guzzlehttp/promises": "^1.4|^2.0",
"league/html-to-markdown": "^5.0",
"league/uri": "^6.5|^7.0",
"masterminds/html5": "^2.7.2",
diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php
index d277992bd5a1c..0537946986d21 100644
--- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php
+++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php
@@ -14,6 +14,7 @@
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
+use Doctrine\ORM\Mapping\FieldMapping;
use Doctrine\ORM\Mapping\JoinColumnMapping;
use Doctrine\ORM\Mapping\MappingException as LegacyMappingException;
use Doctrine\Persistence\ManagerRegistry;
@@ -129,8 +130,10 @@ public function guessMaxLength(string $class, string $property): ?ValueGuess
if ($ret && isset($ret[0]->fieldMappings[$property]) && !$ret[0]->hasAssociation($property)) {
$mapping = $ret[0]->getFieldMapping($property);
- if (isset($mapping['length'])) {
- return new ValueGuess($mapping['length'], Guess::HIGH_CONFIDENCE);
+ $length = $mapping instanceof FieldMapping ? $mapping->length : ($mapping['length'] ?? null);
+
+ if (null !== $length) {
+ return new ValueGuess($length, Guess::HIGH_CONFIDENCE);
}
if (\in_array($ret[0]->getTypeOfField($property), [Types::DECIMAL, Types::FLOAT])) {
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php
index 783cf5a5524e9..92e750929f41e 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php
+++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php
@@ -1314,19 +1314,6 @@ public function testPassIdAndNameToView()
$this->assertEquals('name', $view->vars['full_name']);
}
- public function testStripLeadingUnderscoresAndDigitsFromId()
- {
- $view = $this->factory->createNamed('_09name', static::TESTED_TYPE, null, [
- 'em' => 'default',
- 'class' => self::SINGLE_IDENT_CLASS,
- ])
- ->createView();
-
- $this->assertEquals('name', $view->vars['id']);
- $this->assertEquals('_09name', $view->vars['name']);
- $this->assertEquals('_09name', $view->vars['full_name']);
- }
-
public function testPassIdAndNameToViewWithParent()
{
$view = $this->factory->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE)
diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json
index 7a12292c65468..c5412dd71ba58 100644
--- a/src/Symfony/Bridge/Doctrine/composer.json
+++ b/src/Symfony/Bridge/Doctrine/composer.json
@@ -30,7 +30,7 @@
"symfony/dependency-injection": "^6.2|^7.0",
"symfony/doctrine-messenger": "^5.4|^6.0|^7.0",
"symfony/expression-language": "^5.4|^6.0|^7.0",
- "symfony/form": "^5.4.21|^6.2.7|^7.0",
+ "symfony/form": "^5.4.38|^6.4.6|^7.0.6",
"symfony/http-kernel": "^6.3|^7.0",
"symfony/lock": "^6.3|^7.0",
"symfony/messenger": "^5.4|^6.0|^7.0",
@@ -55,7 +55,7 @@
"doctrine/orm": "<2.15",
"symfony/cache": "<5.4",
"symfony/dependency-injection": "<6.2",
- "symfony/form": "<5.4.21|>=6,<6.2.7",
+ "symfony/form": "<5.4.38|>=6,<6.4.6|>=7,<7.0.6",
"symfony/http-foundation": "<6.3",
"symfony/http-kernel": "<6.2",
"symfony/lock": "<6.3",
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php
index f660a8060f962..a2259fc1304ec 100644
--- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php
@@ -525,7 +525,7 @@ public function testBaselineFileWriteError()
$this->expectException(\ErrorException::class);
$this->expectExceptionMessageMatches('/[Ff]ailed to open stream: Permission denied/');
- set_error_handler(static function (int $errno, string $errstr, string $errfile = null, int $errline = null): bool {
+ set_error_handler(static function (int $errno, string $errstr, ?string $errfile = null, ?int $errline = null): bool {
if ($errno & (E_WARNING | E_WARNING)) {
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
}
diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php
index 19a9bdd5125d3..c12f1150b6986 100644
--- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php
+++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php
@@ -200,7 +200,7 @@ public function __wakeup()
{
}
- public function setProxyInitializer(\Closure $initializer = null)%S
+ public function setProxyInitializer(%S\Closure $initializer = null)%S
{
$this->initializer%s = $initializer;
}
diff --git a/src/Symfony/Bridge/PsrHttpMessage/Tests/Fixtures/ServerRequest.php b/src/Symfony/Bridge/PsrHttpMessage/Tests/Fixtures/ServerRequest.php
index 0bd7d5918983b..99b7abbee3f1b 100644
--- a/src/Symfony/Bridge/PsrHttpMessage/Tests/Fixtures/ServerRequest.php
+++ b/src/Symfony/Bridge/PsrHttpMessage/Tests/Fixtures/ServerRequest.php
@@ -26,10 +26,10 @@ class ServerRequest extends Message implements ServerRequestInterface
public function __construct(
string $version = '1.1',
array $headers = [],
- StreamInterface $body = null,
+ ?StreamInterface $body = null,
private readonly string $requestTarget = '/',
private readonly string $method = 'GET',
- UriInterface|string $uri = null,
+ UriInterface|string|null $uri = null,
private readonly array $server = [],
private readonly array $cookies = [],
private readonly array $query = [],
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig
index cb5a60e079861..17b28fc9ab8d6 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig
@@ -361,12 +361,12 @@
{# Help #}
{%- block form_help -%}
- {% set row_class = row_attr.class|default('') %}
- {% set help_class = ' form-text' %}
- {% if 'input-group' in row_class %}
+ {%- set row_class = row_attr.class|default('') -%}
+ {%- set help_class = ' form-text' -%}
+ {%- if 'input-group' in row_class -%}
{#- Hack to properly display help with input group -#}
- {% set help_class = ' input-group-text' %}
- {% endif %}
+ {%- set help_class = ' input-group-text' -%}
+ {%- endif -%}
{%- if help is not empty -%}
{%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ help_class ~ ' mb-0')|trim}) -%}
{%- endif -%}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
index 40880df4760ac..179f8e981dd32 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
@@ -154,7 +154,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
if ($this->isNfs($realBuildDir)) {
- $io->note('For better performances, you should move the cache and log directories to a non-shared folder of the VM.');
+ $io->note('For better performance, you should move the cache and log directories to a non-shared folder of the VM.');
$fs->remove($realBuildDir);
} else {
$fs->rename($realBuildDir, $oldBuildDir);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php
index a71bf3a7610ec..b57bc0cdbec6b 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php
@@ -597,7 +597,7 @@ private function formatControllerLink(mixed $controller, string $anchorText, ?ca
} elseif (!\is_string($controller)) {
return $anchorText;
} elseif (str_contains($controller, '::')) {
- $r = new \ReflectionMethod($controller);
+ $r = new \ReflectionMethod(...explode('::', $controller, 2));
} else {
$r = new \ReflectionFunction($controller);
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index 29cb4875d2a5d..ad352160822ae 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -2199,6 +2199,7 @@ private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enabl
->end()
->arrayNode('envelope')
->info('Mailer Envelope configuration')
+ ->fixXmlConfig('recipient')
->children()
->scalarNode('sender')->end()
->arrayNode('recipients')
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
index ffa47c3845fc0..aedd4a86fd113 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
@@ -781,7 +781,7 @@
-
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
index 2311df0fcf36e..f70d60429e90f 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
@@ -219,6 +219,6 @@
])
->set('serializer.normalizer.backed_enum', BackedEnumNormalizer::class)
- ->tag('serializer.normalizer', ['priority' => -915])
+ ->tag('serializer.normalizer', ['priority' => -880])
;
};
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php
index a707c02662fd2..68387298270a3 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer_with_dsn.php
@@ -12,7 +12,7 @@
'dsn' => 'smtp://example.com',
'envelope' => [
'sender' => 'sender@example.org',
- 'recipients' => ['redirected@example.org', 'redirected1@example.org'],
+ 'recipients' => ['redirected@example.org'],
],
'headers' => [
'from' => 'from@example.org',
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml
index 02ecd32e757fc..3436cf417caf7 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_dsn.xml
@@ -12,8 +12,7 @@
sender@example.org
- redirected@example.org
- redirected1@example.org
+ redirected@example.org
from@example.org
bcc1@example.org
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml
index 650e6792dd198..1cd8523b680f4 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer_with_transports.xml
@@ -14,8 +14,8 @@
smtp://example2.com
sender@example.org
- redirected@example.org
- redirected1@example.org
+ redirected@example.org
+ redirected1@example.org
from@example.org
bcc1@example.org
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml
index e2793a9e7b7f0..e826d6bdcff97 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer_with_dsn.yml
@@ -10,7 +10,6 @@ framework:
sender: sender@example.org
recipients:
- redirected@example.org
- - redirected1@example.org
headers:
from: from@example.org
bcc: [bcc1@example.org, bcc2@example.org]
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php
index e24031829305f..7e30456900ea7 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php
@@ -361,7 +361,7 @@ public function testWorkflows()
$this->assertSame('state_machine.pull_request.metadata_store', (string) $metadataStoreReference);
$metadataStoreDefinition = $container->getDefinition('state_machine.pull_request.metadata_store');
- $this->assertSame(Workflow\Metadata\InMemoryMetadataStore::class, $metadataStoreDefinition->getClass());
+ $this->assertSame(InMemoryMetadataStore::class, $metadataStoreDefinition->getClass());
$this->assertSame(InMemoryMetadataStore::class, $metadataStoreDefinition->getClass());
$workflowMetadata = $metadataStoreDefinition->getArgument(0);
@@ -2056,21 +2056,27 @@ public function testHttpClientFullDefaultOptions()
$this->assertSame(['foo' => ['bar' => 'baz']], $defaultOptions['extra']);
}
- public static function provideMailer(): array
+ public static function provideMailer(): iterable
{
- return [
- ['mailer_with_dsn', ['main' => 'smtp://example.com']],
- ['mailer_with_transports', [
+ yield [
+ 'mailer_with_dsn',
+ ['main' => 'smtp://example.com'],
+ ['redirected@example.org'],
+ ];
+ yield [
+ 'mailer_with_transports',
+ [
'transport1' => 'smtp://example1.com',
'transport2' => 'smtp://example2.com',
- ]],
+ ],
+ ['redirected@example.org', 'redirected1@example.org'],
];
}
/**
* @dataProvider provideMailer
*/
- public function testMailer(string $configFile, array $expectedTransports)
+ public function testMailer(string $configFile, array $expectedTransports, array $expectedRecipients)
{
$container = $this->createContainerFromFile($configFile);
@@ -2082,7 +2088,7 @@ public function testMailer(string $configFile, array $expectedTransports)
$this->assertTrue($container->hasDefinition('mailer.envelope_listener'));
$l = $container->getDefinition('mailer.envelope_listener');
$this->assertSame('sender@example.org', $l->getArgument(0));
- $this->assertSame(['redirected@example.org', 'redirected1@example.org'], $l->getArgument(1));
+ $this->assertSame($expectedRecipients, $l->getArgument(1));
$this->assertEquals(new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE), $container->getDefinition('mailer.mailer')->getArgument(1));
$this->assertTrue($container->hasDefinition('mailer.message_listener'));
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TranslatableBackedEnum.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TranslatableBackedEnum.php
new file mode 100644
index 0000000000000..775276d84a87d
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TranslatableBackedEnum.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures;
+
+use Symfony\Contracts\Translation\TranslatableInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+enum TranslatableBackedEnum: string implements TranslatableInterface
+{
+ case Get = 'GET';
+
+ public function trans(TranslatorInterface $translator, ?string $locale = null): string
+ {
+ return match ($this) {
+ self::Get => 'custom_get_string',
+ };
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php
index 630bfb7cd3004..2856816d187a1 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php
@@ -11,6 +11,8 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
+use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\TranslatableBackedEnum;
+
/**
* @author Kévin Dunglas
*/
@@ -67,6 +69,15 @@ public static function provideNormalizersAndEncodersWithDefaultContextOption():
['serializer.encoder.csv.alias'],
];
}
+
+ public function testSerializeTranslatableBackedEnum()
+ {
+ static::bootKernel(['test_case' => 'Serializer']);
+
+ $serializer = static::getContainer()->get('serializer.alias');
+
+ $this->assertEquals('GET', $serializer->serialize(TranslatableBackedEnum::Get, 'yaml'));
+ }
}
class Foo
diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php
index 667bc4f93da04..2c0562e4066a3 100644
--- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php
+++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php
@@ -131,6 +131,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
// collect voters and access decision manager information
if ($this->accessDecisionManager instanceof TraceableAccessDecisionManager) {
$this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy();
+ $this->data['voters'] = [];
foreach ($this->accessDecisionManager->getVoters() as $voter) {
if ($voter instanceof TraceableVoter) {
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php
index 65bc90cd8487e..bee9a14c8d259 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php
@@ -397,6 +397,36 @@ public function dispatch(object $event, ?string $eventName = null): object
$this->assertSame($dataCollector->getVoterStrategy(), $strategy, 'Wrong value returned by getVoterStrategy');
}
+ public function testGetVotersIfAccessDecisionManagerHasNoVoters()
+ {
+ $strategy = MainConfiguration::STRATEGY_AFFIRMATIVE;
+
+ $accessDecisionManager = $this->createMock(TraceableAccessDecisionManager::class);
+
+ $accessDecisionManager
+ ->method('getStrategy')
+ ->willReturn($strategy);
+
+ $accessDecisionManager
+ ->method('getVoters')
+ ->willReturn([]);
+
+ $accessDecisionManager
+ ->method('getDecisionLog')
+ ->willReturn([[
+ 'attributes' => ['view'],
+ 'object' => new \stdClass(),
+ 'result' => true,
+ 'voterDetails' => [],
+ ]]);
+
+ $dataCollector = new SecurityDataCollector(null, null, null, $accessDecisionManager, null, null, true);
+
+ $dataCollector->collect(new Request(), new Response());
+
+ $this->assertEmpty($dataCollector->getVoters());
+ }
+
public static function provideRoles(): array
{
return [
diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php
index 13d8fd3ba3301..c79b739594c3e 100644
--- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php
@@ -374,10 +374,10 @@ private function getServerVersion(): string
private function isTableMissing(\PDOException $exception): bool
{
$driver = $this->getDriver();
- $code = $exception->getCode();
+ [$sqlState, $code] = $exception->errorInfo ?? [null, $exception->getCode()];
return match ($driver) {
- 'pgsql' => '42P01' === $code,
+ 'pgsql' => '42P01' === $sqlState,
'sqlite' => str_contains($exception->getMessage(), 'no such table:'),
'oci' => 942 === $code,
'sqlsrv' => 208 === $code,
diff --git a/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php b/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php
index f0d97724a4e3f..0f7337fe6e913 100644
--- a/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php
+++ b/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php
@@ -32,7 +32,7 @@ public function connect(array $params, $username = null, $password = null, array
return $this->driver->connect($params, $username, $password, $driverOptions);
}
- public function getDatabasePlatform(ServerVersionProvider $versionProvider = null): AbstractPlatform
+ public function getDatabasePlatform(?ServerVersionProvider $versionProvider = null): AbstractPlatform
{
return $this->driver->getDatabasePlatform($versionProvider);
}
diff --git a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php
index 8d315ffd2c519..0e3f2de0b5ec3 100644
--- a/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php
+++ b/src/Symfony/Component/Cache/Tests/Traits/RedisProxiesTest.php
@@ -86,32 +86,31 @@ public function testRelayProxy()
*/
public function testRedis6Proxy($class, $stub)
{
- $stub = file_get_contents("https://raw.githubusercontent.com/phpredis/phpredis/develop/{$stub}.stub.php");
+ if (version_compare(phpversion('redis'), '6.0.2', '>')) {
+ $stub = file_get_contents("https://raw.githubusercontent.com/phpredis/phpredis/develop/{$stub}.stub.php");
+ } else {
+ $stub = file_get_contents("https://raw.githubusercontent.com/phpredis/phpredis/6.0.2/{$stub}.stub.php");
+ }
+
$stub = preg_replace('/^class /m', 'return; \0', $stub);
$stub = preg_replace('/^return; class ([a-zA-Z]++)/m', 'interface \1StubInterface', $stub, 1);
$stub = preg_replace('/^ public const .*/m', '', $stub);
eval(substr($stub, 5));
- $r = new \ReflectionClass($class.'StubInterface');
- $proxy = file_get_contents(\dirname(__DIR__, 2)."/Traits/{$class}6Proxy.php");
- $proxy = substr($proxy, 0, 4 + strpos($proxy, '[];'));
+ $this->assertEquals(self::dumpMethods(new \ReflectionClass($class.'StubInterface')), self::dumpMethods(new \ReflectionClass(sprintf('Symfony\Component\Cache\Traits\%s6Proxy', $class))));
+ }
+
+ private static function dumpMethods(\ReflectionClass $class): string
+ {
$methods = [];
- foreach ($r->getMethods() as $method) {
+ foreach ($class->getMethods() as $method) {
if ('reset' === $method->name || method_exists(LazyProxyTrait::class, $method->name)) {
continue;
}
+
$return = $method->getReturnType() instanceof \ReflectionNamedType && 'void' === (string) $method->getReturnType() ? '' : 'return ';
$signature = ProxyHelper::exportSignature($method, false, $args);
-
- if ('Redis' === $class && 'mget' === $method->name) {
- $signature = str_replace(': \Redis|array|false', ': \Redis|array', $signature);
- }
-
- if ('RedisCluster' === $class && 'publish' === $method->name) {
- $signature = str_replace(': \RedisCluster|bool|int', ': \RedisCluster|bool', $signature);
- }
-
$methods[] = "\n ".str_replace('timeout = 0.0', 'timeout = 0', $signature)."\n".<<lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args});
@@ -120,9 +119,8 @@ public function testRedis6Proxy($class, $stub)
EOPHP;
}
- uksort($methods, 'strnatcmp');
- $proxy .= implode('', $methods)."}\n";
+ usort($methods, 'strnatcmp');
- $this->assertStringEqualsFile(\dirname(__DIR__, 2)."/Traits/{$class}6Proxy.php", $proxy);
+ return implode("\n", $methods);
}
}
diff --git a/src/Symfony/Component/Cache/Traits/Redis6Proxy.php b/src/Symfony/Component/Cache/Traits/Redis6Proxy.php
index 59ab11b0f3c55..e41c0d10cc030 100644
--- a/src/Symfony/Component/Cache/Traits/Redis6Proxy.php
+++ b/src/Symfony/Component/Cache/Traits/Redis6Proxy.php
@@ -28,6 +28,7 @@ class Redis6Proxy extends \Redis implements ResetInterface, LazyObjectInterface
use LazyProxyTrait {
resetLazyObject as reset;
}
+ use Redis6ProxyTrait;
private const LAZY_OBJECT_PROPERTY_SCOPES = [];
@@ -96,11 +97,6 @@ public function bgrewriteaof(): \Redis|bool
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args());
}
- public function waitaof($numlocal, $numreplicas, $timeout): \Redis|array|false
- {
- return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->waitaof(...\func_get_args());
- }
-
public function bitcount($key, $start = 0, $end = -1, $bybit = false): \Redis|false|int
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bitcount(...\func_get_args());
@@ -231,11 +227,6 @@ public function discard(): \Redis|bool
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->discard(...\func_get_args());
}
- public function dump($key): \Redis|string
- {
- return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args());
- }
-
public function echo($str): \Redis|false|string
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->echo(...\func_get_args());
@@ -656,11 +647,6 @@ public function ltrim($key, $start, $end): \Redis|bool
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->ltrim(...\func_get_args());
}
- public function mget($keys): \Redis|array
- {
- return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args());
- }
-
public function migrate($host, $port, $key, $dstdb, $timeout, $copy = false, $replace = false, #[\SensitiveParameter] $credentials = null): \Redis|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->migrate(...\func_get_args());
diff --git a/src/Symfony/Component/Cache/Traits/Redis6ProxyTrait.php b/src/Symfony/Component/Cache/Traits/Redis6ProxyTrait.php
new file mode 100644
index 0000000000000..d086d5b3e8a09
--- /dev/null
+++ b/src/Symfony/Component/Cache/Traits/Redis6ProxyTrait.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+if (version_compare(phpversion('redis'), '6.0.2', '>')) {
+ /**
+ * @internal
+ */
+ trait Redis6ProxyTrait
+ {
+ public function dump($key): \Redis|false|string
+ {
+ return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args());
+ }
+
+ public function mget($keys): \Redis|array|false
+ {
+ return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args());
+ }
+
+ public function waitaof($numlocal, $numreplicas, $timeout): \Redis|array|false
+ {
+ return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->waitaof(...\func_get_args());
+ }
+ }
+} else {
+ /**
+ * @internal
+ */
+ trait Redis6ProxyTrait
+ {
+ public function dump($key): \Redis|string
+ {
+ return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->dump(...\func_get_args());
+ }
+
+ public function mget($keys): \Redis|array
+ {
+ return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->mget(...\func_get_args());
+ }
+ }
+}
diff --git a/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php b/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php
index da6526b6eb5d1..6e06b075f27d7 100644
--- a/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php
+++ b/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php
@@ -28,6 +28,7 @@ class RedisCluster6Proxy extends \RedisCluster implements ResetInterface, LazyOb
use LazyProxyTrait {
resetLazyObject as reset;
}
+ use RedisCluster6ProxyTrait;
private const LAZY_OBJECT_PROPERTY_SCOPES = [];
@@ -96,11 +97,6 @@ public function bgrewriteaof($key_or_address): \RedisCluster|bool
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgrewriteaof(...\func_get_args());
}
- public function waitaof($key_or_address, $numlocal, $numreplicas, $timeout): \RedisCluster|array|false
- {
- return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->waitaof(...\func_get_args());
- }
-
public function bgsave($key_or_address): \RedisCluster|bool
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->bgsave(...\func_get_args());
@@ -661,11 +657,6 @@ public function pttl($key): \RedisCluster|false|int
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pttl(...\func_get_args());
}
- public function publish($channel, $message): \RedisCluster|bool
- {
- return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args());
- }
-
public function pubsub($key_or_address, ...$values): mixed
{
return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->pubsub(...\func_get_args());
diff --git a/src/Symfony/Component/Cache/Traits/RedisCluster6ProxyTrait.php b/src/Symfony/Component/Cache/Traits/RedisCluster6ProxyTrait.php
new file mode 100644
index 0000000000000..389c6e1adf347
--- /dev/null
+++ b/src/Symfony/Component/Cache/Traits/RedisCluster6ProxyTrait.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+if (version_compare(phpversion('redis'), '6.0.2', '>')) {
+ /**
+ * @internal
+ */
+ trait RedisCluster6ProxyTrait
+ {
+ public function publish($channel, $message): \RedisCluster|bool|int
+ {
+ return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args());
+ }
+
+ public function waitaof($key_or_address, $numlocal, $numreplicas, $timeout): \RedisCluster|array|false
+ {
+ return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->waitaof(...\func_get_args());
+ }
+ }
+} else {
+ /**
+ * @internal
+ */
+ trait RedisCluster6ProxyTrait
+ {
+ public function publish($channel, $message): \RedisCluster|bool
+ {
+ return ($this->lazyObjectState->realInstance ??= ($this->lazyObjectState->initializer)())->publish(...\func_get_args());
+ }
+ }
+}
diff --git a/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php b/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php
index 67caa05a5bc3a..abcf1bd9e9745 100644
--- a/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php
+++ b/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php
@@ -18,7 +18,6 @@
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
use Symfony\Component\Config\Definition\ScalarNode;
-use Symfony\Component\Config\Definition\VariableNode;
use Symfony\Component\Yaml\Inline;
/**
@@ -99,19 +98,12 @@ private function writeNode(NodeInterface $node, ?NodeInterface $parentNode = nul
$children = $this->getPrototypeChildren($node);
}
- if (!$children) {
- if ($node->hasDefaultValue() && \count($defaultArray = $node->getDefaultValue())) {
- $default = '';
- } elseif (!\is_array($example)) {
- $default = '[]';
- }
+ if (!$children && !($node->hasDefaultValue() && \count($defaultArray = $node->getDefaultValue()))) {
+ $default = '[]';
}
} elseif ($node instanceof EnumNode) {
$comments[] = 'One of '.$node->getPermissibleValues('; ');
$default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~';
- } elseif (VariableNode::class === $node::class && \is_array($example)) {
- // If there is an array example, we are sure we dont need to print a default value
- $default = '';
} else {
$default = '~';
@@ -179,7 +171,7 @@ private function writeNode(NodeInterface $node, ?NodeInterface $parentNode = nul
$this->writeLine('# '.$message.':', $depth * 4 + 4);
- $this->writeArray(array_map(Inline::dump(...), $example), $depth + 1);
+ $this->writeArray(array_map(Inline::dump(...), $example), $depth + 1, true);
}
if ($children) {
@@ -200,7 +192,7 @@ private function writeLine(string $text, int $indent = 0): void
$this->reference .= sprintf($format, $text)."\n";
}
- private function writeArray(array $array, int $depth): void
+ private function writeArray(array $array, int $depth, bool $asComment = false): void
{
$isIndexed = array_is_list($array);
@@ -211,14 +203,16 @@ private function writeArray(array $array, int $depth): void
$val = $value;
}
+ $prefix = $asComment ? '# ' : '';
+
if ($isIndexed) {
- $this->writeLine('- '.$val, $depth * 4);
+ $this->writeLine($prefix.'- '.$val, $depth * 4);
} else {
- $this->writeLine(sprintf('%-20s %s', $key.':', $val), $depth * 4);
+ $this->writeLine(sprintf('%s%-20s %s', $prefix, $key.':', $val), $depth * 4);
}
if (\is_array($value)) {
- $this->writeArray($value, $depth + 1);
+ $this->writeArray($value, $depth + 1, $asComment);
}
}
}
diff --git a/src/Symfony/Component/Config/Tests/Definition/Dumper/XmlReferenceDumperTest.php b/src/Symfony/Component/Config/Tests/Definition/Dumper/XmlReferenceDumperTest.php
index e6ce07588f9d0..84d9f596c1892 100644
--- a/src/Symfony/Component/Config/Tests/Definition/Dumper/XmlReferenceDumperTest.php
+++ b/src/Symfony/Component/Config/Tests/Definition/Dumper/XmlReferenceDumperTest.php
@@ -109,6 +109,8 @@ enum=""
+
+
EOL
diff --git a/src/Symfony/Component/Config/Tests/Definition/Dumper/YamlReferenceDumperTest.php b/src/Symfony/Component/Config/Tests/Definition/Dumper/YamlReferenceDumperTest.php
index 18ad445c3ef5d..cb33603f6cbb0 100644
--- a/src/Symfony/Component/Config/Tests/Definition/Dumper/YamlReferenceDumperTest.php
+++ b/src/Symfony/Component/Config/Tests/Definition/Dumper/YamlReferenceDumperTest.php
@@ -114,11 +114,11 @@ enum: ~ # One of "this"; "that"; Symfony\Component\Config\Tests\
# which should be indented
child3: ~ # Example: 'example setting'
scalar_prototyped: []
- variable:
+ variable: ~
# Examples:
- - foo
- - bar
+ # - foo
+ # - bar
parameters:
# Prototype: Parameter name
@@ -142,6 +142,11 @@ enum: ~ # One of "this"; "that"; Symfony\Component\Config\Tests\
# Prototype
name: []
+ array_with_array_example_and_no_default_value: []
+
+ # Examples:
+ # - foo
+ # - bar
custom_node: true
EOL;
diff --git a/src/Symfony/Component/Config/Tests/Fixtures/Configuration/ExampleConfiguration.php b/src/Symfony/Component/Config/Tests/Fixtures/Configuration/ExampleConfiguration.php
index bdf6d80bff443..9f62a684a38fa 100644
--- a/src/Symfony/Component/Config/Tests/Fixtures/Configuration/ExampleConfiguration.php
+++ b/src/Symfony/Component/Config/Tests/Fixtures/Configuration/ExampleConfiguration.php
@@ -97,6 +97,9 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end()
+ ->arrayNode('array_with_array_example_and_no_default_value')
+ ->example(['foo', 'bar'])
+ ->end()
->append(new CustomNodeDefinition('acme'))
->end()
;
diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php
index f4eec051c19a7..b406292b44b3a 100644
--- a/src/Symfony/Component/Console/Helper/ProgressBar.php
+++ b/src/Symfony/Component/Console/Helper/ProgressBar.php
@@ -183,9 +183,9 @@ public function setMessage(string $message, string $name = 'message'): void
$this->messages[$name] = $message;
}
- public function getMessage(string $name = 'message'): string
+ public function getMessage(string $name = 'message'): ?string
{
- return $this->messages[$name];
+ return $this->messages[$name] ?? null;
}
public function getStartTime(): int
diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php
index 6aad9e95b8c68..1f026dc504adb 100644
--- a/src/Symfony/Component/Console/Helper/Table.php
+++ b/src/Symfony/Component/Console/Helper/Table.php
@@ -371,8 +371,9 @@ public function render()
if ($headers && !$containsColspan) {
if (0 === $idx) {
$rows[] = [sprintf(
- '%s>: %s',
- str_pad($headers[$i] ?? '', $maxHeaderLength, ' ', \STR_PAD_LEFT),
+ '%s%s>: %s',
+ str_repeat(' ', $maxHeaderLength - Helper::width(Helper::removeDecoration($formatter, $headers[$i] ?? ''))),
+ $headers[$i] ?? '',
$part
)];
} else {
diff --git a/src/Symfony/Component/Console/Tests/Helper/TableTest.php b/src/Symfony/Component/Console/Tests/Helper/TableTest.php
index 728ea847f031f..4af12e34c0680 100644
--- a/src/Symfony/Component/Console/Tests/Helper/TableTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/TableTest.php
@@ -1649,6 +1649,28 @@ public static function provideRenderVerticalTests(): \Traversable
$books,
];
+ yield 'With multibyte characters in some headers (the "í" in "Títle") and cells (the "í" in "Dívíne")' => [
+ << [
<< $tags) {
foreach ($tags as $attributes) {
$tag = $this->document->createElement('tag');
- if (!\array_key_exists('name', $attributes)) {
- $tag->setAttribute('name', $name);
- } else {
- $tag->appendChild($this->document->createTextNode($name));
- }
// Check if we have recursive attributes
if (array_filter($attributes, \is_array(...))) {
+ $tag->setAttribute('name', $name);
$this->addTagRecursiveAttributes($tag, $attributes);
} else {
+ if (!\array_key_exists('name', $attributes)) {
+ $tag->setAttribute('name', $name);
+ } else {
+ $tag->appendChild($this->document->createTextNode($name));
+ }
foreach ($attributes as $key => $value) {
$tag->setAttribute($key, $value ?? '');
}
diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php
index 520977763f3ad..b8f31ee41e94e 100644
--- a/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php
+++ b/src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php
@@ -26,7 +26,7 @@ interface DumperInterface
* @param bool|null &$asGhostObject Set to true after the call if the proxy is a ghost object
* @param string|null $id
*/
- public function isProxyCandidate(Definition $definition/* , bool &$asGhostObject = null, string $id = null */): bool;
+ public function isProxyCandidate(Definition $definition/* , ?bool &$asGhostObject = null, ?string $id = null */): bool;
/**
* Generates the code to be used to instantiate a proxy in the dumped factory code.
@@ -38,5 +38,5 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $
*
* @param string|null $id
*/
- public function getProxyCode(Definition $definition/* , string $id = null */): string;
+ public function getProxyCode(Definition $definition/* , ?string $id = null */): string;
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
index 3588a5b2a594a..de8fea8ab4256 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
@@ -35,6 +35,7 @@
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarInterface;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\OptionalParameter;
use Symfony\Component\DependencyInjection\Tests\Fixtures\WithTarget;
use Symfony\Component\DependencyInjection\Tests\Fixtures\WithTargetAnonymous;
use Symfony\Component\DependencyInjection\TypedReference;
@@ -405,6 +406,9 @@ public function testResolveParameter()
$this->assertEquals(Foo::class, $container->getDefinition('bar')->getArgument(0));
}
+ /**
+ * @group legacy
+ */
public function testOptionalParameter()
{
$container = new ContainerBuilder();
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php
index f99a3f9eb5196..4a7d87358aad8 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php
@@ -15,12 +15,12 @@ class Bar implements BarInterface
{
public $quz;
- public function __construct($quz = null, \NonExistent $nonExistent = null, BarInterface $decorated = null, array $foo = [], iterable $baz = [])
+ public function __construct($quz = null, ?\NonExistent $nonExistent = null, ?BarInterface $decorated = null, array $foo = [], iterable $baz = [])
{
$this->quz = $quz;
}
- public static function create(\NonExistent $nonExistent = null, $factory = null)
+ public static function create(?\NonExistent $nonExistent = null, $factory = null)
{
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php
index 65437a63ec743..53f8bb7c3221e 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php
@@ -20,7 +20,7 @@ public function setFoosVariadic(Foo $foo, Foo ...$foos)
$this->foo = $foo;
}
- public function setFoosOptional(Foo $foo, Foo $fooOptional = null)
+ public function setFoosOptional(Foo $foo, ?Foo $fooOptional = null)
{
$this->foo = $foo;
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgument.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgument.php
index 4f348895132ca..98ee3a45a6036 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgument.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarOptionalArgument.php
@@ -6,7 +6,7 @@ class BarOptionalArgument
{
public $foo;
- public function __construct(\stdClass $foo = null)
+ public function __construct(?\stdClass $foo = null)
{
$this->foo = $foo;
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Foo.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Foo.php
index e775def689305..36f027f1dd9c6 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Foo.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/Foo.php
@@ -9,7 +9,7 @@ public static function createBar()
return new Bar(new \stdClass());
}
- public static function createBarArguments(\stdClass $stdClass, \stdClass $stdClassOptional = null)
+ public static function createBarArguments(\stdClass $stdClass, ?\stdClass $stdClassOptional = null)
{
return new Bar($stdClass);
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/OptionalParameter.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/OptionalParameter.php
new file mode 100644
index 0000000000000..8674c648e9005
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/OptionalParameter.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
+
+use Symfony\Component\DependencyInjection\Tests\Compiler\A;
+use Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface;
+use Symfony\Component\DependencyInjection\Tests\Compiler\Foo;
+
+class OptionalParameter
+{
+ public function __construct(?CollisionInterface $c = null, A $a, ?Foo $f = null)
+ {
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/Foo.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/Foo.php
index 807c8b3e20086..0659c968c8e72 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/Foo.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/Foo.php
@@ -8,7 +8,7 @@
#[When(env: 'dev')]
class Foo implements FooInterface, Sub\BarInterface
{
- public function __construct($bar = null, iterable $foo = null, object $baz = null)
+ public function __construct($bar = null, ?iterable $foo = null, ?object $baz = null)
{
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_non_scalar_tags.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_non_scalar_tags.php
index 76c69868cc49a..2a1234fa7e26a 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_non_scalar_tags.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_non_scalar_tags.php
@@ -10,6 +10,7 @@
$container
->register('foo', FooClass::class)
->addTag('foo_tag', [
+ 'name' => 'attributeName',
'foo' => 'bar',
'bar' => [
'foo' => 'bar',
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
index a8e71068775b4..a9ac5c0bff430 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
@@ -30,7 +30,7 @@ class Foo
public static int $counter = 0;
#[Required]
- public function cloneFoo(\stdClass $bar = null): static
+ public function cloneFoo(?\stdClass $bar = null): static
{
++self::$counter;
@@ -120,7 +120,7 @@ public function __construct(A $a, DInterface $d)
class E
{
- public function __construct(D $d = null)
+ public function __construct(?D $d = null)
{
}
}
@@ -176,13 +176,6 @@ public function __construct(Dunglas $j, Dunglas $k)
}
}
-class OptionalParameter
-{
- public function __construct(CollisionInterface $c = null, A $a, Foo $f = null)
- {
- }
-}
-
class BadTypeHintedArgument
{
public function __construct(Dunglas $k, NotARealClass $r)
@@ -216,7 +209,7 @@ public function __construct(A $k, $foo, Dunglas $dunglas, array $bar)
class MultipleArgumentsOptionalScalar
{
- public function __construct(A $a, $foo = 'default_val', Lille $lille = null)
+ public function __construct(A $a, $foo = 'default_val', ?Lille $lille = null)
{
}
}
@@ -240,7 +233,7 @@ public function __construct(
*/
class ClassForResource
{
- public function __construct($foo, Bar $bar = null)
+ public function __construct($foo, ?Bar $bar = null)
{
}
@@ -455,7 +448,7 @@ public function setBar()
{
}
- public function setOptionalNotAutowireable(NotARealClass $n = null)
+ public function setOptionalNotAutowireable(?NotARealClass $n = null)
{
}
@@ -513,7 +506,7 @@ class DecoratorImpl implements DecoratorInterface
class Decorated implements DecoratorInterface
{
- public function __construct($quz = null, \NonExistent $nonExistent = null, DecoratorInterface $decorated = null, array $foo = [])
+ public function __construct($quz = null, ?\NonExistent $nonExistent = null, ?DecoratorInterface $decorated = null, array $foo = [])
{
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php
index 69ca09218812c..0c7cc2a7b7baf 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes_80.php
@@ -107,7 +107,7 @@ public function __construct(string $arg1, #[AutowireDecorated] AsDecoratorInterf
#[AsDecorator(decorates: \NonExistent::class, onInvalid: ContainerInterface::NULL_ON_INVALID_REFERENCE)]
class AsDecoratorBaz implements AsDecoratorInterface
{
- public function __construct(#[AutowireDecorated] AsDecoratorInterface $inner = null)
+ public function __construct(#[AutowireDecorated] ?AsDecoratorInterface $inner = null)
{
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php
index 846c8fe64797b..6084c42c77dd4 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php
@@ -83,7 +83,7 @@ public function callPassed()
class DummyProxyDumper implements DumperInterface
{
- public function isProxyCandidate(Definition $definition, bool &$asGhostObject = null, string $id = null): bool
+ public function isProxyCandidate(Definition $definition, ?bool &$asGhostObject = null, ?string $id = null): bool
{
$asGhostObject = false;
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_array_tags.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_array_tags.xml
index 8e910be31431c..ba8d790571e8b 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_array_tags.xml
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_with_array_tags.xml
@@ -4,6 +4,7 @@
+ attributeName
bar
bar
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_array_tags.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_array_tags.yml
index 3f580df3e30ef..e4f355c045699 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_array_tags.yml
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_with_array_tags.yml
@@ -7,4 +7,4 @@ services:
foo:
class: Bar\FooClass
tags:
- - foo_tag: { foo: bar, bar: { foo: bar, bar: foo } }
+ - foo_tag: { name: attributeName, foo: bar, bar: { foo: bar, bar: foo } }
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php
index c61fb0be569bc..d53fe9398d468 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php
@@ -468,7 +468,7 @@ public function testParseServiceTagsWithArrayAttributes()
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('services_with_array_tags.xml');
- $this->assertEquals(['foo_tag' => [['foo' => 'bar', 'bar' => ['foo' => 'bar', 'bar' => 'foo']]]], $container->getDefinition('foo')->getTags());
+ $this->assertEquals(['foo_tag' => [['name' => 'attributeName', 'foo' => 'bar', 'bar' => ['foo' => 'bar', 'bar' => 'foo']]]], $container->getDefinition('foo')->getTags());
}
public function testParseTagsWithoutNameThrowsException()
diff --git a/src/Symfony/Component/ErrorHandler/ErrorHandler.php b/src/Symfony/Component/ErrorHandler/ErrorHandler.php
index c096eaca3886a..d82ce6c7024cc 100644
--- a/src/Symfony/Component/ErrorHandler/ErrorHandler.php
+++ b/src/Symfony/Component/ErrorHandler/ErrorHandler.php
@@ -595,6 +595,10 @@ public static function handleFatalError(?array $error = null): void
set_exception_handler($h);
}
if (!$handler) {
+ if (null === $error && $exitCode = self::$exitCode) {
+ register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
+ }
+
return;
}
if ($handler !== $h) {
@@ -630,8 +634,7 @@ public static function handleFatalError(?array $error = null): void
// Ignore this re-throw
}
- if ($exit && self::$exitCode) {
- $exitCode = self::$exitCode;
+ if ($exit && $exitCode = self::$exitCode) {
register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
}
}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php
index f216a8fba63e1..2e13fc40e71b7 100644
--- a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php
+++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php
@@ -31,6 +31,13 @@
*/
class ErrorHandlerTest extends TestCase
{
+ protected function tearDown(): void
+ {
+ $r = new \ReflectionProperty(ErrorHandler::class, 'exitCode');
+ $r->setAccessible(true);
+ $r->setValue(null, 0);
+ }
+
public function testRegister()
{
$handler = ErrorHandler::register();
diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/FileLinkFormatterTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/FileLinkFormatterTest.php
index a5f6330679ff9..fd6d44e316af1 100644
--- a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/FileLinkFormatterTest.php
+++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/FileLinkFormatterTest.php
@@ -27,6 +27,11 @@ public function testWhenNoFileLinkFormatAndNoRequest()
public function testAfterUnserialize()
{
+ if (get_cfg_var('xdebug.file_link_format')) {
+ // There is no way to override "xdebug.file_link_format" option in a test.
+ $this->markTestSkipped('php.ini has a custom option for "xdebug.file_link_format".');
+ }
+
$ide = $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? null;
$_ENV['SYMFONY_IDE'] = $_SERVER['SYMFONY_IDE'] = null;
$sut = unserialize(serialize(new FileLinkFormatter()));
diff --git a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ClassWithAnnotatedParameters.php b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ClassWithAnnotatedParameters.php
index 2bac262ddb49d..a9cf0dfcb4d2b 100644
--- a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ClassWithAnnotatedParameters.php
+++ b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ClassWithAnnotatedParameters.php
@@ -14,14 +14,14 @@ public function fooMethod(string $foo)
/**
* @param string $bar parameter not implemented yet
*/
- public function barMethod(/* string $bar = null */)
+ public function barMethod(/* ?string $bar = null */)
{
}
/**
* @param Quz $quz parameter not implemented yet
*/
- public function quzMethod(/* Quz $quz = null */)
+ public function quzMethod(/* ?Quz $quz = null */)
{
}
diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php
index 0a25f882479d7..48c66a8e41b1d 100644
--- a/src/Symfony/Component/Filesystem/Filesystem.php
+++ b/src/Symfony/Component/Filesystem/Filesystem.php
@@ -74,6 +74,9 @@ public function copy(string $originFile, string $targetFile, bool $overwriteNewe
// Like `cp`, preserve executable permission bits
self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
+ // Like `cp`, preserve the file modification time
+ self::box('touch', $targetFile, filemtime($originFile));
+
if ($bytesCopied !== $bytesOrigin = filesize($originFile)) {
throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
}
@@ -198,7 +201,7 @@ private static function doRemove(array $files, bool $isRecursive): void
throw new IOException(sprintf('Failed to remove directory "%s": ', $file).$lastError);
}
- } elseif (!self::box('unlink', $file) && (str_contains(self::$lastError, 'Permission denied') || file_exists($file))) {
+ } elseif (!self::box('unlink', $file) && ((self::$lastError && str_contains(self::$lastError, 'Permission denied')) || file_exists($file))) {
throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError);
}
}
diff --git a/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php b/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php
index cb8ed6a775140..bf4c1466c5894 100644
--- a/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php
+++ b/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php
@@ -28,7 +28,7 @@ class MockStream
* @param string|null $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options,
* opened_path should be set to the full path of the file/resource that was actually opened
*/
- public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool
+ public function stream_open(string $path, string $mode, int $options, ?string &$opened_path = null): bool
{
return true;
}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php
index 77b1e75bd49a5..96bdc7c0de1a1 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php
@@ -109,6 +109,10 @@ public function reverseTransform(mixed $value): ?\DateTime
throw new TransformationFailedException('Expected a string.');
}
+ if (str_contains($value, "\0")) {
+ throw new TransformationFailedException('Null bytes not allowed');
+ }
+
$outputTz = new \DateTimeZone($this->outputTimezone);
$dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz);
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php
index 66ad9ff416e26..f7ef667e769b6 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php
@@ -133,6 +133,19 @@ public function testReverseTransformEmpty()
$this->assertNull($reverseTransformer->reverseTransform(''));
}
+ public function testReverseTransformWithNullBytes()
+ {
+ $transformer = new DateTimeToStringTransformer();
+
+ $nullByte = \chr(0);
+ $value = '2024-03-15 21:11:00'.$nullByte;
+
+ $this->expectException(TransformationFailedException::class);
+ $this->expectExceptionMessage('Null bytes not allowed');
+
+ $transformer->reverseTransform($value);
+ }
+
public function testReverseTransformWithDifferentTimezones()
{
$reverseTransformer = new DateTimeToStringTransformer('America/New_York', 'Asia/Hong_Kong', 'Y-m-d H:i:s');
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTestCase.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTestCase.php
index e86bf9e41ed13..5238e2fd88098 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTestCase.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTestCase.php
@@ -40,16 +40,6 @@ public function testPassIdAndNameToView()
$this->assertEquals('name', $view->vars['full_name']);
}
- public function testStripLeadingUnderscoresAndDigitsFromId()
- {
- $view = $this->factory->createNamed('_09name', $this->getTestedType(), null, $this->getTestOptions())
- ->createView();
-
- $this->assertEquals('name', $view->vars['id']);
- $this->assertEquals('_09name', $view->vars['name']);
- $this->assertEquals('_09name', $view->vars['full_name']);
- }
-
public function testPassIdAndNameToViewWithParent()
{
$view = $this->factory->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE)
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/CustomArrayObject.php b/src/Symfony/Component/Form/Tests/Fixtures/CustomArrayObject.php
index b37bfe5ed2d85..8be0323ae1a9e 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/CustomArrayObject.php
+++ b/src/Symfony/Component/Form/Tests/Fixtures/CustomArrayObject.php
@@ -19,7 +19,7 @@ class CustomArrayObject implements \ArrayAccess, \IteratorAggregate, \Countable
{
private array $array;
- public function __construct(array $array = null)
+ public function __construct(?array $array = null)
{
$this->array = $array ?: [];
}
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FixedTranslator.php b/src/Symfony/Component/Form/Tests/Fixtures/FixedTranslator.php
index 1fc0fa90165f8..432f2ab12db90 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/FixedTranslator.php
+++ b/src/Symfony/Component/Form/Tests/Fixtures/FixedTranslator.php
@@ -22,7 +22,7 @@ public function __construct(array $translations)
$this->translations = $translations;
}
- public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string
+ public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
{
return $this->translations[$id] ?? $id;
}
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/TranslatableTextAlign.php b/src/Symfony/Component/Form/Tests/Fixtures/TranslatableTextAlign.php
index 7a5d5cdff68e7..4464088c78103 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/TranslatableTextAlign.php
+++ b/src/Symfony/Component/Form/Tests/Fixtures/TranslatableTextAlign.php
@@ -20,7 +20,7 @@ enum TranslatableTextAlign implements TranslatableInterface
case Center;
case Right;
- public function trans(TranslatorInterface $translator, string $locale = null): string
+ public function trans(TranslatorInterface $translator, ?string $locale = null): string
{
return $translator->trans($this->name, locale: $locale);
}
diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php
index e74e0263b985f..4446a031e9695 100644
--- a/src/Symfony/Component/HttpClient/CurlHttpClient.php
+++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php
@@ -51,6 +51,9 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
private ?LoggerInterface $logger = null;
+ private int $maxHostConnections;
+ private int $maxPendingPushes;
+
/**
* An internal object to share state between the client and its responses.
*/
@@ -69,18 +72,22 @@ public function __construct(array $defaultOptions = [], int $maxHostConnections
throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\CurlHttpClient" as the "curl" extension is not installed.');
}
+ $this->maxHostConnections = $maxHostConnections;
+ $this->maxPendingPushes = $maxPendingPushes;
+
$this->defaultOptions['buffer'] ??= self::shouldBuffer(...);
if ($defaultOptions) {
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
}
-
- $this->multi = new CurlClientState($maxHostConnections, $maxPendingPushes);
}
public function setLogger(LoggerInterface $logger): void
{
- $this->logger = $this->multi->logger = $logger;
+ $this->logger = $logger;
+ if (isset($this->multi)) {
+ $this->multi->logger = $logger;
+ }
}
/**
@@ -88,6 +95,8 @@ public function setLogger(LoggerInterface $logger): void
*/
public function request(string $method, string $url, array $options = []): ResponseInterface
{
+ $multi = $this->ensureState();
+
[$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions);
$scheme = $url['scheme'];
$authority = $url['authority'];
@@ -165,24 +174,24 @@ public function request(string $method, string $url, array $options = []): Respo
}
// curl's resolve feature varies by host:port but ours varies by host only, let's handle this with our own DNS map
- if (isset($this->multi->dnsCache->hostnames[$host])) {
- $options['resolve'] += [$host => $this->multi->dnsCache->hostnames[$host]];
+ if (isset($multi->dnsCache->hostnames[$host])) {
+ $options['resolve'] += [$host => $multi->dnsCache->hostnames[$host]];
}
- if ($options['resolve'] || $this->multi->dnsCache->evictions) {
+ if ($options['resolve'] || $multi->dnsCache->evictions) {
// First reset any old DNS cache entries then add the new ones
- $resolve = $this->multi->dnsCache->evictions;
- $this->multi->dnsCache->evictions = [];
+ $resolve = $multi->dnsCache->evictions;
+ $multi->dnsCache->evictions = [];
if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) {
// DNS cache removals require curl 7.42 or higher
- $this->multi->reset();
+ $multi->reset();
}
foreach ($options['resolve'] as $host => $ip) {
$resolve[] = null === $ip ? "-$host:$port" : "$host:$port:$ip";
- $this->multi->dnsCache->hostnames[$host] = $ip;
- $this->multi->dnsCache->removals["-$host:$port"] = "-$host:$port";
+ $multi->dnsCache->hostnames[$host] = $ip;
+ $multi->dnsCache->removals["-$host:$port"] = "-$host:$port";
}
$curlopts[\CURLOPT_RESOLVE] = $resolve;
@@ -285,8 +294,8 @@ public function request(string $method, string $url, array $options = []): Respo
$curlopts += $options['extra']['curl'];
}
- if ($pushedResponse = $this->multi->pushedResponses[$url] ?? null) {
- unset($this->multi->pushedResponses[$url]);
+ if ($pushedResponse = $multi->pushedResponses[$url] ?? null) {
+ unset($multi->pushedResponses[$url]);
if (self::acceptPushForRequest($method, $options, $pushedResponse)) {
$this->logger?->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url));
@@ -294,7 +303,7 @@ public function request(string $method, string $url, array $options = []): Respo
// Reinitialize the pushed response with request's options
$ch = $pushedResponse->handle;
$pushedResponse = $pushedResponse->response;
- $pushedResponse->__construct($this->multi, $url, $options, $this->logger);
+ $pushedResponse->__construct($multi, $url, $options, $this->logger);
} else {
$this->logger?->debug(sprintf('Rejecting pushed response: "%s"', $url));
$pushedResponse = null;
@@ -304,7 +313,7 @@ public function request(string $method, string $url, array $options = []): Respo
if (!$pushedResponse) {
$ch = curl_init();
$this->logger?->info(sprintf('Request: "%s %s"', $method, $url));
- $curlopts += [\CURLOPT_SHARE => $this->multi->share];
+ $curlopts += [\CURLOPT_SHARE => $multi->share];
}
foreach ($curlopts as $opt => $value) {
@@ -314,7 +323,7 @@ public function request(string $method, string $url, array $options = []): Respo
}
}
- return $pushedResponse ?? new CurlResponse($this->multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host, $port), CurlClientState::$curlVersion['version_number'], $url);
+ return $pushedResponse ?? new CurlResponse($multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host, $port), CurlClientState::$curlVersion['version_number'], $url);
}
public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface
@@ -323,9 +332,11 @@ public function stream(ResponseInterface|iterable $responses, ?float $timeout =
$responses = [$responses];
}
- if ($this->multi->handle instanceof \CurlMultiHandle) {
+ $multi = $this->ensureState();
+
+ if ($multi->handle instanceof \CurlMultiHandle) {
$active = 0;
- while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)) {
+ while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($multi->handle, $active)) {
}
}
@@ -334,7 +345,9 @@ public function stream(ResponseInterface|iterable $responses, ?float $timeout =
public function reset(): void
{
- $this->multi->reset();
+ if (isset($this->multi)) {
+ $this->multi->reset();
+ }
}
/**
@@ -434,6 +447,16 @@ private static function createRedirectResolver(array $options, string $host, int
};
}
+ private function ensureState(): CurlClientState
+ {
+ if (!isset($this->multi)) {
+ $this->multi = new CurlClientState($this->maxHostConnections, $this->maxPendingPushes);
+ $this->multi->logger = $this->logger;
+ }
+
+ return $this->multi;
+ }
+
private function findConstantName(int $opt): ?string
{
$constants = array_filter(get_defined_constants(), static fn ($v, $k) => $v === $opt && 'C' === $k[0] && (str_starts_with($k, 'CURLOPT_') || str_starts_with($k, 'CURLINFO_')), \ARRAY_FILTER_USE_BOTH);
diff --git a/src/Symfony/Component/HttpClient/Response/JsonMockResponse.php b/src/Symfony/Component/HttpClient/Response/JsonMockResponse.php
index 66372aa8a8149..9372dbe5a0b0d 100644
--- a/src/Symfony/Component/HttpClient/Response/JsonMockResponse.php
+++ b/src/Symfony/Component/HttpClient/Response/JsonMockResponse.php
@@ -21,7 +21,7 @@ class JsonMockResponse extends MockResponse
public function __construct(mixed $body = [], array $info = [])
{
try {
- $json = json_encode($body, \JSON_THROW_ON_ERROR);
+ $json = json_encode($body, \JSON_THROW_ON_ERROR | \JSON_PRESERVE_ZERO_FRACTION);
} catch (\JsonException $e) {
throw new InvalidArgumentException('JSON encoding failed: '.$e->getMessage(), $e->getCode(), $e);
}
diff --git a/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php
index 266b539ef6b59..20cf7ef48291b 100644
--- a/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php
@@ -58,8 +58,8 @@ public function testHandleIsReinitOnReset()
{
$httpClient = $this->getHttpClient(__FUNCTION__);
- $r = new \ReflectionProperty($httpClient, 'multi');
- $clientState = $r->getValue($httpClient);
+ $r = new \ReflectionMethod($httpClient, 'ensureState');
+ $clientState = $r->invoke($httpClient);
$initialShareId = $clientState->share;
$httpClient->reset();
self::assertNotSame($initialShareId, $clientState->share);
diff --git a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php
index a7493100c431d..91ec7ea5f2c7c 100644
--- a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php
@@ -24,6 +24,11 @@ public static function setUpBeforeClass(): void
TestHttpServer::start();
}
+ public static function tearDownAfterClass(): void
+ {
+ TestHttpServer::stop();
+ }
+
public function testItCollectsRequestCount()
{
$httpClient1 = $this->httpClientThatHasTracedRequests([
diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php
index 613d80cb1d3a7..3e81f429622a0 100644
--- a/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php
@@ -72,10 +72,8 @@ public function testPrepareRequestWithBodyIsArray()
public function testNormalizeBodyMultipart()
{
$file = fopen('php://memory', 'r+');
- stream_context_set_option($file, ['http' => [
- 'filename' => 'test.txt',
- 'content_type' => 'text/plain',
- ]]);
+ stream_context_set_option($file, 'http', 'filename', 'test.txt');
+ stream_context_set_option($file, 'http', 'content_type', 'text/plain');
fwrite($file, 'foobarbaz');
rewind($file);
diff --git a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php
index 247588cd359a3..182d52932f724 100644
--- a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php
@@ -32,6 +32,11 @@ public static function setUpBeforeClass(): void
TestHttpServer::start();
}
+ public static function tearDownAfterClass(): void
+ {
+ TestHttpServer::stop();
+ }
+
public function testSendRequest()
{
$client = new HttplugClient(new NativeHttpClient());
diff --git a/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php b/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php
index d4bae3ab5c4a3..d1f4deb03a006 100644
--- a/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php
@@ -28,6 +28,11 @@ public static function setUpBeforeClass(): void
TestHttpServer::start();
}
+ public static function tearDownAfterClass(): void
+ {
+ TestHttpServer::stop();
+ }
+
public function testSendRequest()
{
$factory = new Psr17Factory();
diff --git a/src/Symfony/Component/HttpClient/Tests/Response/JsonMockResponseTest.php b/src/Symfony/Component/HttpClient/Tests/Response/JsonMockResponseTest.php
index b371c08cf4241..768353b04abd1 100644
--- a/src/Symfony/Component/HttpClient/Tests/Response/JsonMockResponseTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/Response/JsonMockResponseTest.php
@@ -59,6 +59,22 @@ public function testJsonEncodeString()
$this->assertSame('application/json', $response->getHeaders()['content-type'][0]);
}
+ public function testJsonEncodeFloat()
+ {
+ $client = new MockHttpClient(new JsonMockResponse([
+ 'foo' => 1.23,
+ 'ccc' => 1.0,
+ 'baz' => 10.,
+ ]));
+ $response = $client->request('GET', 'https://symfony.com');
+
+ $this->assertSame([
+ 'foo' => 1.23,
+ 'ccc' => 1.,
+ 'baz' => 10.,
+ ], $response->toArray());
+ }
+
/**
* @dataProvider responseHeadersProvider
*/
diff --git a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php
index ba9504ae1c66d..a0e39cc46c851 100644
--- a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php
@@ -27,6 +27,11 @@
class RetryableHttpClientTest extends TestCase
{
+ public static function tearDownAfterClass(): void
+ {
+ TestHttpServer::stop();
+ }
+
public function testRetryOnError()
{
$client = new RetryableHttpClient(
diff --git a/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php
index cf437a653bd76..b6a2c03c8f7a3 100644
--- a/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php
@@ -29,6 +29,11 @@ public static function setUpBeforeClass(): void
TestHttpServer::start();
}
+ public static function tearDownAfterClass(): void
+ {
+ TestHttpServer::stop();
+ }
+
public function testItTracesRequest()
{
$httpClient = $this->createMock(HttpClientInterface::class);
diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json
index 33fa3b4558004..ef456a603763c 100644
--- a/src/Symfony/Component/HttpClient/composer.json
+++ b/src/Symfony/Component/HttpClient/composer.json
@@ -25,7 +25,7 @@
"php": ">=8.1",
"psr/log": "^1|^2|^3",
"symfony/deprecation-contracts": "^2.5|^3",
- "symfony/http-client-contracts": "^3",
+ "symfony/http-client-contracts": "^3.4.1",
"symfony/service-contracts": "^2.5|^3"
},
"require-dev": {
@@ -33,7 +33,7 @@
"amphp/http-client": "^4.2.1",
"amphp/http-tunnel": "^1.0",
"amphp/socket": "^1.1",
- "guzzlehttp/promises": "^1.4",
+ "guzzlehttp/promises": "^1.4|^2.0",
"nyholm/psr7": "^1.0",
"php-http/httplug": "^1.0|^2.0",
"psr/http-client": "^1.0",
diff --git a/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php b/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php
index 9e5a3a56e1c30..626163d148b8a 100644
--- a/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php
+++ b/src/Symfony/Component/HttpKernel/Event/ControllerEvent.php
@@ -70,7 +70,7 @@ public function setController(callable $controller, ?array $attributes = null):
if (\is_array($controller) && method_exists(...$controller)) {
$this->controllerReflector = new \ReflectionMethod(...$controller);
} elseif (\is_string($controller) && str_contains($controller, '::')) {
- $this->controllerReflector = new \ReflectionMethod($controller);
+ $this->controllerReflector = new \ReflectionMethod(...explode('::', $controller, 2));
} else {
$this->controllerReflector = new \ReflectionFunction($controller(...));
}
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index d3ed631064403..1b57d2b84809c 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -76,11 +76,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '6.4.5';
- public const VERSION_ID = 60405;
+ public const VERSION = '6.4.6';
+ public const VERSION_ID = 60406;
public const MAJOR_VERSION = 6;
public const MINOR_VERSION = 4;
- public const RELEASE_VERSION = 5;
+ public const RELEASE_VERSION = 6;
public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '11/2026';
diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php
index e1508bf569b4b..ad011fc49de45 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php
@@ -25,7 +25,7 @@ public function __construct($varToClone)
$this->varToClone = $varToClone;
}
- public function collect(Request $request, Response $response, \Throwable $exception = null): void
+ public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
{
$this->data = $this->cloneVar($this->varToClone);
}
diff --git a/src/Symfony/Component/Lock/Store/PdoStore.php b/src/Symfony/Component/Lock/Store/PdoStore.php
index da3d968d2d930..5a57faa67bfed 100644
--- a/src/Symfony/Component/Lock/Store/PdoStore.php
+++ b/src/Symfony/Component/Lock/Store/PdoStore.php
@@ -241,10 +241,10 @@ private function getCurrentTimestampStatement(): string
private function isTableMissing(\PDOException $exception): bool
{
$driver = $this->getDriver();
- $code = $exception->getCode();
+ [$sqlState, $code] = $exception->errorInfo ?? [null, $exception->getCode()];
return match ($driver) {
- 'pgsql' => '42P01' === $code,
+ 'pgsql' => '42P01' === $sqlState,
'sqlite' => str_contains($exception->getMessage(), 'no such table:'),
'oci' => 942 === $code,
'sqlsrv' => 208 === $code,
diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php
index 51d3f72a9fd26..138b9b0dcc22e 100644
--- a/src/Symfony/Component/Lock/Store/RedisStore.php
+++ b/src/Symfony/Component/Lock/Store/RedisStore.php
@@ -294,7 +294,9 @@ private function getNowCode(): string
try {
$this->supportTime = 1 === $this->evaluate($script, 'symfony_check_support_time', []);
} catch (LockStorageException $e) {
- if (!str_contains($e->getMessage(), 'commands not allowed after non deterministic')) {
+ if (!str_contains($e->getMessage(), 'commands not allowed after non deterministic')
+ && !str_contains($e->getMessage(), 'is not allowed from script script')
+ ) {
throw $e;
}
$this->supportTime = false;
diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/RemoteEvent/BrevoPayloadConverter.php b/src/Symfony/Component/Mailer/Bridge/Brevo/RemoteEvent/BrevoPayloadConverter.php
index 8d556b8f37c9a..5f5f0816c23c6 100644
--- a/src/Symfony/Component/Mailer/Bridge/Brevo/RemoteEvent/BrevoPayloadConverter.php
+++ b/src/Symfony/Component/Mailer/Bridge/Brevo/RemoteEvent/BrevoPayloadConverter.php
@@ -60,7 +60,10 @@ public function convert(array $payload): AbstractMailerEvent
$event->setDate($date);
$event->setRecipientEmail($payload['email']);
- $event->setTags($payload['tags']);
+
+ if (isset($payload['tags'])) {
+ $event->setTags($payload['tags']);
+ }
return $event;
}
diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request_without_tags.json b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request_without_tags.json
new file mode 100644
index 0000000000000..4fbeadc58112b
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request_without_tags.json
@@ -0,0 +1,13 @@
+{
+ "id": 814119,
+ "email": "example@gmail.com",
+ "message-id": "<202305311313.92192897094@smtp-relay.mailin.fr>",
+ "date": "2023-05-31 15:13:08",
+ "event": "request",
+ "subject": "Subject Line",
+ "sending_ip": "127.0.0.1",
+ "ts_event": 1685538788,
+ "ts": 1685538788,
+ "reason": "sent",
+ "ts_epoch": 1685538788179
+}
diff --git a/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request_without_tags.php b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request_without_tags.php
new file mode 100644
index 0000000000000..57bb1ff1893ee
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/Brevo/Tests/Webhook/Fixtures/request_without_tags.php
@@ -0,0 +1,9 @@
+', json_decode(file_get_contents(str_replace('.php', '.json', __FILE__)), true, flags: JSON_THROW_ON_ERROR));
+$wh->setRecipientEmail('example@gmail.com');
+$wh->setDate(\DateTimeImmutable::createFromFormat('U', 1685538788));
+
+return $wh;
diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Fixtures/fake-failing-sendmail.php b/src/Symfony/Component/Mailer/Tests/Transport/Fixtures/fake-failing-sendmail.php
new file mode 100755
index 0000000000000..920b980e0f714
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Tests/Transport/Fixtures/fake-failing-sendmail.php
@@ -0,0 +1,4 @@
+#!/usr/bin/env php
+assertStringEqualsFile($this->argsPath, __DIR__.'/Fixtures/fake-sendmail.php -ffrom@mail.com recipient@mail.com');
}
+
+ public function testThrowsTransportExceptionOnFailure()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support shebangs nor non-blocking standard streams');
+ }
+
+ $mail = new Email();
+ $mail
+ ->from('from@mail.com')
+ ->to('to@mail.com')
+ ->subject('Subject')
+ ->text('Some text')
+ ;
+
+ $envelope = new DelayedEnvelope($mail);
+ $envelope->setRecipients([new Address('recipient@mail.com')]);
+
+ $sendmailTransport = new SendmailTransport(self::FAKE_FAILING_SENDMAIL);
+ $this->expectException(TransportException::class);
+ $this->expectExceptionMessage('Process failed with exit code 42: Sending failed');
+ $sendmailTransport->send($mail, $envelope);
+ }
}
diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/DummyStream.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/DummyStream.php
index 407c90810b78b..d67671ea10762 100644
--- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/DummyStream.php
+++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/DummyStream.php
@@ -77,7 +77,7 @@ public function write(string $bytes, $debug = true): void
} elseif (str_starts_with($bytes, 'QUIT')) {
$this->nextResponse = '221 Goodbye';
} else {
- $this->nextResponse = '250 OK';
+ $this->nextResponse = '250 OK queued as 000501c4054c';
}
}
diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php
index 6900320506053..a8d3540a115f9 100644
--- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php
+++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php
@@ -13,6 +13,8 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mailer\Event\MessageEvent;
+use Symfony\Component\Mailer\Event\SentMessageEvent;
use Symfony\Component\Mailer\Exception\LogicException;
use Symfony\Component\Mailer\Exception\TransportException;
use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport;
@@ -24,6 +26,7 @@
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\File;
use Symfony\Component\Mime\RawMessage;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* @group time-sensitive
@@ -137,6 +140,37 @@ public function testWriteEncodedRecipientAndSenderAddresses()
$this->assertContains("RCPT TO:\r\n", $stream->getCommands());
}
+ public function testMessageIdFromServerIsEmbeddedInSentMessageEvent()
+ {
+ $calls = 0;
+ $eventDispatcher = $this->createMock(EventDispatcherInterface::class);
+ $eventDispatcher->expects($this->any())
+ ->method('dispatch')
+ ->with($this->callback(static function ($event) use (&$calls): bool {
+ ++$calls;
+
+ if (1 === $calls && $event instanceof MessageEvent) {
+ return true;
+ }
+
+ if (2 === $calls && $event instanceof SentMessageEvent && '000501c4054c' === $event->getMessage()->getMessageId()) {
+ return true;
+ }
+
+ return false;
+ }));
+ $transport = new SmtpTransport(new DummyStream(), $eventDispatcher);
+
+ $email = new Email();
+ $email->from('sender@example.com');
+ $email->to('recipient@example.com');
+ $email->text('.');
+
+ $transport->send($email);
+
+ $this->assertSame(2, $calls);
+ }
+
public function testAssertResponseCodeNoCodes()
{
$this->expectException(LogicException::class);
diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php
index e05e347919d27..0de38fb2ed690 100644
--- a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php
+++ b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php
@@ -39,7 +39,6 @@ class SmtpTransport extends AbstractTransport
private int $pingThreshold = 100;
private float $lastMessageTime = 0;
private AbstractStream $stream;
- private string $mtaResult = '';
private string $domain = '[127.0.0.1]';
public function __construct(?AbstractStream $stream = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null)
@@ -148,10 +147,6 @@ public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMess
throw $e;
}
- if ($this->mtaResult && $messageId = $this->parseMessageId($this->mtaResult)) {
- $message->setMessageId($messageId);
- }
-
$this->checkRestartThreshold();
return $message;
@@ -235,9 +230,13 @@ protected function doSend(SentMessage $message): void
$this->getLogger()->debug(sprintf('Email transport "%s" stopped', __CLASS__));
throw $e;
}
- $this->mtaResult = $this->executeCommand("\r\n.\r\n", [250]);
+ $mtaResult = $this->executeCommand("\r\n.\r\n", [250]);
$message->appendDebug($this->stream->getDebug());
$this->lastMessageTime = microtime(true);
+
+ if ($mtaResult && $messageId = $this->parseMessageId($mtaResult)) {
+ $message->setMessageId($messageId);
+ }
} catch (TransportExceptionInterface $e) {
$e->appendDebug($this->stream->getDebug());
$this->lastMessageTime = 0;
diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/AbstractStream.php b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/AbstractStream.php
index a68748b6c9585..498dc560c3ede 100644
--- a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/AbstractStream.php
+++ b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/AbstractStream.php
@@ -30,6 +30,7 @@ abstract class AbstractStream
protected $in;
/** @var resource|null */
protected $out;
+ protected $err;
private string $debug = '';
@@ -68,7 +69,7 @@ abstract public function initialize(): void;
public function terminate(): void
{
- $this->stream = $this->out = $this->in = null;
+ $this->stream = $this->err = $this->out = $this->in = null;
}
public function readLine(): string
diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/ProcessStream.php b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/ProcessStream.php
index 3d59ecfecb7d9..8644d7dad7296 100644
--- a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/ProcessStream.php
+++ b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/ProcessStream.php
@@ -45,14 +45,20 @@ public function initialize(): void
}
$this->in = &$pipes[0];
$this->out = &$pipes[1];
+ $this->err = &$pipes[2];
}
public function terminate(): void
{
if (null !== $this->stream) {
fclose($this->in);
+ $out = stream_get_contents($this->out);
fclose($this->out);
- proc_close($this->stream);
+ $err = stream_get_contents($this->err);
+ fclose($this->err);
+ if (0 !== $exitCode = proc_close($this->stream)) {
+ throw new TransportException('Process failed with exit code '.$exitCode.': '.$out.$err);
+ }
}
parent::terminate();
diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php
index a08c102a4b9da..e57dad6cad97e 100644
--- a/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Tests/Transport/ConnectionTest.php
@@ -774,6 +774,73 @@ public function testItCanBeConstructedWithTLSOptionsAndNonTLSDsn()
);
}
+ public function testItCanRetryPublishWhenAMQPConnectionExceptionIsThrown()
+ {
+ $factory = new TestAmqpFactory(
+ $amqpConnection = $this->createMock(\AMQPConnection::class),
+ $amqpChannel = $this->createMock(\AMQPChannel::class),
+ $amqpQueue = $this->createMock(\AMQPQueue::class),
+ $amqpExchange = $this->createMock(\AMQPExchange::class)
+ );
+
+ $amqpExchange->expects($this->exactly(2))
+ ->method('publish')
+ ->willReturnOnConsecutiveCalls(
+ $this->throwException(new \AMQPConnectionException('a socket error occurred')),
+ null
+ );
+
+ $connection = Connection::fromDsn('amqp://localhost', [], $factory);
+ $connection->publish('body');
+ }
+
+ public function testItCanRetryPublishWithDelayWhenAMQPConnectionExceptionIsThrown()
+ {
+ $factory = new TestAmqpFactory(
+ $amqpConnection = $this->createMock(\AMQPConnection::class),
+ $amqpChannel = $this->createMock(\AMQPChannel::class),
+ $amqpQueue = $this->createMock(\AMQPQueue::class),
+ $amqpExchange = $this->createMock(\AMQPExchange::class)
+ );
+
+ $amqpExchange->expects($this->exactly(2))
+ ->method('publish')
+ ->willReturnOnConsecutiveCalls(
+ $this->throwException(new \AMQPConnectionException('a socket error occurred')),
+ null
+ );
+
+ $connection = Connection::fromDsn('amqp://localhost', [], $factory);
+ $connection->publish('body', [], 5000);
+ }
+
+ public function testItWillRetryMaxThreeTimesWhenAMQPConnectionExceptionIsThrown()
+ {
+ $factory = new TestAmqpFactory(
+ $amqpConnection = $this->createMock(\AMQPConnection::class),
+ $amqpChannel = $this->createMock(\AMQPChannel::class),
+ $amqpQueue = $this->createMock(\AMQPQueue::class),
+ $amqpExchange = $this->createMock(\AMQPExchange::class)
+ );
+
+ $exception = new \AMQPConnectionException('a socket error occurred');
+
+ $amqpExchange->expects($this->exactly(4))
+ ->method('publish')
+ ->willReturnOnConsecutiveCalls(
+ $this->throwException($exception),
+ $this->throwException($exception),
+ $this->throwException($exception),
+ $this->throwException($exception),
+ );
+
+ self::expectException($exception::class);
+ self::expectExceptionMessage($exception->getMessage());
+
+ $connection = Connection::fromDsn('amqp://localhost', [], $factory);
+ $connection->publish('body');
+ }
+
private function createDelayOrRetryConnection(\AMQPExchange $delayExchange, string $deadLetterExchangeName, string $delayQueueName): Connection
{
$amqpConnection = $this->createMock(\AMQPConnection::class);
diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php
index b39902623adce..0ae1bff21d7c8 100644
--- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php
+++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php
@@ -287,19 +287,21 @@ public function publish(string $body, array $headers = [], int $delayInMs = 0, ?
$this->setupExchangeAndQueues(); // also setup normal exchange for delayed messages so delay queue can DLX messages to it
}
- if (0 !== $delayInMs) {
- $this->publishWithDelay($body, $headers, $delayInMs, $amqpStamp);
+ $this->withConnectionExceptionRetry(function () use ($body, $headers, $delayInMs, $amqpStamp) {
+ if (0 !== $delayInMs) {
+ $this->publishWithDelay($body, $headers, $delayInMs, $amqpStamp);
- return;
- }
+ return;
+ }
- $this->publishOnExchange(
- $this->exchange(),
- $body,
- $this->getRoutingKeyForMessage($amqpStamp),
- $headers,
- $amqpStamp
- );
+ $this->publishOnExchange(
+ $this->exchange(),
+ $body,
+ $this->getRoutingKeyForMessage($amqpStamp),
+ $headers,
+ $amqpStamp
+ );
+ });
}
/**
@@ -545,11 +547,16 @@ public function exchange(): \AMQPExchange
private function clearWhenDisconnected(): void
{
if (!$this->channel()->isConnected()) {
- unset($this->amqpChannel, $this->amqpExchange, $this->amqpDelayExchange);
- $this->amqpQueues = [];
+ $this->clear();
}
}
+ private function clear(): void
+ {
+ unset($this->amqpChannel, $this->amqpExchange, $this->amqpDelayExchange);
+ $this->amqpQueues = [];
+ }
+
private function getDefaultPublishRoutingKey(): ?string
{
return $this->exchangeOptions['default_publish_routing_key'] ?? null;
@@ -566,4 +573,23 @@ private function getRoutingKeyForMessage(?AmqpStamp $amqpStamp): ?string
{
return $amqpStamp?->getRoutingKey() ?? $this->getDefaultPublishRoutingKey();
}
+
+ private function withConnectionExceptionRetry(callable $callable): void
+ {
+ $maxRetries = 3;
+ $retries = 0;
+
+ retry:
+ try {
+ $callable();
+ } catch (\AMQPConnectionException $e) {
+ if (++$retries <= $maxRetries) {
+ $this->clear();
+
+ goto retry;
+ }
+
+ throw $e;
+ }
+ }
}
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
index e3030d3a1d55f..5118e4ff3aaa5 100644
--- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
@@ -162,6 +162,10 @@ public function get(): ?array
$this->driverConnection->delete($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59']);
} catch (DriverException $e) {
// Ignore the exception
+ } catch (TableNotFoundException $e) {
+ if ($this->autoSetup) {
+ $this->setup();
+ }
}
}
diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md
index 7e68e9f5c2767..43affdabf7223 100644
--- a/src/Symfony/Component/Messenger/CHANGELOG.md
+++ b/src/Symfony/Component/Messenger/CHANGELOG.md
@@ -8,7 +8,6 @@ CHANGELOG
* Add `HandlerDescriptor::getOptions`
* Add support for multiple Redis Sentinel hosts
* Add `--all` option to the `messenger:failed:remove` command
- * `RejectRedeliveredMessageException` implements `UnrecoverableExceptionInterface` in order to not be retried
* Add `WrappedExceptionsInterface` interface for exceptions that hold multiple individual exceptions
* Deprecate `HandlerFailedException::getNestedExceptions()`, `HandlerFailedException::getNestedExceptionsOfClass()`
and `DelayedMessageHandlingException::getExceptions()` which are replaced by a new `getWrappedExceptions()` method
diff --git a/src/Symfony/Component/Messenger/Exception/RejectRedeliveredMessageException.php b/src/Symfony/Component/Messenger/Exception/RejectRedeliveredMessageException.php
index 72283878c1b0c..0befccf4a1d1f 100644
--- a/src/Symfony/Component/Messenger/Exception/RejectRedeliveredMessageException.php
+++ b/src/Symfony/Component/Messenger/Exception/RejectRedeliveredMessageException.php
@@ -14,6 +14,6 @@
/**
* @author Tobias Schultze
*/
-class RejectRedeliveredMessageException extends RuntimeException implements UnrecoverableExceptionInterface
+class RejectRedeliveredMessageException extends RuntimeException
{
}
diff --git a/src/Symfony/Component/Messenger/Handler/BatchHandlerInterface.php b/src/Symfony/Component/Messenger/Handler/BatchHandlerInterface.php
index a2fce4e1bb1e2..42a8590ee70a8 100644
--- a/src/Symfony/Component/Messenger/Handler/BatchHandlerInterface.php
+++ b/src/Symfony/Component/Messenger/Handler/BatchHandlerInterface.php
@@ -23,7 +23,7 @@ interface BatchHandlerInterface
* @return mixed The number of pending messages in the batch if $ack is not null,
* the result from handling the message otherwise
*/
- // public function __invoke(object $message, Acknowledger $ack = null): mixed;
+ // public function __invoke(object $message, ?Acknowledger $ack = null): mixed;
/**
* Flushes any pending buffers.
diff --git a/src/Symfony/Component/Mime/Tests/Fixtures/web/index.php b/src/Symfony/Component/Mime/Tests/Fixtures/web/index.php
new file mode 100644
index 0000000000000..b3d9bbc7f3711
--- /dev/null
+++ b/src/Symfony/Component/Mime/Tests/Fixtures/web/index.php
@@ -0,0 +1 @@
+markTestSkipped('"https" stream wrapper is not enabled.');
}
- $p = DataPart::fromPath($file = 'https://symfony.com/images/common/logo/logo_symfony_header.png');
+ $finder = new PhpExecutableFinder();
+ $process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:8057']));
+ $process->setWorkingDirectory(__DIR__.'/../Fixtures/web');
+ $process->start();
+
+ do {
+ usleep(50000);
+ } while (!@fopen('http://127.0.0.1:8057', 'r'));
+
+ $p = DataPart::fromPath($file = 'http://localhost:8057/logo_symfony_header.png');
$content = file_get_contents($file);
$this->assertEquals($content, $p->getBody());
$maxLineLength = 76;
diff --git a/src/Symfony/Component/Mime/composer.json b/src/Symfony/Component/Mime/composer.json
index a769b4efc79d1..c9cf9eaaa0f60 100644
--- a/src/Symfony/Component/Mime/composer.json
+++ b/src/Symfony/Component/Mime/composer.json
@@ -26,6 +26,7 @@
"league/html-to-markdown": "^5.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.4|^7.0",
"symfony/property-access": "^5.4|^6.0|^7.0",
"symfony/property-info": "^5.4|^6.0|^7.0",
"symfony/serializer": "^6.3.2|^7.0"
diff --git a/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md
index 33792585479a4..15840fbd578d9 100644
--- a/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md
+++ b/src/Symfony/Component/Notifier/Bridge/Esendex/CHANGELOG.md
@@ -21,9 +21,9 @@ CHANGELOG
* The bridge is not marked as `@experimental` anymore
* [BC BREAK] Change signature of `EsendexTransport::__construct()` method from:
- `public function __construct(string $token, string $accountReference, string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)`
+ `public function __construct(string $token, string $accountReference, string $from, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null)`
to:
- `public function __construct(string $email, string $password, string $accountReference, string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)`
+ `public function __construct(string $email, string $password, string $accountReference, string $from, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null)`
5.2.0
-----
diff --git a/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md
index c01ece62d544a..7f8d65492b39f 100644
--- a/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md
+++ b/src/Symfony/Component/Notifier/Bridge/GoogleChat/CHANGELOG.md
@@ -12,9 +12,9 @@ CHANGELOG
* The bridge is not marked as `@experimental` anymore
* [BC BREAK] Remove `GoogleChatTransport::setThreadKey()` method, this parameter should now be provided via the constructor,
which has changed from:
- `__construct(string $space, string $accessKey, string $accessToken, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)`
+ `__construct(string $space, string $accessKey, string $accessToken, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null)`
to:
- `__construct(string $space, string $accessKey, string $accessToken, string $threadKey = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)`
+ `__construct(string $space, string $accessKey, string $accessToken, ?string $threadKey = null, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null)`
* [BC BREAK] Rename the parameter `threadKey` to `thread_key` in DSN
5.2.0
diff --git a/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md
index 8e154d13f0b85..39bc172f8cb7b 100644
--- a/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md
+++ b/src/Symfony/Component/Notifier/Bridge/Mattermost/CHANGELOG.md
@@ -6,9 +6,9 @@ CHANGELOG
* The bridge is not marked as `@experimental` anymore
* [BC BREAK] Change signature of `MattermostTransport::__construct()` method from:
- `public function __construct(string $token, string $channel, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, string $path = null)`
+ `public function __construct(string $token, string $channel, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null, ?string $path = null)`
to:
- `public function __construct(string $token, string $channel, ?string $path = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null)`
+ `public function __construct(string $token, string $channel, ?string $path = null, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null)`
5.1.0
-----
diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
index 60f2d32770b0d..4343c9fc313fe 100644
--- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
+++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
@@ -411,8 +411,18 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid
throw $e;
}
} elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
- if ($access->canBeReference() && !isset($object->$name) && !\array_key_exists($name, (array) $object) && !(new \ReflectionProperty($class, $name))->hasType()) {
- throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', $class, $name));
+ if (!isset($object->$name) && !\array_key_exists($name, (array) $object)) {
+ try {
+ $r = new \ReflectionProperty($class, $name);
+
+ if ($r->isPublic() && !$r->hasType()) {
+ throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', $class, $name));
+ }
+ } catch (\ReflectionException $e) {
+ if (!$ignoreInvalidProperty) {
+ throw new NoSuchPropertyException(sprintf('Can\'t get a way to read the property "%s" in class "%s".', $property, $class));
+ }
+ }
}
$result[self::VALUE] = $object->$name;
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php
index ca4074ca3229b..ed9944d27a67a 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php
@@ -19,7 +19,7 @@ class NonTraversableArrayObject implements \ArrayAccess, \Countable
{
private $array;
- public function __construct(array $array = null)
+ public function __construct(?array $array = null)
{
$this->array = $array ?: [];
}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassMagicGet.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassMagicGet.php
index d1e4d23f9dc53..e3850cca6e32f 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassMagicGet.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassMagicGet.php
@@ -39,4 +39,9 @@ public function __get(string $property)
return 'constant value';
}
}
+
+ public function __isset(string $property)
+ {
+ return \in_array($property, ['magicProperty', 'constantMagicProperty'], true);
+ }
}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php
index bc7ba3d9ffc4a..2e03358597424 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php
@@ -19,7 +19,7 @@ class TraversableArrayObject implements \ArrayAccess, \IteratorAggregate, \Count
{
private $array;
- public function __construct(array $array = null)
+ public function __construct(?array $array = null)
{
$this->array = $array ?: [];
}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
index fc9844944c66e..dc5c5500b18ea 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
@@ -990,12 +990,19 @@ public function testGetValuePropertyThrowsExceptionIfUninitializedWithLazyGhost(
public function testGetValueGetterThrowsExceptionIfUninitializedWithLazyGhost()
{
+ $lazyGhost = $this->createUninitializedObjectPropertyGhost();
+
$this->expectException(UninitializedPropertyException::class);
$this->expectExceptionMessage('The property "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedObjectProperty::$privateUninitialized" is not readable because it is typed "DateTimeInterface". You should initialize it or declare a default value instead.');
+ $this->propertyAccessor->getValue($lazyGhost, 'privateUninitialized');
+ }
+
+ public function testIsReadableWithMissingPropertyAndLazyGhost()
+ {
$lazyGhost = $this->createUninitializedObjectPropertyGhost();
- $this->propertyAccessor->getValue($lazyGhost, 'privateUninitialized');
+ $this->assertFalse($this->propertyAccessor->isReadable($lazyGhost, 'dummy'));
}
private function createUninitializedObjectPropertyGhost(): UninitializedObjectProperty
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
index bc0bc77342f0e..a66a8dbda4c19 100644
--- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
@@ -274,14 +274,12 @@ public function getReadInfo(string $class, string $property, array $context = []
return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic(), false);
}
- if ($allowMagicGet && $reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
- return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
+ if ($allowMagicGet && $reflClass->hasMethod('__get') && (($r = $reflClass->getMethod('__get'))->getModifiers() & $this->methodReflectionFlags)) {
+ return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, $r->returnsReference());
}
- if ($hasProperty && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
- $reflProperty = $reflClass->getProperty($property);
-
- return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), true);
+ if ($hasProperty && (($r = $reflClass->getProperty($property))->getModifiers() & $this->propertyReflectionFlags)) {
+ return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($r), $r->isStatic(), true);
}
if ($allowMagicCall && $reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags)) {
@@ -642,7 +640,7 @@ private function getMutatorMethod(string $class, string $property): ?array
continue;
}
- // Parameter can be optional to allow things like: method(array $foo = null)
+ // Parameter can be optional to allow things like: method(?array $foo = null)
if ($reflectionMethod->getNumberOfParameters() >= 1) {
return [$reflectionMethod, $prefix];
}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
index b6cd09670ecfd..252df9914f683 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
@@ -53,7 +53,6 @@ public static function invalidTypesProvider()
return [
'pub' => ['pub', null, null],
'stat' => ['stat', null, null],
- 'foo' => ['foo', 'Foo.', null],
'bar' => ['bar', 'Bar.', null],
];
}
@@ -68,10 +67,20 @@ public function testInvalid($property, $shortDescription, $longDescription)
$this->assertSame($longDescription, $this->extractor->getLongDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', $property));
}
+ /**
+ * @group legacy
+ */
+ public function testEmptyParamAnnotation()
+ {
+ $this->assertNull($this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', 'foo'));
+ $this->assertSame('Foo.', $this->extractor->getShortDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', 'foo'));
+ $this->assertNull($this->extractor->getLongDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\InvalidDummy', 'foo'));
+ }
+
/**
* @dataProvider typesWithNoPrefixesProvider
*/
- public function testExtractTypesWithNoPrefixes($property, array $type = null)
+ public function testExtractTypesWithNoPrefixes($property, ?array $type = null)
{
$noPrefixExtractor = new PhpDocExtractor(null, [], [], []);
@@ -193,7 +202,7 @@ public static function provideCollectionTypes()
/**
* @dataProvider typesWithCustomPrefixesProvider
*/
- public function testExtractTypesWithCustomPrefixes($property, array $type = null)
+ public function testExtractTypesWithCustomPrefixes($property, ?array $type = null)
{
$customExtractor = new PhpDocExtractor(null, ['add', 'remove'], ['is', 'can']);
@@ -392,7 +401,7 @@ public function testUnknownPseudoType()
/**
* @dataProvider constructorTypesProvider
*/
- public function testExtractConstructorTypes($property, array $type = null)
+ public function testExtractConstructorTypes($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property));
}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
index fdd90c7b43c80..3c32c84d14fd8 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
@@ -44,7 +44,7 @@ protected function setUp(): void
/**
* @dataProvider typesProvider
*/
- public function testExtract($property, array $type = null)
+ public function testExtract($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
}
@@ -75,7 +75,7 @@ public function testInvalid($property)
/**
* @dataProvider typesWithNoPrefixesProvider
*/
- public function testExtractTypesWithNoPrefixes($property, array $type = null)
+ public function testExtractTypesWithNoPrefixes($property, ?array $type = null)
{
$noPrefixExtractor = new PhpStanExtractor([], [], []);
@@ -130,7 +130,7 @@ public static function typesProvider()
/**
* @dataProvider provideCollectionTypes
*/
- public function testExtractCollection($property, array $type = null)
+ public function testExtractCollection($property, ?array $type = null)
{
$this->testExtract($property, $type);
}
@@ -186,7 +186,7 @@ public static function provideCollectionTypes()
/**
* @dataProvider typesWithCustomPrefixesProvider
*/
- public function testExtractTypesWithCustomPrefixes($property, array $type = null)
+ public function testExtractTypesWithCustomPrefixes($property, ?array $type = null)
{
$customExtractor = new PhpStanExtractor(['add', 'remove'], ['is', 'can']);
@@ -344,7 +344,7 @@ public static function propertiesParentTypeProvider(): array
/**
* @dataProvider constructorTypesProvider
*/
- public function testExtractConstructorTypes($property, array $type = null)
+ public function testExtractConstructorTypes($property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property));
}
@@ -459,7 +459,7 @@ public static function intRangeTypeProvider(): array
/**
* @dataProvider php80TypesProvider
*/
- public function testExtractPhp80Type(string $class, $property, array $type = null)
+ public function testExtractPhp80Type(string $class, $property, ?array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes($class, $property, []));
}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php
index ec3bb8da4e200..c76f7a7c8b296 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php
@@ -194,7 +194,7 @@ public function getA()
*
* @param ParentDummy|null $parent
*/
- public function setB(ParentDummy $parent = null)
+ public function setB(?ParentDummy $parent = null)
{
}
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExtendedRoute.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExtendedRoute.php
index 72232cbf6d50a..dca36a7ea2a56 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExtendedRoute.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/ExtendedRoute.php
@@ -7,7 +7,7 @@
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class ExtendedRoute extends Route
{
- public function __construct(array|string $path = null, ?string $name = null, array $defaults = [])
+ public function __construct(array|string|null $path = null, ?string $name = null, array $defaults = [])
{
parent::__construct("/{section<(foo|bar|baz)>}" . $path, $name, [], [], array_merge(['section' => 'foo'], $defaults));
}
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/RedirectableUrlMatcher.php b/src/Symfony/Component/Routing/Tests/Fixtures/RedirectableUrlMatcher.php
index c5f49a83057c3..6c1dd651855ae 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/RedirectableUrlMatcher.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/RedirectableUrlMatcher.php
@@ -19,7 +19,7 @@
*/
class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface
{
- public function redirect(string $path, string $route, string $scheme = null): array
+ public function redirect(string $path, string $route, ?string $scheme = null): array
{
return [
'_controller' => 'Some controller reference...',
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/TraceableAttributeClassLoader.php b/src/Symfony/Component/Routing/Tests/Fixtures/TraceableAttributeClassLoader.php
index 1e2a2637dee8c..36b7619c7df6d 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/TraceableAttributeClassLoader.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/TraceableAttributeClassLoader.php
@@ -20,7 +20,7 @@ final class TraceableAttributeClassLoader extends AttributeClassLoader
/** @var list */
public array $foundClasses = [];
- public function load(mixed $class, string $type = null): RouteCollection
+ public function load(mixed $class, ?string $type = null): RouteCollection
{
if (!is_string($class)) {
throw new \InvalidArgumentException(sprintf('Expected string, got "%s"', get_debug_type($class)));
diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php
index 1bfe5d4737058..b2df2c9b9061a 100644
--- a/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php
+++ b/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php
@@ -130,7 +130,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
$accessorOrMutator = preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches);
if ($accessorOrMutator) {
- $attributeName = lcfirst($matches[2]);
+ $attributeName = $reflectionClass->hasProperty($method->name) ? $method->name : lcfirst($matches[2]);
if (isset($attributesMetadata[$attributeName])) {
$attributeMetadata = $attributesMetadata[$attributeName];
diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php
index 807a43ac6d306..04b51ffade390 100644
--- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php
@@ -90,17 +90,25 @@ protected function extractAttributes(object $object, ?string $format = null, arr
if (str_starts_with($name, 'get') || str_starts_with($name, 'has') || str_starts_with($name, 'can')) {
// getters, hassers and canners
- $attributeName = substr($name, 3);
+ $attributeName = $name;
if (!$reflClass->hasProperty($attributeName)) {
- $attributeName = lcfirst($attributeName);
+ $attributeName = substr($attributeName, 3);
+
+ if (!$reflClass->hasProperty($attributeName)) {
+ $attributeName = lcfirst($attributeName);
+ }
}
} elseif (str_starts_with($name, 'is')) {
// issers
- $attributeName = substr($name, 2);
+ $attributeName = $name;
if (!$reflClass->hasProperty($attributeName)) {
- $attributeName = lcfirst($attributeName);
+ $attributeName = substr($attributeName, 2);
+
+ if (!$reflClass->hasProperty($attributeName)) {
+ $attributeName = lcfirst($attributeName);
+ }
}
}
diff --git a/src/Symfony/Component/Serializer/Tests/Annotation/ContextTest.php b/src/Symfony/Component/Serializer/Tests/Annotation/ContextTest.php
index 7efe8dda598d2..9584d6f1b5e51 100644
--- a/src/Symfony/Component/Serializer/Tests/Annotation/ContextTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Annotation/ContextTest.php
@@ -133,7 +133,7 @@ public static function provideValidInputs(): iterable
DUMP
];
- yield 'named arguemnts: with groups option as array' => [
+ yield 'named arguments: with groups option as array' => [
fn () => new Context(context: ['foo' => 'bar'], groups: ['a', 'b']),
<< $this->foo,
@@ -33,7 +33,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null
];
}
- public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []): void
+ public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, ?string $format = null, array $context = []): void
{
$this->foo = $data['foo'];
$this->bar = $data['bar'];
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyString.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyString.php
index 15bcc6e6bec7f..98e0fa06fcd77 100644
--- a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyString.php
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyString.php
@@ -22,7 +22,7 @@ class DummyString implements DenormalizableInterface
/** @var string $value */
public $value;
- public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []): void
+ public function denormalize(DenormalizerInterface $denormalizer, $data, ?string $format = null, array $context = []): void
{
$this->value = $data;
}
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/EnvelopeNormalizer.php b/src/Symfony/Component/Serializer/Tests/Fixtures/EnvelopeNormalizer.php
index 021c22a04c0ef..4acfdf8304743 100644
--- a/src/Symfony/Component/Serializer/Tests/Fixtures/EnvelopeNormalizer.php
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/EnvelopeNormalizer.php
@@ -20,7 +20,7 @@ class EnvelopeNormalizer implements NormalizerInterface
{
private $serializer;
- public function normalize($envelope, string $format = null, array $context = []): array
+ public function normalize($envelope, ?string $format = null, array $context = []): array
{
$xmlContent = $this->serializer->serialize($envelope->message, 'xml');
@@ -38,7 +38,7 @@ public function getSupportedTypes(?string $format): array
];
}
- public function supportsNormalization($data, string $format = null, array $context = []): bool
+ public function supportsNormalization($data, ?string $format = null, array $context = []): bool
{
return $data instanceof EnvelopeObject;
}
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/EnvelopedMessageNormalizer.php b/src/Symfony/Component/Serializer/Tests/Fixtures/EnvelopedMessageNormalizer.php
index 5d48f4569cb0a..812dbf015730a 100644
--- a/src/Symfony/Component/Serializer/Tests/Fixtures/EnvelopedMessageNormalizer.php
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/EnvelopedMessageNormalizer.php
@@ -18,7 +18,7 @@
*/
class EnvelopedMessageNormalizer implements NormalizerInterface
{
- public function normalize($message, string $format = null, array $context = []): array
+ public function normalize($message, ?string $format = null, array $context = []): array
{
return [
'text' => $message->text,
@@ -32,7 +32,7 @@ public function getSupportedTypes(?string $format): array
];
}
- public function supportsNormalization($data, string $format = null, array $context = []): bool
+ public function supportsNormalization($data, ?string $format = null, array $context = []): bool
{
return $data instanceof EnvelopedMessage;
}
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/FooInterfaceDummyDenormalizer.php b/src/Symfony/Component/Serializer/Tests/Fixtures/FooInterfaceDummyDenormalizer.php
index a8c45373b70ee..0fa3c8202ac95 100644
--- a/src/Symfony/Component/Serializer/Tests/Fixtures/FooInterfaceDummyDenormalizer.php
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/FooInterfaceDummyDenormalizer.php
@@ -15,7 +15,7 @@
final class FooInterfaceDummyDenormalizer implements DenormalizerInterface
{
- public function denormalize(mixed $data, string $type, string $format = null, array $context = []): array
+ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): array
{
$result = [];
foreach ($data as $foo) {
@@ -27,7 +27,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
return $result;
}
- public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
+ public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
{
if (str_ends_with($type, '[]')) {
$className = substr($type, 0, -2);
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/FormatAndContextAwareNormalizer.php b/src/Symfony/Component/Serializer/Tests/Fixtures/FormatAndContextAwareNormalizer.php
index 4042288450637..428c618a9ddd9 100644
--- a/src/Symfony/Component/Serializer/Tests/Fixtures/FormatAndContextAwareNormalizer.php
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/FormatAndContextAwareNormalizer.php
@@ -15,7 +15,7 @@
class FormatAndContextAwareNormalizer extends ObjectNormalizer
{
- protected function isAllowedAttribute($classOrObject, string $attribute, string $format = null, array $context = []): bool
+ protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool
{
return \in_array($attribute, ['foo', 'bar']) && 'foo_and_bar_included' === $format;
}
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/NormalizableTraversableDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/NormalizableTraversableDummy.php
index d6b38b89af1dc..5165120c3bdb5 100644
--- a/src/Symfony/Component/Serializer/Tests/Fixtures/NormalizableTraversableDummy.php
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/NormalizableTraversableDummy.php
@@ -18,7 +18,7 @@
class NormalizableTraversableDummy extends TraversableDummy implements NormalizableInterface, DenormalizableInterface
{
- public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []): array|string|int|float|bool
+ public function normalize(NormalizerInterface $normalizer, ?string $format = null, array $context = []): array|string|int|float|bool
{
return [
'foo' => 'normalizedFoo',
@@ -26,7 +26,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null
];
}
- public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []): void
+ public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, ?string $format = null, array $context = []): void
{
}
}
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/NotNormalizableDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/NotNormalizableDummy.php
index e8c64f57752dd..41da0eac8a999 100644
--- a/src/Symfony/Component/Serializer/Tests/Fixtures/NotNormalizableDummy.php
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/NotNormalizableDummy.php
@@ -24,7 +24,7 @@ public function __construct()
{
}
- public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []): void
+ public function denormalize(DenormalizerInterface $denormalizer, $data, ?string $format = null, array $context = []): void
{
throw new NotNormalizableValueException();
}
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/SamePropertyAsMethodDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/SamePropertyAsMethodDummy.php
new file mode 100644
index 0000000000000..89c8fcb9c399c
--- /dev/null
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/SamePropertyAsMethodDummy.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Serializer\Tests\Fixtures;
+
+class SamePropertyAsMethodDummy
+{
+ private $freeTrial;
+ private $hasSubscribe;
+ private $getReady;
+ private $isActive;
+
+ public function __construct($freeTrial, $hasSubscribe, $getReady, $isActive)
+ {
+ $this->freeTrial = $freeTrial;
+ $this->hasSubscribe = $hasSubscribe;
+ $this->getReady = $getReady;
+ $this->isActive = $isActive;
+ }
+
+ public function getFreeTrial()
+ {
+ return $this->freeTrial;
+ }
+
+ public function hasSubscribe()
+ {
+ return $this->hasSubscribe;
+ }
+
+ public function getReady()
+ {
+ return $this->getReady;
+ }
+
+ public function isActive()
+ {
+ return $this->isActive;
+ }
+}
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php
new file mode 100644
index 0000000000000..203118885853e
--- /dev/null
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Serializer\Tests\Fixtures;
+
+use Symfony\Component\Serializer\Annotation\SerializedName;
+
+class SamePropertyAsMethodWithMethodSerializedNameDummy
+{
+ private $freeTrial;
+ private $hasSubscribe;
+ private $getReady;
+ private $isActive;
+
+ public function __construct($freeTrial, $hasSubscribe, $getReady, $isActive)
+ {
+ $this->freeTrial = $freeTrial;
+ $this->hasSubscribe = $hasSubscribe;
+ $this->getReady = $getReady;
+ $this->isActive = $isActive;
+ }
+
+ #[SerializedName('free_trial_method')]
+ public function getFreeTrial()
+ {
+ return $this->freeTrial;
+ }
+
+ #[SerializedName('has_subscribe_method')]
+ public function hasSubscribe()
+ {
+ return $this->hasSubscribe;
+ }
+
+ #[SerializedName('get_ready_method')]
+ public function getReady()
+ {
+ return $this->getReady;
+ }
+
+ #[SerializedName('is_active_method')]
+ public function isActive()
+ {
+ return $this->isActive;
+ }
+}
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php
new file mode 100644
index 0000000000000..0b681934f2fab
--- /dev/null
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Serializer\Tests\Fixtures;
+
+use Symfony\Component\Serializer\Annotation\SerializedName;
+
+class SamePropertyAsMethodWithPropertySerializedNameDummy
+{
+ #[SerializedName('free_trial_property')]
+ private $freeTrial;
+
+ #[SerializedName('has_subscribe_property')]
+ private $hasSubscribe;
+
+ #[SerializedName('get_ready_property')]
+ private $getReady;
+
+ #[SerializedName('is_active_property')]
+ private $isActive;
+
+ public function __construct($freeTrial, $hasSubscribe, $getReady, $isActive)
+ {
+ $this->freeTrial = $freeTrial;
+ $this->hasSubscribe = $hasSubscribe;
+ $this->getReady = $getReady;
+ $this->isActive = $isActive;
+ }
+
+ public function getFreeTrial()
+ {
+ return $this->freeTrial;
+ }
+
+ public function hasSubscribe()
+ {
+ return $this->hasSubscribe;
+ }
+
+ public function getReady()
+ {
+ return $this->getReady;
+ }
+
+ public function isActive()
+ {
+ return $this->isActive;
+ }
+}
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/ScalarDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/ScalarDummy.php
index 949407d116b68..50617f33eb5e0 100644
--- a/src/Symfony/Component/Serializer/Tests/Fixtures/ScalarDummy.php
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/ScalarDummy.php
@@ -21,12 +21,12 @@ class ScalarDummy implements NormalizableInterface, DenormalizableInterface
public $foo;
public $xmlFoo;
- public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []): array|string|int|float|bool
+ public function normalize(NormalizerInterface $normalizer, ?string $format = null, array $context = []): array|string|int|float|bool
{
return 'xml' === $format ? $this->xmlFoo : $this->foo;
}
- public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []): void
+ public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, ?string $format = null, array $context = []): void
{
if ('xml' === $format) {
$this->xmlFoo = $data;
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/StaticConstructorNormalizer.php b/src/Symfony/Component/Serializer/Tests/Fixtures/StaticConstructorNormalizer.php
index c2e77aa4baf50..1ba6884bde1d0 100644
--- a/src/Symfony/Component/Serializer/Tests/Fixtures/StaticConstructorNormalizer.php
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/StaticConstructorNormalizer.php
@@ -23,17 +23,17 @@ public function getSupportedTypes(?string $format): array
return [StaticConstructorDummy::class];
}
- protected function extractAttributes(object $object, string $format = null, array $context = []): array
+ protected function extractAttributes(object $object, ?string $format = null, array $context = []): array
{
return get_object_vars($object);
}
- protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
+ protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed
{
return $object->$attribute;
}
- protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void
+ protected function setAttributeValue(object $object, string $attribute, mixed $value, ?string $format = null, array $context = []): void
{
$object->$attribute = $value;
}
diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php
index 27db0aa73fad1..32e2adffc4afe 100644
--- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php
@@ -926,12 +926,12 @@ public function testProvidingContextCacheKeyGeneratesSameChildContextCacheKey()
$normalizer = new class() extends AbstractObjectNormalizerDummy {
public $childContextCacheKey;
- protected function extractAttributes(object $object, string $format = null, array $context = []): array
+ protected function extractAttributes(object $object, ?string $format = null, array $context = []): array
{
return array_keys((array) $object);
}
- protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
+ protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed
{
return $object->{$attribute};
}
@@ -966,12 +966,12 @@ public function testChildContextKeepsOriginalContextCacheKey()
$normalizer = new class() extends AbstractObjectNormalizerDummy {
public $childContextCacheKey;
- protected function extractAttributes(object $object, string $format = null, array $context = []): array
+ protected function extractAttributes(object $object, ?string $format = null, array $context = []): array
{
return array_keys((array) $object);
}
- protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
+ protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed
{
return $object->{$attribute};
}
@@ -1001,12 +1001,12 @@ public function testChildContextCacheKeyStaysFalseWhenOriginalCacheKeyIsFalse()
$normalizer = new class() extends AbstractObjectNormalizerDummy {
public $childContextCacheKey;
- protected function extractAttributes(object $object, string $format = null, array $context = []): array
+ protected function extractAttributes(object $object, ?string $format = null, array $context = []): array
{
return array_keys((array) $object);
}
- protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
+ protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed
{
return $object->{$attribute};
}
diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
index f500428cdfc29..10b8b3c96d1b4 100644
--- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
@@ -42,6 +42,9 @@
use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy;
use Symfony\Component\Serializer\Tests\Fixtures\Php74DummyPrivate;
use Symfony\Component\Serializer\Tests\Fixtures\Php80Dummy;
+use Symfony\Component\Serializer\Tests\Fixtures\SamePropertyAsMethodDummy;
+use Symfony\Component\Serializer\Tests\Fixtures\SamePropertyAsMethodWithMethodSerializedNameDummy;
+use Symfony\Component\Serializer\Tests\Fixtures\SamePropertyAsMethodWithPropertySerializedNameDummy;
use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder;
use Symfony\Component\Serializer\Tests\Normalizer\Features\AttributesTestTrait;
use Symfony\Component\Serializer\Tests\Normalizer\Features\CacheableObjectAttributesTestTrait;
@@ -850,6 +853,53 @@ public function testNormalizeStdClass()
$this->assertSame(['baz' => 'baz'], $this->normalizer->normalize($o2));
}
+
+ public function testSamePropertyAsMethod()
+ {
+ $object = new SamePropertyAsMethodDummy('free_trial', 'has_subscribe', 'get_ready', 'is_active');
+ $expected = [
+ 'freeTrial' => 'free_trial',
+ 'hasSubscribe' => 'has_subscribe',
+ 'getReady' => 'get_ready',
+ 'isActive' => 'is_active',
+ ];
+
+ $this->assertSame($expected, $this->normalizer->normalize($object));
+ }
+
+ public function testSamePropertyAsMethodWithPropertySerializedName()
+ {
+ $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
+ $this->normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory));
+ $this->normalizer->setSerializer($this->serializer);
+
+ $object = new SamePropertyAsMethodWithPropertySerializedNameDummy('free_trial', 'has_subscribe', 'get_ready', 'is_active');
+ $expected = [
+ 'free_trial_property' => 'free_trial',
+ 'has_subscribe_property' => 'has_subscribe',
+ 'get_ready_property' => 'get_ready',
+ 'is_active_property' => 'is_active',
+ ];
+
+ $this->assertSame($expected, $this->normalizer->normalize($object));
+ }
+
+ public function testSamePropertyAsMethodWithMethodSerializedName()
+ {
+ $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
+ $this->normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory));
+ $this->normalizer->setSerializer($this->serializer);
+
+ $object = new SamePropertyAsMethodWithMethodSerializedNameDummy('free_trial', 'has_subscribe', 'get_ready', 'is_active');
+ $expected = [
+ 'free_trial_method' => 'free_trial',
+ 'has_subscribe_method' => 'has_subscribe',
+ 'get_ready_method' => 'get_ready',
+ 'is_active_method' => 'is_active',
+ ];
+
+ $this->assertSame($expected, $this->normalizer->normalize($object));
+ }
}
class ProxyObjectDummy extends ObjectDummy
@@ -1024,6 +1074,11 @@ public function __get($name)
return $this->foo = 123;
}
}
+
+ public function __isset($name)
+ {
+ return 'foo' === $name;
+ }
}
class DummyWithConstructorObject
diff --git a/src/Symfony/Component/Validator/Constraints/BicValidator.php b/src/Symfony/Component/Validator/Constraints/BicValidator.php
index 045df64b544e2..6236da86d21f2 100644
--- a/src/Symfony/Component/Validator/Constraints/BicValidator.php
+++ b/src/Symfony/Component/Validator/Constraints/BicValidator.php
@@ -102,16 +102,6 @@ public function validate(mixed $value, Constraint $constraint)
return;
}
- // first 4 letters must be alphabetic (bank code)
- if (!ctype_alpha(substr($canonicalize, 0, 4))) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Bic::INVALID_BANK_CODE_ERROR)
- ->addViolation();
-
- return;
- }
-
$bicCountryCode = substr($canonicalize, 4, 2);
if (!isset(self::BIC_COUNTRY_TO_IBAN_COUNTRY_MAP[$bicCountryCode]) && !Countries::exists($bicCountryCode)) {
$this->context->buildViolation($constraint->message)
diff --git a/src/Symfony/Component/Validator/Constraints/File.php b/src/Symfony/Component/Validator/Constraints/File.php
index c55ceece72699..863e306f281f4 100644
--- a/src/Symfony/Component/Validator/Constraints/File.php
+++ b/src/Symfony/Component/Validator/Constraints/File.php
@@ -41,6 +41,7 @@ class File extends Constraint
self::EMPTY_ERROR => 'EMPTY_ERROR',
self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR',
self::INVALID_MIME_TYPE_ERROR => 'INVALID_MIME_TYPE_ERROR',
+ self::INVALID_EXTENSION_ERROR => 'INVALID_EXTENSION_ERROR',
self::FILENAME_TOO_LONG => 'FILENAME_TOO_LONG',
];
diff --git a/src/Symfony/Component/Validator/Constraints/UniqueValidator.php b/src/Symfony/Component/Validator/Constraints/UniqueValidator.php
index 1e692fe682a21..b0eefd84cb82f 100644
--- a/src/Symfony/Component/Validator/Constraints/UniqueValidator.php
+++ b/src/Symfony/Component/Validator/Constraints/UniqueValidator.php
@@ -43,12 +43,12 @@ public function validate(mixed $value, Constraint $constraint)
$collectionElements = [];
$normalizer = $this->getNormalizer($constraint);
foreach ($value as $element) {
+ $element = $normalizer($element);
+
if ($fields && !$element = $this->reduceElementKeys($fields, $element)) {
continue;
}
- $element = $normalizer($element);
-
if (\in_array($element, $collectionElements, true)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf
index 6ac303a778fa9..dfd398ae95a4f 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf
@@ -136,7 +136,7 @@
This value is not a valid IP address.
- هذه القيمة ليست عنوان IP صالحًا.
+ هذا ليس عنوان IP صحيح.
This value is not a valid language.
@@ -192,7 +192,7 @@
No temporary folder was configured in php.ini, or the configured folder does not exist.
- لم يتم تكوين مجلد مؤقت في ملف php.ini، أو المجلد المعد لا يوجد.
+ لم يتم تكوين مجلد مؤقت في ملف php.ini.
Cannot write temporary file to disk.
@@ -224,7 +224,7 @@
This value is not a valid International Bank Account Number (IBAN).
- هذه القيمة ليست رقم حساب بنكي دولي (IBAN) صالحًا.
+ هذه القيمة ليست رقم حساب بنكي دولي (IBAN) صالحًا.
This value is not a valid ISBN-10.
@@ -312,7 +312,7 @@
This value is not a valid Business Identifier Code (BIC).
- هذه القيمة ليست رمز معرف الأعمال (BIC) صالحًا.
+ هذه القيمة ليست رمز معرف أعمال (BIC) صالحًا.
Error
@@ -320,7 +320,7 @@
This value is not a valid UUID.
- هذه القيمة ليست UUID صالحًا.
+ هذه القيمة ليست UUID صالحًا.
This value should be a multiple of {{ compared_value }}.
@@ -432,11 +432,11 @@
The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}.
- تم اكتشاف ترميز الأحرف غير صالح ({{ detected }}). الترميزات المسموح بها هي {{ encodings }}.
+ تم اكتشاف ترميز أحرف غير صالح ({{ detected }}). الترميزات المسموح بها هي {{ encodings }}.
This value is not a valid MAC address.
- هذه القيمة ليست عنوان MAC صالحًا.
+ هذه القيمة ليست عنوان MAC صالحًا.