diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 90e51d60536d6..3d21822287b6b 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,6 +1,6 @@
| Q | A
| ------------- | ---
-| Branch? | 7.3 for features / 5.4, 6.4, 7.1, and 7.2 for bug fixes
+| Branch? | 7.3 for features / 6.4, 7.1, and 7.2 for bug fixes
| Bug fix? | yes/no
| New feature? | yes/no
| Deprecations? | yes/no
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index e5defe2a989f9..8849fd3a94c58 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -33,6 +33,7 @@ jobs:
mode: low-deps
- php: '8.3'
- php: '8.4'
+ - php: '8.5'
#mode: experimental
fail-fast: false
diff --git a/CHANGELOG-6.4.md b/CHANGELOG-6.4.md
index 94111d16ed62b..56df1b333f50b 100644
--- a/CHANGELOG-6.4.md
+++ b/CHANGELOG-6.4.md
@@ -7,6 +7,25 @@ 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.17 (2024-12-31)
+
+ * bug #59304 [PropertyInfo] Remove ``@internal`` from `PropertyReadInfo` and `PropertyWriteInfo` (Dario Guarracino)
+ * bug #59318 [Finder] Fix using `==` as default operator in `DateComparator` (MatTheCat)
+ * bug #59321 [HtmlSanitizer] reject URLs containing whitespaces (xabbuh)
+ * bug #59250 [HttpClient] Fix a typo in NoPrivateNetworkHttpClient (Jontsa)
+ * bug #59103 [Messenger] ensure exception on rollback does not hide previous exception (nikophil)
+ * bug #59226 [FrameworkBundle] require the writer to implement getFormats() in the translation:extract (xabbuh)
+ * bug #59213 [FrameworkBundle] don't require fake notifier transports to be installed as non-dev dependencies (xabbuh)
+ * bug #59160 [BeanstalkMessenger] Round delay to an integer to avoid deprecation warning (plantas)
+ * bug #59012 [PropertyInfo] Fix interface handling in `PhpStanTypeHelper` (janedbal)
+ * bug #59134 [HttpKernel] Denormalize request data using the csv format when using "#[MapQueryString]" or "#[MapRequestPayload]" (except for content data) (ovidiuenache)
+ * bug #59140 [WebProfilerBundle] fix: white-space in highlighted code (chr-hertel)
+ * bug #59124 [FrameworkBundle] fix: notifier push channel bus abstract arg (raphael-geffroy)
+ * bug #59069 [Console] Fix division by 0 error (Rindula)
+ * bug #59070 [PropertyInfo] evaluate access flags for properties with asymmetric visibility (xabbuh)
+ * bug #59062 [HttpClient] Always set CURLOPT_CUSTOMREQUEST to the correct HTTP method in CurlHttpClient (KurtThiemann)
+ * bug #59023 [HttpClient] Fix streaming and redirecting with NoPrivateNetworkHttpClient (nicolas-grekas)
+
* 6.4.16 (2024-11-27)
* bug #59013 [HttpClient] Fix checking for private IPs before connecting (nicolas-grekas)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index bcc33dc4892f2..d0472fa4bd167 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -19,8 +19,8 @@ The Symfony Connect username in parenthesis allows to get more information
- Jordi Boggiano (seldaek)
- Maxime Steinhausser (ogizanagi)
- Kévin Dunglas (dunglas)
- - Victor Berchet (victor)
- Javier Eguiluz (javier.eguiluz)
+ - Victor Berchet (victor)
- Ryan Weaver (weaverryan)
- Jérémy DERUSSÉ (jderusse)
- Jules Pietri (heah)
@@ -34,8 +34,8 @@ The Symfony Connect username in parenthesis allows to get more information
- Tobias Nyholm (tobias)
- HypeMC (hypemc)
- Jérôme Tamarelle (gromnan)
- - Samuel ROZE (sroze)
- Antoine Lamirault (alamirault)
+ - Samuel ROZE (sroze)
- Pascal Borreli (pborreli)
- Romain Neutron
- Joseph Bielawski (stloyd)
@@ -51,15 +51,15 @@ The Symfony Connect username in parenthesis allows to get more information
- Igor Wiedler
- Jan Schädlich (jschaedl)
- Mathieu Lechat (mat_the_cat)
+ - Mathias Arlaud (mtarld)
+ - Simon André (simonandre)
- Matthias Pigulla (mpdude)
- Gabriel Ostrolucký (gadelat)
- - Simon André (simonandre)
- Jonathan Wage (jwage)
- Vincent Langlet (deviling)
- Valentin Udaltsov (vudaltsov)
- - Mathias Arlaud (mtarld)
- - Alexandre Salomé (alexandresalome)
- Grégoire Paris (greg0ire)
+ - Alexandre Salomé (alexandresalome)
- William DURAND
- ornicar
- Dany Maillard (maidmaid)
@@ -73,21 +73,21 @@ The Symfony Connect username in parenthesis allows to get more information
- Pierre du Plessis (pierredup)
- David Maicher (dmaicher)
- Tomasz Kowalczyk (thunderer)
+ - Mathieu Santostefano (welcomattic)
- Bulat Shakirzyanov (avalanche123)
- Iltar van der Berg
- Miha Vrhovnik (mvrhov)
- Gary PEGEOT (gary-p)
- - Mathieu Santostefano (welcomattic)
- Saša Stamenković (umpirsky)
- Allison Guilhem (a_guilhem)
- Alexander Schranz (alexander-schranz)
+ - Dariusz Ruminski
- Mathieu Piot (mpiot)
- Vasilij Duško (staff)
- Sarah Khalil (saro0h)
- Laurent VOULLEMIER (lvo)
- Konstantin Kudryashov (everzet)
- Guilhem N (guilhemn)
- - Dariusz Ruminski
- Bilal Amarni (bamarni)
- Eriksen Costa
- Florin Patan (florinpatan)
@@ -110,12 +110,12 @@ The Symfony Connect username in parenthesis allows to get more information
- Baldini
- Alex Pott
- Fran Moreno (franmomu)
+ - Hubert Lenoir (hubert_lenoir)
- Charles Sarrazin (csarrazi)
- Henrik Westphal (snc)
- Dariusz Górecki (canni)
- - Hubert Lenoir (hubert_lenoir)
- - Ener-Getick
- Antoine Makdessi (amakdessi)
+ - Ener-Getick
- Graham Campbell (graham)
- Tugdual Saunier (tucksaun)
- Lee McDermott
@@ -144,20 +144,20 @@ 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)
- Brice BERNARD (brikou)
+ - Jacob Dreesen (jdreesen)
+ - Nicolas Philippe (nikophil)
- Martin Auswöger
- Michal Piotrowski
- marc.weistroff
- Lars Strojny (lstrojny)
- lenar
- Vladimir Tsykun (vtsykun)
- - Jacob Dreesen (jdreesen)
- Włodzimierz Gajda (gajdaw)
- - Valtteri R (valtzu)
- - Nicolas Philippe (nikophil)
- Javier Spagnoletti (phansys)
- Adrien Brault (adrienbrault)
- Florian Voutzinos (florianv)
@@ -170,6 +170,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Baptiste Clavié (talus)
- Alexander Schwenn (xelaris)
- Fabien Pennequin (fabienpennequin)
+ - Dāvis Zālītis (k0d3r1s)
- Gordon Franke (gimler)
- Malte Schlüter (maltemaltesich)
- jeremyFreeAgent (jeremyfreeagent)
@@ -178,14 +179,15 @@ The Symfony Connect username in parenthesis allows to get more information
- Vasilij Dusko
- Daniel Wehner (dawehner)
- Maxime Helias (maxhelias)
- - Dāvis Zālītis (k0d3r1s)
- Robert Schönthal (digitalkaoz)
- Smaine Milianni (ismail1432)
+ - Hugo Alliaume (kocal)
- François-Xavier de Guillebon (de-gui_f)
- Andreas Schempp (aschempp)
- noniagriconomie
- Eric GELOEN (gelo)
- Gabriel Caruso
+ - Christopher Hertel (chertel)
- Stefano Sala (stefano.sala)
- Ion Bazan (ionbazan)
- Niels Keurentjes (curry684)
@@ -193,20 +195,19 @@ The Symfony Connect username in parenthesis allows to get more information
- Jhonny Lidfors (jhonne)
- Juti Noppornpitak (shiroyuki)
- Gregor Harlan (gharlan)
- - Hugo Alliaume (kocal)
+ - Alexis Lefebvre
- Anthony MARTIN
- Sebastian Hörl (blogsh)
- Tigran Azatyan (tigranazatyan)
- Florent Mata (fmata)
- - Christopher Hertel (chertel)
- Jonathan Scheiber (jmsche)
- Daniel Gomes (danielcsgomes)
- Hidenori Goto (hidenorigoto)
+ - Thomas Landauer (thomas-landauer)
- Arnaud Kleinpeter (nanocom)
- Guilherme Blanco (guilhermeblanco)
- Saif Eddin Gmati (azjezz)
- Farhad Safarov (safarov)
- - Alexis Lefebvre
- SpacePossum
- Richard van Laak (rvanlaak)
- Andreas Braun
@@ -214,7 +215,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Alessandro Chitolina (alekitto)
- Rafael Dohms (rdohms)
- Roman Martinuk (a2a4)
- - Thomas Landauer (thomas-landauer)
- jwdeitch
- David Prévot (taffit)
- Jérôme Parmentier (lctrs)
@@ -223,6 +223,7 @@ The Symfony Connect username in parenthesis allows to get more information
- soyuka
- Jérémy Derussé
- Matthieu Napoli (mnapoli)
+ - Bob van de Vijver (bobvandevijver)
- Tomas Votruba (tomas_votruba)
- Arman Hosseini (arman)
- Sokolov Evgeniy (ewgraf)
@@ -242,7 +243,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Fabien Bourigault (fbourigault)
- Olivier Dolbeau (odolbeau)
- Rouven Weßling (realityking)
- - Bob van de Vijver (bobvandevijver)
- Daniel Burger
- Ben Davies (bendavies)
- YaFou
@@ -270,6 +270,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Samuel NELA (snela)
- Baptiste Leduc (korbeil)
- Vincent AUBERT (vincent)
+ - Nate Wiebe (natewiebe13)
- Michael Voříšek
- zairig imad (zairigimad)
- Colin O'Dell (colinodell)
@@ -336,7 +337,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Julien Pauli
- Michael Lee (zerustech)
- Florian Lonqueu-Brochard (florianlb)
- - Nate Wiebe (natewiebe13)
- Joe Bennett (kralos)
- Leszek Prabucki (l3l0)
- Wojciech Kania
@@ -351,6 +351,7 @@ The Symfony Connect username in parenthesis allows to get more information
- fd6130 (fdtvui)
- Priyadi Iman Nurcahyo (priyadi)
- Alan Poulain (alanpoulain)
+ - Oleg Andreyev (oleg.andreyev)
- Maciej Malarz (malarzm)
- Marcin Sikoń (marphi)
- Michele Orselli (orso)
@@ -390,13 +391,14 @@ The Symfony Connect username in parenthesis allows to get more information
- Alexander Kotynia (olden)
- Elnur Abdurrakhimov (elnur)
- Manuel Reinhard (sprain)
+ - Zan Baldwin (zanbaldwin)
+ - Tim Goudriaan (codedmonkey)
- Antonio J. García Lagar (ajgarlag)
- BoShurik
- Quentin Devos
- Adam Prager (padam87)
- Benoît Burnichon (bburnichon)
- maxime.steinhausser
- - Oleg Andreyev (oleg.andreyev)
- Roman Ring (inori)
- Xavier Montaña Carreras (xmontana)
- Arjen van der Meijden
@@ -415,6 +417,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Pierre-Yves Lebecq (pylebecq)
- Benjamin Leveque (benji07)
- Jordan Samouh (jordansamouh)
+ - David Badura (davidbadura)
- Sullivan SENECHAL (soullivaneuh)
- Uwe Jäger (uwej711)
- javaDeveloperKid
@@ -458,9 +461,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Wodor Wodorski
- Beau Simensen (simensen)
- Magnus Nordlander (magnusnordlander)
- - Tim Goudriaan (codedmonkey)
- Robert Kiss (kepten)
- - Zan Baldwin (zanbaldwin)
- Alexandre Quercia (alquerci)
- Marcos Sánchez
- Emanuele Panzeri (thepanz)
@@ -483,10 +484,11 @@ The Symfony Connect username in parenthesis allows to get more information
- Marco Petersen (ocrampete16)
- Bohan Yang (brentybh)
- Vilius Grigaliūnas
- - David Badura (davidbadura)
+ - Jordane VASPARD (elementaire)
- Chris Smith (cs278)
- Thomas Bisignani (toma)
- Florian Klein (docteurklein)
+ - Raphaël Geffroy (raphael-geffroy)
- Damien Alexandre (damienalexandre)
- Manuel Kießling (manuelkiessling)
- Alexey Kopytko (sanmai)
@@ -582,7 +584,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Alexander Menshchikov
- Clément Gautier (clementgautier)
- roman joly (eltharin)
- - Jordane VASPARD (elementaire)
- James Gilliland (neclimdul)
- Sanpi (sanpi)
- Eduardo Gulias (egulias)
@@ -683,9 +684,11 @@ The Symfony Connect username in parenthesis allows to get more information
- Neil Peyssard (nepey)
- Niklas Fiekas
- Mark Challoner (markchalloner)
+ - Andreas Hennings
- Markus Bachmann (baachi)
- Gunnstein Lye (glye)
- Erkhembayar Gantulga (erheme318)
+ - Yi-Jyun Pan
- Sergey Melesh (sergex)
- Greg Anderson
- lancergr
@@ -761,6 +764,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Soufian EZ ZANTAR (soezz)
- Marek Zajac
- Adam Harvey
+ - Klaus Silveira (klaussilveira)
- ilyes kooli (skafandri)
- Anton Bakai
- battye
@@ -787,6 +791,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Joshua Nye
- Martin Kirilov (wucdbm)
- Koen Reiniers (koenre)
+ - Kurt Thiemann
- Nathan Dench (ndenc2)
- Gijs van Lammeren
- Sebastian Bergmann
@@ -797,6 +802,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Kev
- Kevin McBride
- Sergio Santoro
+ - Jonas Elfering
- Philipp Rieber (bicpi)
- Dmitriy Derepko
- Manuel de Ruiter (manuel)
@@ -899,6 +905,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Daniel Tiringer
- Lenar Lõhmus
- Ilija Tovilo (ilijatovilo)
+ - Maxime Pinot (maximepinot)
- Sander Toonen (xatoo)
- Zach Badgett (zachbadgett)
- Loïc Faugeron
@@ -949,7 +956,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Franck RANAIVO-HARISOA (franckranaivo)
- Yi-Jyun Pan
- Egor Taranov
- - Andreas Hennings
- Arnaud Frézet
- Philippe Segatori
- Jon Gotlin (jongotlin)
@@ -966,6 +972,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Ricky Su (ricky)
- scyzoryck
- Kyle Evans (kevans91)
+ - Ioan Ovidiu Enache (ionutenache)
- Max Rath (drak3)
- Cristoforo Cervino (cristoforocervino)
- marie
@@ -975,7 +982,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Noémi Salaün (noemi-salaun)
- Sinan Eldem (sineld)
- Gennady Telegin
- - Yi-Jyun Pan
- ampaze
- Alexandre Dupuy (satchette)
- Michel Hunziker
@@ -998,6 +1004,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Åsmund Garfors
- Maxime Douailin
- Jean Pasdeloup
+ - Maxime COLIN (maximecolin)
- Lorenzo Millucci (lmillucci)
- Javier López (loalf)
- Reinier Kip
@@ -1064,6 +1071,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Quentin de Longraye (quentinus95)
- Chris Heng (gigablah)
- Mickaël Buliard (mbuliard)
+ - Jan Nedbal
- Cornel Cruceru (amne)
- Richard Bradley
- Jan Walther (janwalther)
@@ -1156,7 +1164,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Aleksandr Volochnev (exelenz)
- Robin van der Vleuten (robinvdvleuten)
- Grinbergs Reinis (shima5)
- - Klaus Silveira (klaussilveira)
- Michael Piecko (michael.piecko)
- Toni Peric (tperic)
- yclian
@@ -1241,7 +1248,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Thorry84
- Romanavr
- michaelwilliams
- - Raphaël Geffroy (raphael-geffroy)
- Alexandre Parent
- 1emming
- Nykopol (nykopol)
@@ -1295,6 +1301,7 @@ The Symfony Connect username in parenthesis allows to get more information
- _sir_kane (waly)
- Olivier Maisonneuve
- Gálik Pál
+ - Bálint Szekeres
- Andrei C. (moldman)
- Mike Meier (mykon)
- Pedro Miguel Maymone de Resende (pedroresende)
@@ -1306,6 +1313,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Kagan Balga (kagan-balga)
- Nikita Nefedov (nikita2206)
- Alex Bacart
+ - StefanoTarditi
- cgonzalez
- hugovms
- Ben
@@ -1342,6 +1350,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Francisco Alvarez (sormes)
- Martin Parsiegla (spea)
- Maxim Tugaev (tugmaks)
+ - ywisax
- Manuel Alejandro Paz Cetina
- Denis Charrier (brucewouaigne)
- Youssef Benhssaien (moghreb)
@@ -1418,6 +1427,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Jason Woods
- mwsaz
- bogdan
+ - wanxiangchwng
- Geert De Deckere
- grizlik
- Derek ROTH
@@ -1432,7 +1442,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Dmytro Boiko (eagle)
- Shin Ohno (ganchiku)
- Matthieu Mota (matthieumota)
- - Maxime Pinot (maximepinot)
- Jean-Baptiste GOMOND (mjbgo)
- Jakub Podhorsky (podhy)
- abdul malik ikhsan (samsonasik)
@@ -1447,7 +1456,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Morten Wulff (wulff)
- Kieran
- Don Pinkster
- - Jonas Elfering
- Maksim Muruev
- Emil Einarsson
- 243083df
@@ -1600,7 +1608,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Grégoire Hébert (gregoirehebert)
- Franz Wilding (killerpoke)
- Ferenczi Krisztian (fchris82)
- - Ioan Ovidiu Enache (ionutenache)
- Artyum Petrov
- Oleg Golovakhin (doc_tr)
- Guillaume Smolders (guillaumesmo)
@@ -1624,6 +1631,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Luciano Mammino (loige)
- LHommet Nicolas (nicolaslh)
- fabios
+ - eRIZ
- Sander Coolen (scoolen)
- Vic D'Elfant (vicdelfant)
- Amirreza Shafaat (amirrezashafaat)
@@ -1779,6 +1787,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Evgeny Anisiforov
- otsch
- TristanPouliquen
+ - Dominic Luidold
- Piotr Antosik (antek88)
- Nacho Martin (nacmartin)
- Thibaut Chieux
@@ -1824,6 +1833,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Claus Due (namelesscoder)
- Christian
- Alexandru Patranescu
+ - Sébastien Lévêque (legenyes)
- ju1ius
- Denis Golubovskiy (bukashk0zzz)
- Arkadiusz Rzadkowolski (flies)
@@ -2034,6 +2044,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Vladimir Mantulo (mantulo)
- Boullé William (williamboulle)
- Jesper Noordsij
+ - Bart Baaten
- Frederic Godfrin
- Paul Matthews
- aim8604
@@ -2068,6 +2079,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Dalibor Karlović
- Cesar Scur (cesarscur)
- Cyril Vermandé (cyve)
+ - Daniele Orru' (danydev)
- Raul Garcia Canet (juagarc4)
- Sagrario Meneses
- Dmitri Petmanson
@@ -2161,6 +2173,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Maxime THIRY
- Norman Soetbeer
- Ludek Stepan
+ - Benjamin BOUDIER
- Frederik Schwan
- Mark van den Berg
- Aaron Stephens (astephens)
@@ -2191,6 +2204,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Harald Tollefsen
- PabloKowalczyk
- Matthieu
+ - ZiYao54
- Arend-Jan Tetteroo
- Albin Kerouaton
- Sébastien HOUZÉ
@@ -2250,6 +2264,7 @@ The Symfony Connect username in parenthesis allows to get more information
- George Giannoulopoulos
- Alexander Pasichnik (alex_brizzz)
- Florian Merle (florian-merle)
+ - Felix Eymonot (hyanda)
- Luis Ramirez (luisdeimos)
- Ilia Sergunin (maranqz)
- Daniel Richter (richtermeister)
@@ -2276,6 +2291,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Frank Neff (fneff)
- Volodymyr Kupriienko (greeflas)
- Ilya Biryukov (ibiryukov)
+ - Mathieu Ledru (matyo91)
- Roma (memphys)
- Florian Caron (shalalalala)
- Serhiy Lunak (slunak)
@@ -2374,6 +2390,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Ivan Tse
- René Kerner
- Nathaniel Catchpole
+ - Igor Plantaš
- upchuk
- Adrien Samson (adriensamson)
- Samuel Gordalina (gordalina)
@@ -2381,7 +2398,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Nicolas Eeckeloo (neeckeloo)
- Andriy Prokopenko (sleepyboy)
- Dariusz Ruminski
- - Bálint Szekeres
- Starfox64
- Ivo Valchev
- Thomas Hanke
@@ -2394,9 +2410,11 @@ The Symfony Connect username in parenthesis allows to get more information
- Wojciech Gorczyca
- Neagu Cristian-Doru (cristian-neagu)
- Mathieu Morlon (glutamatt)
+ - NIRAV MUKUNDBHAI PATEL (niravpatel919)
- Owen Gray (otis)
- Rafał Muszyński (rafmus90)
- Sébastien Decrême (sebdec)
+ - Wu (wu-agriconomie)
- Timothy Anido (xanido)
- Robert-Jan de Dreu
- Mara Blaga
@@ -2481,6 +2499,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Jeremiah VALERIE
- Alexandre Beaujour
- Franck Ranaivo-Harisoa
+ - Grégoire Rabasse
- Cas van Dongen
- Patrik Patie Gmitter
- George Yiannoulopoulos
@@ -2560,6 +2579,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Tobias Genberg (lorceroth)
- Michael Simonson (mikes)
- Nicolas Badey (nico-b)
+ - Florent Blaison (orkin)
- Olivier Scherler (oscherler)
- Flo Gleixner (redflo)
- Romain Jacquart (romainjacquart)
@@ -2599,7 +2619,6 @@ The Symfony Connect username in parenthesis allows to get more information
- tpetry
- JustDylan23
- Juraj Surman
- - ywisax
- Martin Eckhardt
- natechicago
- Victor
@@ -2746,6 +2765,7 @@ The Symfony Connect username in parenthesis allows to get more information
- botbotbot
- tatankat
- Cláudio Cesar
+ - Sven Nolting
- Timon van der Vorm
- nuncanada
- Thierry Marianne
@@ -3158,6 +3178,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Vlad Dumitrache
- wetternest
- Erik van Wingerden
+ - matlec
- Valouleloup
- Pathpat
- Jaymin G
@@ -3302,6 +3323,7 @@ The Symfony Connect username in parenthesis allows to get more information
- dasmfm
- Claas Augner
- Mathias Geat
+ - neodevcode
- Angel Fernando Quiroz Campos (angelfqc)
- Arnaud Buathier (arnapou)
- Curtis (ccorliss)
@@ -3347,6 +3369,7 @@ The Symfony Connect username in parenthesis allows to get more information
- jersoe
- Brian Debuire
- Eric Grimois
+ - Christian Schiffler
- Piers Warmers
- Sylvain Lorinet
- klyk50
@@ -3393,6 +3416,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Menno Holtkamp
- Ser5
- Michael Hudson-Doyle
+ - Matthew Burns
- Daniel Bannert
- Karim Miladi
- Michael Genereux
@@ -3438,6 +3462,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Kaipi Yann
- wiseguy1394
- adam-mospan
+ - AUDUL
- Steve Hyde
- AbdelatifAitBara
- nerdgod
@@ -3771,6 +3796,7 @@ The Symfony Connect username in parenthesis allows to get more information
- damaya
- Kevin Weber
- Alexandru Năstase
+ - Carl Julian Sauter
- Dionysis Arvanitis
- Sergey Fedotov
- Konstantin Scheumann
@@ -3801,7 +3827,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Courcier Marvin (helyakin)
- Henne Van Och (hennevo)
- Jeroen De Dauw (jeroendedauw)
- - Maxime COLIN (maximecolin)
- Muharrem Demirci (mdemirci)
- Evgeny Z (meze)
- Aleksandar Dimitrov (netbull)
diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php
index e4831557f01db..8e10891b0ba74 100644
--- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php
+++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php
@@ -27,15 +27,17 @@ class DoctrineTransactionMiddleware extends AbstractDoctrineMiddleware
protected function handleForManager(EntityManagerInterface $entityManager, Envelope $envelope, StackInterface $stack): Envelope
{
$entityManager->getConnection()->beginTransaction();
+
+ $success = false;
try {
$envelope = $stack->next()->handle($envelope, $stack);
$entityManager->flush();
$entityManager->getConnection()->commit();
+ $success = true;
+
return $envelope;
} catch (\Throwable $exception) {
- $entityManager->getConnection()->rollBack();
-
if ($exception instanceof HandlerFailedException) {
// Remove all HandledStamp from the envelope so the retry will execute all handlers again.
// When a handler fails, the queries of allegedly successful previous handlers just got rolled back.
@@ -43,6 +45,12 @@ protected function handleForManager(EntityManagerInterface $entityManager, Envel
}
throw $exception;
+ } finally {
+ $connection = $entityManager->getConnection();
+
+ if (!$success && $connection->isTransactionActive()) {
+ $connection->rollBack();
+ }
}
}
}
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php
index 977f32e30fa61..05e5dae1b34ac 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php
+++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php
@@ -56,12 +56,9 @@ public function testMiddlewareWrapsInTransactionAndFlushes()
public function testTransactionIsRolledBackOnException()
{
- $this->connection->expects($this->once())
- ->method('beginTransaction')
- ;
- $this->connection->expects($this->once())
- ->method('rollBack')
- ;
+ $this->connection->expects($this->once())->method('beginTransaction');
+ $this->connection->expects($this->once())->method('isTransactionActive')->willReturn(true);
+ $this->connection->expects($this->once())->method('rollBack');
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Thrown from next middleware.');
@@ -69,6 +66,27 @@ public function testTransactionIsRolledBackOnException()
$this->middleware->handle(new Envelope(new \stdClass()), $this->getThrowingStackMock());
}
+ public function testExceptionInRollBackDoesNotHidePreviousException()
+ {
+ $this->connection->expects($this->once())->method('beginTransaction');
+ $this->connection->expects($this->once())->method('isTransactionActive')->willReturn(true);
+ $this->connection->expects($this->once())->method('rollBack')->willThrowException(new \RuntimeException('Thrown from rollBack.'));
+
+ try {
+ $this->middleware->handle(new Envelope(new \stdClass()), $this->getThrowingStackMock());
+ } catch (\Throwable $exception) {
+ }
+
+ self::assertNotNull($exception);
+ self::assertInstanceOf(\RuntimeException::class, $exception);
+ self::assertSame('Thrown from rollBack.', $exception->getMessage());
+
+ $previous = $exception->getPrevious();
+ self::assertNotNull($previous);
+ self::assertInstanceOf(\RuntimeException::class, $previous);
+ self::assertSame('Thrown from next middleware.', $previous->getMessage());
+ }
+
public function testInvalidEntityManagerThrowsException()
{
$managerRegistry = $this->createMock(ManagerRegistry::class);
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php
index 9b7eb0b1165c8..a7057fda57d88 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php
@@ -81,7 +81,7 @@ public function testGenerateFragmentUri()
]);
$twig->addRuntimeLoader($loader);
- $this->assertSame('/_fragment?_hash=XCg0hX8QzSwik8Xuu9aMXhoCeI4oJOob7lUVacyOtyY%3D&_path=template%3Dfoo.html.twig%26_format%3Dhtml%26_locale%3Den%26_controller%3DSymfony%255CBundle%255CFrameworkBundle%255CController%255CTemplateController%253A%253AtemplateAction', $twig->render('index'));
+ $this->assertMatchesRegularExpression('#/_fragment\?_hash=.+&_path=template%3Dfoo.html.twig%26_format%3Dhtml%26_locale%3Den%26_controller%3DSymfony%255CBundle%255CFrameworkBundle%255CController%255CTemplateController%253A%253AtemplateAction$#', $twig->render('index'));
}
protected function getFragmentHandler($returnOrException): FragmentHandler
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php
index 96f707cdfdf2c..f6dd5f623baee 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php
@@ -206,6 +206,68 @@ public function testDefaultTranslationDomainWithNamedArguments()
$this->assertEquals('foo (custom)foo (foo)foo (custom)foo (custom)foo (fr)foo (custom)foo (fr)', trim($template->render([])));
}
+ public function testDefaultTranslationDomainWithExpression()
+ {
+ $templates = [
+ 'index' => '
+ {%- extends "base" %}
+
+ {%- trans_default_domain custom_domain %}
+
+ {%- block content %}
+ {{- "foo"|trans }}
+ {%- endblock %}
+ ',
+
+ 'base' => '
+ {%- block content "" %}
+ ',
+ ];
+
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foo (messages)'], 'en');
+ $translator->addResource('array', ['foo' => 'foo (custom)'], 'en', 'custom');
+ $translator->addResource('array', ['foo' => 'foo (foo)'], 'en', 'foo');
+
+ $template = $this->getTemplate($templates, $translator);
+
+ $this->assertEquals('foo (foo)', trim($template->render(['custom_domain' => 'foo'])));
+ }
+
+ public function testDefaultTranslationDomainWithExpressionAndInheritance()
+ {
+ $templates = [
+ 'index' => '
+ {%- extends "base" %}
+
+ {%- trans_default_domain foo_domain %}
+
+ {%- block content %}
+ {{- "foo"|trans }}
+ {%- endblock %}
+ ',
+
+ 'base' => '
+ {%- trans_default_domain custom_domain %}
+
+ {{- "foo"|trans }}
+ {%- block content "" %}
+ {{- "foo"|trans }}
+ ',
+ ];
+
+ $translator = new Translator('en');
+ $translator->addLoader('array', new ArrayLoader());
+ $translator->addResource('array', ['foo' => 'foo (messages)'], 'en');
+ $translator->addResource('array', ['foo' => 'foo (custom)'], 'en', 'custom');
+ $translator->addResource('array', ['foo' => 'foo (foo)'], 'en', 'foo');
+
+ $template = $this->getTemplate($templates, $translator);
+
+ $this->assertEquals('foo (custom)foo (foo)foo (custom)', trim($template->render(['foo_domain' => 'foo', 'custom_domain' => 'custom'])));
+ }
+
private function getTemplate($template, ?TranslatorInterface $translator = null): TemplateWrapper
{
$translator ??= new Translator('en');
diff --git a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php
index 5c2bacf19d5f8..47ec58acb36cb 100644
--- a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php
@@ -22,6 +22,7 @@
use Twig\Node\Expression\ConditionalExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\NameExpression;
+use Twig\Node\Expression\Ternary\ConditionalTernary;
use Twig\Node\Expression\Variable\ContextVariable;
use Twig\Node\Node;
use Twig\Node\Nodes;
@@ -308,32 +309,32 @@ public function testCompileLabelWithLabelAndAttributes()
public function testCompileLabelWithLabelThatEvaluatesToNull()
{
+ if (class_exists(ConditionalTernary::class)) {
+ $conditional = new ConditionalTernary(
+ // if
+ new ConstantExpression(true, 0),
+ // then
+ new ConstantExpression(null, 0),
+ // else
+ new ConstantExpression(null, 0),
+ 0
+ );
+ } else {
+ $conditional = new ConditionalExpression(
+ // if
+ new ConstantExpression(true, 0),
+ // then
+ new ConstantExpression(null, 0),
+ // else
+ new ConstantExpression(null, 0),
+ 0
+ );
+ }
+
if (class_exists(Nodes::class)) {
- $arguments = new Nodes([
- new ContextVariable('form', 0),
- new ConditionalExpression(
- // if
- new ConstantExpression(true, 0),
- // then
- new ConstantExpression(null, 0),
- // else
- new ConstantExpression(null, 0),
- 0
- ),
- ]);
+ $arguments = new Nodes([new ContextVariable('form', 0), $conditional]);
} else {
- $arguments = new Node([
- new NameExpression('form', 0),
- new ConditionalExpression(
- // if
- new ConstantExpression(true, 0),
- // then
- new ConstantExpression(null, 0),
- // else
- new ConstantExpression(null, 0),
- 0
- ),
- ]);
+ $arguments = new Node([new NameExpression('form', 0), $conditional]);
}
if (class_exists(FirstClassTwigCallableReady::class)) {
@@ -359,18 +360,32 @@ public function testCompileLabelWithLabelThatEvaluatesToNull()
public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes()
{
+ if (class_exists(ConditionalTernary::class)) {
+ $conditional = new ConditionalTernary(
+ // if
+ new ConstantExpression(true, 0),
+ // then
+ new ConstantExpression(null, 0),
+ // else
+ new ConstantExpression(null, 0),
+ 0
+ );
+ } else {
+ $conditional = new ConditionalExpression(
+ // if
+ new ConstantExpression(true, 0),
+ // then
+ new ConstantExpression(null, 0),
+ // else
+ new ConstantExpression(null, 0),
+ 0
+ );
+ }
+
if (class_exists(Nodes::class)) {
$arguments = new Nodes([
new ContextVariable('form', 0),
- new ConditionalExpression(
- // if
- new ConstantExpression(true, 0),
- // then
- new ConstantExpression(null, 0),
- // else
- new ConstantExpression(null, 0),
- 0
- ),
+ $conditional,
new ArrayExpression([
new ConstantExpression('foo', 0),
new ConstantExpression('bar', 0),
@@ -381,12 +396,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes()
} else {
$arguments = new Node([
new NameExpression('form', 0),
- new ConditionalExpression(
- new ConstantExpression(true, 0),
- new ConstantExpression(null, 0),
- new ConstantExpression(null, 0),
- 0
- ),
+ $conditional,
new ArrayExpression([
new ConstantExpression('foo', 0),
new ConstantExpression('bar', 0),
diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php
index 91e7e33491d97..0ffe6a949d472 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php
@@ -64,6 +64,10 @@ public function __construct(TranslationWriterInterface $writer, TranslationReade
{
parent::__construct();
+ if (!method_exists($writer, 'getFormats')) {
+ throw new \InvalidArgumentException(sprintf('The writer class "%s" does not implement the "getFormats()" method.', $writer::class));
+ }
+
$this->writer = $writer;
$this->reader = $reader;
$this->extractor = $extractor;
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslationUpdateCommandPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslationUpdateCommandPass.php
new file mode 100644
index 0000000000000..7542191d0e83e
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TranslationUpdateCommandPass.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+class TranslationUpdateCommandPass implements CompilerPassInterface
+{
+ public function process(ContainerBuilder $container): void
+ {
+ if (!$container->hasDefinition('console.command.translation_extract')) {
+ return;
+ }
+
+ $translationWriterClass = $container->getParameterBag()->resolveValue($container->findDefinition('translation.writer')->getClass());
+
+ if (!method_exists($translationWriterClass, 'getFormats')) {
+ $container->removeDefinition('console.command.translation_extract');
+ }
+ }
+}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 83518061fed36..aed6bdefa2c6f 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -122,6 +122,8 @@
use Symfony\Component\Mime\MimeTypeGuesserInterface;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Notifier\Bridge as NotifierBridge;
+use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory;
+use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory;
use Symfony\Component\Notifier\ChatterInterface;
use Symfony\Component\Notifier\Notifier;
use Symfony\Component\Notifier\Recipient\Recipient;
@@ -2765,7 +2767,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
$container->removeDefinition('notifier.channel.email');
}
- foreach (['texter', 'chatter', 'notifier.channel.chat', 'notifier.channel.email', 'notifier.channel.sms'] as $serviceId) {
+ foreach (['texter', 'chatter', 'notifier.channel.chat', 'notifier.channel.email', 'notifier.channel.sms', 'notifier.channel.push'] as $serviceId) {
if (!$container->hasDefinition($serviceId)) {
continue;
}
@@ -2812,8 +2814,6 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
NotifierBridge\Engagespot\EngagespotTransportFactory::class => 'notifier.transport_factory.engagespot',
NotifierBridge\Esendex\EsendexTransportFactory::class => 'notifier.transport_factory.esendex',
NotifierBridge\Expo\ExpoTransportFactory::class => 'notifier.transport_factory.expo',
- NotifierBridge\FakeChat\FakeChatTransportFactory::class => 'notifier.transport_factory.fake-chat',
- NotifierBridge\FakeSms\FakeSmsTransportFactory::class => 'notifier.transport_factory.fake-sms',
NotifierBridge\Firebase\FirebaseTransportFactory::class => 'notifier.transport_factory.firebase',
NotifierBridge\FortySixElks\FortySixElksTransportFactory::class => 'notifier.transport_factory.forty-six-elks',
NotifierBridge\FreeMobile\FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile',
@@ -2891,20 +2891,26 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
$container->removeDefinition($classToServices[NotifierBridge\Mercure\MercureTransportFactory::class]);
}
- if (ContainerBuilder::willBeAvailable('symfony/fake-chat-notifier', NotifierBridge\FakeChat\FakeChatTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'])) {
- $container->getDefinition($classToServices[NotifierBridge\FakeChat\FakeChatTransportFactory::class])
- ->replaceArgument(0, new Reference('mailer'))
- ->replaceArgument(1, new Reference('logger'))
+ // don't use ContainerBuilder::willBeAvailable() as these are not needed in production
+ if (class_exists(FakeChatTransportFactory::class)) {
+ $container->getDefinition('notifier.transport_factory.fake-chat')
+ ->replaceArgument(0, new Reference('mailer', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
+ ->replaceArgument(1, new Reference('logger', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
->addArgument(new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE));
+ } else {
+ $container->removeDefinition('notifier.transport_factory.fake-chat');
}
- if (ContainerBuilder::willBeAvailable('symfony/fake-sms-notifier', NotifierBridge\FakeSms\FakeSmsTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'])) {
- $container->getDefinition($classToServices[NotifierBridge\FakeSms\FakeSmsTransportFactory::class])
- ->replaceArgument(0, new Reference('mailer'))
- ->replaceArgument(1, new Reference('logger'))
+ // don't use ContainerBuilder::willBeAvailable() as these are not needed in production
+ if (class_exists(FakeSmsTransportFactory::class)) {
+ $container->getDefinition('notifier.transport_factory.fake-sms')
+ ->replaceArgument(0, new Reference('mailer', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
+ ->replaceArgument(1, new Reference('logger', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
->addArgument(new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE));
+ } else {
+ $container->removeDefinition('notifier.transport_factory.fake-sms');
}
if (isset($config['admin_recipients'])) {
diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
index 0da10da9d77f8..c371d10dbc684 100644
--- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
@@ -20,6 +20,7 @@
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RemoveUnusedSessionMarshallingHandlerPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass;
+use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationUpdateCommandPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\VirtualRequestStackPass;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
@@ -193,6 +194,7 @@ public function build(ContainerBuilder $container)
// must be registered after MonologBundle's LoggerChannelPass
$container->addCompilerPass(new ErrorLoggerCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
$container->addCompilerPass(new VirtualRequestStackPass());
+ $container->addCompilerPass(new TranslationUpdateCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
if ($container->getParameter('kernel.debug')) {
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 2);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php
index 6ce674148a878..bcc1248208c61 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php
@@ -73,7 +73,10 @@
->tag('notifier.channel', ['channel' => 'email'])
->set('notifier.channel.push', PushChannel::class)
- ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()])
+ ->args([
+ service('texter.transports'),
+ abstract_arg('message bus'),
+ ])
->tag('notifier.channel', ['channel' => 'push'])
->set('notifier.monolog_handler', NotifierHandler::class)
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php
index 6d8966a171ba2..48d5c327a3986 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php
@@ -50,6 +50,6 @@ public function testGenerateFragmentUri()
$client = self::createClient(['test_case' => 'Fragment', 'root_config' => 'config.yml', 'debug' => true]);
$client->request('GET', '/fragment_uri');
- $this->assertSame('/_fragment?_hash=CCRGN2D%2FoAJbeGz%2F%2FdoH3bNSPwLCrmwC1zAYCGIKJ0E%3D&_path=_format%3Dhtml%26_locale%3Den%26_controller%3DSymfony%255CBundle%255CFrameworkBundle%255CTests%255CFunctional%255CBundle%255CTestBundle%255CController%255CFragmentController%253A%253AindexAction', $client->getResponse()->getContent());
+ $this->assertMatchesRegularExpression('#/_fragment\?_hash=.+&_path=_format%3Dhtml%26_locale%3Den%26_controller%3DSymfony%255CBundle%255CFrameworkBundle%255CTests%255CFunctional%255CBundle%255CTestBundle%255CController%255CFragmentController%253A%253AindexAction$#', $client->getResponse()->getContent());
}
}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.css.twig
index af9f0a4ceaba3..55589c2945d88 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.css.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.css.twig
@@ -40,6 +40,7 @@
#source .source-content ol li {
margin: 0 0 2px 0;
padding-left: 5px;
+ white-space: preserve nowrap;
}
#source .source-content ol li::marker {
color: var(--color-muted);
diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php
index b406292b44b3a..23157e3c7b2db 100644
--- a/src/Symfony/Component/Console/Helper/ProgressBar.php
+++ b/src/Symfony/Component/Console/Helper/ProgressBar.php
@@ -229,7 +229,7 @@ public function getEstimated(): float
public function getRemaining(): float
{
- if (!$this->step) {
+ if (0 === $this->step || $this->step === $this->startingStep) {
return 0;
}
diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
index a5a0eca245080..a1db94583db49 100644
--- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
@@ -110,6 +110,16 @@ public function testRegularTimeEstimation()
);
}
+ public function testRegularTimeRemainingWithDifferentStartAtAndCustomDisplay()
+ {
+ $this->expectNotToPerformAssertions();
+
+ ProgressBar::setFormatDefinition('custom', ' %current%/%max% [%bar%] %percent:3s%% %remaining% %estimated%');
+ $bar = new ProgressBar($this->getOutputStream(), 1_200, 0);
+ $bar->setFormat('custom');
+ $bar->start(1_200, 600);
+ }
+
public function testResumedTimeEstimation()
{
$bar = new ProgressBar($output = $this->getOutputStream(), 1_200, 0);
diff --git a/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt b/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt
index cfb10d03dafdd..81becafd8e350 100644
--- a/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt
+++ b/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt
@@ -24,7 +24,7 @@ var_dump([
$eHandler[0]->setExceptionHandler('print_r');
if (true) {
- class Broken implements \JsonSerializable
+ class Broken implements \Iterator
{
}
}
@@ -37,14 +37,14 @@ array(1) {
}
object(Symfony\Component\ErrorHandler\Error\FatalError)#%d (%d) {
["message":protected]=>
- string(186) "Error: Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
+ string(209) "Error: Class Symfony\Component\ErrorHandler\Broken contains 5 abstract methods and must therefore be declared abstract or implement the remaining methods (Iterator::current, Iterator::next, Iterator::key, ...)"
%a
["error":"Symfony\Component\ErrorHandler\Error\FatalError":private]=>
array(4) {
["type"]=>
int(1)
["message"]=>
- string(179) "Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
+ string(202) "Class Symfony\Component\ErrorHandler\Broken contains 5 abstract methods and must therefore be declared abstract or implement the remaining methods (Iterator::current, Iterator::next, Iterator::key, ...)"
["file"]=>
string(%d) "%s"
["line"]=>
diff --git a/src/Symfony/Component/Finder/Comparator/DateComparator.php b/src/Symfony/Component/Finder/Comparator/DateComparator.php
index e0c523d05523b..f7c27de677fb1 100644
--- a/src/Symfony/Component/Finder/Comparator/DateComparator.php
+++ b/src/Symfony/Component/Finder/Comparator/DateComparator.php
@@ -36,7 +36,7 @@ public function __construct(string $test)
throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
}
- $operator = $matches[1] ?? '==';
+ $operator = $matches[1] ?: '==';
if ('since' === $operator || 'after' === $operator) {
$operator = '>';
}
diff --git a/src/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php b/src/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php
index 47bcc4838bd26..e50b713062638 100644
--- a/src/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php
+++ b/src/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php
@@ -59,6 +59,7 @@ public static function getTestData()
['after 2005-10-10', [strtotime('2005-10-15')], [strtotime('2005-10-09')]],
['since 2005-10-10', [strtotime('2005-10-15')], [strtotime('2005-10-09')]],
['!= 2005-10-10', [strtotime('2005-10-11')], [strtotime('2005-10-10')]],
+ ['2005-10-10', [strtotime('2005-10-10')], [strtotime('2005-10-11')]],
];
}
}
diff --git a/src/Symfony/Component/HtmlSanitizer/Tests/TextSanitizer/UrlSanitizerTest.php b/src/Symfony/Component/HtmlSanitizer/Tests/TextSanitizer/UrlSanitizerTest.php
index fe0e0d39cd9d9..c00b8f7dfbfe5 100644
--- a/src/Symfony/Component/HtmlSanitizer/Tests/TextSanitizer/UrlSanitizerTest.php
+++ b/src/Symfony/Component/HtmlSanitizer/Tests/TextSanitizer/UrlSanitizerTest.php
@@ -358,10 +358,10 @@ public static function provideParse(): iterable
'non-special://:@untrusted.com/x' => ['scheme' => 'non-special', 'host' => 'untrusted.com'],
'http:foo.com' => ['scheme' => 'http', 'host' => null],
" :foo.com \n" => null,
- ' foo.com ' => ['scheme' => null, 'host' => null],
+ ' foo.com ' => null,
'a: foo.com' => null,
- 'http://f:21/ b ? d # e ' => ['scheme' => 'http', 'host' => 'f'],
- 'lolscheme:x x#x x' => ['scheme' => 'lolscheme', 'host' => null],
+ 'http://f:21/ b ? d # e ' => null,
+ 'lolscheme:x x#x x' => null,
'http://f:/c' => ['scheme' => 'http', 'host' => 'f'],
'http://f:0/c' => ['scheme' => 'http', 'host' => 'f'],
'http://f:00000000000000/c' => ['scheme' => 'http', 'host' => 'f'],
@@ -434,7 +434,7 @@ public static function provideParse(): iterable
'javascript:example.com/' => ['scheme' => 'javascript', 'host' => null],
'mailto:example.com/' => ['scheme' => 'mailto', 'host' => null],
'/a/b/c' => ['scheme' => null, 'host' => null],
- '/a/ /c' => ['scheme' => null, 'host' => null],
+ '/a/ /c' => null,
'/a%2fc' => ['scheme' => null, 'host' => null],
'/a/%2f/c' => ['scheme' => null, 'host' => null],
'#β' => ['scheme' => null, 'host' => null],
@@ -495,10 +495,10 @@ public static function provideParse(): iterable
'http://example.com/你好你好' => ['scheme' => 'http', 'host' => 'example.com'],
'http://example.com/‥/foo' => ['scheme' => 'http', 'host' => 'example.com'],
"http://example.com/\u{feff}/foo" => ['scheme' => 'http', 'host' => 'example.com'],
- "http://example.com\u{002f}\u{202e}\u{002f}\u{0066}\u{006f}\u{006f}\u{002f}\u{202d}\u{002f}\u{0062}\u{0061}\u{0072}\u{0027}\u{0020}" => ['scheme' => 'http', 'host' => 'example.com'],
+ "http://example.com\u{002f}\u{202e}\u{002f}\u{0066}\u{006f}\u{006f}\u{002f}\u{202d}\u{002f}\u{0062}\u{0061}\u{0072}\u{0027}\u{0020}" => null,
'http://www.google.com/foo?bar=baz#' => ['scheme' => 'http', 'host' => 'www.google.com'],
- 'http://www.google.com/foo?bar=baz# »' => ['scheme' => 'http', 'host' => 'www.google.com'],
- 'data:test# »' => ['scheme' => 'data', 'host' => null],
+ 'http://www.google.com/foo?bar=baz# »' => null,
+ 'data:test# »' => null,
'http://www.google.com' => ['scheme' => 'http', 'host' => 'www.google.com'],
'http://192.0x00A80001' => ['scheme' => 'http', 'host' => '192.0x00A80001'],
'http://www/foo%2Ehtml' => ['scheme' => 'http', 'host' => 'www'],
@@ -706,11 +706,11 @@ public static function provideParse(): iterable
'test-a-colon-slash-slash-b.html' => ['scheme' => null, 'host' => null],
'http://example.org/test?a#bc' => ['scheme' => 'http', 'host' => 'example.org'],
'http:\\/\\/f:b\\/c' => ['scheme' => 'http', 'host' => null],
- 'http:\\/\\/f: \\/c' => ['scheme' => 'http', 'host' => null],
+ 'http:\\/\\/f: \\/c' => null,
'http:\\/\\/f:fifty-two\\/c' => ['scheme' => 'http', 'host' => null],
'http:\\/\\/f:999999\\/c' => ['scheme' => 'http', 'host' => null],
'non-special:\\/\\/f:999999\\/c' => ['scheme' => 'non-special', 'host' => null],
- 'http:\\/\\/f: 21 \\/ b ? d # e ' => ['scheme' => 'http', 'host' => null],
+ 'http:\\/\\/f: 21 \\/ b ? d # e ' => null,
'http:\\/\\/[1::2]:3:4' => ['scheme' => 'http', 'host' => null],
'http:\\/\\/2001::1' => ['scheme' => 'http', 'host' => null],
'http:\\/\\/2001::1]' => ['scheme' => 'http', 'host' => null],
@@ -734,8 +734,8 @@ public static function provideParse(): iterable
'http:@:www.example.com' => ['scheme' => 'http', 'host' => null],
'http:\\/@:www.example.com' => ['scheme' => 'http', 'host' => null],
'http:\\/\\/@:www.example.com' => ['scheme' => 'http', 'host' => null],
- 'http:\\/\\/example example.com' => ['scheme' => 'http', 'host' => null],
- 'http:\\/\\/Goo%20 goo%7C|.com' => ['scheme' => 'http', 'host' => null],
+ 'http:\\/\\/example example.com' => null,
+ 'http:\\/\\/Goo%20 goo%7C|.com' => null,
'http:\\/\\/[]' => ['scheme' => 'http', 'host' => null],
'http:\\/\\/[:]' => ['scheme' => 'http', 'host' => null],
'http:\\/\\/GOO\\u00a0\\u3000goo.com' => ['scheme' => 'http', 'host' => null],
@@ -752,8 +752,8 @@ public static function provideParse(): iterable
'http:\\/\\/hello%00' => ['scheme' => 'http', 'host' => null],
'http:\\/\\/192.168.0.257' => ['scheme' => 'http', 'host' => null],
'http:\\/\\/%3g%78%63%30%2e%30%32%35%30%2E.01' => ['scheme' => 'http', 'host' => null],
- 'http:\\/\\/192.168.0.1 hello' => ['scheme' => 'http', 'host' => null],
- 'https:\\/\\/x x:12' => ['scheme' => 'https', 'host' => null],
+ 'http:\\/\\/192.168.0.1 hello' => null,
+ 'https:\\/\\/x x:12' => null,
'http:\\/\\/[www.google.com]\\/' => ['scheme' => 'http', 'host' => null],
'http:\\/\\/[google.com]' => ['scheme' => 'http', 'host' => null],
'http:\\/\\/[::1.2.3.4x]' => ['scheme' => 'http', 'host' => null],
@@ -763,7 +763,7 @@ public static function provideParse(): iterable
'..\\/i' => ['scheme' => null, 'host' => null],
'\\/i' => ['scheme' => null, 'host' => null],
'sc:\\/\\/\\u0000\\/' => ['scheme' => 'sc', 'host' => null],
- 'sc:\\/\\/ \\/' => ['scheme' => 'sc', 'host' => null],
+ 'sc:\\/\\/ \\/' => null,
'sc:\\/\\/@\\/' => ['scheme' => 'sc', 'host' => null],
'sc:\\/\\/te@s:t@\\/' => ['scheme' => 'sc', 'host' => null],
'sc:\\/\\/:\\/' => ['scheme' => 'sc', 'host' => null],
diff --git a/src/Symfony/Component/HtmlSanitizer/TextSanitizer/UrlSanitizer.php b/src/Symfony/Component/HtmlSanitizer/TextSanitizer/UrlSanitizer.php
index a806981de770f..05d86ba15da8e 100644
--- a/src/Symfony/Component/HtmlSanitizer/TextSanitizer/UrlSanitizer.php
+++ b/src/Symfony/Component/HtmlSanitizer/TextSanitizer/UrlSanitizer.php
@@ -94,7 +94,13 @@ public static function parse(string $url): ?array
}
try {
- return UriString::parse($url);
+ $parsedUrl = UriString::parse($url);
+
+ if (preg_match('/\s/', $url)) {
+ return null;
+ }
+
+ return $parsedUrl;
} catch (SyntaxError) {
return null;
}
diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php
index 7d996200527eb..3e15bef74cc9e 100644
--- a/src/Symfony/Component/HttpClient/CurlHttpClient.php
+++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php
@@ -197,13 +197,12 @@ public function request(string $method, string $url, array $options = []): Respo
$curlopts[\CURLOPT_RESOLVE] = $resolve;
}
+ $curlopts[\CURLOPT_CUSTOMREQUEST] = $method;
if ('POST' === $method) {
// Use CURLOPT_POST to have browser-like POST-to-GET redirects for 301, 302 and 303
$curlopts[\CURLOPT_POST] = true;
} elseif ('HEAD' === $method) {
$curlopts[\CURLOPT_NOBODY] = true;
- } else {
- $curlopts[\CURLOPT_CUSTOMREQUEST] = $method;
}
if ('\\' !== \DIRECTORY_SEPARATOR && $options['timeout'] < 1) {
diff --git a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php
index b38b0178b90f2..4094f98806323 100644
--- a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php
+++ b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php
@@ -20,7 +20,6 @@
use Symfony\Contracts\HttpClient\ChunkInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
-use Symfony\Contracts\HttpClient\ResponseStreamInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
@@ -81,17 +80,6 @@ public function request(string $method, string $url, array $options = []): Respo
$ip = self::dnsResolve($dnsCache, $host, $this->ipFlags, $options);
self::ipCheck($ip, $this->subnets, $this->ipFlags, $host, $url);
- if (0 < $maxRedirects = $options['max_redirects']) {
- $options['max_redirects'] = 0;
- $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = $options['headers'];
-
- if (isset($options['normalized_headers']['host']) || isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) {
- $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) {
- return 0 !== stripos($h, 'Host:') && 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
- });
- }
- }
-
$onProgress = $options['on_progress'] ?? null;
$subnets = $this->subnets;
$ipFlags = $this->ipFlags;
@@ -99,7 +87,7 @@ public function request(string $method, string $url, array $options = []): Respo
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets, $ipFlags): void {
static $lastPrimaryIp = '';
- if (($info['primary_ip'] ?? '') !== $lastPrimaryIp) {
+ if (!\in_array($info['primary_ip'] ?? '', ['', $lastPrimaryIp], true)) {
self::ipCheck($info['primary_ip'], $subnets, $ipFlags, null, $info['url']);
$lastPrimaryIp = $info['primary_ip'];
}
@@ -107,6 +95,19 @@ public function request(string $method, string $url, array $options = []): Respo
null !== $onProgress && $onProgress($dlNow, $dlSize, $info);
};
+ if (0 >= $maxRedirects = $options['max_redirects']) {
+ return new AsyncResponse($this->client, $method, $url, $options);
+ }
+
+ $options['max_redirects'] = 0;
+ $redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = $options['headers'];
+
+ if (isset($options['normalized_headers']['host']) || isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) {
+ $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) {
+ return 0 !== stripos($h, 'Host:') && 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
+ });
+ }
+
return new AsyncResponse($this->client, $method, $url, $options, static function (ChunkInterface $chunk, AsyncContext $context) use (&$method, &$options, $maxRedirects, &$redirectHeaders, $subnets, $ipFlags, $dnsCache): \Generator {
if (null !== $chunk->getError() || $chunk->isTimeout() || !$chunk->isFirst()) {
yield $chunk;
@@ -137,7 +138,7 @@ public function request(string $method, string $url, array $options = []): Respo
$filterContentHeaders = static function ($h) {
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
};
- $options['header'] = array_filter($options['header'], $filterContentHeaders);
+ $options['headers'] = array_filter($options['headers'], $filterContentHeaders);
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
}
@@ -158,11 +159,6 @@ public function request(string $method, string $url, array $options = []): Respo
});
}
- public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface
- {
- return $this->client->stream($responses, $timeout);
- }
-
public function setLogger(LoggerInterface $logger): void
{
if ($this->client instanceof LoggerAwareInterface) {
diff --git a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php
index 91ec7ea5f2c7c..a7493100c431d 100644
--- a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php
@@ -24,11 +24,6 @@ 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/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php
index 3385323f75c67..79763bc1019f3 100644
--- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php
+++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php
@@ -518,6 +518,73 @@ public function testNoPrivateNetworkWithResolveAndRedirect()
$client->request('GET', 'http://localhost:8057/302?location=https://symfony.com/');
}
+ public function testNoPrivateNetwork304()
+ {
+ $client = $this->getHttpClient(__FUNCTION__);
+ $client = new NoPrivateNetworkHttpClient($client, '104.26.14.6/32');
+ $response = $client->request('GET', 'http://localhost:8057/304', [
+ 'headers' => ['If-Match' => '"abc"'],
+ 'buffer' => false,
+ ]);
+
+ $this->assertSame(304, $response->getStatusCode());
+ $this->assertSame('', $response->getContent(false));
+ }
+
+ public function testNoPrivateNetwork302()
+ {
+ $client = $this->getHttpClient(__FUNCTION__);
+ $client = new NoPrivateNetworkHttpClient($client, '104.26.14.6/32');
+ $response = $client->request('GET', 'http://localhost:8057/302/relative');
+
+ $body = $response->toArray();
+
+ $this->assertSame('/', $body['REQUEST_URI']);
+ $this->assertNull($response->getInfo('redirect_url'));
+
+ $response = $client->request('GET', 'http://localhost:8057/302/relative', [
+ 'max_redirects' => 0,
+ ]);
+
+ $this->assertSame(302, $response->getStatusCode());
+ $this->assertSame('http://localhost:8057/', $response->getInfo('redirect_url'));
+ }
+
+ public function testNoPrivateNetworkStream()
+ {
+ $client = $this->getHttpClient(__FUNCTION__);
+
+ $response = $client->request('GET', 'http://localhost:8057');
+ $client = new NoPrivateNetworkHttpClient($client, '104.26.14.6/32');
+
+ $response = $client->request('GET', 'http://localhost:8057');
+ $chunks = $client->stream($response);
+ $result = [];
+
+ foreach ($chunks as $r => $chunk) {
+ if ($chunk->isTimeout()) {
+ $result[] = 't';
+ } elseif ($chunk->isLast()) {
+ $result[] = 'l';
+ } elseif ($chunk->isFirst()) {
+ $result[] = 'f';
+ }
+ }
+
+ $this->assertSame($response, $r);
+ $this->assertSame(['f', 'l'], $result);
+
+ $chunk = null;
+ $i = 0;
+
+ foreach ($client->stream($response) as $chunk) {
+ ++$i;
+ }
+
+ $this->assertSame(1, $i);
+ $this->assertTrue($chunk->isLast());
+ }
+
public function testNoRedirectWithInvalidLocation()
{
$client = $this->getHttpClient(__FUNCTION__);
@@ -584,4 +651,48 @@ public function testDefaultContentType()
$this->assertSame(['abc' => 'def', 'content-type' => 'application/json', 'REQUEST_METHOD' => 'POST'], $response->toArray());
}
+
+ public function testHeadRequestWithClosureBody()
+ {
+ $p = TestHttpServer::start(8067);
+
+ try {
+ $client = $this->getHttpClient(__FUNCTION__);
+
+ $response = $client->request('HEAD', 'http://localhost:8057/head', [
+ 'body' => fn () => '',
+ ]);
+ $headers = $response->getHeaders();
+ } finally {
+ $p->stop();
+ }
+
+ $this->assertArrayHasKey('x-request-vars', $headers);
+
+ $vars = json_decode($headers['x-request-vars'][0], true);
+ $this->assertIsArray($vars);
+ $this->assertSame('HEAD', $vars['REQUEST_METHOD']);
+ }
+
+ /**
+ * @testWith [301]
+ * [302]
+ * [303]
+ */
+ public function testPostToGetRedirect(int $status)
+ {
+ $p = TestHttpServer::start(8067);
+
+ try {
+ $client = $this->getHttpClient(__FUNCTION__);
+
+ $response = $client->request('POST', 'http://localhost:8057/custom?status=' . $status . '&headers[]=Location%3A%20%2F');
+ $body = $response->toArray();
+ } finally {
+ $p->stop();
+ }
+
+ $this->assertSame('GET', $body['REQUEST_METHOD']);
+ $this->assertSame('/', $body['REQUEST_URI']);
+ }
}
diff --git a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php
index f5f6d8ddcf9be..b500c9548ebb0 100644
--- a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php
@@ -32,11 +32,6 @@ public static function setUpBeforeClass(): void
TestHttpServer::start();
}
- public static function tearDownAfterClass(): void
- {
- TestHttpServer::stop();
- }
-
/**
* @requires function ob_gzhandler
*/
diff --git a/src/Symfony/Component/HttpClient/Tests/NoPrivateNetworkHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/NoPrivateNetworkHttpClientTest.php
index fb940790b0b3f..06ffc128187cf 100644
--- a/src/Symfony/Component/HttpClient/Tests/NoPrivateNetworkHttpClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/NoPrivateNetworkHttpClientTest.php
@@ -173,6 +173,27 @@ public function testNonCallableOnProgressCallback()
$client->request('GET', $url, ['on_progress' => $customCallback]);
}
+ public function testHeadersArePassedOnRedirect()
+ {
+ $ipAddr = '104.26.14.6';
+ $url = sprintf('http://%s/', $ipAddr);
+ $content = 'foo';
+
+ $callback = function ($method, $url, $options) use ($content): MockResponse {
+ $this->assertArrayHasKey('headers', $options);
+ $this->assertNotContains('content-type: application/json', $options['headers']);
+ $this->assertContains('foo: bar', $options['headers']);
+ return new MockResponse($content);
+ };
+ $responses = [
+ new MockResponse('', ['http_code' => 302, 'redirect_url' => 'http://104.26.14.7']),
+ $callback,
+ ];
+ $client = new NoPrivateNetworkHttpClient(new MockHttpClient($responses));
+ $response = $client->request('POST', $url, ['headers' => ['foo' => 'bar', 'content-type' => 'application/json']]);
+ $this->assertEquals($content, $response->getContent());
+ }
+
private function getMockHttpClient(string $ipAddr, string $content)
{
return new MockHttpClient(new MockResponse($content, ['primary_ip' => $ipAddr]));
diff --git a/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php b/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php
index 65b7f5b3f6794..bf49535ae3e66 100644
--- a/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php
@@ -28,11 +28,6 @@ public static function setUpBeforeClass(): void
TestHttpServer::start();
}
- public static function tearDownAfterClass(): void
- {
- TestHttpServer::stop();
- }
-
/**
* @requires function ob_gzhandler
*/
diff --git a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php
index a0e39cc46c851..ba9504ae1c66d 100644
--- a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php
@@ -27,11 +27,6 @@
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 b6a2c03c8f7a3..cf437a653bd76 100644
--- a/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/TraceableHttpClientTest.php
@@ -29,11 +29,6 @@ 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 23437d5363f28..9c9ee14a4a3ff 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.4.3|^3.5.1",
+ "symfony/http-client-contracts": "~3.4.4|^3.5.2",
"symfony/service-contracts": "^2.5|^3"
},
"require-dev": {
diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php
index b104f861f92ee..f0f735da42524 100644
--- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php
+++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php
@@ -40,11 +40,9 @@
class RequestPayloadValueResolver implements ValueResolverInterface, EventSubscriberInterface
{
/**
- * @see \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT
* @see DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS
*/
private const CONTEXT_DENORMALIZE = [
- 'disable_type_enforcement' => true,
'collect_denormalization_errors' => true,
];
@@ -165,7 +163,7 @@ private function mapQueryString(Request $request, string $type, MapQueryString $
return null;
}
- return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
+ return $this->serializer->denormalize($data, $type, 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
}
private function mapRequestPayload(Request $request, string $type, MapRequestPayload $attribute): ?object
@@ -179,7 +177,7 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
}
if ($data = $request->request->all()) {
- return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
+ return $this->serializer->denormalize($data, $type, 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
}
if ('' === $data = $request->getContent()) {
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index e4d06fad61928..eb98942076da3 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.16';
- public const VERSION_ID = 60416;
+ public const VERSION = '6.4.17';
+ public const VERSION_ID = 60417;
public const MAJOR_VERSION = 6;
public const MINOR_VERSION = 4;
- public const RELEASE_VERSION = 16;
+ public const RELEASE_VERSION = 17;
public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '11/2026';
diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php
index 9b68c80ff27e5..1b4c20672ab46 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php
@@ -22,6 +22,7 @@
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\PropertyAccess\Exception\InvalidTypeException;
+use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
@@ -394,6 +395,38 @@ public function testQueryStringValidationPassed()
$this->assertEquals([$payload], $event->getArguments());
}
+ public function testQueryStringParameterTypeMismatch()
+ {
+ $query = ['price' => 'not a float'];
+
+ $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
+ $serializer = new Serializer([$normalizer], ['json' => new JsonEncoder()]);
+
+ $validator = $this->createMock(ValidatorInterface::class);
+ $validator->expects($this->never())->method('validate');
+
+ $resolver = new RequestPayloadValueResolver($serializer, $validator);
+
+ $argument = new ArgumentMetadata('invalid', RequestPayload::class, false, false, null, false, [
+ MapQueryString::class => new MapQueryString(),
+ ]);
+
+ $request = Request::create('/', 'GET', $query);
+
+ $kernel = $this->createMock(HttpKernelInterface::class);
+ $arguments = $resolver->resolve($request, $argument);
+ $event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
+
+ try {
+ $resolver->onKernelControllerArguments($event);
+ $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
+ } catch (HttpException $e) {
+ $validationFailedException = $e->getPrevious();
+ $this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
+ $this->assertSame('This value should be of type float.', $validationFailedException->getViolations()[0]->getMessage());
+ }
+ }
+
public function testRequestInputValidationPassed()
{
$input = ['price' => '50'];
@@ -422,6 +455,38 @@ public function testRequestInputValidationPassed()
$this->assertEquals([$payload], $event->getArguments());
}
+ public function testRequestInputTypeMismatch()
+ {
+ $input = ['price' => 'not a float'];
+
+ $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
+ $serializer = new Serializer([$normalizer], ['json' => new JsonEncoder()]);
+
+ $validator = $this->createMock(ValidatorInterface::class);
+ $validator->expects($this->never())->method('validate');
+
+ $resolver = new RequestPayloadValueResolver($serializer, $validator);
+
+ $argument = new ArgumentMetadata('invalid', RequestPayload::class, false, false, null, false, [
+ MapRequestPayload::class => new MapRequestPayload(),
+ ]);
+
+ $request = Request::create('/', 'POST', $input);
+
+ $kernel = $this->createMock(HttpKernelInterface::class);
+ $arguments = $resolver->resolve($request, $argument);
+ $event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
+
+ try {
+ $resolver->onKernelControllerArguments($event);
+ $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class));
+ } catch (HttpException $e) {
+ $validationFailedException = $e->getPrevious();
+ $this->assertInstanceOf(ValidationFailedException::class, $validationFailedException);
+ $this->assertSame('This value should be of type float.', $validationFailedException->getViolations()[0]->getMessage());
+ }
+ }
+
public function testItThrowsOnVariadicArgument()
{
$serializer = new Serializer();
diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php
index fa9885d2753cd..43c740ee12b98 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php
@@ -60,8 +60,8 @@ public function testRenderControllerReference()
$reference = new ControllerReference('main_controller', [], []);
$altReference = new ControllerReference('alt_controller', [], []);
- $this->assertEquals(
- '',
+ $this->assertMatchesRegularExpression(
+ '#^$#',
$strategy->render($reference, $request, ['alt' => $altReference])->getContent()
);
}
@@ -78,8 +78,8 @@ public function testRenderControllerReferenceWithAbsoluteUri()
$reference = new ControllerReference('main_controller', [], []);
$altReference = new ControllerReference('alt_controller', [], []);
- $this->assertSame(
- '',
+ $this->assertMatchesRegularExpression(
+ '#^$#',
$strategy->render($reference, $request, ['alt' => $altReference, 'absolute_uri' => true])->getContent()
);
}
diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/HIncludeFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/HIncludeFragmentRendererTest.php
index f74887ade36f4..8e4b59e5feeb9 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Fragment/HIncludeFragmentRendererTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/HIncludeFragmentRendererTest.php
@@ -32,7 +32,7 @@ public function testRenderWithControllerAndSigner()
{
$strategy = new HIncludeFragmentRenderer(null, new UriSigner('foo'));
- $this->assertEquals('', $strategy->render(new ControllerReference('main_controller', [], []), Request::create('/'))->getContent());
+ $this->assertMatchesRegularExpression('#^$#', $strategy->render(new ControllerReference('main_controller', [], []), Request::create('/'))->getContent());
}
public function testRenderWithUri()
diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/SsiFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/SsiFragmentRendererTest.php
index 4af00f9f75137..7fd04c5a5b0b7 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Fragment/SsiFragmentRendererTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/SsiFragmentRendererTest.php
@@ -51,8 +51,8 @@ public function testRenderControllerReference()
$reference = new ControllerReference('main_controller', [], []);
$altReference = new ControllerReference('alt_controller', [], []);
- $this->assertEquals(
- '',
+ $this->assertMatchesRegularExpression(
+ '{^$}',
$strategy->render($reference, $request, ['alt' => $altReference])->getContent()
);
}
@@ -69,8 +69,8 @@ public function testRenderControllerReferenceWithAbsoluteUri()
$reference = new ControllerReference('main_controller', [], []);
$altReference = new ControllerReference('alt_controller', [], []);
- $this->assertSame(
- '',
+ $this->assertMatchesRegularExpression(
+ '{^$}',
$strategy->render($reference, $request, ['alt' => $altReference, 'absolute_uri' => true])->getContent()
);
}
diff --git a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/ConnectionTest.php
index f4cc745846584..bf54ac1272483 100644
--- a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/ConnectionTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Tests/Transport/ConnectionTest.php
@@ -330,4 +330,25 @@ public function testSendWhenABeanstalkdExceptionOccurs()
$connection->send($body, $headers, $delay);
}
+
+ public function testSendWithRoundedDelay()
+ {
+ $tube = 'xyz';
+ $body = 'foo';
+ $headers = ['test' => 'bar'];
+ $delay = 920;
+ $expectedDelay = 0;
+
+ $client = $this->createMock(PheanstalkInterface::class);
+ $client->expects($this->once())->method('useTube')->with($tube)->willReturn($client);
+ $client->expects($this->once())->method('put')->with(
+ $this->anything(),
+ $this->anything(),
+ $expectedDelay,
+ $this->anything(),
+ );
+
+ $connection = new Connection(['tube_name' => $tube], $client);
+ $connection->send($body, $headers, $delay);
+ }
}
diff --git a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/Connection.php
index 98ab37c875d18..ff520ddb3071f 100644
--- a/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/Connection.php
+++ b/src/Symfony/Component/Messenger/Bridge/Beanstalkd/Transport/Connection.php
@@ -123,7 +123,7 @@ public function send(string $body, array $headers, int $delay = 0): string
$job = $this->client->useTube($this->tube)->put(
$message,
PheanstalkInterface::DEFAULT_PRIORITY,
- $delay / 1000,
+ (int) ($delay / 1000),
$this->ttr
);
} catch (Exception $exception) {
diff --git a/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php b/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php
index 904c86d6b3cfd..0db5dfa0abe44 100644
--- a/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php
+++ b/src/Symfony/Component/Mime/Part/Multipart/FormDataPart.php
@@ -26,7 +26,7 @@ final class FormDataPart extends AbstractMultipartPart
private array $fields = [];
/**
- * @param array $fields
+ * @param array $fields
*/
public function __construct(array $fields = [])
{
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
index 8e00e6473a4e8..89239a53f3505 100644
--- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
@@ -581,8 +581,12 @@ private function isAllowedProperty(string $class, string $property, bool $writeA
return false;
}
- if (\PHP_VERSION_ID >= 80400 && ($reflectionProperty->isProtectedSet() || $reflectionProperty->isPrivateSet())) {
- return false;
+ if (\PHP_VERSION_ID >= 80400 && $reflectionProperty->isProtectedSet()) {
+ return (bool) ($this->propertyReflectionFlags & \ReflectionProperty::IS_PROTECTED);
+ }
+
+ if (\PHP_VERSION_ID >= 80400 && $reflectionProperty->isPrivateSet()) {
+ return (bool) ($this->propertyReflectionFlags & \ReflectionProperty::IS_PRIVATE);
}
if (\PHP_VERSION_ID >= 80400 &&$reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) {
diff --git a/src/Symfony/Component/PropertyInfo/PropertyReadInfo.php b/src/Symfony/Component/PropertyInfo/PropertyReadInfo.php
index 8de070dc046c9..d006e32483896 100644
--- a/src/Symfony/Component/PropertyInfo/PropertyReadInfo.php
+++ b/src/Symfony/Component/PropertyInfo/PropertyReadInfo.php
@@ -15,8 +15,6 @@
* The property read info tells how a property can be read.
*
* @author Joel Wurtz
- *
- * @internal
*/
final class PropertyReadInfo
{
diff --git a/src/Symfony/Component/PropertyInfo/PropertyWriteInfo.php b/src/Symfony/Component/PropertyInfo/PropertyWriteInfo.php
index 6bc7abcdf849e..81ce7eda6d5b0 100644
--- a/src/Symfony/Component/PropertyInfo/PropertyWriteInfo.php
+++ b/src/Symfony/Component/PropertyInfo/PropertyWriteInfo.php
@@ -15,8 +15,6 @@
* The write mutator defines how a property can be written.
*
* @author Joel Wurtz
- *
- * @internal
*/
final class PropertyWriteInfo
{
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
index b97032574ab86..b7987668b4f8f 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
@@ -14,10 +14,13 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
+use Symfony\Component\PropertyInfo\Tests\Fixtures\Clazz;
use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithoutDocBlock;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyCollection;
+use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyGeneric;
+use Symfony\Component\PropertyInfo\Tests\Fixtures\IFace;
use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80PromotedDummy;
@@ -482,7 +485,88 @@ public static function php80TypesProvider()
public function testGenericInterface()
{
- $this->assertNull($this->extractor->getTypes(Dummy::class, 'genericInterface'));
+ $this->assertEquals(
+ [
+ new Type(
+ builtinType: Type::BUILTIN_TYPE_OBJECT,
+ class: \BackedEnum::class,
+ collectionValueType: new Type(
+ builtinType: Type::BUILTIN_TYPE_STRING,
+ )
+ ),
+ ],
+ $this->extractor->getTypes(Dummy::class, 'genericInterface')
+ );
+ }
+
+ /**
+ * @param list $expectedTypes
+ * @dataProvider genericsProvider
+ */
+ public function testGenericsLegacy(string $property, array $expectedTypes)
+ {
+ $this->assertEquals($expectedTypes, $this->extractor->getTypes(DummyGeneric::class, $property));
+ }
+
+ /**
+ * @return iterable}>
+ */
+ public static function genericsProvider(): iterable
+ {
+ yield [
+ 'basicClass',
+ [
+ new Type(
+ builtinType: Type::BUILTIN_TYPE_OBJECT,
+ class: Clazz::class,
+ collectionValueType: new Type(
+ builtinType: Type::BUILTIN_TYPE_OBJECT,
+ class: Dummy::class,
+ )
+ ),
+ ],
+ ];
+ yield [
+ 'nullableClass',
+ [
+ new Type(
+ builtinType: Type::BUILTIN_TYPE_OBJECT,
+ class: Clazz::class,
+ nullable: true,
+ collectionValueType: new Type(
+ builtinType: Type::BUILTIN_TYPE_OBJECT,
+ class: Dummy::class,
+ )
+ ),
+ ],
+ ];
+ yield [
+ 'basicInterface',
+ [
+ new Type(
+ builtinType: Type::BUILTIN_TYPE_OBJECT,
+ class: IFace::class,
+ collectionValueType: new Type(
+ builtinType: Type::BUILTIN_TYPE_OBJECT,
+ class: Dummy::class,
+ )
+ ),
+ ],
+ ];
+ yield [
+ 'nullableInterface',
+ [
+ new Type(
+ builtinType: Type::BUILTIN_TYPE_OBJECT,
+ class: IFace::class,
+ nullable: true,
+ collectionValueType: new Type(
+ builtinType: Type::BUILTIN_TYPE_OBJECT,
+ class: Dummy::class,
+ )
+ ),
+ ],
+ ];
}
}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
index a6315103a2266..45565096d9963 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
@@ -672,6 +672,51 @@ public function testAsymmetricVisibility()
$this->assertFalse($this->extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate'));
}
+ /**
+ * @requires PHP 8.4
+ */
+ public function testAsymmetricVisibilityAllowPublicOnly()
+ {
+ $extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC);
+
+ $this->assertTrue($extractor->isReadable(AsymmetricVisibility::class, 'publicPrivate'));
+ $this->assertTrue($extractor->isReadable(AsymmetricVisibility::class, 'publicProtected'));
+ $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'protectedPrivate'));
+ $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicPrivate'));
+ $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicProtected'));
+ $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate'));
+ }
+
+ /**
+ * @requires PHP 8.4
+ */
+ public function testAsymmetricVisibilityAllowProtectedOnly()
+ {
+ $extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PROTECTED);
+
+ $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicPrivate'));
+ $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicProtected'));
+ $this->assertTrue($extractor->isReadable(AsymmetricVisibility::class, 'protectedPrivate'));
+ $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicPrivate'));
+ $this->assertTrue($extractor->isWritable(AsymmetricVisibility::class, 'publicProtected'));
+ $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate'));
+ }
+
+ /**
+ * @requires PHP 8.4
+ */
+ public function testAsymmetricVisibilityAllowPrivateOnly()
+ {
+ $extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PRIVATE);
+
+ $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicPrivate'));
+ $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'publicProtected'));
+ $this->assertFalse($extractor->isReadable(AsymmetricVisibility::class, 'protectedPrivate'));
+ $this->assertTrue($extractor->isWritable(AsymmetricVisibility::class, 'publicPrivate'));
+ $this->assertFalse($extractor->isWritable(AsymmetricVisibility::class, 'publicProtected'));
+ $this->assertTrue($extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate'));
+ }
+
/**
* @requires PHP 8.4
*/
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyGeneric.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyGeneric.php
new file mode 100644
index 0000000000000..5863fbfc95450
--- /dev/null
+++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyGeneric.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\PropertyInfo\Tests\Fixtures;
+
+interface IFace {}
+
+class Clazz {}
+
+class DummyGeneric
+{
+
+ /**
+ * @var Clazz
+ */
+ public $basicClass;
+
+ /**
+ * @var ?Clazz
+ */
+ public $nullableClass;
+
+ /**
+ * @var IFace
+ */
+ public $basicInterface;
+
+ /**
+ * @var ?IFace
+ */
+ public $nullableInterface;
+
+}
diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php
index 7d439f55660dd..56a6b509172c7 100644
--- a/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php
+++ b/src/Symfony/Component/PropertyInfo/Util/PhpStanTypeHelper.php
@@ -128,7 +128,7 @@ private function extractTypes(TypeNode $node, NameScope $nameScope): array
$collection = $mainType->isCollection() || \is_a($mainType->getClassName(), \Traversable::class, true) || \is_a($mainType->getClassName(), \ArrayAccess::class, true);
// it's safer to fall back to other extractors if the generic type is too abstract
- if (!$collection && !class_exists($mainType->getClassName())) {
+ if (!$collection && !class_exists($mainType->getClassName()) && !interface_exists($mainType->getClassName(), false)) {
return [];
}
diff --git a/src/Symfony/Component/Scheduler/Tests/Trigger/PeriodicalTriggerTest.php b/src/Symfony/Component/Scheduler/Tests/Trigger/PeriodicalTriggerTest.php
index 22162e792fa24..8f1ce90fee72a 100644
--- a/src/Symfony/Component/Scheduler/Tests/Trigger/PeriodicalTriggerTest.php
+++ b/src/Symfony/Component/Scheduler/Tests/Trigger/PeriodicalTriggerTest.php
@@ -108,9 +108,9 @@ public static function provideForToString()
/**
* @dataProvider providerGetNextRunDates
*/
- public function testGetNextRunDates(\DateTimeImmutable $from, TriggerInterface $trigger, array $expected, int $count = 0)
+ public function testGetNextRunDates(\DateTimeImmutable $from, TriggerInterface $trigger, array $expected, int $count)
{
- $this->assertEquals($expected, $this->getNextRunDates($from, $trigger, $count ?? \count($expected)));
+ $this->assertEquals($expected, $this->getNextRunDates($from, $trigger, $count));
}
public static function providerGetNextRunDates(): iterable
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf
index 3977f37433060..485d69add1ee8 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf
@@ -444,27 +444,27 @@
This value is too short. It should contain at least one word.|This value is too short. It should contain at least {{ min }} words.
- This value is too short. It should contain at least one word.|This value is too short. It should contain at least {{ min }} words.
+ این مقدار بسیار کوتاه است. باید حداقل یک کلمه داشته باشد.|این مقدار بسیار کوتاه است. باید حداقل {{ min }} کلمه داشته باشد.
This value is too long. It should contain one word.|This value is too long. It should contain {{ max }} words or less.
- This value is too long. It should contain one word.|This value is too long. It should contain {{ max }} words or less.
+ این مقدار بیش از حد طولانی است. باید فقط یک کلمه باشد.|این مقدار بیش از حد طولانی است. باید حداکثر {{ max }} کلمه داشته باشد.
This value does not represent a valid week in the ISO 8601 format.
- This value does not represent a valid week in the ISO 8601 format.
+ این مقدار یک هفته معتبر در قالب ISO 8601 را نشان نمیدهد.
This value is not a valid week.
- This value is not a valid week.
+ این مقدار یک هفته معتبر نیست.
This value should not be before week "{{ min }}".
- This value should not be before week "{{ min }}".
+ این مقدار نباید قبل از هفته "{{ min }}" باشد.
This value should not be after week "{{ max }}".
- This value should not be after week "{{ max }}".
+ این مقدار نباید بعد از هفته "{{ max }}" باشد.