diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 72ea7cfa3e9c0..f7b87a1ce8cd4 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,6 +1,6 @@
| Q | A
| ------------- | ---
-| Branch? | 6.3 for features / 5.4 or 6.2 for bug fixes
+| Branch? | 6.4 for features / 5.4, 6.2, or 6.3 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 69192939d066a..3757f2523e928 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -65,7 +65,7 @@ jobs:
echo COLUMNS=120 >> $GITHUB_ENV
echo PHPUNIT="$(pwd)/phpunit --exclude-group tty,benchmark,intl-data,integration" >> $GITHUB_ENV
- echo COMPOSER_UP='composer update --no-progress --ansi'$([[ "${{ matrix.php }}" = "8.2" ]] && echo ' --ignore-platform-req=php+') >> $GITHUB_ENV
+ echo COMPOSER_UP='composer update --no-progress --ansi'$([[ "${{ matrix.mode }}" = experimental ]] && echo ' --ignore-platform-req=php+') >> $GITHUB_ENV
SYMFONY_VERSIONS=$(git ls-remote -q --heads | cut -f2 | grep -o '/[1-9][0-9]*\.[0-9].*' | sort -V)
SYMFONY_VERSION=$(grep ' VERSION = ' src/Symfony/Component/HttpKernel/Kernel.php | cut -d "'" -f2 | cut -d '.' -f 1-2)
diff --git a/CHANGELOG-5.4.md b/CHANGELOG-5.4.md
index 8241220390c11..dca25ff5b0d50 100644
--- a/CHANGELOG-5.4.md
+++ b/CHANGELOG-5.4.md
@@ -7,6 +7,34 @@ in 5.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/v5.4.0...v5.4.1
+* 5.4.24 (2023-05-27)
+
+ * bug #50429 [Console] block input stream if needed (joelwurtz)
+ * bug #50315 [Translation] Fix handling of null messages in `ArrayLoader` (rob006)
+ * bug #50338 [Console] Remove ``exec`` and replace it by ``shell_exec`` (maxbeckers)
+ * bug #50362 [FrameworkBundle] Fix Workflow without a marking store definition uses marking store definition of previously defined workflow (krciga22)
+ * bug #50309 [HttpFoundation] UrlHelper is now aware of RequestContext changes (giosh94mhz)
+ * bug #50309 [HttpFoundation] UrlHelper is now aware of RequestContext changes (giosh94mhz)
+ * bug #50354 [Process] Stop the process correctly even if underlying input stream is not closed (joelwurtz)
+ * bug #50332 [PropertyInfo] Fix `PhpStanExtractor` when constructor has no docblock (HypeMC)
+ * bug #50253 [FrameworkBundle] Generate caches consistently on successive run of `cache:clear` command (Okhoshi)
+ * bug #49063 [Messenger] Respect `isRetryable` decision of the retry strategy for re-delivery (FlyingDR)
+ * bug #50251 [Serializer] Handle datetime deserialization in U format (tugmaks)
+ * bug #50266 [HttpFoundation] Fix file streaming after connection aborted (rlshukhov)
+ * bug #50269 Fix param type annotation (l-vo)
+ * bug #50256 [HttpClient] Fix setting duplicate-name headers when redirecting with AmpHttpClient (nicolas-grekas)
+ * bug #50214 [WebProfilerBundle] Remove legacy filters remnants (MatTheCat)
+ * bug #50235 [HttpClient] Fix getting through proxies via CONNECT (nicolas-grekas)
+ * bug #50244 [HttpKernel] Fix restoring surrogate content from cache (nicolas-grekas)
+ * bug #50246 [DependencyInjection] Do not check errored definitions’ type (MatTheCat)
+ * bug #49557 [PropertyInfo] Fix phpDocExtractor nullable array value type (fabpot)
+ * bug #50213 [ErrorHandler] Prevent conflicts with WebProfilerBundle’s JavaScript (MatTheCat)
+ * bug #50192 [Serializer] backed enum throw notNormalizableValueException outside construct method (alli83)
+ * bug #50238 [HttpKernel] Don't use eval() to render ESI/SSI (nicolas-grekas)
+ * bug #50226 [HttpClient] Ensure HttplugClient ignores invalid HTTP headers (nicolas-grekas)
+ * bug #50203 [Messenger] Fix registering message handlers (nicolas-grekas)
+ * bug #50204 [ErrorHandler] Skip Httplug deprecations for HttplugClient (nicolas-grekas)
+
* 5.4.23 (2023-04-28)
* bug #50143 [Console] trim(): Argument #1 () must be of type string, bool given (danepowell)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 24afd64907140..cf5f960f7a101 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -21,9 +21,9 @@ The Symfony Connect username in parenthesis allows to get more information
- Jordi Boggiano (seldaek)
- Roland Franssen (ro0)
- Victor Berchet (victor)
+ - Javier Eguiluz (javier.eguiluz)
- Yonel Ceruto (yonelceruto)
- Tobias Nyholm (tobias)
- - Javier Eguiluz (javier.eguiluz)
- Oskar Stark (oskarstark)
- Ryan Weaver (weaverryan)
- Johannes S (johannes)
@@ -40,8 +40,8 @@ The Symfony Connect username in parenthesis allows to get more information
- Abdellatif Ait boudad (aitboudad)
- Jan Schädlich (jschaedl)
- Lukas Kahwe Smith (lsmith)
- - Jérôme Tamarelle (gromnan)
- Kevin Bond (kbond)
+ - Jérôme Tamarelle (gromnan)
- Martin Hasoň (hason)
- Jeremy Mikola (jmikola)
- Jean-François Simon (jfsimon)
@@ -52,21 +52,21 @@ The Symfony Connect username in parenthesis allows to get more information
- Valentin Udaltsov (vudaltsov)
- Vasilij Duško (staff)
- Matthias Pigulla (mpdude)
+ - Antoine Lamirault (alamirault)
- Gabriel Ostrolucký (gadelat)
- - Antoine Makdessi (amakdessi)
- Laurent VOULLEMIER (lvo)
+ - Antoine Makdessi (amakdessi)
- Pierre du Plessis (pierredup)
- - Antoine Lamirault (alamirault)
- Grégoire Paris (greg0ire)
- Jonathan Wage (jwage)
+ - Mathieu Lechat (mat_the_cat)
- Titouan Galopin (tgalopin)
- David Maicher (dmaicher)
+ - Alexander Schranz (alexander-schranz)
- Gábor Egyed (1ed)
- Mathieu Santostefano (welcomattic)
- - Alexander Schranz (alexander-schranz)
- Alexandre Salomé (alexandresalome)
- William DURAND
- - Mathieu Lechat (mat_the_cat)
- ornicar
- Dany Maillard (maidmaid)
- Eriksen Costa
@@ -76,10 +76,10 @@ The Symfony Connect username in parenthesis allows to get more information
- Francis Besset (francisbesset)
- Vasilij Dusko | CREATION
- Bulat Shakirzyanov (avalanche123)
+ - Vincent Langlet (deviling)
- Iltar van der Berg
- Miha Vrhovnik (mvrhov)
- Mathieu Piot (mpiot)
- - Vincent Langlet (deviling)
- Saša Stamenković (umpirsky)
- Alex Pott
- Guilhem N (guilhemn)
@@ -95,14 +95,14 @@ The Symfony Connect username in parenthesis allows to get more information
- Ruud Kamphuis (ruudk)
- Henrik Bjørnskov (henrikbjorn)
- David Buchmann (dbu)
+ - Massimiliano Arione (garak)
- Andrej Hudec (pulzarraider)
- Julien Falque (julienfalque)
- - Massimiliano Arione (garak)
- Jáchym Toušek (enumag)
- Douglas Greenshields (shieldo)
+ - Mathias Arlaud (mtarld)
- Christian Raue
- Fran Moreno (franmomu)
- - Mathias Arlaud (mtarld)
- Graham Campbell (graham)
- Michel Weimerskirch (mweimerskirch)
- Eric Clemmons (ericclemmons)
@@ -117,18 +117,18 @@ The Symfony Connect username in parenthesis allows to get more information
- Dariusz Górecki (canni)
- Maxime Helias (maxhelias)
- Ener-Getick
- - Sebastiaan Stok (sstok)
- Tugdual Saunier (tucksaun)
+ - Sebastiaan Stok (sstok)
- Jérôme Vasseur (jvasseur)
- Ion Bazan (ionbazan)
- Rokas Mikalkėnas (rokasm)
+ - Yanick Witschi (toflar)
- Lee McDermott
- Brandon Turner
- Luis Cordova (cordoval)
- Daniel Holmes (dholmes)
- Toni Uebernickel (havvg)
- Bart van den Burg (burgov)
- - Yanick Witschi (toflar)
- Jordan Alliot (jalliot)
- Smaine Milianni (ismail1432)
- John Wards (johnwards)
@@ -137,6 +137,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Antoine Hérault (herzult)
- Konstantin.Myakshin
- Arman Hosseini (arman)
+ - gnito-org
- Saif Eddin Gmati (azjezz)
- Simon Berger
- Arnaud Le Blanc (arnaud-lb)
@@ -152,6 +153,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Chris Wilkinson (thewilkybarkid)
- Brice BERNARD (brikou)
- Roman Martinuk (a2a4)
+ - Joel Wurtz (brouznouf)
- Gregor Harlan (gharlan)
- Baptiste Clavié (talus)
- Adrien Brault (adrienbrault)
@@ -168,7 +170,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Guillaume (guill)
- Christopher Hertel (chertel)
- Jacob Dreesen (jdreesen)
- - Joel Wurtz (brouznouf)
- Olivier Dolbeau (odolbeau)
- Florian Voutzinos (florianv)
- zairig imad (zairigimad)
@@ -180,6 +181,7 @@ The Symfony Connect username in parenthesis allows to get more information
- HeahDude
- Richard van Laak (rvanlaak)
- Paráda József (paradajozsef)
+ - Hubert Lenoir (hubert_lenoir)
- Alessandro Lai (jean85)
- Alexander Schwenn (xelaris)
- Fabien Pennequin (fabienpennequin)
@@ -203,7 +205,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Tigran Azatyan (tigranazatyan)
- Eric GELOEN (gelo)
- Matthieu Napoli (mnapoli)
- - Hubert Lenoir (hubert_lenoir)
- Tomáš Votruba (tomas_votruba)
- Joshua Thijssen
- Stefano Sala (stefano.sala)
@@ -295,7 +296,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Samuel NELA (snela)
- Romain Monteil (ker0x)
- dFayet
- - gnito-org
- Karoly Gossler (connorhu)
- Vincent AUBERT (vincent)
- Sebastien Morel (plopix)
@@ -304,7 +304,9 @@ The Symfony Connect username in parenthesis allows to get more information
- Timothée Barray (tyx)
- Sébastien Alfaiate (seb33300)
- James Halsall (jaitsu)
+ - Maximilian Beckers (maxbeckers)
- Mikael Pajunen
+ - Marcin Sikoń (marphi)
- Warnar Boekkooi (boekkooi)
- Marco Petersen (ocrampete16)
- Benjamin Leveque (benji07)
@@ -335,6 +337,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Urinbayev Shakhobiddin (shokhaa)
- Ahmed Raafat
- Philippe Segatori
+ - Allison Guilhem (a_guilhem)
- Thibaut Cheymol (tcheymol)
- Julien Pauli
- Islam Israfilov (islam93)
@@ -346,7 +349,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Ruben Gonzalez (rubenrua)
- Benjamin Dulau (dbenjamin)
- Pavel Kirpitsov (pavel-kirpichyov)
- - Maximilian Beckers (maxbeckers)
- Mathieu Lemoine (lemoinem)
- Christian Schmidt
- Andreas Hucks (meandmymonkey)
@@ -357,7 +359,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Clara van Miert
- Martin Auswöger
- Alexander Menshchikov
- - Marcin Sikoń (marphi)
- Stepan Anchugov (kix)
- bronze1man
- sun (sun)
@@ -375,11 +376,13 @@ The Symfony Connect username in parenthesis allows to get more information
- Kyle
- Dominique Bongiraud
- Hidde Wieringa (hiddewie)
+ - Dane Powell
- Christopher Davis (chrisguitarguy)
- Lukáš Holeczy (holicz)
- Michael Lee (zerustech)
- Florian Lonqueu-Brochard (florianlb)
- Leszek Prabucki (l3l0)
+ - Giorgio Premi
- Emanuele Panzeri (thepanz)
- Matthew Smeets
- François Zaninotto (fzaninotto)
@@ -415,14 +418,12 @@ The Symfony Connect username in parenthesis allows to get more information
- Mantis Development
- Pablo Lozano (arkadis)
- quentin neyrat (qneyrat)
- - Dane Powell
- Antonio Jose Cerezo (ajcerezo)
- Marcin Szepczynski (czepol)
- Lescot Edouard (idetox)
- Loïc Frémont (loic425)
- Rob Frawley 2nd (robfrawley)
- Mohammad Emran Hasan (phpfour)
- - Allison Guilhem (a_guilhem)
- Dmitriy Mamontov (mamontovdmitriy)
- Kévin THERAGE (kevin_therage)
- Nikita Konstantinov (unkind)
@@ -430,7 +431,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Francois Zaninotto
- Laurent Masforné (heisenberg)
- Claude Khedhiri (ck-developer)
- - Giorgio Premi
- Daniel Tschinder
- Christian Schmidt
- Alexander Kotynia (olden)
@@ -576,6 +576,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Grégoire Passault (gregwar)
- Jerzy Zawadzki (jzawadzki)
- Ismael Ambrosi (iambrosi)
+ - Samaël Villette (samadu61)
- Saif Eddin G
- Emmanuel BORGES (eborges78)
- siganushka (siganushka)
@@ -691,6 +692,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Ruben Jacobs (rubenj)
- Arkadius Stefanski (arkadius)
- Jérémy M (th3mouk)
+ - Tristan Pouliquen
- Terje Bråten
- Pierre Rineau
- Renan Gonçalves (renan_saddam)
@@ -720,6 +722,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Eric COURTIAL
- Xesxen
- ShinDarth
+ - Phil E. Taylor (philetaylor)
- Arun Philip
- Stéphane PY (steph_py)
- Philipp Kräutli (pkraeutli)
@@ -752,10 +755,10 @@ The Symfony Connect username in parenthesis allows to get more information
- Hassan Amouhzi
- Antonin CLAUZIER (0x346e3730)
- Andrei C. (moldman)
- - Samaël Villette (samadu61)
- Tamas Szijarto
- stlrnz
- Adrien Wilmet (adrienfr)
+ - Mathieu Rochette (mathroc)
- Alex Bacart
- hugovms
- Michele Locati
@@ -898,6 +901,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Jonas Flodén (flojon)
- Adrien Lucas (adrienlucas)
- Dominik Zogg
+ - Quentin Devos
- Kai Dederichs
- Luc Vieillescazes (iamluc)
- Thomas Nunninger
@@ -956,7 +960,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Gigino Chianese (sajito)
- Xav` (xavismeh)
- Remi Collet
- - Mathieu Rochette (mathroc)
- Vicent Soria Durá (vicentgodella)
- Michael Moravec
- Anthony Ferrara
@@ -1026,6 +1029,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Loïc Faugeron
- Aurélien Fredouelle
- Pavel Campr (pcampr)
+ - Markus Staab
- Forfarle (forfarle)
- Johnny Robeson (johnny)
- Kai Eichinger (kai_eichinger)
@@ -1267,7 +1271,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Szijarto Tamas
- Arend Hummeling
- Makdessi Alex
- - Phil E. Taylor (philetaylor)
- Juan Miguel Besada Vidal (soutlink)
- dlorek
- Stuart Fyfe
@@ -1295,7 +1298,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Simon Schick (simonsimcity)
- Victor Macko (victor_m)
- Tristan Roussel
- - Quentin Devos
- Jorge Vahldick (jvahldick)
- Vladimir Mantulo (mantulo)
- aim8604
@@ -1303,6 +1305,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Maciej Zgadzaj
- David Legatt (dlegatt)
- Maarten de Boer (mdeboer)
+ - Alexandre parent
- Cameron Porter
- Hossein Bukhamsin
- Oliver Hoff
@@ -1449,7 +1452,6 @@ The Symfony Connect username in parenthesis allows to get more information
- Michael Olšavský
- Benny Born
- Emirald Mateli
- - Tristan Pouliquen
- Jose Gonzalez
- Claudio Zizza
- Ivo Valchev
@@ -1841,7 +1843,6 @@ The Symfony Connect username in parenthesis allows to get more information
- vladyslavstartsev
- Kévin
- Marc Abramowitz
- - Markus Staab
- michal
- Martijn Evers
- Sjoerd Adema
@@ -2220,11 +2221,13 @@ The Symfony Connect username in parenthesis allows to get more information
- Andrew Tch
- Alexander Cheprasov
- Rodrigo Díez Villamuera (rodrigodiez)
+ - Brad Treloar
- Stephen Clouse
- e-ivanov
- Abderrahman DAIF (death_maker)
- Yann Rabiller (einenlum)
- Jochen Bayer (jocl)
+ - Constantine Shtompel
- VAN DER PUTTE Guillaume (guillaume_vdp)
- Patrick Carlo-Hickman
- Bruno MATEU
@@ -2235,6 +2238,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Viacheslav Sychov
- Nicolas Sauveur (baishu)
- Helmut Hummel (helhum)
+ - Andrew Neil Forster (krciga22)
- Matt Brunt
- Carlos Ortega Huetos
- Péter Buri (burci)
@@ -2285,7 +2289,6 @@ The Symfony Connect username in parenthesis allows to get more information
- John Espiritu (johnillo)
- Oxan van Leeuwen
- pkowalczyk
- - Alexandre parent
- Soner Sayakci
- Max Voloshin (maxvoloshin)
- Nicolas Fabre (nfabre)
@@ -2400,6 +2403,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Claudiu Cristea
- Zacharias Luiten
- Sebastian Utz
+ - Oliver Hader
- Adrien Gallou (agallou)
- Maks Rafalko (bornfree)
- Conrad Kleinespel (conradk)
@@ -2417,6 +2421,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Cédric Lahouste (rapotor)
- Samuel Vogel (samuelvogel)
- Berat Doğan
+ - Christian Kolb
- Guillaume LECERF
- Juanmi Rodriguez Cerón
- twifty
@@ -2437,6 +2442,8 @@ The Symfony Connect username in parenthesis allows to get more information
- Eric Stern
- ShiraNai7
- Antal Áron (antalaron)
+ - Alexander Grimalovsky (flying)
+ - Ivan Pepelko (pepelko)
- Vašek Purchart (vasek-purchart)
- Janusz Jabłoński (yanoosh)
- Fleuv
@@ -2581,6 +2588,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Nicolas Schwartz (nicoschwartz)
- Tim Jabs (rubinum)
- Stéphane Seng (stephaneseng)
+ - Robert Korulczyk
- Jonathan Gough
- Benoit Leveque
- Benjamin Bender
@@ -2652,6 +2660,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Martin Schophaus (m_schophaus_adcada)
- Martynas Sudintas (martiis)
- Anton Sukhachev (mrsuh)
+ - Vitaliy Zhuk (zhukv)
- Marcel Siegert
- ryunosuke
- Roy de Vos Burchart
@@ -2707,6 +2716,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Jovan Perovic (jperovic)
- Pablo Maria Martelletti (pmartelletti)
- Sander van der Vlugt (stranding)
+ - Maxim Tugaev (tugmaks)
- Florian Bogey
- Waqas Ahmed
- Bert Hekman
@@ -3141,6 +3151,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Antoine LA
- Vyacheslav Slinko
- Benjamin Laugueux
+ - Lane Shukhov
- Jakub Chábek
- William Pinaud (DocFX)
- Johannes
@@ -3244,6 +3255,7 @@ The Symfony Connect username in parenthesis allows to get more information
- Ahmed Abdulrahman
- dinitrol
- Penny Leach
+ - Kevin Mian Kraiker
- Yurii K
- Richard Trebichavský
- g123456789l
@@ -3282,6 +3294,7 @@ The Symfony Connect username in parenthesis allows to get more information
- ADmad
- Nicolas Roudaire
- Abdouni Karim (abdounikarim)
+ - Adrian Günter (adrianguenter)
- Andreas Forsblom (aforsblo)
- Alex Olmos (alexolmos)
- Cedric BERTOLINI (alsciende)
diff --git a/composer.json b/composer.json
index d5e8a209d3be0..469c30715f775 100644
--- a/composer.json
+++ b/composer.json
@@ -136,13 +136,14 @@
"nyholm/psr7": "^1.0",
"pda/pheanstalk": "^4.0",
"php-http/httplug": "^1.0|^2.0",
+ "php-http/message-factory": "^1.0",
"phpstan/phpdoc-parser": "^1.0",
"predis/predis": "~1.1",
"psr/http-client": "^1.0",
"psr/simple-cache": "^1.0|^2.0",
"egulias/email-validator": "^2.1.10|^3.1|^4",
"symfony/mercure-bundle": "^0.3",
- "symfony/phpunit-bridge": "^5.2|^6.0",
+ "symfony/phpunit-bridge": "^5.4|^6.0|^7.0",
"symfony/runtime": "self.version",
"symfony/security-acl": "~2.8|~3.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
@@ -160,10 +161,11 @@
"phpdocumentor/reflection-docblock": "<3.2.2",
"phpdocumentor/type-resolver": "<1.4.0",
"ocramius/proxy-manager": "<2.1",
- "phpunit/phpunit": "<5.4.3"
+ "phpunit/phpunit": "<7.5|9.1.2"
},
"config": {
"allow-plugins": {
+ "php-http/discovery": false,
"symfony/runtime": true
}
},
diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php
index 71da329a996cd..eb835caa41b25 100644
--- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php
+++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php
@@ -46,8 +46,8 @@ public function stop(): void
}
/**
- * @param string|int $param
- * @param string|int|float|bool|null $variable
+ * @param string|int $param
+ * @param mixed $variable
*/
public function setParam($param, &$variable, int $type): void
{
diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json
index a0dbbe9636adb..3aa2e352401ca 100644
--- a/src/Symfony/Bridge/Doctrine/composer.json
+++ b/src/Symfony/Bridge/Doctrine/composer.json
@@ -54,7 +54,6 @@
"doctrine/dbal": "<2.13.1",
"doctrine/lexer": "<1.1",
"doctrine/orm": "<2.7.4",
- "phpunit/phpunit": "<5.4.3",
"symfony/cache": "<5.4",
"symfony/dependency-injection": "<4.4",
"symfony/form": "<5.4.21|>=6,<6.2.7",
diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
index b0d5565398d2d..053810757a0d8 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
@@ -137,14 +137,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if ($output->isVerbose()) {
$io->comment('Warming up optional cache...');
}
- $warmer = $kernel->getContainer()->get('cache_warmer');
- // non optional warmers already ran during container compilation
- $warmer->enableOnlyOptionalWarmers();
- $preload = (array) $warmer->warmUp($realCacheDir);
-
- if ($preload && file_exists($preloadFile = $realCacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
- Preloader::append($preloadFile, $preload);
- }
+ $this->warmupOptionals($realCacheDir);
}
} else {
$fs->mkdir($warmupDir);
@@ -153,7 +146,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if ($output->isVerbose()) {
$io->comment('Warming up cache...');
}
- $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers'));
+ $this->warmup($warmupDir, $realBuildDir);
+
+ if (!$input->getOption('no-optional-warmers')) {
+ if ($output->isVerbose()) {
+ $io->comment('Warming up optional cache...');
+ }
+ $this->warmupOptionals($realCacheDir);
+ }
}
if (!$fs->exists($warmupDir.'/'.$containerDir)) {
@@ -227,7 +227,7 @@ private function isNfs(string $dir): bool
return false;
}
- private function warmup(string $warmupDir, string $realBuildDir, bool $enableOptionalWarmers = true)
+ private function warmup(string $warmupDir, string $realBuildDir): void
{
// create a temporary kernel
$kernel = $this->getApplication()->getKernel();
@@ -236,18 +236,6 @@ private function warmup(string $warmupDir, string $realBuildDir, bool $enableOpt
}
$kernel->reboot($warmupDir);
- // warmup temporary dir
- if ($enableOptionalWarmers) {
- $warmer = $kernel->getContainer()->get('cache_warmer');
- // non optional warmers already ran during container compilation
- $warmer->enableOnlyOptionalWarmers();
- $preload = (array) $warmer->warmUp($warmupDir);
-
- if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
- Preloader::append($preloadFile, $preload);
- }
- }
-
// fix references to cached files with the real cache directory name
$search = [$warmupDir, str_replace('\\', '\\\\', $warmupDir)];
$replace = str_replace('\\', '/', $realBuildDir);
@@ -258,4 +246,17 @@ private function warmup(string $warmupDir, string $realBuildDir, bool $enableOpt
}
}
}
+
+ private function warmupOptionals(string $realCacheDir): void
+ {
+ $kernel = $this->getApplication()->getKernel();
+ $warmer = $kernel->getContainer()->get('cache_warmer');
+ // non optional warmers already ran during container compilation
+ $warmer->enableOnlyOptionalWarmers();
+ $preload = (array) $warmer->warmUp($realCacheDir);
+
+ if ($preload && file_exists($preloadFile = $realCacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
+ Preloader::append($preloadFile, $preload);
+ }
+ }
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 10d479a11c699..82b798b18e698 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -920,6 +920,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
$workflows[$workflowId] = $definitionDefinition;
// Create MarkingStore
+ $markingStoreDefinition = null;
if (isset($workflow['marking_store']['type'])) {
$markingStoreDefinition = new ChildDefinition('workflow.marking_store.method');
$markingStoreDefinition->setArguments([
@@ -933,7 +934,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
// Create Workflow
$workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type));
$workflowDefinition->replaceArgument(0, new Reference(sprintf('%s.definition', $workflowId)));
- $workflowDefinition->replaceArgument(1, $markingStoreDefinition ?? null);
+ $workflowDefinition->replaceArgument(1, $markingStoreDefinition);
$workflowDefinition->replaceArgument(3, $name);
$workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']);
$workflowDefinition->addTag('container.private', [
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php
index a26dfb5adc612..cc2810fc6dbb4 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php
@@ -110,7 +110,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : []
->set('url_helper', UrlHelper::class)
->args([
service('request_stack'),
- service('router.request_context')->ignoreOnInvalid(),
+ service('router')->ignoreOnInvalid(),
])
->alias(UrlHelper::class, 'url_helper')
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php
index c763e2bd211b4..871b62e8811da 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php
@@ -85,6 +85,62 @@ public function testWorkflowValidationStateMachine()
});
}
+ public function testWorkflowDefaultMarkingStoreDefinition()
+ {
+ $container = $this->createContainerFromClosure(function ($container) {
+ $container->loadFromExtension('framework', [
+ 'workflows' => [
+ 'workflow_a' => [
+ 'type' => 'state_machine',
+ 'marking_store' => [
+ 'type' => 'method',
+ 'property' => 'status',
+ ],
+ 'supports' => [
+ __CLASS__,
+ ],
+ 'places' => [
+ 'a',
+ 'b',
+ ],
+ 'transitions' => [
+ 'a_to_b' => [
+ 'from' => ['a'],
+ 'to' => ['b'],
+ ],
+ ],
+ ],
+ 'workflow_b' => [
+ 'type' => 'state_machine',
+ 'supports' => [
+ __CLASS__,
+ ],
+ 'places' => [
+ 'a',
+ 'b',
+ ],
+ 'transitions' => [
+ 'a_to_b' => [
+ 'from' => ['a'],
+ 'to' => ['b'],
+ ],
+ ],
+ ],
+ ],
+ ]);
+ });
+
+ $workflowA = $container->getDefinition('state_machine.workflow_a');
+ $argumentsA = $workflowA->getArguments();
+ $this->assertArrayHasKey('index_1', $argumentsA, 'workflow_a has a marking_store argument');
+ $this->assertNotNull($argumentsA['index_1'], 'workflow_a marking_store argument is not null');
+
+ $workflowB = $container->getDefinition('state_machine.workflow_b');
+ $argumentsB = $workflowB->getArguments();
+ $this->assertArrayHasKey('index_1', $argumentsB, 'workflow_b has a marking_store argument');
+ $this->assertNull($argumentsB['index_1'], 'workflow_b marking_store argument is null');
+ }
+
public function testRateLimiterWithLockFactory()
{
try {
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index 089e2e0827fea..320b993b1f8d1 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -24,7 +24,7 @@
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/event-dispatcher": "^5.1|^6.0",
"symfony/error-handler": "^4.4.1|^5.0.1|^6.0",
- "symfony/http-foundation": "^5.3|^6.0",
+ "symfony/http-foundation": "^5.4.24|^6.2.11",
"symfony/http-kernel": "^5.4|^6.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "^1.16",
@@ -74,7 +74,6 @@
"doctrine/persistence": "<1.3",
"phpdocumentor/reflection-docblock": "<3.2.2",
"phpdocumentor/type-resolver": "<1.4.0",
- "phpunit/phpunit": "<5.4.3",
"symfony/asset": "<5.3",
"symfony/console": "<5.2.5",
"symfony/dotenv": "<5.1",
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig
index a8a5c273656b5..f48c9fce7c4df 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig
@@ -155,22 +155,20 @@
-
-
{% endblock messages %}
{% endif %}
{% endblock %}
{% macro render_table(messages, is_fallback) %}
-
+
- Locale |
+ Locale |
{% if is_fallback %}
Fallback locale |
{% endif %}
- Domain |
+ Domain |
Times used |
Message ID |
Message Preview |
@@ -178,7 +176,7 @@
{% for message in messages %}
-
+
{{ message.locale }} |
{% if is_fallback %}
{{ message.fallbackLocale|default('-') }} |
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
index 24ec0863d6117..2c59150b5a345 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
@@ -963,42 +963,6 @@ tr.status-warning td {
display: block;
}
-{# Filters
- ========================================================================= #}
-[data-filters] { position: relative; }
-[data-filtered] { cursor: pointer; }
-[data-filtered]:after { content: '\00a0\25BE'; }
-[data-filtered]:hover .filter-list li { display: inline-flex; }
-[class*="filter-hidden-"] { display: none; }
-.filter-list { position: absolute; border: var(--border); box-shadow: var(--shadow); margin: 0; padding: 0; display: flex; flex-direction: column; }
-.filter-list :after { content: ''; }
-.filter-list li {
- background: var(--tab-disabled-background);
- border-bottom: var(--border);
- color: var(--tab-disabled-color);
- display: none;
- list-style: none;
- margin: 0;
- padding: 5px 10px;
- text-align: left;
- font-weight: normal;
-}
-.filter-list li.active {
- background: var(--tab-background);
- color: var(--tab-color);
-}
-.filter-list li.last-active {
- background: var(--tab-active-background);
- color: var(--tab-active-color);
-}
-
-.filter-list-level li { cursor: s-resize; }
-.filter-list-level li.active { cursor: n-resize; }
-.filter-list-level li.last-active { cursor: default; }
-.filter-list-level li.last-active:before { content: '\2714\00a0'; }
-.filter-list-choice li:before { content: '\2714\00a0'; color: transparent; }
-.filter-list-choice li.active:before { color: unset; }
-
{# Twig panel
========================================================================= #}
#twig-dump pre {
diff --git a/src/Symfony/Component/Console/Command/CompleteCommand.php b/src/Symfony/Component/Console/Command/CompleteCommand.php
index 11ada4e4489b3..0e35143c3335d 100644
--- a/src/Symfony/Component/Console/Command/CompleteCommand.php
+++ b/src/Symfony/Component/Console/Command/CompleteCommand.php
@@ -155,10 +155,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
throw $e;
}
- return self::FAILURE;
+ return 2;
}
- return self::SUCCESS;
+ return 0;
}
private function createCompletionInput(InputInterface $input): CompletionInput
diff --git a/src/Symfony/Component/Console/Command/DumpCompletionCommand.php b/src/Symfony/Component/Console/Command/DumpCompletionCommand.php
index 6f809e2f139a1..eaf22be1a9ad4 100644
--- a/src/Symfony/Component/Console/Command/DumpCompletionCommand.php
+++ b/src/Symfony/Component/Console/Command/DumpCompletionCommand.php
@@ -85,7 +85,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if ($input->getOption('debug')) {
$this->tailDebugLog($commandName, $output);
- return self::SUCCESS;
+ return 0;
}
$shell = $input->getArgument('shell') ?? self::guessShell();
@@ -102,12 +102,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->writeln(sprintf('Shell not detected, Symfony shell completion only supports "%s").>', implode('", "', $supportedShells)));
}
- return self::INVALID;
+ return 2;
}
$output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, $this->getApplication()->getVersion()], file_get_contents($completionFile)));
- return self::SUCCESS;
+ return 0;
}
private static function guessShell(): string
diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php
index 10602038c299f..e236be92a3913 100644
--- a/src/Symfony/Component/Console/Helper/QuestionHelper.php
+++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php
@@ -128,7 +128,18 @@ private function doAsk(OutputInterface $output, Question $question)
}
if (false === $ret) {
+ $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true;
+
+ if (!$isBlocked) {
+ stream_set_blocking($inputStream, true);
+ }
+
$ret = $this->readInput($inputStream, $question);
+
+ if (!$isBlocked) {
+ stream_set_blocking($inputStream, false);
+ }
+
if (false === $ret) {
throw new MissingInputException('Aborted.');
}
@@ -500,13 +511,11 @@ private function isInteractiveInput($inputStream): bool
return self::$stdinIsInteractive = @posix_isatty(fopen('php://stdin', 'r'));
}
- if (!\function_exists('exec')) {
+ if (!\function_exists('shell_exec')) {
return self::$stdinIsInteractive = true;
}
- exec('stty 2> /dev/null', $output, $status);
-
- return self::$stdinIsInteractive = 1 !== $status;
+ return self::$stdinIsInteractive = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
}
/**
diff --git a/src/Symfony/Component/Console/Input/InputArgument.php b/src/Symfony/Component/Console/Input/InputArgument.php
index ecfcdad58b907..8a64f7ac8a8e9 100644
--- a/src/Symfony/Component/Console/Input/InputArgument.php
+++ b/src/Symfony/Component/Console/Input/InputArgument.php
@@ -32,7 +32,7 @@ class InputArgument
/**
* @param string $name The argument name
- * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL
+ * @param int|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY
* @param string $description A description text
* @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only)
*
diff --git a/src/Symfony/Component/Console/Terminal.php b/src/Symfony/Component/Console/Terminal.php
index 04653d3bd2bbc..b91e8afc5cac4 100644
--- a/src/Symfony/Component/Console/Terminal.php
+++ b/src/Symfony/Component/Console/Terminal.php
@@ -64,14 +64,12 @@ public static function hasSttyAvailable(): bool
return self::$stty;
}
- // skip check if exec function is disabled
- if (!\function_exists('exec')) {
+ // skip check if shell_exec function is disabled
+ if (!\function_exists('shell_exec')) {
return false;
}
- exec('stty 2>&1', $output, $exitcode);
-
- return self::$stty = 0 === $exitcode;
+ return self::$stty = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
}
private static function initDimensions()
diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
index 9761e8f979345..74315d8982638 100644
--- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
@@ -430,7 +430,7 @@ public function testAskHiddenResponse()
$this->assertEquals('8AM', $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("8AM\n")), $this->createOutputInterface(), $question));
}
- public function testAskHiddenResponseTrimmed()
+ public function testAskHiddenResponseNotTrimmed()
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->markTestSkipped('This test is not supported on Windows');
@@ -442,7 +442,7 @@ public function testAskHiddenResponseTrimmed()
$question->setHidden(true);
$question->setTrimmable(false);
- $this->assertEquals(' 8AM', $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream(' 8AM')), $this->createOutputInterface(), $question));
+ $this->assertEquals(' 8AM'.\PHP_EOL, $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream(' 8AM'.\PHP_EOL)), $this->createOutputInterface(), $question));
}
public function testAskMultilineResponseWithEOF()
diff --git a/src/Symfony/Component/Console/Tests/TerminalTest.php b/src/Symfony/Component/Console/Tests/TerminalTest.php
index c2d36fe0ab899..34d32b08ebecb 100644
--- a/src/Symfony/Component/Console/Tests/TerminalTest.php
+++ b/src/Symfony/Component/Console/Tests/TerminalTest.php
@@ -77,8 +77,8 @@ public function testSttyOnWindows()
$this->markTestSkipped('Must be on windows');
}
- $sttyString = exec('(stty -a | grep columns) 2>&1', $output, $exitcode);
- if (0 !== $exitcode) {
+ $sttyString = shell_exec('(stty -a | grep columns) 2> NUL');
+ if (!$sttyString) {
$this->markTestSkipped('Must have stty support');
}
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
index b7ec85cefb489..59e39067e777e 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
@@ -210,7 +210,7 @@ private function checkType(Definition $checkedDefinition, $value, \ReflectionPar
$class = null;
if ($value instanceof Definition) {
- if ($value->getFactory()) {
+ if ($value->hasErrors() || $value->getFactory()) {
return;
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
index 97e167c99cb10..e3c87ef13b74f 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
@@ -23,6 +23,7 @@
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Bar;
+use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarErroredDependency;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarMethodCall;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarOptionalArgument;
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarOptionalArgumentNotNull;
@@ -1010,6 +1011,20 @@ public function testIgnoreDefinitionFactoryArgument()
$this->addToAssertionCount(1);
}
+
+ public function testErroredDefinitionsAreNotChecked()
+ {
+ $container = new ContainerBuilder();
+ $container->register('errored_dependency', BarErroredDependency::class)
+ ->setArguments([
+ (new Definition(Foo::class))
+ ->addError('error'),
+ ]);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->addToAssertionCount(1);
+ }
}
class CallableClass
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarErroredDependency.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarErroredDependency.php
new file mode 100644
index 0000000000000..d1368c3f7ef44
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarErroredDependency.php
@@ -0,0 +1,10 @@
+checkClass($use);
}
if (isset(self::$deprecated[$use]) && strncmp($vendor, str_replace('_', '\\', $use), $vendorLen) && !isset(self::$deprecated[$class])
- && !(HttplugClient::class === $class && \in_array($use, [\Http\Message\RequestFactory::class, \Http\Message\StreamFactory::class, \Http\Message\UriFactory::class], true))
+ && !(HttplugClient::class === $class && \in_array($use, [\Http\Client\HttpClient::class, \Http\Message\RequestFactory::class, \Http\Message\StreamFactory::class, \Http\Message\UriFactory::class], true))
) {
$type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
$verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
diff --git a/src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js b/src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js
index a85409da3cc89..95b8ea17197c9 100644
--- a/src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js
+++ b/src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js
@@ -1,297 +1,285 @@
/* This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig.
If you make any change in this file, verify the same change is needed in the other file. */
/* .tab');
+ var tabNavigation = document.createElement('ul');
+ tabNavigation.className = 'tab-navigation';
+
+ var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */
+ for (var j = 0; j < tabs.length; j++) {
+ var tabId = 'tab-' + i + '-' + j;
+ var tabTitle = tabs[j].querySelector('.tab-title').innerHTML;
+
+ var tabNavigationItem = document.createElement('li');
+ tabNavigationItem.setAttribute('data-tab-id', tabId);
+ if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; }
+ if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); }
+ tabNavigationItem.innerHTML = tabTitle;
+ tabNavigation.appendChild(tabNavigationItem);
+
+ var tabContent = tabs[j].querySelector('.tab-content');
+ tabContent.parentElement.setAttribute('id', tabId);
+ }
- if (navigator.clipboard) {
- document.querySelectorAll('[data-clipboard-text]').forEach(function(element) {
- removeClass(element, 'hidden');
- element.addEventListener('click', function() {
- navigator.clipboard.writeText(element.getAttribute('data-clipboard-text'));
- })
- });
+ tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild);
+ addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active');
}
- return {
- addEventListener: addEventListener,
+ /* display the active tab and add the 'click' event listeners */
+ for (i = 0; i < tabGroups.length; i++) {
+ tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li');
- createTabs: function() {
- var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])');
+ for (j = 0; j < tabNavigation.length; j++) {
+ tabId = tabNavigation[j].getAttribute('data-tab-id');
+ document.getElementById(tabId).querySelector('.tab-title').className = 'hidden';
- /* create the tab navigation for each group of tabs */
- for (var i = 0; i < tabGroups.length; i++) {
- var tabs = tabGroups[i].querySelectorAll(':scope > .tab');
- var tabNavigation = document.createElement('ul');
- tabNavigation.className = 'tab-navigation';
-
- var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */
- for (var j = 0; j < tabs.length; j++) {
- var tabId = 'tab-' + i + '-' + j;
- var tabTitle = tabs[j].querySelector('.tab-title').innerHTML;
+ if (hasClass(tabNavigation[j], 'active')) {
+ document.getElementById(tabId).className = 'block';
+ } else {
+ document.getElementById(tabId).className = 'hidden';
+ }
- var tabNavigationItem = document.createElement('li');
- tabNavigationItem.setAttribute('data-tab-id', tabId);
- if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; }
- if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); }
- tabNavigationItem.innerHTML = tabTitle;
- tabNavigation.appendChild(tabNavigationItem);
+ tabNavigation[j].addEventListener('click', function(e) {
+ var activeTab = e.target || e.srcElement;
- var tabContent = tabs[j].querySelector('.tab-content');
- tabContent.parentElement.setAttribute('id', tabId);
+ /* needed because when the tab contains HTML contents, user can click */
+ /* on any of those elements instead of their parent '' element */
+ while (activeTab.tagName.toLowerCase() !== 'li') {
+ activeTab = activeTab.parentNode;
}
- tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild);
- addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active');
- }
+ /* get the full list of tabs through the parent of the active tab element */
+ var tabNavigation = activeTab.parentNode.children;
+ for (var k = 0; k < tabNavigation.length; k++) {
+ var tabId = tabNavigation[k].getAttribute('data-tab-id');
+ document.getElementById(tabId).className = 'hidden';
+ removeClass(tabNavigation[k], 'active');
+ }
- /* display the active tab and add the 'click' event listeners */
- for (i = 0; i < tabGroups.length; i++) {
- tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li');
+ addClass(activeTab, 'active');
+ var activeTabId = activeTab.getAttribute('data-tab-id');
+ document.getElementById(activeTabId).className = 'block';
+ });
+ }
- for (j = 0; j < tabNavigation.length; j++) {
- tabId = tabNavigation[j].getAttribute('data-tab-id');
- document.getElementById(tabId).querySelector('.tab-title').className = 'hidden';
+ tabGroups[i].setAttribute('data-processed', 'true');
+ }
+ })();
- if (hasClass(tabNavigation[j], 'active')) {
- document.getElementById(tabId).className = 'block';
- } else {
- document.getElementById(tabId).className = 'hidden';
- }
+ (function createToggles() {
+ var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])');
- tabNavigation[j].addEventListener('click', function(e) {
- var activeTab = e.target || e.srcElement;
+ for (var i = 0; i < toggles.length; i++) {
+ var elementSelector = toggles[i].getAttribute('data-toggle-selector');
+ var element = document.querySelector(elementSelector);
- /* needed because when the tab contains HTML contents, user can click */
- /* on any of those elements instead of their parent '' element */
- while (activeTab.tagName.toLowerCase() !== 'li') {
- activeTab = activeTab.parentNode;
- }
+ addClass(element, 'sf-toggle-content');
- /* get the full list of tabs through the parent of the active tab element */
- var tabNavigation = activeTab.parentNode.children;
- for (var k = 0; k < tabNavigation.length; k++) {
- var tabId = tabNavigation[k].getAttribute('data-tab-id');
- document.getElementById(tabId).className = 'hidden';
- removeClass(tabNavigation[k], 'active');
- }
+ if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') {
+ addClass(toggles[i], 'sf-toggle-on');
+ addClass(element, 'sf-toggle-visible');
+ } else {
+ addClass(toggles[i], 'sf-toggle-off');
+ addClass(element, 'sf-toggle-hidden');
+ }
- addClass(activeTab, 'active');
- var activeTabId = activeTab.getAttribute('data-tab-id');
- document.getElementById(activeTabId).className = 'block';
- });
- }
+ addEventListener(toggles[i], 'click', function(e) {
+ e.preventDefault();
- tabGroups[i].setAttribute('data-processed', 'true');
+ if ('' !== window.getSelection().toString()) {
+ /* Don't do anything on text selection */
+ return;
}
- },
-
- createToggles: function() {
- var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])');
-
- for (var i = 0; i < toggles.length; i++) {
- var elementSelector = toggles[i].getAttribute('data-toggle-selector');
- var element = document.querySelector(elementSelector);
-
- addClass(element, 'sf-toggle-content');
-
- if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') {
- addClass(toggles[i], 'sf-toggle-on');
- addClass(element, 'sf-toggle-visible');
- } else {
- addClass(toggles[i], 'sf-toggle-off');
- addClass(element, 'sf-toggle-hidden');
- }
-
- addEventListener(toggles[i], 'click', function(e) {
- e.preventDefault();
- if ('' !== window.getSelection().toString()) {
- /* Don't do anything on text selection */
- return;
- }
+ var toggle = e.target || e.srcElement;
- var toggle = e.target || e.srcElement;
+ /* needed because when the toggle contains HTML contents, user can click */
+ /* on any of those elements instead of their parent '.sf-toggle' element */
+ while (!hasClass(toggle, 'sf-toggle')) {
+ toggle = toggle.parentNode;
+ }
- /* needed because when the toggle contains HTML contents, user can click */
- /* on any of those elements instead of their parent '.sf-toggle' element */
- while (!hasClass(toggle, 'sf-toggle')) {
- toggle = toggle.parentNode;
- }
+ var element = document.querySelector(toggle.getAttribute('data-toggle-selector'));
- var element = document.querySelector(toggle.getAttribute('data-toggle-selector'));
+ toggleClass(toggle, 'sf-toggle-on');
+ toggleClass(toggle, 'sf-toggle-off');
+ toggleClass(element, 'sf-toggle-hidden');
+ toggleClass(element, 'sf-toggle-visible');
- toggleClass(toggle, 'sf-toggle-on');
- toggleClass(toggle, 'sf-toggle-off');
- toggleClass(element, 'sf-toggle-hidden');
- toggleClass(element, 'sf-toggle-visible');
+ /* the toggle doesn't change its contents when clicking on it */
+ if (!toggle.hasAttribute('data-toggle-alt-content')) {
+ return;
+ }
- /* the toggle doesn't change its contents when clicking on it */
- if (!toggle.hasAttribute('data-toggle-alt-content')) {
- return;
- }
+ if (!toggle.hasAttribute('data-toggle-original-content')) {
+ toggle.setAttribute('data-toggle-original-content', toggle.innerHTML);
+ }
- if (!toggle.hasAttribute('data-toggle-original-content')) {
- toggle.setAttribute('data-toggle-original-content', toggle.innerHTML);
- }
+ var currentContent = toggle.innerHTML;
+ var originalContent = toggle.getAttribute('data-toggle-original-content');
+ var altContent = toggle.getAttribute('data-toggle-alt-content');
+ toggle.innerHTML = currentContent !== altContent ? altContent : originalContent;
+ });
- var currentContent = toggle.innerHTML;
- var originalContent = toggle.getAttribute('data-toggle-original-content');
- var altContent = toggle.getAttribute('data-toggle-alt-content');
- toggle.innerHTML = currentContent !== altContent ? altContent : originalContent;
- });
+ /* Prevents from disallowing clicks on links inside toggles */
+ var toggleLinks = toggles[i].querySelectorAll('a');
+ for (var j = 0; j < toggleLinks.length; j++) {
+ addEventListener(toggleLinks[j], 'click', function(e) {
+ e.stopPropagation();
+ });
+ }
- /* Prevents from disallowing clicks on links inside toggles */
- var toggleLinks = toggles[i].querySelectorAll('a');
- for (var j = 0; j < toggleLinks.length; j++) {
- addEventListener(toggleLinks[j], 'click', function(e) {
- e.stopPropagation();
- });
- }
+ /* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */
+ var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]');
+ for (var k = 0; k < copyToClipboardElements.length; k++) {
+ addEventListener(copyToClipboardElements[k], 'click', function(e) {
+ e.stopPropagation();
+ });
+ }
- /* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */
- var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]');
- for (var k = 0; k < copyToClipboardElements.length; k++) {
- addEventListener(copyToClipboardElements[k], 'click', function(e) {
- e.stopPropagation();
- });
- }
+ toggles[i].setAttribute('data-processed', 'true');
+ }
+ })();
- toggles[i].setAttribute('data-processed', 'true');
+ (function createFilters() {
+ document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) {
+ var filters = filter.closest('[data-filters]'),
+ type = 'choice',
+ name = filter.dataset.filter,
+ ucName = name.charAt(0).toUpperCase()+name.slice(1),
+ list = document.createElement('ul'),
+ values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'),
+ labels = {},
+ defaults = null,
+ indexed = {},
+ processed = {};
+ if (typeof values === 'string') {
+ type = 'level';
+ labels = values.split(',');
+ values = values.toLowerCase().split(',');
+ defaults = values.length - 1;
+ }
+ addClass(list, 'filter-list');
+ addClass(list, 'filter-list-'+type);
+ values.forEach(function (value, i) {
+ if (value instanceof HTMLElement) {
+ value = value.dataset['filter'+ucName];
}
- },
-
- createFilters: function() {
- document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) {
- var filters = filter.closest('[data-filters]'),
- type = 'choice',
- name = filter.dataset.filter,
- ucName = name.charAt(0).toUpperCase()+name.slice(1),
- list = document.createElement('ul'),
- values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'),
- labels = {},
- defaults = null,
- indexed = {},
- processed = {};
- if (typeof values === 'string') {
- type = 'level';
- labels = values.split(',');
- values = values.toLowerCase().split(',');
- defaults = values.length - 1;
- }
- addClass(list, 'filter-list');
- addClass(list, 'filter-list-'+type);
- values.forEach(function (value, i) {
- if (value instanceof HTMLElement) {
- value = value.dataset['filter'+ucName];
- }
- if (value in processed) {
+ if (value in processed) {
+ return;
+ }
+ var option = document.createElement('li'),
+ label = i in labels ? labels[i] : value,
+ active = false,
+ matches;
+ if ('' === label) {
+ option.innerHTML = '(none)';
+ } else {
+ option.innerText = label;
+ }
+ option.dataset.filter = value;
+ option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows');
+ indexed[value] = i;
+ list.appendChild(option);
+ addEventListener(option, 'click', function () {
+ if ('choice' === type) {
+ filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
+ if (option.dataset.filter === row.dataset['filter'+ucName]) {
+ toggleClass(row, 'filter-hidden-'+name);
+ }
+ });
+ toggleClass(option, 'active');
+ } else if ('level' === type) {
+ if (i === this.parentNode.querySelectorAll('.active').length - 1) {
return;
}
- var option = document.createElement('li'),
- label = i in labels ? labels[i] : value,
- active = false,
- matches;
- if ('' === label) {
- option.innerHTML = '(none)';
- } else {
- option.innerText = label;
- }
- option.dataset.filter = value;
- option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows');
- indexed[value] = i;
- list.appendChild(option);
- addEventListener(option, 'click', function () {
- if ('choice' === type) {
- filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
- if (option.dataset.filter === row.dataset['filter'+ucName]) {
- toggleClass(row, 'filter-hidden-'+name);
- }
- });
- toggleClass(option, 'active');
- } else if ('level' === type) {
- if (i === this.parentNode.querySelectorAll('.active').length - 1) {
- return;
+ this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) {
+ if (j <= i) {
+ addClass(currentOption, 'active');
+ if (i === j) {
+ addClass(currentOption, 'last-active');
+ } else {
+ removeClass(currentOption, 'last-active');
}
- this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) {
- if (j <= i) {
- addClass(currentOption, 'active');
- if (i === j) {
- addClass(currentOption, 'last-active');
- } else {
- removeClass(currentOption, 'last-active');
- }
- } else {
- removeClass(currentOption, 'active');
- removeClass(currentOption, 'last-active');
- }
- });
- filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
- if (i < indexed[row.dataset['filter'+ucName]]) {
- addClass(row, 'filter-hidden-'+name);
- } else {
- removeClass(row, 'filter-hidden-'+name);
- }
- });
+ } else {
+ removeClass(currentOption, 'active');
+ removeClass(currentOption, 'last-active');
}
});
- if ('choice' === type) {
- active = null === defaults || 0 <= defaults.indexOf(value);
- } else if ('level' === type) {
- active = i <= defaults;
- if (active && i === defaults) {
- addClass(option, 'last-active');
+ filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
+ if (i < indexed[row.dataset['filter'+ucName]]) {
+ addClass(row, 'filter-hidden-'+name);
+ } else {
+ removeClass(row, 'filter-hidden-'+name);
}
- }
- if (active) {
- addClass(option, 'active');
- } else {
- filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) {
- toggleClass(row, 'filter-hidden-'+name);
- });
- }
- processed[value] = true;
- });
-
- if (1 < list.childNodes.length) {
- filter.appendChild(list);
- filter.dataset.filtered = '';
+ });
}
});
+ if ('choice' === type) {
+ active = null === defaults || 0 <= defaults.indexOf(value);
+ } else if ('level' === type) {
+ active = i <= defaults;
+ if (active && i === defaults) {
+ addClass(option, 'last-active');
+ }
+ }
+ if (active) {
+ addClass(option, 'active');
+ } else {
+ filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) {
+ toggleClass(row, 'filter-hidden-'+name);
+ });
+ }
+ processed[value] = true;
+ });
+
+ if (1 < list.childNodes.length) {
+ filter.appendChild(list);
+ filter.dataset.filtered = '';
}
- };
+ });
})();
-
- Sfjs.addEventListener(document, 'DOMContentLoaded', function() {
- Sfjs.createTabs();
- Sfjs.createToggles();
- Sfjs.createFilters();
- });
-}
+})();
/*]]>*/
diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json
index 6b02d77e29dff..39babd350174e 100644
--- a/src/Symfony/Component/Form/composer.json
+++ b/src/Symfony/Component/Form/composer.json
@@ -44,7 +44,6 @@
"symfony/uid": "^5.1|^6.0"
},
"conflict": {
- "phpunit/phpunit": "<5.4.3",
"symfony/console": "<4.4",
"symfony/dependency-injection": "<4.4",
"symfony/doctrine-bridge": "<5.4.21|>=6,<6.2.7",
diff --git a/src/Symfony/Component/HttpClient/HttplugClient.php b/src/Symfony/Component/HttpClient/HttplugClient.php
index 491ea9e4033b7..2d9eec30f1238 100644
--- a/src/Symfony/Component/HttpClient/HttplugClient.php
+++ b/src/Symfony/Component/HttpClient/HttplugClient.php
@@ -47,7 +47,7 @@
}
if (!interface_exists(RequestFactory::class)) {
- throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require nyholm/psr7".');
+ throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory".');
}
/**
diff --git a/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php b/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php
index 9f5658f560fbc..c61be22e34405 100644
--- a/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php
+++ b/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php
@@ -120,7 +120,11 @@ public function createPsr7Response(ResponseInterface $response, bool $buffer = f
foreach ($response->getHeaders(false) as $name => $values) {
foreach ($values as $value) {
- $psrResponse = $psrResponse->withAddedHeader($name, $value);
+ try {
+ $psrResponse = $psrResponse->withAddedHeader($name, $value);
+ } catch (\InvalidArgumentException $e) {
+ // ignore invalid header
+ }
}
}
diff --git a/src/Symfony/Component/HttpClient/Response/AmpResponse.php b/src/Symfony/Component/HttpClient/Response/AmpResponse.php
index 6d0ce6e33e01f..900c70d6e0e3a 100644
--- a/src/Symfony/Component/HttpClient/Response/AmpResponse.php
+++ b/src/Symfony/Component/HttpClient/Response/AmpResponse.php
@@ -47,7 +47,6 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
private $multi;
private $options;
- private $canceller;
private $onProgress;
private static $delay;
@@ -73,7 +72,7 @@ public function __construct(AmpClientState $multi, Request $request, array $opti
$info = &$this->info;
$headers = &$this->headers;
- $canceller = $this->canceller = new CancellationTokenSource();
+ $canceller = new CancellationTokenSource();
$handle = &$this->handle;
$info['url'] = (string) $request->getUri();
@@ -358,7 +357,7 @@ private static function followRedirects(Request $originRequest, AmpClientState $
}
foreach ($originRequest->getRawHeaders() as [$name, $value]) {
- $request->setHeader($name, $value);
+ $request->addHeader($name, $value);
}
if ($request->getUri()->getAuthority() !== $originRequest->getUri()->getAuthority()) {
diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php
index 7cfad581af4c4..2418203060c82 100644
--- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php
+++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php
@@ -76,17 +76,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
}
curl_setopt($ch, \CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int {
- if (0 !== substr_compare($data, "\r\n", -2)) {
- return 0;
- }
-
- $len = 0;
-
- foreach (explode("\r\n", substr($data, 0, -2)) as $data) {
- $len += 2 + self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger);
- }
-
- return $len;
+ return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger);
});
if (null === $options) {
@@ -381,19 +371,29 @@ private static function select(ClientState $multi, float $timeout): int
*/
private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int
{
+ if (!str_ends_with($data, "\r\n")) {
+ return 0;
+ }
+
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
if ('H' !== $waitFor[0]) {
return \strlen($data); // Ignore HTTP trailers
}
- if ('' !== $data) {
+ $statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE);
+
+ if ($statusCode !== $info['http_code'] && !preg_match("#^HTTP/\d+(?:\.\d+)? {$statusCode}(?: |\r\n$)#", $data)) {
+ return \strlen($data); // Ignore headers from responses to CONNECT requests
+ }
+
+ if ("\r\n" !== $data) {
// Regular header line: add it to the list
- self::addResponseHeaders([$data], $info, $headers);
+ self::addResponseHeaders([substr($data, 0, -2)], $info, $headers);
if (!str_starts_with($data, 'HTTP/')) {
if (0 === stripos($data, 'Location:')) {
- $location = trim(substr($data, 9));
+ $location = trim(substr($data, 9, -2));
}
return \strlen($data);
@@ -416,7 +416,7 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
// End of headers: handle informational responses, redirects, etc.
- if (200 > $statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE)) {
+ if (200 > $statusCode) {
$multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers);
$location = null;
diff --git a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php
index 1f48be5c574c2..ba8fcbe3d68eb 100644
--- a/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/HttplugClientTest.php
@@ -267,4 +267,22 @@ function (\Exception $exception) use ($errorMessage, &$failureCallableCalled, $c
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('OK', (string) $response->getBody());
}
+
+ public function testInvalidHeaderResponse()
+ {
+ $responseHeaders = [
+ // space in header name not allowed in RFC 7230
+ ' X-XSS-Protection' => '0',
+ 'Cache-Control' => 'no-cache',
+ ];
+ $response = new MockResponse('body', ['response_headers' => $responseHeaders]);
+ $this->assertArrayHasKey(' x-xss-protection', $response->getHeaders());
+
+ $client = new HttplugClient(new MockHttpClient($response));
+ $request = $client->createRequest('POST', 'http://localhost:8057/post')
+ ->withBody($client->createStream('foo=0123456789'));
+
+ $resultResponse = $client->sendRequest($request);
+ $this->assertCount(1, $resultResponse->getHeaders());
+ }
}
diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json
index 57d31c12dfd8c..7f546b3a23981 100644
--- a/src/Symfony/Component/HttpClient/composer.json
+++ b/src/Symfony/Component/HttpClient/composer.json
@@ -38,6 +38,7 @@
"guzzlehttp/promises": "^1.4",
"nyholm/psr7": "^1.0",
"php-http/httplug": "^1.0|^2.0",
+ "php-http/message-factory": "^1.0",
"psr/http-client": "^1.0",
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
"symfony/http-kernel": "^4.4.13|^5.1.5|^6.0",
diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php
index 03cf1a23ee3d8..d3caa36aa08ff 100644
--- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php
+++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php
@@ -349,7 +349,7 @@ public function sendContent()
while ('' !== $data) {
$read = fwrite($out, $data);
if (false === $read || connection_aborted()) {
- break;
+ break 2;
}
if (0 < $length) {
$length -= $read;
diff --git a/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php b/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php
index 2057dd7097cc8..080b5ffea3482 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/UrlHelperTest.php
@@ -16,6 +16,7 @@
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\UrlHelper;
use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\RequestContextAwareInterface;
class UrlHelperTest extends TestCase
{
@@ -64,11 +65,46 @@ public function testGenerateAbsoluteUrlWithRequestContext($path, $baseUrl, $host
}
$requestContext = new RequestContext($baseUrl, 'GET', $host, $scheme, $httpPort, $httpsPort, $path);
+
$helper = new UrlHelper(new RequestStack(), $requestContext);
$this->assertEquals($expected, $helper->getAbsoluteUrl($path));
}
+ /**
+ * @dataProvider getGenerateAbsoluteUrlRequestContextData
+ */
+ public function testGenerateAbsoluteUrlWithRequestContextAwareInterface($path, $baseUrl, $host, $scheme, $httpPort, $httpsPort, $expected)
+ {
+ if (!class_exists(RequestContext::class)) {
+ $this->markTestSkipped('The Routing component is needed to run tests that depend on its request context.');
+ }
+
+ $requestContext = new RequestContext($baseUrl, 'GET', $host, $scheme, $httpPort, $httpsPort, $path);
+ $contextAware = new class($requestContext) implements RequestContextAwareInterface {
+ private $requestContext;
+
+ public function __construct($requestContext)
+ {
+ $this->requestContext = $requestContext;
+ }
+
+ public function setContext(RequestContext $context)
+ {
+ $this->requestContext = $context;
+ }
+
+ public function getContext()
+ {
+ return $this->requestContext;
+ }
+ };
+
+ $helper = new UrlHelper(new RequestStack(), $contextAware);
+
+ $this->assertEquals($expected, $helper->getAbsoluteUrl($path));
+ }
+
/**
* @dataProvider getGenerateAbsoluteUrlRequestContextData
*/
diff --git a/src/Symfony/Component/HttpFoundation/UrlHelper.php b/src/Symfony/Component/HttpFoundation/UrlHelper.php
index c15f101cdf80b..90659947dba6d 100644
--- a/src/Symfony/Component/HttpFoundation/UrlHelper.php
+++ b/src/Symfony/Component/HttpFoundation/UrlHelper.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\RequestContextAwareInterface;
/**
* A helper service for manipulating URLs within and outside the request scope.
@@ -23,8 +24,15 @@ final class UrlHelper
private $requestStack;
private $requestContext;
- public function __construct(RequestStack $requestStack, RequestContext $requestContext = null)
+ /**
+ * @param RequestContextAwareInterface|RequestContext|null $requestContext
+ */
+ public function __construct(RequestStack $requestStack, $requestContext = null)
{
+ if (null !== $requestContext && !$requestContext instanceof RequestContext && !$requestContext instanceof RequestContextAwareInterface) {
+ throw new \TypeError(__METHOD__.': Argument #2 ($requestContext) must of type Symfony\Component\Routing\RequestContextAwareInterface|Symfony\Component\Routing\RequestContext|null, '.get_debug_type($requestContext).' given.');
+ }
+
$this->requestStack = $requestStack;
$this->requestContext = $requestContext;
}
@@ -73,28 +81,36 @@ public function getRelativePath(string $path): string
private function getAbsoluteUrlFromContext(string $path): string
{
- if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) {
+ if (null === $context = $this->requestContext) {
+ return $path;
+ }
+
+ if ($context instanceof RequestContextAwareInterface) {
+ $context = $context->getContext();
+ }
+
+ if ('' === $host = $context->getHost()) {
return $path;
}
- $scheme = $this->requestContext->getScheme();
+ $scheme = $context->getScheme();
$port = '';
- if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) {
- $port = ':'.$this->requestContext->getHttpPort();
- } elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) {
- $port = ':'.$this->requestContext->getHttpsPort();
+ if ('http' === $scheme && 80 !== $context->getHttpPort()) {
+ $port = ':'.$context->getHttpPort();
+ } elseif ('https' === $scheme && 443 !== $context->getHttpsPort()) {
+ $port = ':'.$context->getHttpsPort();
}
if ('#' === $path[0]) {
- $queryString = $this->requestContext->getQueryString();
- $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path;
+ $queryString = $context->getQueryString();
+ $path = $context->getPathInfo().($queryString ? '?'.$queryString : '').$path;
} elseif ('?' === $path[0]) {
- $path = $this->requestContext->getPathInfo().$path;
+ $path = $context->getPathInfo().$path;
}
if ('/' !== $path[0]) {
- $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path;
+ $path = rtrim($context->getBaseUrl(), '/').'/'.$path;
}
return $scheme.'://'.$host.$port.$path;
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php b/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php
index f2d809e8de97d..e1d73dc74827d 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php
@@ -133,4 +133,15 @@ protected function removeFromControl(Response $response)
$response->headers->set('Surrogate-Control', preg_replace(sprintf('#content="%s/1.0",\s*#', $upperName), '', $value));
}
}
+
+ protected static function generateBodyEvalBoundary(): string
+ {
+ static $cookie;
+ $cookie = hash('md5', $cookie ?? $cookie = random_bytes(16), true);
+ $boundary = base64_encode($cookie);
+
+ \assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === \strlen($boundary));
+
+ return $boundary;
+ }
}
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php
index cd6a00a10d61f..9f453249325b2 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php
@@ -80,8 +80,8 @@ public function process(Request $request, Response $response)
$content = preg_replace('#.*?#s', '', $content);
$content = preg_replace('#]+>#s', '', $content);
+ $boundary = self::generateBodyEvalBoundary();
$chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE);
- $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]);
$i = 1;
while (isset($chunks[$i])) {
@@ -95,16 +95,10 @@ public function process(Request $request, Response $response)
throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.');
}
- $chunks[$i] = sprintf('surrogate->handle($this, %s, %s, %s) ?>'."\n",
- var_export($options['src'], true),
- var_export($options['alt'] ?? '', true),
- isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false'
- );
- ++$i;
- $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]);
- ++$i;
+ $chunks[$i] = $boundary.$options['src']."\n".($options['alt'] ?? '')."\n".('continue' === ($options['onerror'] ?? ''))."\n";
+ $i += 2;
}
- $content = implode('', $chunks);
+ $content = $boundary.implode('', $chunks).$boundary;
$response->setContent($content);
$response->headers->set('X-Body-Eval', 'ESI');
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php
index 5688fc0c13ccd..b01bd722607a9 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php
@@ -29,6 +29,8 @@
*/
class HttpCache implements HttpKernelInterface, TerminableInterface
{
+ public const BODY_EVAL_BOUNDARY_LENGTH = 24;
+
private $kernel;
private $store;
private $request;
@@ -631,12 +633,22 @@ protected function store(Request $request, Response $response)
private function restoreResponseBody(Request $request, Response $response)
{
if ($response->headers->has('X-Body-Eval')) {
+ \assert(self::BODY_EVAL_BOUNDARY_LENGTH === 24);
+
ob_start();
- if ($response->headers->has('X-Body-File')) {
- include $response->headers->get('X-Body-File');
- } else {
- eval('; ?>'.$response->getContent().'getContent();
+ $boundary = substr($content, 0, 24);
+ $j = strpos($content, $boundary, 24);
+ echo substr($content, 24, $j - 24);
+ $i = $j + 24;
+
+ while (false !== $j = strpos($content, $boundary, $i)) {
+ [$uri, $alt, $ignoreErrors, $part] = explode("\n", substr($content, $i, $j - $i), 4);
+ $i = $j + 24;
+
+ echo $this->surrogate->handle($this, $uri, $alt, $ignoreErrors);
+ echo $part;
}
$response->setContent(ob_get_clean());
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php
index cf8682257ee8f..5f372c566dd44 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php
@@ -147,7 +147,7 @@ public function update(Response $response)
if (is_numeric($this->ageDirectives['expires'])) {
$date = clone $response->getDate();
- $date->modify('+'.($this->ageDirectives['expires'] + $this->age).' seconds');
+ $date = $date->modify('+'.($this->ageDirectives['expires'] + $this->age).' seconds');
$response->setExpires($date);
}
}
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php
index f114e05cfb2f6..61909100e6157 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php
@@ -64,9 +64,8 @@ public function process(Request $request, Response $response)
// we don't use a proper XML parser here as we can have SSI tags in a plain text response
$content = $response->getContent();
-
+ $boundary = self::generateBodyEvalBoundary();
$chunks = preg_split('##', $content, -1, \PREG_SPLIT_DELIM_CAPTURE);
- $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]);
$i = 1;
while (isset($chunks[$i])) {
@@ -80,14 +79,10 @@ public function process(Request $request, Response $response)
throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.');
}
- $chunks[$i] = sprintf('surrogate->handle($this, %s, \'\', false) ?>'."\n",
- var_export($options['virtual'], true)
- );
- ++$i;
- $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]);
- ++$i;
+ $chunks[$i] = $boundary.$options['virtual']."\n\n\n";
+ $i += 2;
}
- $content = implode('', $chunks);
+ $content = $boundary.implode('', $chunks).$boundary;
$response->setContent($content);
$response->headers->set('X-Body-Eval', 'SSI');
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php
index 5db94f73d68c2..9d7f3e4f6949d 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php
@@ -475,15 +475,25 @@ private function persistResponse(Response $response): array
/**
* Restores a Response from the HTTP headers and body.
*/
- private function restoreResponse(array $headers, string $path = null): Response
+ private function restoreResponse(array $headers, string $path = null): ?Response
{
$status = $headers['X-Status'][0];
unset($headers['X-Status']);
+ $content = null;
if (null !== $path) {
$headers['X-Body-File'] = [$path];
+ unset($headers['x-body-file']);
+
+ if ($headers['X-Body-Eval'] ?? $headers['x-body-eval'] ?? false) {
+ $content = file_get_contents($path);
+ \assert(HttpCache::BODY_EVAL_BOUNDARY_LENGTH === 24);
+ if (48 > \strlen($content) || substr($content, -24) !== substr($content, 0, 24)) {
+ return null;
+ }
+ }
}
- return new Response($path, $status, $headers);
+ return new Response($content, $status, $headers);
}
}
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index 7006a45cf2f49..f44eac93104ea 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -78,11 +78,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static $freshCache = [];
- public const VERSION = '5.4.23';
- public const VERSION_ID = 50423;
+ public const VERSION = '5.4.24';
+ public const VERSION_ID = 50424;
public const MAJOR_VERSION = 5;
public const MINOR_VERSION = 4;
- public const RELEASE_VERSION = 23;
+ public const RELEASE_VERSION = 24;
public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '11/2024';
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php
index 290bd94bdcb97..e876f28189087 100644
--- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php
@@ -102,7 +102,7 @@ public function testMultilineEsiRemoveTagsAreRemoved()
$response = new Response(' Keep this'." And this");
$this->assertSame($response, $esi->process($request, $response));
- $this->assertEquals(' Keep this And this', $response->getContent());
+ $this->assertEquals(' Keep this And this', substr($response->getContent(), 24, -24));
}
public function testCommentTagsAreRemoved()
@@ -113,7 +113,7 @@ public function testCommentTagsAreRemoved()
$response = new Response(' Keep this');
$this->assertSame($response, $esi->process($request, $response));
- $this->assertEquals(' Keep this', $response->getContent());
+ $this->assertEquals(' Keep this', substr($response->getContent(), 24, -24));
}
public function testProcess()
@@ -124,23 +124,27 @@ public function testProcess()
$response = new Response('foo ');
$this->assertSame($response, $esi->process($request, $response));
- $this->assertEquals('foo surrogate->handle($this, \'...\', \'alt\', true) ?>'."\n", $response->getContent());
+ $content = explode(substr($response->getContent(), 0, 24), $response->getContent());
+ $this->assertSame(['', 'foo ', "...\nalt\n1\n", ''], $content);
$this->assertEquals('ESI', $response->headers->get('x-body-eval'));
$response = new Response('foo ');
$this->assertSame($response, $esi->process($request, $response));
- $this->assertEquals('foo surrogate->handle($this, \'foo\\\'\', \'bar\\\'\', true) ?>'."\n", $response->getContent());
+ $content = explode(substr($response->getContent(), 0, 24), $response->getContent());
+ $this->assertSame(['', 'foo ', "foo'\nbar'\n1\n", ''], $content);
$response = new Response('foo ');
$this->assertSame($response, $esi->process($request, $response));
- $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
+ $content = explode(substr($response->getContent(), 0, 24), $response->getContent());
+ $this->assertSame(['', 'foo ', "...\n\n\n", ''], $content);
$response = new Response('foo ');
$this->assertSame($response, $esi->process($request, $response));
- $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
+ $content = explode(substr($response->getContent(), 0, 24), $response->getContent());
+ $this->assertSame(['', 'foo ', "...\n\n\n", ''], $content);
}
public function testProcessEscapesPhpTags()
@@ -151,7 +155,8 @@ public function testProcessEscapesPhpTags()
$response = new Response('');
$this->assertSame($response, $esi->process($request, $response));
- $this->assertEquals('php cript language=php>', $response->getContent());
+ $content = explode(substr($response->getContent(), 0, 24), $response->getContent());
+ $this->assertSame(['', '', ''], $content);
}
public function testProcessWhenNoSrcInAnEsi()
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php
index e47631d1780ea..c8b48ff811c76 100644
--- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php
@@ -18,7 +18,7 @@
use Symfony\Component\HttpKernel\HttpCache\Store;
use Symfony\Component\HttpKernel\HttpKernelInterface;
-class HttpCacheTestCase extends TestCase
+abstract class HttpCacheTestCase extends TestCase
{
protected $kernel;
protected $cache;
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php
index c5c510f85832e..ce9f5ba1a158a 100644
--- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php
@@ -257,7 +257,7 @@ public function testCacheControlMerging(array $expects, array $master, array $su
case 'expires':
$expires = clone $response->getDate();
- $expires->modify('+'.$value.' seconds');
+ $expires = $expires->modify('+'.$value.' seconds');
$response->setExpires($expires);
break;
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php
index a1f1f1593d3f3..97cc8fccd03d0 100644
--- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php
@@ -101,13 +101,15 @@ public function testProcess()
$response = new Response('foo ');
$ssi->process($request, $response);
- $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
+ $content = explode(substr($response->getContent(), 0, 24), $response->getContent());
+ $this->assertSame(['', 'foo ', "...\n\n\n", ''], $content);
$this->assertEquals('SSI', $response->headers->get('x-body-eval'));
$response = new Response('foo ');
$ssi->process($request, $response);
- $this->assertEquals("foo surrogate->handle(\$this, 'foo\\'', '', false) ?>\n", $response->getContent());
+ $content = explode(substr($response->getContent(), 0, 24), $response->getContent());
+ $this->assertSame(['', 'foo ', "foo'\n\n\n", ''], $content);
}
public function testProcessEscapesPhpTags()
@@ -118,7 +120,8 @@ public function testProcessEscapesPhpTags()
$response = new Response('');
$ssi->process($request, $response);
- $this->assertEquals('php cript language=php>', $response->getContent());
+ $content = explode(substr($response->getContent(), 0, 24), $response->getContent());
+ $this->assertSame(['', '', ''], $content);
}
public function testProcessWhenNoSrcInAnSsi()
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php
index 239361bc8c337..aff5329cc96f8 100644
--- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php
@@ -200,7 +200,7 @@ public function testRestoresResponseContentFromEntityStoreWithLookup()
{
$this->storeSimpleEntry();
$response = $this->store->lookup($this->request);
- $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test')), $response->getContent());
+ $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test')), $response->headers->get('X-Body-File'));
}
public function testInvalidatesMetaAndEntityStoreEntriesWithInvalidate()
@@ -253,9 +253,9 @@ public function testStoresMultipleResponsesForEachVaryCombination()
$res3 = new Response('test 3', 200, ['Vary' => 'Foo Bar']);
$this->store->write($req3, $res3);
- $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->getContent());
- $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->getContent());
- $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent());
+ $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->headers->get('X-Body-File'));
+ $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->headers->get('X-Body-File'));
+ $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->headers->get('X-Body-File'));
$this->assertCount(3, $this->getStoreMetadata($key));
}
@@ -265,17 +265,17 @@ public function testOverwritesNonVaryingResponseWithStore()
$req1 = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar']);
$res1 = new Response('test 1', 200, ['Vary' => 'Foo Bar']);
$this->store->write($req1, $res1);
- $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent());
+ $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->headers->get('X-Body-File'));
$req2 = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam']);
$res2 = new Response('test 2', 200, ['Vary' => 'Foo Bar']);
$this->store->write($req2, $res2);
- $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->getContent());
+ $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->headers->get('X-Body-File'));
$req3 = Request::create('/test', 'get', [], [], [], ['HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar']);
$res3 = new Response('test 3', 200, ['Vary' => 'Foo Bar']);
$key = $this->store->write($req3, $res3);
- $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->getContent());
+ $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->headers->get('X-Body-File'));
$this->assertCount(2, $this->getStoreMetadata($key));
}
@@ -330,6 +330,33 @@ public function testDoesNotStorePrivateHeaders()
$this->assertNotEmpty($response->headers->getCookies());
}
+ public function testDiscardsInvalidBodyEval()
+ {
+ $request = Request::create('https://example.com/foo');
+ $response = new Response('foo', 200, ['X-Body-Eval' => 'SSI']);
+
+ $this->store->write($request, $response);
+ $this->assertNull($this->store->lookup($request));
+
+ $request = Request::create('https://example.com/foo');
+ $content = str_repeat('a', 24).'b'.str_repeat('a', 24).'b';
+ $response = new Response($content, 200, ['X-Body-Eval' => 'SSI']);
+
+ $this->store->write($request, $response);
+ $this->assertNull($this->store->lookup($request));
+ }
+
+ public function testLoadsBodyEval()
+ {
+ $request = Request::create('https://example.com/foo');
+ $content = str_repeat('a', 24).'b'.str_repeat('a', 24);
+ $response = new Response($content, 200, ['X-Body-Eval' => 'SSI']);
+
+ $this->store->write($request, $response);
+ $response = $this->store->lookup($request);
+ $this->assertSame($content, $response->getContent());
+ }
+
protected function storeSimpleEntry($path = null, $headers = [])
{
if (null === $path) {
diff --git a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php
index f423889713972..4a4e00c81dcbf 100644
--- a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php
+++ b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php
@@ -143,7 +143,7 @@ private function registerHandlers(ContainerBuilder $container, array $busIds)
}
if ('__invoke' !== $method) {
- $wrapperDefinition = (new Definition('callable'))->addArgument([new Reference($serviceId), $method])->setFactory('Closure::fromCallable');
+ $wrapperDefinition = (new Definition('Closure'))->addArgument([new Reference($serviceId), $method])->setFactory('Closure::fromCallable');
$definitions[$definitionId = '.messenger.method_on_object_wrapper.'.ContainerBuilder::hash($message.':'.$priority.':'.$serviceId.':'.$method)] = $wrapperDefinition;
} else {
diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php
index dab74b203f795..ddd7f1b1f61b0 100644
--- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php
+++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php
@@ -123,7 +123,8 @@ public static function getSubscribedEvents()
private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInterface $retryStrategy): bool
{
- if ($e instanceof RecoverableExceptionInterface) {
+ $isRetryable = $retryStrategy->isRetryable($envelope, $e);
+ if ($isRetryable && $e instanceof RecoverableExceptionInterface) {
return true;
}
@@ -132,7 +133,7 @@ private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInt
if ($e instanceof HandlerFailedException) {
$shouldNotRetry = true;
foreach ($e->getNestedExceptions() as $nestedException) {
- if ($nestedException instanceof RecoverableExceptionInterface) {
+ if ($isRetryable && $nestedException instanceof RecoverableExceptionInterface) {
return true;
}
@@ -150,7 +151,7 @@ private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInt
return false;
}
- return $retryStrategy->isRetryable($envelope, $e);
+ return $isRetryable;
}
private function getRetryStrategyForTransport(string $alias): ?RetryStrategyInterface
diff --git a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php
index 3d29497d34c1a..3411b0bbef482 100644
--- a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php
+++ b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php
@@ -255,7 +255,7 @@ public function testGetClassesAndMethodsAndPrioritiesFromTheSubscriber()
$dummyHandlerReference = $dummyHandlerDescriptorDefinition->getArgument(0);
$dummyHandlerDefinition = $container->getDefinition($dummyHandlerReference);
- $this->assertSame('callable', $dummyHandlerDefinition->getClass());
+ $this->assertSame('Closure', $dummyHandlerDefinition->getClass());
$this->assertEquals([new Reference(HandlerMappingMethods::class), 'dummyMethod'], $dummyHandlerDefinition->getArgument(0));
$this->assertSame(['Closure', 'fromCallable'], $dummyHandlerDefinition->getFactory());
diff --git a/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php b/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php
index a5fe10e85578b..8d795d7b86c23 100644
--- a/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php
+++ b/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php
@@ -63,7 +63,7 @@ public function testRecoverableStrategyCausesRetry()
$senderLocator->expects($this->once())->method('has')->willReturn(true);
$senderLocator->expects($this->once())->method('get')->willReturn($sender);
$retryStategy = $this->createMock(RetryStrategyInterface::class);
- $retryStategy->expects($this->never())->method('isRetryable');
+ $retryStategy->expects($this->once())->method('isRetryable')->willReturn(true);
$retryStategy->expects($this->once())->method('getWaitingTime')->willReturn(1000);
$retryStrategyLocator = $this->createMock(ContainerInterface::class);
$retryStrategyLocator->expects($this->once())->method('has')->willReturn(true);
@@ -78,6 +78,27 @@ public function testRecoverableStrategyCausesRetry()
$listener->onMessageFailed($event);
}
+ public function testRetryIsOnlyAllowedWhenPermittedByRetryStrategy()
+ {
+ $senderLocator = $this->createMock(ContainerInterface::class);
+ $senderLocator->expects($this->never())->method('has');
+ $senderLocator->expects($this->never())->method('get');
+ $retryStrategy = $this->createMock(RetryStrategyInterface::class);
+ $retryStrategy->expects($this->once())->method('isRetryable')->willReturn(false);
+ $retryStrategy->expects($this->never())->method('getWaitingTime');
+ $retryStrategyLocator = $this->createMock(ContainerInterface::class);
+ $retryStrategyLocator->expects($this->once())->method('has')->willReturn(true);
+ $retryStrategyLocator->expects($this->once())->method('get')->willReturn($retryStrategy);
+
+ $listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator);
+
+ $exception = new RecoverableMessageHandlingException('retry');
+ $envelope = new Envelope(new \stdClass());
+ $event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception);
+
+ $listener->onMessageFailed($event);
+ }
+
public function testEnvelopeIsSentToTransportOnRetry()
{
$exception = new \Exception('no!');
diff --git a/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsOptions.php b/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsOptions.php
index fe82d23972325..9e482c156e78b 100644
--- a/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsOptions.php
+++ b/src/Symfony/Component/Notifier/Bridge/AmazonSns/AmazonSnsOptions.php
@@ -52,7 +52,7 @@ public function recipient(string $topic): self
}
/**
- * @see PublishInput::$Subject
+ * @see PublishInput::$subject
*
* @return $this
*/
@@ -64,7 +64,7 @@ public function subject(string $subject): self
}
/**
- * @see PublishInput::$MessageStructure
+ * @see PublishInput::$messageStructure
*
* @return $this
*/
diff --git a/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md b/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md
index 3dbcbb1546247..db4759327f502 100644
--- a/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md
+++ b/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md
@@ -1,7 +1,7 @@
Amazon Notifier
===============
-Provides [Amazon SNS](https://aws.amazon.com/de/sns/) integration for Symfony Notifier.
+Provides [Amazon SNS](https://aws.amazon.com/en/sns/) integration for Symfony Notifier.
DSN example
-----------
@@ -10,6 +10,30 @@ DSN example
AMAZON_SNS_DSN=sns://ACCESS_ID:ACCESS_KEY@default?region=REGION
```
+Adding Options to a Chat Message
+--------------------------------
+
+With an Amazon SNS Chat Message, you can use the `AmazonSnsOptions` class to add
+message options.
+
+```php
+use Symfony\Component\Notifier\Message\ChatMessage;
+use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsOptions;
+
+$chatMessage = new ChatMessage('Contribute To Symfony');
+
+$options = (new AmazonSnsOptions('topic_arn'))
+ ->subject('subject')
+ ->messageStructure('json')
+ // ...
+ ;
+
+// Add the custom options to the chat message and send the message
+$chatMessage->options($options);
+
+$chatter->send($chatMessage);
+```
+
Resources
---------
diff --git a/src/Symfony/Component/Notifier/Bridge/Mobyt/README.md b/src/Symfony/Component/Notifier/Bridge/Mobyt/README.md
index 2d9b0b6d1488d..d9760759c9f90 100644
--- a/src/Symfony/Component/Notifier/Bridge/Mobyt/README.md
+++ b/src/Symfony/Component/Notifier/Bridge/Mobyt/README.md
@@ -16,6 +16,29 @@ where:
- `FROM` is the sender
- `TYPE_QUALITY` is the quality of your message: `N` for high, `L` for medium, `LL` for low (default: `L`)
+Adding Options to a Message
+---------------------------
+
+With a Mobyt Message, you can use the `MobytOptions` class to add
+[message options](https://gatewayapi.com/docs/apis/rest/).
+
+```php
+use Symfony\Component\Notifier\Message\SmsMessage;
+use Symfony\Component\Notifier\Bridge\Mobyt\MobytOptions;
+
+$sms = new SmsMessage('+1411111111', 'My message');
+
+$options = (new MobytOptions())
+ ->messageType(MobytOptions::MESSAGE_TYPE_QUALITY_HIGH)
+ // ...
+ ;
+
+// Add the custom options to the sms message and send the message
+$sms->options($options);
+
+$texter->send($sms);
+```
+
Resources
---------
diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php
index b47ecca17bfe5..9b19475ac5d78 100644
--- a/src/Symfony/Component/Process/Process.php
+++ b/src/Symfony/Component/Process/Process.php
@@ -428,7 +428,7 @@ public function wait(callable $callback = null)
do {
$this->checkTimeout();
- $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen());
$this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
} while ($running);
diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php
index 790167fcade94..36acf02a7cd6b 100644
--- a/src/Symfony/Component/Process/Tests/ProcessTest.php
+++ b/src/Symfony/Component/Process/Tests/ProcessTest.php
@@ -1538,6 +1538,16 @@ public function testEnvCaseInsensitiveOnWindows()
}
}
+ public function testNotTerminableInputPipe()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->setInput(\STDIN);
+ $process->start();
+ $process->setTimeout(2);
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ }
+
/**
* @param string|array $commandline
* @param mixed $input
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php
index 52a43d16aee46..a964b5036893b 100644
--- a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php
@@ -171,7 +171,10 @@ private function getDocBlockFromConstructor(string $class, string $property): ?P
return null;
}
- $rawDocNode = $reflectionConstructor->getDocComment();
+ if (!$rawDocNode = $reflectionConstructor->getDocComment()) {
+ return null;
+ }
+
$tokens = new TokenIterator($this->lexer->tokenize($rawDocNode));
$phpDocNode = $this->phpDocParser->parse($tokens);
$tokens->consumeTokenType(Lexer::TOKEN_END);
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
index c6a02b5f2f3e4..b3489d9fb0c10 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
@@ -169,7 +169,7 @@ public function testExtractCollection($property, array $type = null, $shortDescr
public static function provideCollectionTypes()
{
return [
- ['iteratorCollection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, null, new Type(Type::BUILTIN_TYPE_STRING))], null, null],
+ ['iteratorCollection', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)], new Type(Type::BUILTIN_TYPE_STRING))], null, null],
['iteratorCollectionWithKey', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], null, null],
[
'nestedIterators',
@@ -265,6 +265,8 @@ public static function typesWithCustomPrefixesProvider()
['i', [new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)], null, null],
['j', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTime')], null, null],
['nullableCollectionOfNonNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, false))], null, null],
+ ['nonNullableCollectionOfNullableElements', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT, true))], null, null],
+ ['nullableCollectionOfMultipleNonNullableElementTypes', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, new Type(Type::BUILTIN_TYPE_INT), [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)])], null, null],
['donotexist', null, null, null],
['staticGetter', null, null, null],
['staticSetter', null, null, null],
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
index a517e7ac30469..c607f2abc3761 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
@@ -14,6 +14,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
+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\ParentDummy;
@@ -353,6 +354,14 @@ public function testExtractConstructorTypes($property, array $type = null)
$this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property));
}
+ /**
+ * @dataProvider constructorTypesProvider
+ */
+ public function testExtractConstructorTypesReturnNullOnEmptyDocBlock($property)
+ {
+ $this->assertNull($this->extractor->getTypesFromConstructor(ConstructorDummyWithoutDocBlock::class, $property));
+ }
+
public static function constructorTypesProvider()
{
return [
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
index f33ad70c4bdf8..c8bb2758f2b50 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
@@ -62,6 +62,8 @@ public function testGetProperties()
'i',
'j',
'nullableCollectionOfNonNullableElements',
+ 'nonNullableCollectionOfNullableElements',
+ 'nullableCollectionOfMultipleNonNullableElementTypes',
'emptyVar',
'iteratorCollection',
'iteratorCollectionWithKey',
@@ -124,6 +126,8 @@ public function testGetPropertiesWithCustomPrefixes()
'i',
'j',
'nullableCollectionOfNonNullableElements',
+ 'nonNullableCollectionOfNullableElements',
+ 'nullableCollectionOfMultipleNonNullableElementTypes',
'emptyVar',
'iteratorCollection',
'iteratorCollectionWithKey',
@@ -175,6 +179,8 @@ public function testGetPropertiesWithNoPrefixes()
'i',
'j',
'nullableCollectionOfNonNullableElements',
+ 'nonNullableCollectionOfNullableElements',
+ 'nullableCollectionOfMultipleNonNullableElementTypes',
'emptyVar',
'iteratorCollection',
'iteratorCollectionWithKey',
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithoutDocBlock.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithoutDocBlock.php
new file mode 100644
index 0000000000000..7e2087d467f7d
--- /dev/null
+++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithoutDocBlock.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
+
+class ConstructorDummyWithoutDocBlock
+{
+ public function __construct(\DateTimeZone $timezone, $date, $dateObject, \DateTimeImmutable $dateTime, $mixed)
+ {
+ }
+}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php
index 8d956a1103fc0..2fb3d2e0f807c 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php
@@ -98,6 +98,16 @@ class Dummy extends ParentDummy
*/
public $nullableCollectionOfNonNullableElements;
+ /**
+ * @var array
+ */
+ public $nonNullableCollectionOfNullableElements;
+
+ /**
+ * @var null|array
+ */
+ public $nullableCollectionOfMultipleNonNullableElementTypes;
+
/**
* @var array
*/
diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
index 2c858c3bf9f8b..44a4614985563 100644
--- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
+++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
@@ -115,15 +115,10 @@ private function createType(DocType $type, bool $nullable, string $docType = nul
[$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen);
- $key = $this->getTypes($type->getKeyType());
- $value = $this->getTypes($type->getValueType());
+ $keys = $this->getTypes($type->getKeyType());
+ $values = $this->getTypes($type->getValueType());
- // More than 1 type returned means it is a Compound type, which is
- // not handled by Type, so better use a null value.
- $key = 1 === \count($key) ? $key[0] : null;
- $value = 1 === \count($value) ? $value[0] : null;
-
- return new Type($phpType, $nullable, $class, true, $key, $value);
+ return new Type($phpType, $nullable, $class, true, $keys, $values);
}
// Cannot guess
@@ -131,27 +126,20 @@ private function createType(DocType $type, bool $nullable, string $docType = nul
return null;
}
- if (str_ends_with($docType, '[]')) {
- $collectionKeyType = new Type(Type::BUILTIN_TYPE_INT);
- $collectionValueType = $this->createType($type, false, substr($docType, 0, -2));
+ if (str_ends_with($docType, '[]') && $type instanceof Array_) {
+ $collectionKeyTypes = new Type(Type::BUILTIN_TYPE_INT);
+ $collectionValueTypes = $this->getTypes($type->getValueType());
- return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType);
+ return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes);
}
if ((str_starts_with($docType, 'list<') || str_starts_with($docType, 'array<')) && $type instanceof Array_) {
// array is converted to x[] which is handled above
// so it's only necessary to handle array here
- $collectionKeyType = $this->getTypes($type->getKeyType())[0];
-
+ $collectionKeyTypes = $this->getTypes($type->getKeyType());
$collectionValueTypes = $this->getTypes($type->getValueType());
- if (1 != \count($collectionValueTypes)) {
- // the Type class does not support union types yet, so assume that no type was defined
- $collectionValueType = null;
- } else {
- $collectionValueType = $collectionValueTypes[0];
- }
- return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType);
+ return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes);
}
$docType = $this->normalizeType($docType);
diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
index efd8cbb567637..bd41b8da6fa72 100644
--- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
@@ -342,6 +342,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex
$constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes);
if ($constructor) {
+ $context['has_constructor'] = true;
if (true !== $constructor->isPublic()) {
return $reflectionClass->newInstanceWithoutConstructor();
}
@@ -431,6 +432,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex
}
}
+ unset($context['has_constructor']);
+
return new $class();
}
diff --git a/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php
index 21fac3248cd6e..e7efb0057c09f 100644
--- a/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php
@@ -64,7 +64,11 @@ public function denormalize($data, string $type, string $format = null, array $c
try {
return $type::from($data);
} catch (\ValueError $e) {
- throw new InvalidArgumentException('The data must belong to a backed enumeration of type '.$type);
+ if (isset($context['has_constructor'])) {
+ throw new InvalidArgumentException('The data must belong to a backed enumeration of type '.$type);
+ }
+
+ throw NotNormalizableValueException::createForUnexpectedDataType('The data must belong to a backed enumeration of type '.$type, $data, [$type], $context['deserialization_path'] ?? null, true, 0, $e);
}
}
diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
index 71df2e6bdfb7c..9736d8e78e4a0 100644
--- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
@@ -91,7 +91,14 @@ public function denormalize($data, string $type, string $format = null, array $c
$dateTimeFormat = $context[self::FORMAT_KEY] ?? null;
$timezone = $this->getTimezone($context);
- if (null === $data || !\is_string($data) || '' === trim($data)) {
+ if (\is_int($data) || \is_float($data)) {
+ switch ($dateTimeFormat) {
+ case 'U': $data = sprintf('%d', $data); break;
+ case 'U.u': $data = sprintf('%.6F', $data); break;
+ }
+ }
+
+ if (!\is_string($data) || '' === trim($data)) {
throw NotNormalizableValueException::createForUnexpectedDataType('The data is either not an string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.', $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
}
diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyObjectWithEnumProperty.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyObjectWithEnumProperty.php
new file mode 100644
index 0000000000000..f2677195f2820
--- /dev/null
+++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyObjectWithEnumProperty.php
@@ -0,0 +1,10 @@
+expectException(InvalidArgumentException::class);
+ $this->expectException(NotNormalizableValueException::class);
$this->expectExceptionMessage('The data must belong to a backed enumeration of type '.StringBackedEnumDummy::class);
$this->normalizer->denormalize('POST', StringBackedEnumDummy::class);
diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php
index 674dfaab5382d..25b7c784fe0e2 100644
--- a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php
@@ -178,6 +178,8 @@ public function testDenormalize()
$this->assertEquals(new \DateTimeImmutable('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTimeImmutable::class));
$this->assertEquals(new \DateTime('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTime::class));
$this->assertEquals(new \DateTime('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize(' 2016-01-01T00:00:00+00:00 ', \DateTime::class));
+ $this->assertEquals(new \DateTimeImmutable('2023-05-06T17:35:34.000000+0000', new \DateTimeZone('UTC')), $this->normalizer->denormalize(1683394534, \DateTimeImmutable::class, null, [DateTimeNormalizer::FORMAT_KEY => 'U']));
+ $this->assertEquals(new \DateTimeImmutable('2023-05-06T17:35:34.123400+0000', new \DateTimeZone('UTC')), $this->normalizer->denormalize(1683394534.1234, \DateTimeImmutable::class, null, [DateTimeNormalizer::FORMAT_KEY => 'U.u']));
}
public function testDenormalizeUsingTimezonePassedInConstructor()
diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php
index fc0b6cc5af876..b4e84132a0858 100644
--- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php
@@ -60,6 +60,7 @@
use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne;
use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberTwo;
use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumConstructor;
+use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty;
use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy;
use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy;
use Symfony\Component\Serializer\Tests\Fixtures\Php74Full;
@@ -1230,7 +1231,51 @@ public function testCollectDenormalizationErrorsWithEnumConstructor()
/**
* @requires PHP 8.1
*/
- public function testNoCollectDenormalizationErrorsWithWrongEnum()
+ public function testCollectDenormalizationErrorsWithWrongPropertyWithoutConstruct()
+ {
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader());
+ $reflectionExtractor = new ReflectionExtractor();
+ $propertyInfoExtractor = new PropertyInfoExtractor([], [$reflectionExtractor], [], [], []);
+
+ $serializer = new Serializer(
+ [
+ new BackedEnumNormalizer(),
+ new ObjectNormalizer($classMetadataFactory, null, null, $propertyInfoExtractor),
+ ],
+ ['json' => new JsonEncoder()]
+ );
+
+ try {
+ $serializer->deserialize('{"get": "POST"}', DummyObjectWithEnumProperty::class, 'json', [
+ DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
+ ]);
+ } catch (\Throwable $e) {
+ $this->assertInstanceOf(PartialDenormalizationException::class, $e);
+ }
+
+ $exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array {
+ return [
+ 'currentType' => $e->getCurrentType(),
+ 'useMessageForUser' => $e->canUseMessageForUser(),
+ 'message' => $e->getMessage(),
+ ];
+ }, $e->getErrors());
+
+ $expected = [
+ [
+ 'currentType' => 'string',
+ 'useMessageForUser' => true,
+ 'message' => 'The data must belong to a backed enumeration of type Symfony\Component\Serializer\Tests\Fixtures\StringBackedEnumDummy',
+ ],
+ ];
+
+ $this->assertSame($expected, $exceptionsAsArray);
+ }
+
+ /**
+ * @requires PHP 8.1
+ */
+ public function testNoCollectDenormalizationErrorsWithWrongEnumOnConstructor()
{
$serializer = new Serializer(
[
@@ -1241,7 +1286,7 @@ public function testNoCollectDenormalizationErrorsWithWrongEnum()
);
try {
- $serializer->deserialize('{"get": "invalid"}', DummyObjectWithEnumConstructor::class, 'json', [
+ $serializer->deserialize('{"get": "POST"}', DummyObjectWithEnumConstructor::class, 'json', [
DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
]);
} catch (\Throwable $th) {
diff --git a/src/Symfony/Component/Translation/Loader/ArrayLoader.php b/src/Symfony/Component/Translation/Loader/ArrayLoader.php
index 0758da8b59a54..feed0de4b0025 100644
--- a/src/Symfony/Component/Translation/Loader/ArrayLoader.php
+++ b/src/Symfony/Component/Translation/Loader/ArrayLoader.php
@@ -46,9 +46,11 @@ private function flatten(array $messages): array
foreach ($messages as $key => $value) {
if (\is_array($value)) {
foreach ($this->flatten($value) as $k => $v) {
- $result[$key.'.'.$k] = $v;
+ if (null !== $v) {
+ $result[$key.'.'.$k] = $v;
+ }
}
- } else {
+ } elseif (null !== $value) {
$result[$key] = $value;
}
}
diff --git a/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php
index 230c02e539e45..cac0b6f87823a 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php
@@ -30,6 +30,15 @@ public function testLoad()
$this->assertEquals([new FileResource($resource)], $catalogue->getResources());
}
+ public function testLoadNonStringMessages()
+ {
+ $loader = new YamlFileLoader();
+ $resource = __DIR__.'/../fixtures/non-string.yml';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertSame(['root.foo2' => '', 'root.bar' => 'bar'], $catalogue->all('domain1'));
+ }
+
public function testLoadDoesNothingIfEmpty()
{
$loader = new YamlFileLoader();
diff --git a/src/Symfony/Component/Translation/Tests/fixtures/non-string.yml b/src/Symfony/Component/Translation/Tests/fixtures/non-string.yml
new file mode 100644
index 0000000000000..41e245e19a4a6
--- /dev/null
+++ b/src/Symfony/Component/Translation/Tests/fixtures/non-string.yml
@@ -0,0 +1,4 @@
+root:
+ foo1:
+ foo2: ''
+ bar: 'bar'
diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json
index 019a46fc15282..3e860daa30e30 100644
--- a/src/Symfony/Component/Validator/composer.json
+++ b/src/Symfony/Component/Validator/composer.json
@@ -49,7 +49,6 @@
"doctrine/annotations": "<1.13",
"doctrine/cache": "<1.11",
"doctrine/lexer": "<1.1",
- "phpunit/phpunit": "<5.4.3",
"symfony/dependency-injection": "<4.4",
"symfony/expression-language": "<5.1",
"symfony/http-kernel": "<4.4",
diff --git a/src/Symfony/Component/VarDumper/composer.json b/src/Symfony/Component/VarDumper/composer.json
index dc46f58d99eca..fc127d721ab16 100644
--- a/src/Symfony/Component/VarDumper/composer.json
+++ b/src/Symfony/Component/VarDumper/composer.json
@@ -28,7 +28,6 @@
"twig/twig": "^2.13|^3.0.4"
},
"conflict": {
- "phpunit/phpunit": "<5.4.3",
"symfony/console": "<4.4"
},
"suggest": {
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy