diff --git a/CHANGELOG-7.2.md b/CHANGELOG-7.2.md
index 1125f1a72875d..0bb8758194576 100644
--- a/CHANGELOG-7.2.md
+++ b/CHANGELOG-7.2.md
@@ -7,6 +7,29 @@ in 7.2 minor versions.
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v7.2.0...v7.2.1
+* 7.2.5 (2025-03-28)
+
+ * bug #60054 [Form] Use duplicate_preferred_choices to set value of ChoiceType (aleho)
+ * bug #60026 [Serializer] Fix ObjectNormalizer default context with named serializers (HypeMC)
+ * bug #60030 [Cache][DoctrineBridge][HttpFoundation][Lock][Messenger] use `Table::addPrimaryKeyConstraint()` with Doctrine DBAL 4.3+ (xabbuh)
+ * bug #59844 [TypeInfo] Fix `isSatisfiedBy` not traversing type tree (mtarld)
+ * bug #59858 Update `JsDelivrEsmResolver::IMPORT_REGEX` to support dynamic imports (natepage)
+ * bug #60019 [HttpKernel] Fix `TraceableEventDispatcher` when the `Stopwatch` service has been reset (lyrixx)
+ * bug #59975 [HttpKernel] Only remove `E_WARNING` from error level during kernel init (fritzmg)
+ * bug #59988 [FrameworkBundle] Remove redundant `name` attribute from `default_context` (HypeMC)
+ * bug #59963 [TypeInfo] Fix ``@var`` tag reading for promoted properties (mtarld)
+ * bug #59949 [Process] Use a pipe for stderr in pty mode to avoid mixed output between stdout and stderr (joelwurtz)
+ * bug #59940 [Cache] Fix missing cache data in profiler (dcmbrs)
+ * bug #59965 [VarExporter] Fix support for hooks and asymmetric visibility (nicolas-grekas)
+ * bug #59924 Extract no type ``@param`` annotation with `PhpStanExtractor` (thomasdubuffet)
+ * bug #59908 [Messenger] Reduce keepalive request noise (ro0NL)
+ * bug #59874 [Console] fix progress bar messing output in section when there is an EOL (joelwurtz)
+ * bug #59888 [PhpUnitBridge] don't trigger "internal" deprecations for PHPUnit Stub objects (xabbuh)
+ * bug #59830 [Yaml] drop comments while lexing unquoted strings (xabbuh)
+ * bug #59884 [VarExporter] Fix support for asymmetric visibility (nicolas-grekas)
+ * bug #59881 [VarExporter] Fix support for abstract properties (nicolas-grekas)
+ * bug #59841 [Cache] fix cache data collector on late collect (dcmbrs)
+
* 7.2.4 (2025-02-26)
* bug #59198 [Messenger] Filter out non-consumable receivers when registering `ConsumeMessagesCommand` (wazum)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 8af7f51d72c7d..f8902ba18f029 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -53,9 +53,9 @@ The Symfony Connect username in parenthesis allows to get more information
- Mathieu Lechat (mat_the_cat)
- Mathias Arlaud (mtarld)
- Simon André (simonandre)
+ - Vincent Langlet (deviling)
- Matthias Pigulla (mpdude)
- Gabriel Ostrolucký (gadelat)
- - Vincent Langlet (deviling)
- Jonathan Wage (jwage)
- Valentin Udaltsov (vudaltsov)
- Grégoire Paris (greg0ire)
@@ -73,9 +73,9 @@ The Symfony Connect username in parenthesis allows to get more information
- Titouan Galopin (tgalopin)
- Pierre du Plessis (pierredup)
- David Maicher (dmaicher)
+ - Dariusz Ruminski
- Tomasz Kowalczyk (thunderer)
- Bulat Shakirzyanov (avalanche123)
- - Dariusz Ruminski
- Iltar van der Berg
- Miha Vrhovnik (mvrhov)
- Gary PEGEOT (gary-p)
@@ -101,6 +101,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Tomas Norkūnas (norkunas)
- Christian Raue
- Eric Clemmons (ericclemmons)
+ - Hubert Lenoir (hubert_lenoir)
- Denis (yethee)
- Alex Pott
- Michel Weimerskirch (mweimerskirch)
@@ -110,7 +111,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Frank A. Fiebig (fafiebig)
- Baldini
- Fran Moreno (franmomu)
- - Hubert Lenoir (hubert_lenoir)
- Charles Sarrazin (csarrazi)
- Henrik Westphal (snc)
- Dariusz Górecki (canni)
@@ -135,6 +135,7 @@ The Symfony Connect username in parenthesis allows to get more information
- John Wards (johnwards)
- Yanick Witschi (toflar)
- Antoine Hérault (herzult)
+ - Valtteri R (valtzu)
- Konstantin.Myakshin
- Jeroen Spee (jeroens)
- Arnaud Le Blanc (arnaud-lb)
@@ -144,7 +145,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Tac Tacelosky (tacman1123)
- gnito-org
- Tim Nagel (merk)
- - Valtteri R (valtzu)
- Chris Wilkinson (thewilkybarkid)
- Jérôme Vasseur (jvasseur)
- Peter Kokot (peterkokot)
@@ -194,6 +194,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Niels Keurentjes (curry684)
- OGAWA Katsuhiro (fivestar)
- Jhonny Lidfors (jhonne)
+ - Florent Morselli (spomky_)
- Juti Noppornpitak (shiroyuki)
- Gregor Harlan (gharlan)
- Anthony MARTIN
@@ -206,6 +207,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Thomas Landauer (thomas-landauer)
- Arnaud Kleinpeter (nanocom)
- Guilherme Blanco (guilhermeblanco)
+ - David Prévot (taffit)
- Saif Eddin Gmati (azjezz)
- Farhad Safarov (safarov)
- SpacePossum
@@ -217,8 +219,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Rafael Dohms (rdohms)
- Roman Martinuk (a2a4)
- jwdeitch
- - David Prévot (taffit)
- - Florent Morselli (spomky_)
- Jérôme Parmentier (lctrs)
- Ahmed TAILOULOUTE (ahmedtai)
- Simon Berger
@@ -317,6 +317,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Mathieu Lemoine (lemoinem)
- Christian Schmidt
- Andreas Hucks (meandmymonkey)
+ - Artem Lopata
- Indra Gunawan (indragunawan)
- Noel Guilbert (noel)
- Bastien Jaillot (bastnic)
@@ -352,6 +353,7 @@ The Symfony Connect username in parenthesis allows to get more information
- John Kary (johnkary)
- Võ Xuân Tiến (tienvx)
- fd6130 (fdtvui)
+ - Antonio J. García Lagar (ajgarlag)
- Priyadi Iman Nurcahyo (priyadi)
- Alan Poulain (alanpoulain)
- Oleg Andreyev (oleg.andreyev)
@@ -388,6 +390,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Dariusz
- Daniel Gorgan
- Francois Zaninotto
+ - Aurélien Pillevesse (aurelienpillevesse)
- Daniel Tschinder
- Christian Schmidt
- Alexander Kotynia (olden)
@@ -395,7 +398,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Manuel Reinhard (sprain)
- Zan Baldwin (zanbaldwin)
- Tim Goudriaan (codedmonkey)
- - Antonio J. García Lagar (ajgarlag)
- BoShurik
- Quentin Devos
- Adam Prager (padam87)
@@ -409,7 +411,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Sylvain Fabre (sylfabre)
- Xavier Perez
- Arjen Brouwer (arjenjb)
- - Artem Lopata
- Patrick McDougle (patrick-mcdougle)
- Arnt Gulbrandsen
- Michel Roca (mroca)
@@ -460,7 +461,6 @@ The Symfony Connect username in parenthesis allows to get more information
- renanbr
- Sébastien Lavoie (lavoiesl)
- Alex Rock (pierstoval)
- - Aurélien Pillevesse (aurelienpillevesse)
- Matthieu Lempereur (mryamous)
- Wodor Wodorski
- Beau Simensen (simensen)
@@ -514,6 +514,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Ismael Ambrosi (iambrosi)
- Craig Duncan (duncan3dc)
- Emmanuel BORGES
+ - Mathieu Rochette (mathroc)
- Karoly Negyesi (chx)
- Aurelijus Valeiša (aurelijus)
- Jan Decavele (jandc)
@@ -540,6 +541,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Ahmed Raafat
- Philippe Segatori
- Thibaut Cheymol (tcheymol)
+ - Raffaele Carelle
- Erin Millard
- Matthew Lewinski (lewinski)
- Islam Israfilov (islam93)
@@ -628,7 +630,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Saif Eddin G
- Endre Fejes
- Tobias Naumann (tna)
- - Mathieu Rochette (mathroc)
- Daniel Beyer
- Ivan Sarastov (isarastov)
- flack (flack)
@@ -674,8 +675,10 @@ The Symfony Connect username in parenthesis allows to get more information
- Benjamin (yzalis)
- Jeanmonod David (jeanmonod)
- Webnet team (webnet)
+ - Christian Gripp (core23)
- Tobias Bönner
- Nicolas Rigaud
+ - PHAS Developer
- Ben Ramsey (ramsey)
- Berny Cantos (xphere81)
- Antonio Jose Cerezo (ajcerezo)
@@ -692,7 +695,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Neil Peyssard (nepey)
- Niklas Fiekas
- Mark Challoner (markchalloner)
- - Raffaele Carelle
- Andreas Hennings
- Markus Bachmann (baachi)
- Gunnstein Lye (glye)
@@ -755,6 +757,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Arnaud POINTET (oipnet)
- Tristan Pouliquen
- Miro Michalicka
+ - Hans Mackowiak
- M. Vondano
- Dominik Zogg
- Maximilian Zumbansen
@@ -948,7 +951,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Paul Oms
- James Hemery
- wuchen90
- - PHAS Developer
- Wouter van der Loop (toppy-hennie)
- Ninos
- julien57
@@ -991,6 +993,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Noémi Salaün (noemi-salaun)
- Sinan Eldem (sineld)
- Gennady Telegin
+ - Benedikt Lenzen (demigodcode)
- ampaze
- Alexandre Dupuy (satchette)
- Michel Hunziker
@@ -1224,6 +1227,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Besnik Br
- Issam Raouf (iraouf)
- Simon Mönch
+ - Valmonzo
- Sherin Bloemendaal
- Jose Gonzalez
- Jonathan (jlslew)
@@ -1279,6 +1283,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Alexander Grimalovsky (flying)
- Andrew Hilobok (hilobok)
- Noah Heck (myesain)
+ - Sébastien JEAN (sebastien76)
- Christian Soronellas (theunic)
- Max Baldanza
- Volodymyr Panivko
@@ -1340,7 +1345,6 @@ The Symfony Connect username in parenthesis allows to get more information
- James Michael DuPont
- Tinjo Schöni
- Carlos Buenosvinos (carlosbuenosvinos)
- - Christian Gripp (core23)
- Jake (jakesoft)
- Rustam Bakeev (nommyde)
- Vincent CHALAMON
@@ -1548,8 +1552,10 @@ The Symfony Connect username in parenthesis allows to get more information
- Guillaume Gammelin
- Valérian Galliat
- Sorin Pop (sorinpop)
+ - Elías Fernández
- d-ph
- Stewart Malik
+ - Frank Schulze (xit)
- Renan Taranto (renan-taranto)
- Ninos Ego
- Samael tomas
@@ -1635,6 +1641,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Michael H. Arieli
- Miloš Milutinović
- Jitendra Adhikari (adhocore)
+ - Kevin Jansen
- Nicolas Martin (cocorambo)
- Tom Panier (neemzy)
- Fred Cox
@@ -1650,6 +1657,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Adoni Pavlakis (adoni)
- Nicolas Le Goff (nlegoff)
- Maarten Nusteling (nusje2000)
+ - Peter van Dommelen
- Anne-Sophie Bachelard
- Gordienko Vladislav
- Ahmed EBEN HASSINE (famas23)
@@ -1762,6 +1770,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Asil Barkin Elik (asilelik)
- Bhujagendra Ishaya
- Guido Donnari
+ - Jérôme Dumas
- Mert Simsek (mrtsmsk0)
- Lin Clark
- Christophe Meneses (c77men)
@@ -1802,6 +1811,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Dominic Luidold
- Piotr Antosik (antek88)
- Nacho Martin (nacmartin)
+ - Thomas Bibaut
- Thibaut Chieux
- mwos
- Aydin Hassan
@@ -1857,7 +1867,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Mikkel Paulson
- Michał Strzelecki
- Bert Ramakers
- - Hans Mackowiak
- Hugo Fonseca (fonsecas72)
- Marc Duboc (icemad)
- uncaught
@@ -1942,11 +1951,13 @@ The Symfony Connect username in parenthesis allows to get more information
- Joas Schilling
- Ener-Getick
- Markus Thielen
+ - Peter Trebaticky
- Moza Bogdan (bogdan_moza)
- Viacheslav Sychov
- Nicolas Sauveur (baishu)
- Helmut Hummel (helhum)
- Matt Brunt
+ - David Vancl
- Carlos Ortega Huetos
- Péter Buri (burci)
- Evgeny Efimov (edefimov)
@@ -1982,10 +1993,14 @@ The Symfony Connect username in parenthesis allows to get more information
- rchoquet
- v.shevelev
- rvoisin
+ - Dan Brown
- gitlost
- Taras Girnyk
+ - Simon Mönch
+ - Barthold Bos
- cthulhu
- Andoni Larzabal (andonilarz)
+ - Staormin
- Dmitry Derepko
- Rémi Leclerc
- Jan Vernarsky
@@ -2113,6 +2128,7 @@ The Symfony Connect username in parenthesis allows to get more information
- martkop26
- Raphaël Davaillaud
- Sander Hagen
+ - Alexander Menk
- cilefen (cilefen)
- Prasetyo Wicaksono (jowy)
- Mo Di (modi)
@@ -2293,6 +2309,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Willem Verspyck
- Kim Laï Trinh
- Johan de Ruijter
+ - InbarAbraham
- Jason Desrosiers
- m.chwedziak
- marbul
@@ -2348,6 +2365,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Phillip Look (plook)
- Foxprodev
- Artfaith
+ - Tom Kaminski
- developer-av
- Max Summe
- Ema Panz
@@ -2637,6 +2655,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Jakub Simon
- TheMhv
- Eviljeks
+ - Juliano Petronetto
- robin.de.croock
- Brandon Antonio Lorenzo
- Bouke Haarsma
@@ -2826,6 +2845,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Botond Dani (picur)
- Rémi Faivre (rfv)
- Radek Wionczek (rwionczek)
+ - tinect (tinect)
- Nick Stemerdink
- Bernhard Rusch
- David Stone
@@ -3008,6 +3028,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Viet Pham
- Alan Bondarchuk
- Pchol
+ - Benjamin Ellis
- Shamimul Alam
- Cyril HERRERA
- dropfen
@@ -3305,7 +3326,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Jimmy Leger (redpanda)
- Ronny López (ronnylt)
- Julius (sakalys)
- - Sébastien JEAN (sebastien76)
- Dmitry (staratel)
- Marcin Szepczynski (szepczynski)
- Tito Miguel Costa (titomiguelcosta)
@@ -3578,6 +3598,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Thorsten Hallwas
- Brian Freytag
- Arend Hummeling
+ - Joseph FRANCLIN
- Marco Pfeiffer
- Alex Nostadt
- Michael Squires
@@ -3658,13 +3679,13 @@ The Symfony Connect username in parenthesis allows to get more information
- Julia
- Lin Lu
- arduanov
- - Valmonzo
- sualko
- Marc Bennewitz
- Fabien
- Martin Komischke
- Yendric
- ADmad
+ - Hugo Posnic
- Nicolas Roudaire
- Marc Jauvin
- Matthias Meyer
diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php b/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php
index 6f3410313d00a..cfe07b37da493 100644
--- a/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php
+++ b/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php
@@ -13,6 +13,9 @@
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception\TableNotFoundException;
+use Doctrine\DBAL\Schema\Name\Identifier;
+use Doctrine\DBAL\Schema\Name\UnqualifiedName;
+use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
@@ -30,7 +33,12 @@ protected function getIsSameDatabaseChecker(Connection $connection): \Closure
$table->addColumn('id', Types::INTEGER)
->setAutoincrement(true)
->setNotnull(true);
- $table->setPrimaryKey(['id']);
+
+ if (class_exists(PrimaryKeyConstraint::class)) {
+ $table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
+ } else {
+ $table->setPrimaryKey(['id']);
+ }
$schemaManager->createTable($table);
diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php
index 251b011b5d44e..79cc0f0a31a4d 100644
--- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php
+++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php
@@ -13,6 +13,9 @@
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ParameterType;
+use Doctrine\DBAL\Schema\Name\Identifier;
+use Doctrine\DBAL\Schema\Name\UnqualifiedName;
+use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
@@ -193,6 +196,11 @@ private function addTableToSchema(Schema $schema): void
$table->addColumn('lastUsed', Types::DATETIME_IMMUTABLE);
$table->addColumn('class', Types::STRING, ['length' => 100]);
$table->addColumn('username', Types::STRING, ['length' => 200]);
- $table->setPrimaryKey(['series']);
+
+ if (class_exists(PrimaryKeyConstraint::class)) {
+ $table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('series'))], true));
+ } else {
+ $table->setPrimaryKey(['series']);
+ }
}
}
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php
index d6d00aa1fa0d3..d109785c2526a 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php
+++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php
@@ -16,6 +16,7 @@
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
+use Doctrine\ORM\Mapping\PropertyAccessors\RawValuePropertyAccessor;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
@@ -132,11 +133,21 @@ class_exists(ClassMetadataInfo::class) ? ClassMetadataInfo::class : ClassMetadat
->willReturn(true)
;
$refl = $this->createMock(\ReflectionProperty::class);
+ $refl
+ ->method('getName')
+ ->willReturn('name')
+ ;
$refl
->method('getValue')
->willReturn(true)
;
- $classMetadata->reflFields = ['name' => $refl];
+
+ if (property_exists(ClassMetadata::class, 'propertyAccessors')) {
+ $classMetadata->propertyAccessors['name'] = RawValuePropertyAccessor::fromReflectionProperty($refl);
+ } else {
+ $classMetadata->reflFields = ['name' => $refl];
+ }
+
$em->expects($this->any())
->method('getClassMetadata')
->willReturn($classMetadata)
diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php
index 57dc44236e630..87eebbca142c6 100644
--- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php
+++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php
@@ -11,6 +11,7 @@
namespace Symfony\Bridge\Doctrine\Validator\Constraints;
+use Doctrine\ORM\Mapping\ClassMetadata as OrmClassMetadata;
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\Mapping\ClassMetadata;
@@ -287,9 +288,13 @@ private function getFieldValues(mixed $object, ClassMetadata $class, array $fiel
throw new ConstraintDefinitionException(\sprintf('The field "%s" is not a property of class "%s".', $fieldName, $objectClass));
}
- $fieldValues[$entityFieldName] = $isValueEntity && $object instanceof ($class->getName())
- ? $class->reflFields[$fieldName]->getValue($object)
- : $this->getPropertyValue($objectClass, $fieldName, $object);
+ if ($isValueEntity && $object instanceof ($class->getName()) && property_exists(OrmClassMetadata::class, 'propertyAccessors')) {
+ $fieldValues[$entityFieldName] = $class->propertyAccessors[$fieldName]->getValue($object);
+ } elseif ($isValueEntity && $object instanceof ($class->getName())) {
+ $fieldValues[$entityFieldName] = $class->reflFields[$fieldName]->getValue($object);
+ } else {
+ $fieldValues[$entityFieldName] = $this->getPropertyValue($objectClass, $fieldName, $object);
+ }
}
return $fieldValues;
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
index 1e421d5f9f5a9..537849faebaa4 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
@@ -89,7 +89,7 @@
{{- block('choice_widget_options') -}}
{%- else -%}
-
+
{%- endif -%}
{% endfor %}
{%- endblock choice_widget_options -%}
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php
index cfa2c5c6475cf..28e8997a12e9f 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php
@@ -870,6 +870,56 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable()
);
}
+ public function testSingleChoiceWithoutDuplicatePreferredIsSelected()
+ {
+ $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&d', [
+ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c', 'Choice&D' => '&d'],
+ 'preferred_choices' => ['&b', '&d'],
+ 'duplicate_preferred_choices' => false,
+ 'multiple' => false,
+ 'expanded' => false,
+ ]);
+
+ $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --'],
+ '/select
+ [@name="name"]
+ [
+ ./option[@value="&d"][@selected="selected"]
+ /following-sibling::option[@disabled="disabled"][.="-- sep --"]
+ /following-sibling::option[@value="&a"][not(@selected)]
+ /following-sibling::option[@value="&c"][not(@selected)]
+ ]
+ [count(./option)=5]
+'
+ );
+ }
+
+ public function testSingleChoiceWithoutDuplicateNotPreferredIsSelected()
+ {
+ $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&d', [
+ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c', 'Choice&D' => '&d'],
+ 'preferred_choices' => ['&b', '&d'],
+ 'duplicate_preferred_choices' => true,
+ 'multiple' => false,
+ 'expanded' => false,
+ ]);
+
+ $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --'],
+ '/select
+ [@name="name"]
+ [
+ ./option[@value="&d"][not(@selected)]
+ /following-sibling::option[@disabled="disabled"][.="-- sep --"]
+ /following-sibling::option[@value="&a"][not(@selected)]
+ /following-sibling::option[@value="&b"][not(@selected)]
+ /following-sibling::option[@value="&c"][not(@selected)]
+ /following-sibling::option[@value="&d"][@selected="selected"]
+ ]
+ [count(./option)=7]
+'
+ );
+ }
+
public function testFormEndWithRest()
{
$view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType')
diff --git a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TwigNodeProvider.php b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TwigNodeProvider.php
index a6910855e38b1..64ce92bc04355 100644
--- a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TwigNodeProvider.php
+++ b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TwigNodeProvider.php
@@ -14,6 +14,7 @@
use Symfony\Bridge\Twig\Node\TransDefaultDomainNode;
use Symfony\Bridge\Twig\Node\TransNode;
use Twig\Node\BodyNode;
+use Twig\Node\EmptyNode;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
@@ -27,13 +28,15 @@ class TwigNodeProvider
{
public static function getModule($content)
{
+ $emptyNodeExists = class_exists(EmptyNode::class);
+
return new ModuleNode(
new BodyNode([new ConstantExpression($content, 0)]),
null,
- new ArrayExpression([], 0),
- new ArrayExpression([], 0),
- new ArrayExpression([], 0),
- null,
+ $emptyNodeExists ? new EmptyNode() : new ArrayExpression([], 0),
+ $emptyNodeExists ? new EmptyNode() : new ArrayExpression([], 0),
+ $emptyNodeExists ? new EmptyNode() : new ArrayExpression([], 0),
+ $emptyNodeExists ? new EmptyNode() : null,
new Source('', '')
);
}
diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json
index 3af8ccbb7ecce..f0ae491de58a1 100644
--- a/src/Symfony/Bridge/Twig/composer.json
+++ b/src/Symfony/Bridge/Twig/composer.json
@@ -30,7 +30,7 @@
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/emoji": "^7.1",
"symfony/finder": "^6.4|^7.0",
- "symfony/form": "^6.4|^7.0",
+ "symfony/form": "^6.4.20|^7.2.5",
"symfony/html-sanitizer": "^6.4|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index bd27c27a6948c..8d64adeca341d 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -1946,24 +1946,14 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
$container->setParameter('serializer.default_context', $defaultContext);
}
- if (!$container->hasDefinition('serializer.normalizer.object')) {
- return;
- }
-
- $arguments = $container->getDefinition('serializer.normalizer.object')->getArguments();
- $context = $arguments[6] ?? $defaultContext;
-
- if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) {
- $context += ['circular_reference_handler' => new Reference($config['circular_reference_handler'])];
- $container->getDefinition('serializer.normalizer.object')->setArgument(5, null);
+ if ($config['circular_reference_handler'] ?? false) {
+ $container->setParameter('.serializer.circular_reference_handler', $config['circular_reference_handler']);
}
if ($config['max_depth_handler'] ?? false) {
- $context += ['max_depth_handler' => new Reference($config['max_depth_handler'])];
+ $container->setParameter('.serializer.max_depth_handler', $config['max_depth_handler']);
}
- $container->getDefinition('serializer.normalizer.object')->setArgument(6, $context);
-
$container->getDefinition('serializer.normalizer.property')->setArgument(5, $defaultContext);
$container->setParameter('.serializer.named_serializers', $config['named_serializers'] ?? []);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
index 4686a88f662d6..b291f51ac8546 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
@@ -129,7 +129,7 @@
service('property_info')->ignoreOnInvalid(),
service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(),
null,
- null,
+ abstract_arg('default context, set in the SerializerPass'),
service('property_info')->ignoreOnInvalid(),
])
->tag('serializer.normalizer', ['built_in' => true, 'priority' => -1000])
diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/HttpClientAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/HttpClientAssertionsTrait.php
index 9d22a822fb851..4a8afbab4ab98 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Test/HttpClientAssertionsTrait.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Test/HttpClientAssertionsTrait.php
@@ -14,10 +14,9 @@
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector;
-/*
+/**
* @author Mathieu Santostefano
*/
-
trait HttpClientAssertionsTrait
{
public static function assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array|null $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client'): void
diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php
index b68473561eb4d..2c4c5467d4ebd 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Test/NotificationAssertionsTrait.php
@@ -17,7 +17,7 @@
use Symfony\Component\Notifier\Message\MessageInterface;
use Symfony\Component\Notifier\Test\Constraint as NotifierConstraint;
-/*
+/**
* @author Smaïne Milianni
*/
trait NotificationAssertionsTrait
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php
index 5f5f418010663..7bf66512d2b2b 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php
@@ -33,6 +33,7 @@
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveTaggedIteratorArgumentPass;
@@ -67,6 +68,7 @@
use Symfony\Component\Notifier\TexterInterface;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\Security\Core\AuthenticationEvents;
+use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
@@ -1447,9 +1449,6 @@ public function testSerializerEnabled()
$this->assertEquals(AttributeLoader::class, $argument[0]->getClass());
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1));
$this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3));
- $this->assertArrayHasKey('circular_reference_handler', $container->getDefinition('serializer.normalizer.object')->getArgument(6));
- $this->assertArrayHasKey('max_depth_handler', $container->getDefinition('serializer.normalizer.object')->getArgument(6));
- $this->assertEquals($container->getDefinition('serializer.normalizer.object')->getArgument(6)['max_depth_handler'], new Reference('my.max.depth.handler'));
}
public function testSerializerWithoutTranslator()
@@ -1547,13 +1546,22 @@ public function testJsonSerializableNormalizerRegistered()
public function testObjectNormalizerRegistered()
{
- $container = $this->createContainerFromFile('full');
+ $container = $this->createContainerFromFile('full', compile: false);
+ $container->addCompilerPass(new SerializerPass());
+ $container->addCompilerPass(new ResolveBindingsPass());
+ $container->compile();
$definition = $container->getDefinition('serializer.normalizer.object');
$tag = $definition->getTag('serializer.normalizer');
$this->assertEquals(ObjectNormalizer::class, $definition->getClass());
$this->assertEquals(-1000, $tag[0]['priority']);
+
+ $this->assertEquals([
+ 'enable_max_depth' => true,
+ 'circular_reference_handler' => new Reference('my.circular.reference.handler'),
+ 'max_depth_handler' => new Reference('my.max.depth.handler'),
+ ], $definition->getArgument(6));
}
public function testConstraintViolationListNormalizerRegistered()
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index 9b3e7c86ea3ff..6689b61b05990 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -59,7 +59,7 @@
"symfony/scheduler": "^6.4.4|^7.0.4",
"symfony/security-bundle": "^6.4|^7.0",
"symfony/semaphore": "^6.4|^7.0",
- "symfony/serializer": "^7.1",
+ "symfony/serializer": "^7.2.5",
"symfony/stopwatch": "^6.4|^7.0",
"symfony/string": "^6.4|^7.0",
"symfony/translation": "^6.4|^7.0",
@@ -97,7 +97,7 @@
"symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4",
"symfony/security-csrf": "<7.2",
"symfony/security-core": "<6.4",
- "symfony/serializer": "<7.1",
+ "symfony/serializer": "<7.2.5",
"symfony/stopwatch": "<6.4",
"symfony/translation": "<6.4",
"symfony/twig-bridge": "<6.4",
diff --git a/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php b/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php
index f2e4da9b96b49..b88f0e792d44f 100644
--- a/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php
+++ b/src/Symfony/Component/AssetMapper/ImportMap/Resolver/JsDelivrEsmResolver.php
@@ -28,7 +28,7 @@ final class JsDelivrEsmResolver implements PackageResolverInterface
public const URL_PATTERN_DIST = self::URL_PATTERN_DIST_CSS.'/+esm';
public const URL_PATTERN_ENTRYPOINT = 'https://data.jsdelivr.com/v1/packages/npm/%s@%s/entrypoints';
- public const IMPORT_REGEX = '#(?:import\s*(?:[\w$]+,)?(?:(?:\{[^}]*\}|[\w$]+|\*\s*as\s+[\w$]+)\s*\bfrom\s*)?|export\s*(?:\{[^}]*\}|\*)\s*from\s*)("/npm/((?:@[^/]+/)?[^@]+?)(?:@([^/]+))?((?:/[^/]+)*?)/\+esm")#';
+ public const IMPORT_REGEX = '#(?:import\s*(?:[\w$]+,)?(?:(?:\{[^}]*\}|[\w$]+|\*\s*as\s+[\w$]+)\s*\bfrom\s*)?|export\s*(?:\{[^}]*\}|\*)\s*from\s*|await\simport\()("/npm/((?:@[^/]+/)?[^@]+?)(?:@([^/]+))?((?:/[^/]+)*?)/\+esm")(?:\)*)#';
private const ES_MODULE_SHIMS = 'es-module-shims';
diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php
index 8b7d82c8c6f06..d9650fd7c29d3 100644
--- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php
+++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/Resolver/JsDelivrEsmResolverTest.php
@@ -693,6 +693,13 @@ public static function provideImportRegex(): iterable
['jquery', '3.7.0'],
],
];
+
+ yield 'dynamic import with path' => [
+ 'return(await import("/npm/@datadog/browser-rum@6.3.0/esm/boot/startRecording.js/+esm")).startRecording',
+ [
+ ['@datadog/browser-rum/esm/boot/startRecording.js', '6.3.0'],
+ ],
+ ];
}
private static function createRemoteEntry(string $importName, string $version, ImportMapType $type = ImportMapType::JS, ?string $packageSpecifier = null): ImportMapEntry
diff --git a/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php b/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php
index c69c777c993e7..d67464a4fd560 100644
--- a/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php
@@ -19,6 +19,9 @@
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory;
+use Doctrine\DBAL\Schema\Name\Identifier;
+use Doctrine\DBAL\Schema\Name\UnqualifiedName;
+use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Tools\DsnParser;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
@@ -378,6 +381,11 @@ private function addTableToSchema(Schema $schema): void
$table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
$table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
$table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
- $table->setPrimaryKey([$this->idCol]);
+
+ if (class_exists(PrimaryKeyConstraint::class)) {
+ $table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted($this->idCol))], true));
+ } else {
+ $table->setPrimaryKey([$this->idCol]);
+ }
}
}
diff --git a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php
index b9bcdaf132572..22a5a0391673f 100644
--- a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php
+++ b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php
@@ -38,15 +38,7 @@ public function addInstance(string $name, TraceableAdapter $instance): void
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
{
- $empty = ['calls' => [], 'adapters' => [], 'config' => [], 'options' => [], 'statistics' => []];
- $this->data = ['instances' => $empty, 'total' => $empty];
- foreach ($this->instances as $name => $instance) {
- $this->data['instances']['calls'][$name] = $instance->getCalls();
- $this->data['instances']['adapters'][$name] = get_debug_type($instance->getPool());
- }
-
- $this->data['instances']['statistics'] = $this->calculateStatistics();
- $this->data['total']['statistics'] = $this->calculateTotalStatistics();
+ $this->lateCollect();
}
public function reset(): void
@@ -59,6 +51,15 @@ public function reset(): void
public function lateCollect(): void
{
+ $empty = ['calls' => [], 'adapters' => [], 'config' => [], 'options' => [], 'statistics' => []];
+ $this->data = ['instances' => $empty, 'total' => $empty];
+ foreach ($this->instances as $name => $instance) {
+ $this->data['instances']['calls'][$name] = $instance->getCalls();
+ $this->data['instances']['adapters'][$name] = get_debug_type($instance->getPool());
+ }
+
+ $this->data['instances']['statistics'] = $this->calculateStatistics();
+ $this->data['total']['statistics'] = $this->calculateTotalStatistics();
$this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']);
}
diff --git a/src/Symfony/Component/Cache/Tests/DataCollector/CacheDataCollectorTest.php b/src/Symfony/Component/Cache/Tests/DataCollector/CacheDataCollectorTest.php
index bb102363cf758..cea761f5f99ac 100644
--- a/src/Symfony/Component/Cache/Tests/DataCollector/CacheDataCollectorTest.php
+++ b/src/Symfony/Component/Cache/Tests/DataCollector/CacheDataCollectorTest.php
@@ -17,6 +17,7 @@
use Symfony\Component\Cache\DataCollector\CacheDataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\VarDumper\Cloner\Data;
class CacheDataCollectorTest extends TestCase
{
@@ -104,6 +105,27 @@ public function testCollectBeforeEnd()
$this->assertSame(1, $stats[self::INSTANCE_NAME]['misses'], 'misses');
}
+ public function testLateCollect()
+ {
+ $adapter = new TraceableAdapter(new NullAdapter());
+
+ $collector = new CacheDataCollector();
+ $collector->addInstance(self::INSTANCE_NAME, $adapter);
+
+ $adapter->get('foo', function () use ($collector) {
+ $collector->lateCollect();
+
+ return 123;
+ });
+
+ $stats = $collector->getStatistics();
+ $this->assertGreaterThan(0, $stats[self::INSTANCE_NAME]['time']);
+ $this->assertEquals($stats[self::INSTANCE_NAME]['hits'], 0, 'hits');
+ $this->assertEquals($stats[self::INSTANCE_NAME]['misses'], 1, 'misses');
+ $this->assertEquals($stats[self::INSTANCE_NAME]['calls'], 1, 'calls');
+ $this->assertInstanceOf(Data::class, $collector->getCalls());
+ }
+
private function getCacheDataCollectorStatisticsFromEvents(array $traceableAdapterEvents)
{
$traceableAdapterMock = $this->createMock(TraceableAdapter::class);
diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php
index 49e34d8d8c9e8..dc3605ad2fee0 100644
--- a/src/Symfony/Component/Console/Helper/ProgressBar.php
+++ b/src/Symfony/Component/Console/Helper/ProgressBar.php
@@ -513,12 +513,21 @@ private function overwrite(string $message): void
if ($this->output instanceof ConsoleSectionOutput) {
$messageLines = explode("\n", $this->previousMessage);
$lineCount = \count($messageLines);
+
+ $lastLineWithoutDecoration = Helper::removeDecoration($this->output->getFormatter(), end($messageLines) ?? '');
+
+ // When the last previous line is empty (without formatting) it is already cleared by the section output, so we don't need to clear it again
+ if ('' === $lastLineWithoutDecoration) {
+ --$lineCount;
+ }
+
foreach ($messageLines as $messageLine) {
$messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine));
if ($messageLineLength > $this->terminal->getWidth()) {
$lineCount += floor($messageLineLength / $this->terminal->getWidth());
}
}
+
$this->output->clear($lineCount);
} else {
$lineCount = substr_count($this->previousMessage, "\n");
diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
index 3d1bfa48fce27..0df42e738a6f6 100644
--- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
@@ -416,6 +416,81 @@ public function testOverwriteWithSectionOutput()
);
}
+ public function testOverwriteWithSectionOutputAndEol()
+ {
+ $sections = [];
+ $stream = $this->getOutputStream(true);
+ $output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
+
+ $bar = new ProgressBar($output, 50, 0);
+ $bar->setFormat('[%bar%] %percent:3s%%' . PHP_EOL . '%message%' . PHP_EOL);
+ $bar->setMessage('');
+ $bar->start();
+ $bar->display();
+ $bar->setMessage('Doing something...');
+ $bar->advance();
+ $bar->setMessage('Doing something foo...');
+ $bar->advance();
+
+ rewind($output->getStream());
+ $this->assertEquals(escapeshellcmd(
+ '[>---------------------------] 0%'.\PHP_EOL.\PHP_EOL.
+ "\x1b[2A\x1b[0J".'[>---------------------------] 2%'.\PHP_EOL. 'Doing something...' . \PHP_EOL .
+ "\x1b[2A\x1b[0J".'[=>--------------------------] 4%'.\PHP_EOL. 'Doing something foo...' . \PHP_EOL),
+ escapeshellcmd(stream_get_contents($output->getStream()))
+ );
+ }
+
+ public function testOverwriteWithSectionOutputAndEolWithEmptyMessage()
+ {
+ $sections = [];
+ $stream = $this->getOutputStream(true);
+ $output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
+
+ $bar = new ProgressBar($output, 50, 0);
+ $bar->setFormat('[%bar%] %percent:3s%%' . PHP_EOL . '%message%');
+ $bar->setMessage('Start');
+ $bar->start();
+ $bar->display();
+ $bar->setMessage('');
+ $bar->advance();
+ $bar->setMessage('Doing something...');
+ $bar->advance();
+
+ rewind($output->getStream());
+ $this->assertEquals(escapeshellcmd(
+ '[>---------------------------] 0%'.\PHP_EOL.'Start'.\PHP_EOL.
+ "\x1b[2A\x1b[0J".'[>---------------------------] 2%'.\PHP_EOL .
+ "\x1b[1A\x1b[0J".'[=>--------------------------] 4%'.\PHP_EOL. 'Doing something...' . \PHP_EOL),
+ escapeshellcmd(stream_get_contents($output->getStream()))
+ );
+ }
+
+ public function testOverwriteWithSectionOutputAndEolWithEmptyMessageComment()
+ {
+ $sections = [];
+ $stream = $this->getOutputStream(true);
+ $output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
+
+ $bar = new ProgressBar($output, 50, 0);
+ $bar->setFormat('[%bar%] %percent:3s%%' . PHP_EOL . '%message%');
+ $bar->setMessage('Start');
+ $bar->start();
+ $bar->display();
+ $bar->setMessage('');
+ $bar->advance();
+ $bar->setMessage('Doing something...');
+ $bar->advance();
+
+ rewind($output->getStream());
+ $this->assertEquals(escapeshellcmd(
+ '[>---------------------------] 0%'.\PHP_EOL."\x1b[33mStart\x1b[39m".\PHP_EOL.
+ "\x1b[2A\x1b[0J".'[>---------------------------] 2%'.\PHP_EOL .
+ "\x1b[1A\x1b[0J".'[=>--------------------------] 4%'.\PHP_EOL. "\x1b[33mDoing something...\x1b[39m" . \PHP_EOL),
+ escapeshellcmd(stream_get_contents($output->getStream()))
+ );
+ }
+
public function testOverwriteWithAnsiSectionOutput()
{
// output has 43 visible characters plus 2 invisible ANSI characters
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php
index 58ad91e764076..b9e9164573672 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php
@@ -76,7 +76,7 @@ class WitherProxy1991f2a extends \Symfony\Component\DependencyInjection\Tests\Co
use \Symfony\Component\VarExporter\LazyProxyTrait;
private const LAZY_OBJECT_PROPERTY_SCOPES = [
- 'foo' => [parent::class, 'foo', null],
+ 'foo' => [parent::class, 'foo', null, 4],
];
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php
index 0df7e0c98e274..d70588f655329 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy_non_shared.php
@@ -78,7 +78,7 @@ class WitherProxyE94fdba extends \Symfony\Component\DependencyInjection\Tests\Co
use \Symfony\Component\VarExporter\LazyProxyTrait;
private const LAZY_OBJECT_PROPERTY_SCOPES = [
- 'foo' => [parent::class, 'foo', null],
+ 'foo' => [parent::class, 'foo', null, 4],
];
}
diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json
index b5fda9bdeb990..460751088f451 100644
--- a/src/Symfony/Component/DependencyInjection/composer.json
+++ b/src/Symfony/Component/DependencyInjection/composer.json
@@ -20,7 +20,7 @@
"psr/container": "^1.1|^2.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/service-contracts": "^3.5",
- "symfony/var-exporter": "^6.4|^7.0"
+ "symfony/var-exporter": "^6.4.20|^7.2.5"
},
"require-dev": {
"symfony/yaml": "^6.4|^7.0",
diff --git a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php
index 39ee082391d88..3a2ef4fb7d525 100644
--- a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php
+++ b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php
@@ -18,6 +18,7 @@
use Phake\IMock;
use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;
use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\MockObject\Stub;
use Prophecy\Prophecy\ProphecySubjectInterface;
use ProxyManager\Proxy\ProxyInterface;
use Symfony\Component\DependencyInjection\Argument\LazyClosure;
@@ -257,6 +258,7 @@ public static function checkClasses(): bool
for (; $i < \count($symbols); ++$i) {
if (!is_subclass_of($symbols[$i], MockObject::class)
+ && !is_subclass_of($symbols[$i], Stub::class)
&& !is_subclass_of($symbols[$i], ProphecySubjectInterface::class)
&& !is_subclass_of($symbols[$i], Proxy::class)
&& !is_subclass_of($symbols[$i], ProxyInterface::class)
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
index 35a3175924ed4..fc083ee40d516 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
@@ -275,6 +275,8 @@ public function buildView(FormView $view, FormInterface $form, array $options):
public function finishView(FormView $view, FormInterface $form, array $options): void
{
+ $view->vars['duplicate_preferred_choices'] = $options['duplicate_preferred_choices'];
+
if ($options['expanded']) {
// Radio buttons should have the same name as the parent
$childName = $view->vars['full_name'];
diff --git a/src/Symfony/Component/Form/Resources/translations/validators.es.xlf b/src/Symfony/Component/Form/Resources/translations/validators.es.xlf
index 301e2b33f7ed3..a9989737c33eb 100644
--- a/src/Symfony/Component/Form/Resources/translations/validators.es.xlf
+++ b/src/Symfony/Component/Form/Resources/translations/validators.es.xlf
@@ -52,7 +52,7 @@
Please enter a valid date.
- Por favor, ingrese una fecha valida.
+ Por favor, ingrese una fecha válida.
Please select a valid file.
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
index f08471e9b9130..e2fb4f129a124 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
@@ -11,6 +11,9 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
+use Doctrine\DBAL\Schema\Name\Identifier;
+use Doctrine\DBAL\Schema\Name\UnqualifiedName;
+use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
@@ -224,7 +227,13 @@ public function configureSchema(Schema $schema, ?\Closure $isSameDatabase = null
default:
throw new \DomainException(\sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
}
- $table->setPrimaryKey([$this->idCol]);
+
+ if (class_exists(PrimaryKeyConstraint::class)) {
+ $table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted($this->idCol))], true));
+ } else {
+ $table->setPrimaryKey([$this->idCol]);
+ }
+
$table->addIndex([$this->lifetimeCol], $this->lifetimeCol.'_idx');
}
diff --git a/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php
index 9c4f4981a5bba..beca6bfb149a1 100644
--- a/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php
+++ b/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php
@@ -66,7 +66,11 @@ protected function afterDispatch(string $eventName, object $event): void
if (null === $sectionId) {
break;
}
- $this->stopwatch->stopSection($sectionId);
+ try {
+ $this->stopwatch->stopSection($sectionId);
+ } catch (\LogicException) {
+ // The stop watch service might have been reset in the meantime
+ }
break;
case KernelEvents::TERMINATE:
// In the special case described in the `preDispatch` method above, the `$token` section
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index ead55c9272043..d2e1eda84c5e8 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -73,11 +73,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '7.2.4';
- public const VERSION_ID = 70204;
+ public const VERSION = '7.2.5';
+ public const VERSION_ID = 70205;
public const MAJOR_VERSION = 7;
public const MINOR_VERSION = 2;
- public const RELEASE_VERSION = 4;
+ public const RELEASE_VERSION = 5;
public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '07/2025';
@@ -399,7 +399,8 @@ protected function initializeContainer(): void
$cachePath = $cache->getPath();
// Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
- $errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
+ $errorLevel = error_reporting();
+ error_reporting($errorLevel & ~\E_WARNING);
try {
if (is_file($cachePath) && \is_object($this->container = include $cachePath)
diff --git a/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php b/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php
index dc2d5ee39bd06..f042620b71a6b 100644
--- a/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php
+++ b/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php
@@ -18,6 +18,9 @@
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory;
+use Doctrine\DBAL\Schema\Name\Identifier;
+use Doctrine\DBAL\Schema\Name\UnqualifiedName;
+use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Tools\DsnParser;
use Symfony\Component\Lock\Exception\InvalidArgumentException;
@@ -214,7 +217,12 @@ public function configureSchema(Schema $schema, \Closure $isSameDatabase): void
$table->addColumn($this->idCol, 'string', ['length' => 64]);
$table->addColumn($this->tokenCol, 'string', ['length' => 44]);
$table->addColumn($this->expirationCol, 'integer', ['unsigned' => true]);
- $table->setPrimaryKey([$this->idCol]);
+
+ if (class_exists(PrimaryKeyConstraint::class)) {
+ $table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted($this->idCol))], true));
+ } else {
+ $table->setPrimaryKey([$this->idCol]);
+ }
}
/**
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
index e0b06cfd7ef8c..4901824a85c1b 100644
--- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
@@ -23,6 +23,9 @@
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Schema\AbstractAsset;
+use Doctrine\DBAL\Schema\Name\Identifier;
+use Doctrine\DBAL\Schema\Name\UnqualifiedName;
+use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Types;
@@ -519,7 +522,11 @@ private function addTableToSchema(Schema $schema): void
->setNotnull(true);
$table->addColumn('delivered_at', Types::DATETIME_IMMUTABLE)
->setNotnull(false);
- $table->setPrimaryKey(['id']);
+ if (class_exists(PrimaryKeyConstraint::class)) {
+ $table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
+ } else {
+ $table->setPrimaryKey(['id']);
+ }
$table->addIndex(['queue_name']);
$table->addIndex(['available_at']);
$table->addIndex(['delivered_at']);
diff --git a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php
index f1b7d5a7ec836..1a84381008318 100644
--- a/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php
+++ b/src/Symfony/Component/Messenger/Command/ConsumeMessagesCommand.php
@@ -290,7 +290,7 @@ public function handleSignal(int $signal, int|false $previousExitCode = 0): int|
}
if (\SIGALRM === $signal) {
- $this->logger?->info('Sending keepalive request.', ['transport_names' => $this->worker->getMetadata()->getTransportNames()]);
+ $this->logger?->debug('Sending keepalive request.', ['transport_names' => $this->worker->getMetadata()->getTransportNames()]);
$this->worker->keepalive($this->getApplication()->getAlarmInterval());
diff --git a/src/Symfony/Component/Process/Pipes/UnixPipes.php b/src/Symfony/Component/Process/Pipes/UnixPipes.php
index 8e95afaafb877..6f95a3328ffd1 100644
--- a/src/Symfony/Component/Process/Pipes/UnixPipes.php
+++ b/src/Symfony/Component/Process/Pipes/UnixPipes.php
@@ -70,7 +70,7 @@ public function getDescriptors(): array
return [
['pty'],
['pty'],
- ['pty'],
+ ['pipe', 'w'], // stderr needs to be in a pipe to correctly split error and output, since PHP will use the same stream for both
];
}
diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php
index b17bfc7a31aa0..00d06208eb6a8 100644
--- a/src/Symfony/Component/Process/Tests/ProcessTest.php
+++ b/src/Symfony/Component/Process/Tests/ProcessTest.php
@@ -559,6 +559,20 @@ public function testExitCodeTextIsNullWhenExitCodeIsNull()
$this->assertNull($process->getExitCodeText());
}
+ public function testStderrNotMixedWithStdout()
+ {
+ if (!Process::isPtySupported()) {
+ $this->markTestSkipped('PTY is not supported on this operating system.');
+ }
+
+ $process = $this->getProcess('echo "foo" && echo "bar" >&2');
+ $process->setPty(true);
+ $process->run();
+
+ $this->assertSame("foo\r\n", $process->getOutput());
+ $this->assertSame("bar\n", $process->getErrorOutput());
+ }
+
public function testPTYCommand()
{
if (!Process::isPtySupported()) {
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php
index cbf634933511a..f5f83d968f7ba 100644
--- a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php
@@ -16,6 +16,8 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
+use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
+use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
@@ -206,7 +208,7 @@ public function getType(string $class, string $property, array $context = []): ?
$types = [];
foreach ($docNode->getTagsByName($tag) as $tagDocNode) {
- if ($tagDocNode->value instanceof InvalidTagValueNode) {
+ if (!$tagDocNode->value instanceof ParamTagValueNode && !$tagDocNode->value instanceof ReturnTagValueNode && !$tagDocNode->value instanceof VarTagValueNode) {
continue;
}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
index 0d77497c2e1da..d7aaac1b226a7 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
@@ -694,6 +694,7 @@ public static function invalidTypesProvider(): iterable
yield 'stat' => ['stat'];
yield 'foo' => ['foo'];
yield 'bar' => ['bar'];
+ yield 'baz' => ['baz'];
}
/**
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/InvalidDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/InvalidDummy.php
index 0a4cc81f36be8..0940c287b072d 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/InvalidDummy.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/InvalidDummy.php
@@ -47,4 +47,11 @@ public function getBar()
{
return 'bar';
}
+
+ /**
+ * @param $baz
+ */
+ public function setBaz($baz)
+ {
+ }
}
diff --git a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php
index 7b7f6f1c2313b..179b7a3d92e9d 100644
--- a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php
+++ b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php
@@ -19,6 +19,7 @@
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Serializer\Debug\TraceableEncoder;
use Symfony\Component\Serializer\Debug\TraceableNormalizer;
+use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\SerializerInterface;
/**
@@ -54,17 +55,27 @@ public function process(ContainerBuilder $container): void
throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.');
}
+ $defaultContext = [];
if ($container->hasParameter('serializer.default_context')) {
$defaultContext = $container->getParameter('serializer.default_context');
- $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext);
$container->getParameterBag()->remove('serializer.default_context');
$container->getDefinition('serializer')->setArgument('$defaultContext', $defaultContext);
}
+ /** @var ?string $circularReferenceHandler */
+ $circularReferenceHandler = $container->hasParameter('.serializer.circular_reference_handler')
+ ? $container->getParameter('.serializer.circular_reference_handler') : null;
+
+ /** @var ?string $maxDepthHandler */
+ $maxDepthHandler = $container->hasParameter('.serializer.max_depth_handler')
+ ? $container->getParameter('.serializer.max_depth_handler') : null;
+
+ $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext, $circularReferenceHandler, $maxDepthHandler);
+
$this->configureSerializer($container, 'serializer', $normalizers, $encoders, 'default');
if ($namedSerializers) {
- $this->configureNamedSerializers($container);
+ $this->configureNamedSerializers($container, $circularReferenceHandler, $maxDepthHandler);
}
}
@@ -98,11 +109,22 @@ private function createNamedSerializerTags(ContainerBuilder $container, string $
}
}
- private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext): void
+ private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext, ?string $circularReferenceHandler, ?string $maxDepthHandler): void
{
foreach ($services as $id) {
$definition = $container->getDefinition((string) $id);
- $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings());
+
+ $context = $defaultContext;
+ if (is_a($definition->getClass(), ObjectNormalizer::class, true)) {
+ if (null !== $circularReferenceHandler) {
+ $context += ['circular_reference_handler' => new Reference($circularReferenceHandler)];
+ }
+ if (null !== $maxDepthHandler) {
+ $context += ['max_depth_handler' => new Reference($maxDepthHandler)];
+ }
+ }
+
+ $definition->setBindings(['array $defaultContext' => new BoundArgument($context, false)] + $definition->getBindings());
}
}
@@ -125,7 +147,7 @@ private function configureSerializer(ContainerBuilder $container, string $id, ar
$serializerDefinition->replaceArgument(1, $encoders);
}
- private function configureNamedSerializers(ContainerBuilder $container): void
+ private function configureNamedSerializers(ContainerBuilder $container, ?string $circularReferenceHandler, ?string $maxDepthHandler): void
{
$defaultSerializerNameConverter = $container->hasParameter('.serializer.name_converter')
? $container->getParameter('.serializer.name_converter') : null;
@@ -149,7 +171,7 @@ private function configureNamedSerializers(ContainerBuilder $container): void
$normalizers = $this->buildChildDefinitions($container, $serializerName, $normalizers, $config);
$encoders = $this->buildChildDefinitions($container, $serializerName, $encoders, $config);
- $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']);
+ $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context'], $circularReferenceHandler, $maxDepthHandler);
$container->registerChild($serializerId, 'serializer')->setArgument('$defaultContext', $config['default_context']);
$container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer');
@@ -184,7 +206,9 @@ private function buildChildDefinitions(ContainerBuilder $container, string $seri
foreach ($services as &$id) {
$childId = $id.'.'.$serializerName;
- $definition = $container->registerChild($childId, (string) $id);
+ $definition = $container->registerChild($childId, (string) $id)
+ ->setClass($container->getDefinition((string) $id)->getClass())
+ ;
if (null !== $nameConverterIndex = $this->findNameConverterIndex($container, (string) $id)) {
$definition->replaceArgument($nameConverterIndex, new Reference($config['name_converter']));
diff --git a/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php b/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php
index 769243be25f88..88ec02b87c57d 100644
--- a/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php
+++ b/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php
@@ -19,6 +19,7 @@
use Symfony\Component\Serializer\Debug\TraceableNormalizer;
use Symfony\Component\Serializer\Debug\TraceableSerializer;
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
+use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\SerializerInterface;
/**
@@ -99,6 +100,32 @@ public function testBindSerializerDefaultContext()
$this->assertEquals($context, $container->getDefinition('serializer')->getArgument('$defaultContext'));
}
+ /**
+ * @testWith [{}, {}]
+ * [{"serializer.default_context": {"enable_max_depth": true}}, {"enable_max_depth": true}]
+ * [{".serializer.circular_reference_handler": "foo"}, {"circular_reference_handler": "foo"}]
+ * [{".serializer.max_depth_handler": "bar"}, {"max_depth_handler": "bar"}]
+ * [{"serializer.default_context": {"enable_max_depth": true}, ".serializer.circular_reference_handler": "foo", ".serializer.max_depth_handler": "bar"}, {"enable_max_depth": true, "circular_reference_handler": "foo", "max_depth_handler": "bar"}]
+ */
+ public function testBindObjectNormalizerDefaultContext(array $parameters, array $context)
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('kernel.debug', false);
+ $container->register('serializer')->setArguments([null, null, []]);
+ $container->getParameterBag()->add($parameters);
+ $definition = $container->register('serializer.normalizer.object')
+ ->setClass(ObjectNormalizer::class)
+ ->addTag('serializer.normalizer')
+ ->addTag('serializer.encoder')
+ ;
+
+ $serializerPass = new SerializerPass();
+ $serializerPass->process($container);
+
+ $bindings = $definition->getBindings();
+ $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false));
+ }
+
public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingData()
{
$container = new ContainerBuilder();
@@ -565,7 +592,9 @@ public function testBindSerializerDefaultContextToNamedSerializers()
$serializerPass = new SerializerPass();
$serializerPass->process($container);
- $this->assertEmpty($definition->getBindings());
+ $bindings = $definition->getBindings();
+ $this->assertArrayHasKey('array $defaultContext', $bindings);
+ $this->assertEquals($bindings['array $defaultContext'], new BoundArgument([], false));
$bindings = $container->getDefinition('n1.api')->getBindings();
$this->assertArrayHasKey('array $defaultContext', $bindings);
@@ -574,6 +603,37 @@ public function testBindSerializerDefaultContextToNamedSerializers()
$this->assertEquals($defaultContext, $container->getDefinition('serializer.api')->getArgument('$defaultContext'));
}
+ /**
+ * @testWith [{}, {}, {}]
+ * [{"enable_max_depth": true}, {}, {"enable_max_depth": true}]
+ * [{}, {".serializer.circular_reference_handler": "foo"}, {"circular_reference_handler": "foo"}]
+ * [{}, {".serializer.max_depth_handler": "bar"}, {"max_depth_handler": "bar"}]
+ * [{"enable_max_depth": true}, {".serializer.circular_reference_handler": "foo", ".serializer.max_depth_handler": "bar"}, {"enable_max_depth": true, "circular_reference_handler": "foo", "max_depth_handler": "bar"}]
+ */
+ public function testBindNamedSerializerObjectNormalizerDefaultContext(array $defaultContext, array $parameters, array $context)
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('kernel.debug', false);
+ $container->setParameter('.serializer.named_serializers', [
+ 'api' => ['default_context' => $defaultContext],
+ ]);
+
+ $container->register('serializer')->setArguments([null, null, []]);
+ $container->getParameterBag()->add($parameters);
+ $container->register('serializer.normalizer.object')
+ ->setClass(ObjectNormalizer::class)
+ ->addTag('serializer.normalizer', ['serializer' => '*'])
+ ->addTag('serializer.encoder', ['serializer' => '*'])
+ ;
+
+ $serializerPass = new SerializerPass();
+ $serializerPass->process($container);
+
+ $bindings = $container->getDefinition('serializer.normalizer.object.api')->getBindings();
+ $this->assertArrayHasKey('array $defaultContext', $bindings);
+ $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false));
+ }
+
public function testNamedSerializersAreRegistered()
{
$container = new ContainerBuilder();
diff --git a/src/Symfony/Component/TypeInfo/Tests/Fixtures/DummyWithPhpDoc.php b/src/Symfony/Component/TypeInfo/Tests/Fixtures/DummyWithPhpDoc.php
index 30141f95c3d0f..035457d12a95f 100644
--- a/src/Symfony/Component/TypeInfo/Tests/Fixtures/DummyWithPhpDoc.php
+++ b/src/Symfony/Component/TypeInfo/Tests/Fixtures/DummyWithPhpDoc.php
@@ -11,9 +11,18 @@ final class DummyWithPhpDoc
/**
* @param bool $promoted
+ * @param bool $promotedVarAndParam
*/
public function __construct(
public mixed $promoted,
+ /**
+ * @var string
+ */
+ public mixed $promotedVar,
+ /**
+ * @var string
+ */
+ public mixed $promotedVarAndParam,
) {
}
diff --git a/src/Symfony/Component/TypeInfo/Tests/TypeResolver/PhpDocAwareReflectionTypeResolverTest.php b/src/Symfony/Component/TypeInfo/Tests/TypeResolver/PhpDocAwareReflectionTypeResolverTest.php
index 7e92638a9ce38..996b04ff11238 100644
--- a/src/Symfony/Component/TypeInfo/Tests/TypeResolver/PhpDocAwareReflectionTypeResolverTest.php
+++ b/src/Symfony/Component/TypeInfo/Tests/TypeResolver/PhpDocAwareReflectionTypeResolverTest.php
@@ -29,6 +29,8 @@ public function testReadPhpDoc()
$this->assertEquals(Type::array(Type::object(Dummy::class)), $resolver->resolve($reflection->getProperty('arrayOfDummies')));
$this->assertEquals(Type::bool(), $resolver->resolve($reflection->getProperty('promoted')));
+ $this->assertEquals(Type::string(), $resolver->resolve($reflection->getProperty('promotedVar')));
+ $this->assertEquals(Type::string(), $resolver->resolve($reflection->getProperty('promotedVarAndParam')));
$this->assertEquals(Type::object(Dummy::class), $resolver->resolve($reflection->getMethod('getNextDummy')));
$this->assertEquals(Type::object(Dummy::class), $resolver->resolve($reflection->getMethod('getNextDummy')->getParameters()[0]));
}
diff --git a/src/Symfony/Component/TypeInfo/Tests/TypeTest.php b/src/Symfony/Component/TypeInfo/Tests/TypeTest.php
index 6d60b5dc21eca..8b2bc4d900c48 100644
--- a/src/Symfony/Component/TypeInfo/Tests/TypeTest.php
+++ b/src/Symfony/Component/TypeInfo/Tests/TypeTest.php
@@ -13,6 +13,8 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\TypeInfo\Type;
+use Symfony\Component\TypeInfo\Type\CollectionType;
+use Symfony\Component\TypeInfo\Type\UnionType;
use Symfony\Component\TypeInfo\TypeIdentifier;
class TypeTest extends TestCase
@@ -34,4 +36,12 @@ public function testIsNullable()
$this->assertFalse(Type::int()->isNullable());
}
+
+ public function testIsSatisfiedBy()
+ {
+ $this->assertTrue(Type::union(Type::int(), Type::string())->isSatisfiedBy(fn (Type $t): bool => 'int' === (string) $t));
+ $this->assertTrue(Type::union(Type::int(), Type::string())->isSatisfiedBy(fn (Type $t): bool => $t instanceof UnionType));
+ $this->assertTrue(Type::list(Type::int())->isSatisfiedBy(fn (Type $t): bool => $t instanceof CollectionType && 'int' === (string) $t->getCollectionValueType()));
+ $this->assertFalse(Type::list(Type::int())->isSatisfiedBy(fn (Type $t): bool => 'int' === (string) $t));
+ }
}
diff --git a/src/Symfony/Component/TypeInfo/Type.php b/src/Symfony/Component/TypeInfo/Type.php
index 2a39f14e7b5bf..c14b86263a784 100644
--- a/src/Symfony/Component/TypeInfo/Type.php
+++ b/src/Symfony/Component/TypeInfo/Type.php
@@ -29,6 +29,14 @@ abstract class Type implements \Stringable
*/
public function isSatisfiedBy(callable $specification): bool
{
+ if ($this instanceof WrappingTypeInterface && $this->wrappedTypeIsSatisfiedBy($specification)) {
+ return true;
+ }
+
+ if ($this instanceof CompositeTypeInterface && $this->composedTypesAreSatisfiedBy($specification)) {
+ return true;
+ }
+
return $specification($this);
}
@@ -37,19 +45,17 @@ public function isSatisfiedBy(callable $specification): bool
*/
public function isIdentifiedBy(TypeIdentifier|string ...$identifiers): bool
{
- $specification = static function (Type $type) use (&$specification, $identifiers): bool {
- if ($type instanceof WrappingTypeInterface) {
- return $type->wrappedTypeIsSatisfiedBy($specification);
- }
+ $specification = static fn (Type $type): bool => $type->isIdentifiedBy(...$identifiers);
- if ($type instanceof CompositeTypeInterface) {
- return $type->composedTypesAreSatisfiedBy($specification);
- }
+ if ($this instanceof WrappingTypeInterface && $this->wrappedTypeIsSatisfiedBy($specification)) {
+ return true;
+ }
- return $type->isIdentifiedBy(...$identifiers);
- };
+ if ($this instanceof CompositeTypeInterface && $this->composedTypesAreSatisfiedBy($specification)) {
+ return true;
+ }
- return $this->isSatisfiedBy($specification);
+ return false;
}
public function isNullable(): bool
diff --git a/src/Symfony/Component/TypeInfo/TypeResolver/PhpDocAwareReflectionTypeResolver.php b/src/Symfony/Component/TypeInfo/TypeResolver/PhpDocAwareReflectionTypeResolver.php
index 9f71ee4bc2ed8..24aa20c7d7d72 100644
--- a/src/Symfony/Component/TypeInfo/TypeResolver/PhpDocAwareReflectionTypeResolver.php
+++ b/src/Symfony/Component/TypeInfo/TypeResolver/PhpDocAwareReflectionTypeResolver.php
@@ -64,36 +64,38 @@ public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type
throw new UnsupportedException(\sprintf('Expected subject to be a "ReflectionProperty", a "ReflectionParameter" or a "ReflectionFunctionAbstract", "%s" given.', get_debug_type($subject)), $subject);
}
- $docComment = match (true) {
- $subject instanceof \ReflectionProperty => $subject->isPromoted() ? $subject->getDeclaringClass()?->getConstructor()?->getDocComment() : $subject->getDocComment(),
- $subject instanceof \ReflectionParameter => $subject->getDeclaringFunction()->getDocComment(),
- $subject instanceof \ReflectionFunctionAbstract => $subject->getDocComment(),
+ $typeContext ??= $this->typeContextFactory->createFromReflection($subject);
+
+ $docComments = match (true) {
+ $subject instanceof \ReflectionProperty => $subject->isPromoted()
+ ? ['@var' => $subject->getDocComment(), '@param' => $subject->getDeclaringClass()?->getConstructor()?->getDocComment()]
+ : ['@var' => $subject->getDocComment()],
+ $subject instanceof \ReflectionParameter => ['@param' => $subject->getDeclaringFunction()->getDocComment()],
+ $subject instanceof \ReflectionFunctionAbstract => ['@return' => $subject->getDocComment()],
};
- if (!$docComment) {
- return $this->reflectionTypeResolver->resolve($subject);
- }
+ foreach ($docComments as $tagName => $docComment) {
+ if (!$docComment) {
+ continue;
+ }
- $typeContext ??= $this->typeContextFactory->createFromReflection($subject);
+ $tokens = new TokenIterator($this->lexer->tokenize($docComment));
+ $docNode = $this->phpDocParser->parse($tokens);
- $tagName = match (true) {
- $subject instanceof \ReflectionProperty => $subject->isPromoted() ? '@param' : '@var',
- $subject instanceof \ReflectionParameter => '@param',
- $subject instanceof \ReflectionFunctionAbstract => '@return',
- };
+ foreach ($docNode->getTagsByName($tagName) as $tag) {
+ $tagValue = $tag->value;
- $tokens = new TokenIterator($this->lexer->tokenize($docComment));
- $docNode = $this->phpDocParser->parse($tokens);
+ if ('@var' === $tagName && $tagValue instanceof VarTagValueNode) {
+ return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext);
+ }
- foreach ($docNode->getTagsByName($tagName) as $tag) {
- $tagValue = $tag->value;
+ if ('@param' === $tagName && $tagValue instanceof ParamTagValueNode && '$'.$subject->getName() === $tagValue->parameterName) {
+ return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext);
+ }
- if (
- $tagValue instanceof VarTagValueNode
- || $tagValue instanceof ParamTagValueNode && $tagName && '$'.$subject->getName() === $tagValue->parameterName
- || $tagValue instanceof ReturnTagValueNode
- ) {
- return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext);
+ if ('@return' === $tagName && $tagValue instanceof ReturnTagValueNode) {
+ return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext);
+ }
}
}
diff --git a/src/Symfony/Component/Validator/Constraints/All.php b/src/Symfony/Component/Validator/Constraints/All.php
index 1da939dd5559f..1284545849ac0 100644
--- a/src/Symfony/Component/Validator/Constraints/All.php
+++ b/src/Symfony/Component/Validator/Constraints/All.php
@@ -25,8 +25,8 @@ class All extends Composite
public array|Constraint $constraints = [];
/**
- * @param array|array|null $constraints
- * @param string[]|null $groups
+ * @param array|array|Constraint|null $constraints
+ * @param string[]|null $groups
*/
public function __construct(mixed $constraints = null, ?array $groups = null, mixed $payload = null)
{
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ja.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ja.xlf
index c4b36fd45f7f0..f6d2e0c28a33e 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.ja.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.ja.xlf
@@ -444,31 +444,31 @@
This value is too short. It should contain at least one word.|This value is too short. It should contain at least {{ min }} words.
- この値は短すぎます。少なくとも 1 つの単語を含める必要があります。|この値は短すぎます。少なくとも {{ min }} 個の単語を含める必要があります。
+ この値は短すぎます。{{ min }}単語以上にする必要があります。
This value is too long. It should contain one word.|This value is too long. It should contain {{ max }} words or less.
- この値は長すぎます。1 つの単語のみを含める必要があります。|この値は長すぎます。{{ max }} 個以下の単語を含める必要があります。
+ この値は長すぎます。{{ max }}単語以下にする必要があります。
This value does not represent a valid week in the ISO 8601 format.
- この値は ISO 8601 形式の有効な週を表していません。
+ この値は ISO 8601 形式の有効な週を表していません。
This value is not a valid week.
- この値は有効な週ではありません。
+ この値は有効な週ではありません。
This value should not be before week "{{ min }}".
- この値は週 "{{ min }}" より前であってはなりません。
+ この値は週 "{{ min }}" より前であってはいけません。
This value should not be after week "{{ max }}".
- この値は週 "{{ max }}" 以降であってはなりません。
+ この値は週 "{{ max }}" 以降であってはいけません。
This value is not a valid slug.
- この値は有効なスラグではありません。
+ この値は有効なスラグではありません。