diff --git a/.travis.yml b/.travis.yml
index fc26dc6491e43..fb4d2846fc053 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -31,6 +31,9 @@ matrix:
- php: nightly
services: [memcached]
fast_finish: true
+ allow_failures:
+ - php: nightly
+ services: [memcached]
cache:
directories:
diff --git a/CHANGELOG-4.4.md b/CHANGELOG-4.4.md
index 20f38b3422f1f..2f51e38874c8f 100644
--- a/CHANGELOG-4.4.md
+++ b/CHANGELOG-4.4.md
@@ -7,6 +7,28 @@ in 4.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/v4.4.0...v4.4.1
+* 4.4.10 (2020-06-12)
+
+ * bug #37227 [DependencyInjection][CheckTypeDeclarationsPass] Handle unresolved parameters pointing to environment variables (fancyweb)
+ * bug #37103 [Form] switch the context when validating nested forms (xabbuh)
+ * bug #37182 [HttpKernel] Fix regression where Store does not return response body correctly (mpdude)
+ * bug #37193 [DependencyInjection][CheckTypeDeclarationsPass] Always resolve parameters (fancyweb)
+ * bug #37191 [HttpClient] fix offset computation for data chunks (nicolas-grekas)
+ * bug #37177 [Ldap] fix refreshUser() ignoring extra_fields (arkste)
+ * bug #37181 [Mailer] Remove an internal annot (fabpot)
+ * bug #36913 [FrameworkBundle] fix type annotation on ControllerTrait::addFlash() (ThomasLandauer)
+ * bug #37162 [Mailer] added the reply-to addresses to the API SES transport request. (ribeiropaulor)
+ * bug #37167 [Mime] use fromString when creating a new Address (fabpot)
+ * bug #37169 [Cache] fix forward compatibility with Doctrine DBAL 3 (xabbuh)
+ * bug #37159 [Mailer] Fixed generator bug when creating multiple transports using Transport::fromDsn (atailouloute)
+ * bug #37048 [HttpClient] fix monitoring timeouts when other streams are active (nicolas-grekas)
+ * bug #37085 [Form] properly cascade validation to child forms (xabbuh)
+ * bug #37095 [PhpUnitBridge] Fix undefined index when output of "composer show" cannot be parsed (nicolas-grekas)
+ * bug #37092 [PhpUnitBridge] fix undefined var on version 3.4 (nicolas-grekas)
+ * bug #37065 [HttpClient] Throw JsonException instead of TransportException on empty response in Response::toArray() (jeroennoten)
+ * bug #37077 [WebProfilerBundle] Move ajax clear event listener initialization on loadToolbar (Bruno BOUTAREL)
+ * bug #37049 [Serializer] take into account the context when preserving empty array objects (xabbuh)
+
* 4.4.9 (2020-05-31)
* bug #37008 [Security] Fixed AbstractToken::hasUserChanged() (wouterj)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 449973d7578eb..baf025cfe0487 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -23,15 +23,15 @@ Symfony is the result of the work of many people who made the code better
- Johannes S (johannes)
- Kris Wallsmith (kriswallsmith)
- Yonel Ceruto (yonelceruto)
- - Hugo Hamon (hhamon)
- Wouter de Jong (wouterj)
+ - Hugo Hamon (hhamon)
- Thomas Calvet (fancyweb)
+ - Alexander M. Turek (derrabus)
- Abdellatif Ait boudad (aitboudad)
- Samuel ROZE (sroze)
- Romain Neutron (romain)
- Pascal Borreli (pborreli)
- Joseph Bielawski (stloyd)
- - Alexander M. Turek (derrabus)
- Karma Dordrak (drak)
- Lukas Kahwe Smith (lsmith)
- Jules Pietri (heah)
@@ -60,16 +60,16 @@ Symfony is the result of the work of many people who made the code better
- Alexander Mols (asm89)
- Konstantin Myakshin (koc)
- Grégoire Paris (greg0ire)
- - Bulat Shakirzyanov (avalanche123)
- Valentin Udaltsov (vudaltsov)
+ - Bulat Shakirzyanov (avalanche123)
- Kevin Bond (kbond)
- Saša Stamenković (umpirsky)
- Peter Rehm (rpet)
- - Henrik Bjørnskov (henrikbjorn)
- Gabriel Ostrolucký (gadelat)
+ - Henrik Bjørnskov (henrikbjorn)
+ - Gábor Egyed (1ed)
- Miha Vrhovnik
- David Maicher (dmaicher)
- - Gábor Egyed (1ed)
- Diego Saint Esteben (dii3g0)
- Jan Schädlich (jschaedl)
- Titouan Galopin (tgalopin)
@@ -132,6 +132,7 @@ Symfony is the result of the work of many people who made the code better
- Daniel Wehner (dawehner)
- Tugdual Saunier (tucksaun)
- excelwebzone
+ - Massimiliano Arione (garak)
- Gordon Franke (gimler)
- Joel Wurtz (brouznouf)
- Fabien Pennequin (fabienpennequin)
@@ -139,7 +140,6 @@ Symfony is the result of the work of many people who made the code better
- Przemysław Bogusz (przemyslaw-bogusz)
- Eric GELOEN (gelo)
- Lars Strojny (lstrojny)
- - Massimiliano Arione (garak)
- Jannik Zschiesche (apfelbox)
- Robert Schönthal (digitalkaoz)
- Gregor Harlan (gharlan)
@@ -161,6 +161,7 @@ Symfony is the result of the work of many people who made the code better
- Yanick Witschi (toflar)
- Arnaud Kleinpeter (nanocom)
- Guilherme Blanco (guilhermeblanco)
+ - Laurent VOULLEMIER (lvo)
- SpacePossum
- Pablo Godel (pgodel)
- Jérémie Augustin (jaugustin)
@@ -181,6 +182,7 @@ Symfony is the result of the work of many people who made the code better
- jeremyFreeAgent (jeremyfreeagent)
- Rouven Weßling (realityking)
- Jérôme Parmentier (lctrs)
+ - Ben Davies (bendavies)
- Andreas Schempp (aschempp)
- Clemens Tolboom
- Helmer Aaviksoo
@@ -195,7 +197,7 @@ Symfony is the result of the work of many people who made the code better
- Tyson Andre
- GDIBass
- Samuel NELA (snela)
- - Ben Davies (bendavies)
+ - Saif (╯°□°)╯ (azjezz)
- James Halsall (jaitsu)
- Matthieu Napoli (mnapoli)
- Florent Mata (fmata)
@@ -204,6 +206,7 @@ Symfony is the result of the work of many people who made the code better
- Dmitrii Chekaliuk (lazyhammer)
- Clément JOBEILI (dator)
- Marek Štípek (maryo)
+ - Filippo Tessarotto (slamdunk)
- Daniel Espendiller
- Possum
- Dorian Villet (gnutix)
@@ -211,11 +214,11 @@ Symfony is the result of the work of many people who made the code better
- Sergey Linnik (linniksa)
- Richard Miller (mr_r_miller)
- Albert Casademont (acasademont)
+ - Wouter J
- Mario A. Alvarez Garcia (nomack84)
- Dennis Benkert (denderello)
- DQNEO
- Andre Rømcke (andrerom)
- - Saif (╯°□°)╯ (azjezz)
- mcfedr (mcfedr)
- Gary PEGEOT (gary-p)
- Ruben Gonzalez (rubenrua)
@@ -232,7 +235,6 @@ Symfony is the result of the work of many people who made the code better
- Stadly
- Stepan Anchugov (kix)
- bronze1man
- - Filippo Tessarotto (slamdunk)
- sun (sun)
- Larry Garfield (crell)
- Nikolay Labinskiy (e-moe)
@@ -245,7 +247,6 @@ Symfony is the result of the work of many people who made the code better
- fivestar
- Dominique Bongiraud
- Jeremy Livingston (jeremylivingston)
- - Laurent VOULLEMIER (lvo)
- Michael Lee (zerustech)
- Matthieu Auger (matthieuauger)
- Mathias Arlaud (mtarld)
@@ -255,6 +256,7 @@ Symfony is the result of the work of many people who made the code better
- Dustin Whittle (dustinwhittle)
- jeff
- John Kary (johnkary)
+ - Tien Vo (tienvx)
- Justin Hileman (bobthecow)
- Blanchon Vincent (blanchonvincent)
- Michele Orselli (orso)
@@ -269,7 +271,6 @@ Symfony is the result of the work of many people who made the code better
- Tristan Darricau (nicofuma)
- Victor Bocharsky (bocharsky_bw)
- Tomas Norkūnas (norkunas)
- - Wouter J
- Smaine Milianni (ismail1432)
- Marcel Beerta (mazen)
- Christopher Hertel (chertel)
@@ -295,7 +296,6 @@ Symfony is the result of the work of many people who made the code better
- Marcos Sánchez
- Elnur Abdurrakhimov (elnur)
- Manuel Reinhard (sprain)
- - Tien Vo (tienvx)
- Danny Berger (dpb587)
- Antonio J. García Lagar (ajgarlag)
- Adam Prager (padam87)
@@ -309,6 +309,7 @@ Symfony is the result of the work of many people who made the code better
- Arjen Brouwer (arjenjb)
- Katsuhiro OGAWA
- Patrick McDougle (patrick-mcdougle)
+ - Marc Weistroff (futurecat)
- Alif Rachmawadi
- Anton Chernikov (anton_ch1989)
- Kristen Gilden (kgilden)
@@ -318,6 +319,7 @@ Symfony is the result of the work of many people who made the code better
- Jakub Kucharovic (jkucharovic)
- Loick Piera (pyrech)
- Uwe Jäger (uwej711)
+ - Martin Hujer (martinhujer)
- Eugene Leonovich (rybakit)
- Joseph Rouff (rouffj)
- Félix Labrecque (woodspire)
@@ -354,9 +356,9 @@ Symfony is the result of the work of many people who made the code better
- Wouter Van Hecke
- Peter Kruithof (pkruithof)
- Michael Holm (hollo)
+ - Antonio Pauletich (x-coder264)
- Arjen van der Meijden
- Mathieu Lechat
- - Marc Weistroff (futurecat)
- Damien Alexandre (damienalexandre)
- Simon Mönch (sm)
- Christian Schmidt
@@ -388,6 +390,7 @@ Symfony is the result of the work of many people who made the code better
- Aurelijus Valeiša (aurelijus)
- Jan Decavele (jandc)
- Gustavo Piltcher
+ - Jesse Rushlow (geeshoe)
- Stepan Tanasiychuk (stfalcon)
- Tiago Ribeiro (fixe)
- Hidde Boomsma (hboomsma)
@@ -416,7 +419,9 @@ Symfony is the result of the work of many people who made the code better
- Nicolas LEFEVRE (nicoweb)
- alquerci
- Oleg Andreyev
+ - Langlet Vincent (deviling)
- Mateusz Sip (mateusz_sip)
+ - Alessandro Lai (jean85)
- Francesco Levorato
- Vitaliy Zakharov (zakharovvi)
- Tobias Sjösten (tobiassjosten)
@@ -427,14 +432,15 @@ Symfony is the result of the work of many people who made the code better
- Tomasz Kowalczyk (thunderer)
- Artur Eshenbrener
- Timo Bakx (timobakx)
- - Antonio Pauletich (x-coder264)
- Thomas Perez (scullwm)
- Felix Labrecque
- Yaroslav Kiliba
- Terje Bråten
- Robbert Klarenbeek (robbertkl)
+ - soyuka
- Eric Masoero (eric-masoero)
- Denis Brumann (dbrumann)
+ - Gocha Ossinkine (ossinkine)
- JhonnyL
- Haralan Dobrev (hkdobrev)
- hossein zolfi (ocean)
@@ -449,10 +455,10 @@ Symfony is the result of the work of many people who made the code better
- Philipp Kräutli (pkraeutli)
- Grzegorz (Greg) Zdanowski (kiler129)
- Iker Ibarguren (ikerib)
+ - Dimitri Gritsajuk (ottaviano)
- Kirill chEbba Chebunin (chebba)
- Rokas Mikalkėnas (rokasm)
- Greg Thornton (xdissent)
- - Martin Hujer (martinhujer)
- Alex Bowers
- Philipp Cordes
- Costin Bereveanu (schniper)
@@ -473,6 +479,7 @@ Symfony is the result of the work of many people who made the code better
- Endre Fejes
- Tobias Naumann (tna)
- Daniel Beyer
+ - Timothée Barray (tyx)
- Shein Alexey
- Romain Gautier (mykiwi)
- Joe Lencioni
@@ -489,7 +496,6 @@ Symfony is the result of the work of many people who made the code better
- Xavier HAUSHERR
- Albert Jessurum (ajessu)
- Laszlo Korte
- - Jesse Rushlow (geeshoe)
- Miha Vrhovnik
- Alessandro Desantis
- hubert lecorche (hlecorche)
@@ -501,6 +507,7 @@ Symfony is the result of the work of many people who made the code better
- Christophe L. (christophelau)
- Sander Toonen (xatoo)
- Anthon Pang (robocoder)
+ - Marko Kaznovac (kaznovac)
- Sébastien Santoro (dereckson)
- Brian King
- Michel Salib (michelsalib)
@@ -524,7 +531,6 @@ Symfony is the result of the work of many people who made the code better
- Mihai Stancu
- Ivan Nikolaev (destillat)
- Gildas Quéméner (gquemener)
- - Alessandro Lai (jean85)
- Desjardins Jérôme (jewome62)
- Arturs Vonda
- Josip Kruslin
@@ -561,7 +567,6 @@ Symfony is the result of the work of many people who made the code better
- Marek Pietrzak
- Luc Vieillescazes (iamluc)
- franek (franek)
- - soyuka
- Raulnet
- Christian Wahler
- Giso Stallenberg (gisostallenberg)
@@ -571,7 +576,6 @@ Symfony is the result of the work of many people who made the code better
- HypeMC
- Soufian EZ-ZANTAR (soezz)
- Zander Baldwin
- - Gocha Ossinkine (ossinkine)
- Adam Harvey
- Anton Bakai
- Martin Auswöger
@@ -603,9 +607,9 @@ Symfony is the result of the work of many people who made the code better
- Philipp Rieber (bicpi)
- Manuel de Ruiter (manuel)
- Nathanael Noblet (gnat)
- - Dimitri Gritsajuk (ottaviano)
- nikos.sotiropoulos
- Eduardo Oliveira (entering)
+ - Oleksii Zhurbytskyi
- Ilya Antipenko (aivus)
- Ricardo Oliveira (ricardolotr)
- Roy Van Ginneken (rvanginneken)
@@ -645,9 +649,9 @@ Symfony is the result of the work of many people who made the code better
- Gábor Fási
- DUPUCH (bdupuch)
- Nate (frickenate)
- - Timothée Barray (tyx)
- jhonnyL
- Jacek Jędrzejewski (jacek.jedrzejewski)
+ - Stefan Kruppa
- sasezaki
- Bozhidar Hristov (warxcell)
- Dawid Pakuła (zulusx)
@@ -684,7 +688,6 @@ Symfony is the result of the work of many people who made the code better
- Pavel Campr (pcampr)
- Andrii Dembitskyi
- Johnny Robeson (johnny)
- - Marko Kaznovac (kaznovac)
- Guilliam Xavier
- Disquedur
- Michiel Boeckaert (milio)
@@ -711,6 +714,7 @@ Symfony is the result of the work of many people who made the code better
- vitaliytv
- Philippe Segatori
- Dalibor Karlović (dkarlovi)
+ - Andrey Sevastianov
- Sebastian Blum
- Alexis Lefebvre
- aubx
@@ -728,6 +732,7 @@ Symfony is the result of the work of many people who made the code better
- Sinan Eldem
- BoShurik
- Alexandre Dupuy (satchette)
+ - Michel Hunziker
- Malte Blättermann
- Simeon Kolev (simeon_kolev9)
- Joost van Driel (j92)
@@ -741,7 +746,6 @@ Symfony is the result of the work of many people who made the code better
- Stefan Gehrig (sgehrig)
- Hany el-Kerdany
- Wang Jingyu
- - Langlet Vincent (deviling)
- Åsmund Garfors
- Gunnstein Lye (glye)
- Maxime Douailin
@@ -847,8 +851,10 @@ Symfony is the result of the work of many people who made the code better
- Michael Lutz
- Koen Reiniers (koenre)
- jochenvdv
+ - Michel Roca (mroca)
- Reedy
- Arturas Smorgun (asarturas)
+ - Michał (bambucha15)
- Alexander Volochnev (exelenz)
- Michael Piecko
- Toni Peric (tperic)
@@ -879,12 +885,12 @@ Symfony is the result of the work of many people who made the code better
- Axel Guckelsberger (guite)
- Jose Gonzalez
- Jonathan (jls-esokia)
- - Oleksii Zhurbytskyi
- Dariusz Ruminski
- Joshua Nye
- Claudio Zizza
- Dave Marshall (davedevelopment)
- Jakub Kulhan (jakubkulhan)
+ - Nathan Dench (ndenc2)
- Shaharia Azam
- avorobiev
- stoccc
@@ -948,9 +954,11 @@ Symfony is the result of the work of many people who made the code better
- GDIBass
- Antoine Lamirault
- Adrien Lucas (adrienlucas)
+ - Jeroen Thora (bolle)
- Zhuravlev Alexander (scif)
- Stefano Degenkamp (steef)
- James Michael DuPont
+ - Carlos Buenosvinos (carlosbuenosvinos)
- Tom Klingenberg
- Christopher Hall (mythmakr)
- Patrick Dawkins (pjcdawkins)
@@ -982,6 +990,7 @@ Symfony is the result of the work of many people who made the code better
- Julie Hourcade (juliehde)
- Dmitry Parnas (parnas)
- Paul LE CORRE
+ - Loïc Beurlet
- Daniel Gorgan
- Tony Malzhacker
- Mathieu MARCHOIS
@@ -1006,7 +1015,6 @@ Symfony is the result of the work of many people who made the code better
- Jelle Kapitein
- Benoît Bourgeois
- mantulo
- - Stefan Kruppa
- corphi
- JoppeDC
- grizlik
@@ -1049,6 +1057,7 @@ Symfony is the result of the work of many people who made the code better
- Arno Geurts
- Adán Lobato (adanlobato)
- Ian Jenkins (jenkoian)
+ - Hugo Alliaume (kocal)
- Marcos Gómez Vilches (markitosgv)
- Matthew Davis (mdavis1982)
- Markus S. (staabm)
@@ -1062,6 +1071,7 @@ Symfony is the result of the work of many people who made the code better
- Daniel Cestari
- Matt Janssen
- David Lima
+ - Dmitriy Derepko
- Stéphane Delprat
- Brian Freytag (brianfreytag)
- Samuele Lilli (doncallisto)
@@ -1148,6 +1158,7 @@ Symfony is the result of the work of many people who made the code better
- Anton Babenko (antonbabenko)
- Irmantas Šiupšinskas (irmantas)
- Danilo Silva
+ - Giuseppe Campanelli
- Arnaud PETITPAS (apetitpa)
- Ken Stanley
- Zachary Tong (polyfractal)
@@ -1164,6 +1175,7 @@ Symfony is the result of the work of many people who made the code better
- Tero Alén (tero)
- Stanislav Kocanda
- DerManoMann
+ - MatTheCat
- Guillaume Royer
- Artem (digi)
- boite
@@ -1184,7 +1196,6 @@ Symfony is the result of the work of many people who made the code better
- Danijel Obradović
- Pablo Borowicz
- Mathieu Santostefano
- - Michel Hunziker
- Arjan Keeman
- Máximo Cuadros (mcuadros)
- Lukas Mencl
@@ -1277,7 +1288,6 @@ Symfony is the result of the work of many people who made the code better
- Jakub Sacha
- Olaf Klischat
- orlovv
- - Andrey Sevastianov
- Claude Dioudonnat
- Jonathan Hedstrom
- Peter Smeets (darkspartan)
@@ -1297,12 +1307,10 @@ Symfony is the result of the work of many people who made the code better
- James Hudson
- Stephen Clouse
- e-ivanov
- - Michał (bambucha15)
- Benjamin Dos Santos
- Einenlum
- Jérémy Jarrié (gagnar)
- Jochen Bayer (jocl)
- - Michel Roca (mroca)
- Patrick Carlo-Hickman
- Bruno MATEU
- Jeremy Bush
@@ -1416,6 +1424,7 @@ Symfony is the result of the work of many people who made the code better
- Florian Hermann (fhermann)
- Mo Di (modi)
- Pablo Schläpfer
+ - Christian Rishøj
- Patrick Berenschot
- SuRiKmAn
- Gert de Pagter
@@ -1423,6 +1432,7 @@ Symfony is the result of the work of many people who made the code better
- David Négrier (moufmouf)
- Quique Porta (quiqueporta)
- mohammadreza honarkhah
+ - Artem Oliynyk (artemoliynyk)
- Andrea Quintino (dirk39)
- Tomasz Szymczyk (karion)
- Alex Vasilchenko
@@ -1453,6 +1463,7 @@ Symfony is the result of the work of many people who made the code better
- Andrei Igna
- Adam Prickett
- azine
+ - Anton Kroshilin
- Dawid Sajdak
- Ludek Stepan
- Aaron Stephens (astephens)
@@ -1605,6 +1616,7 @@ Symfony is the result of the work of many people who made the code better
- Robert Queck
- Peter Bouwdewijn
- mlively
+ - Wouter Diesveld
- Amine Matmati
- caalholm
- Nouhail AL FIDI (alfidi)
@@ -1612,6 +1624,7 @@ Symfony is the result of the work of many people who made the code better
- Felipy Tavares Amorim (felipyamorim)
- Guillaume Loulier (guikingone)
- Klaus Silveira (klaussilveira)
+ - Pedro Casado (pdr33n)
- Pierre Grimaud (pgrimaud)
- Thomas Chmielowiec (chmielot)
- Jānis Lukss
@@ -1724,6 +1737,7 @@ Symfony is the result of the work of many people who made the code better
- Stanislav Gamayunov (happyproff)
- Iwan van Staveren (istaveren)
- Alexander McCullagh (mccullagh)
+ - Paul L McNeely (mcneely)
- Povilas S. (povilas)
- Laurent Negre (raulnet)
- Evrard Boulou
@@ -1809,6 +1823,7 @@ Symfony is the result of the work of many people who made the code better
- Mathieu Dewet (mdewet)
- Nicolas Tallefourtané (nicolab)
- Botond Dani (picur)
+ - Rémi Faivre (rfv)
- Romaric Drigon (romaricdrigon)
- Thierry Marianne (thierrymarianne)
- Nick Stemerdink
@@ -1816,6 +1831,7 @@ Symfony is the result of the work of many people who made the code better
- jjanvier
- Julius Beckmann
- loru88
+ - Thibaut Salanon
- Romain Dorgueil
- Christopher Parotat
- Dennis Haarbrink
@@ -1860,6 +1876,7 @@ Symfony is the result of the work of many people who made the code better
- Chris
- Farid Jalilov
- Florent Olivaud
+ - Eric Hertwig
- JakeFr
- Simon Sargeant
- efeen
@@ -1920,6 +1937,7 @@ Symfony is the result of the work of many people who made the code better
- Michael van Tricht
- ReScO
- Tim Strehle
+ - Sébastien COURJEAN
- Sam Ward
- Michael Voříšek
- Walther Lalk
@@ -1972,6 +1990,7 @@ Symfony is the result of the work of many people who made the code better
- Martijn Boers (plebian)
- Pedro Magalhães (pmmaga)
- Rares Vlaseanu (raresvla)
+ - Sergii Dolgushev (serhey)
- tante kinast (tante)
- Stephen Lewis (tehanomalousone)
- Ahmed Hannachi (tiecoders)
@@ -1981,6 +2000,7 @@ Symfony is the result of the work of many people who made the code better
- Darryl Hein (xmmedia)
- Sadicov Vladimir (xtech)
- Kevin EMO (zarcox)
+ - sdkawata
- Andrzej
- Alexander Zogheb
- Rémi Blaise
@@ -2016,6 +2036,7 @@ Symfony is the result of the work of many people who made the code better
- Ashura
- Götz Gottwald
- Veres Lajos
+ - Ernest Hymel
- Nick Chiu
- grifx
- Robert Campbell
@@ -2045,6 +2066,7 @@ Symfony is the result of the work of many people who made the code better
- Rowan Manning
- Per Modin
- David Windell
+ - Christian Scheb
- Gabriel Birke
- skafandri
- Derek Bonner
@@ -2088,7 +2110,6 @@ Symfony is the result of the work of many people who made the code better
- baron (bastien)
- Rosio (ben-rosio)
- Simon Paarlberg (blamh)
- - Jeroen Thora (bolle)
- Brieuc THOMAS (brieucthomas)
- Masao Maeda (brtriver)
- Damien Harper (damien.harper)
@@ -2148,6 +2169,7 @@ Symfony is the result of the work of many people who made the code better
- Michael Orlitzky
- Nicolas A. Bérard-Nault
- Quentin Favrie
+ - Matthias Derer
- Saem Ghani
- Stefan Oderbolz
- Curtis
@@ -2173,7 +2195,6 @@ Symfony is the result of the work of many people who made the code better
- Daniele Cesarini (ijanki)
- Ismail Asci (ismailasci)
- Jeffrey Moelands (jeffreymoelands)
- - Hugo Alliaume (kocal)
- Simon CONSTANS (kosssi)
- Dennis Langen (nijusan)
- Paulius Jarmalavičius (pjarmalavicius)
@@ -2218,6 +2239,7 @@ Symfony is the result of the work of many people who made the code better
- Antonio Angelino
- Jens Schulze
- Matt Fields
+ - Olatunbosun Egberinde
- Niklas Keller
- Andras Debreczeni
- Vladimir Sazhin
@@ -2352,11 +2374,11 @@ Symfony is the result of the work of many people who made the code better
- Karolis Daužickas
- Nicolas
- Sergio Santoro
- - Dmitriy Derepko
- tirnanog06
- phc
- Дмитрий Пацура
- Signor Pedro
+ - Matthias Larisch
- ilyes kooli
- Ilia Lazarev
- Michaël VEROUX
@@ -2388,8 +2410,10 @@ Symfony is the result of the work of many people who made the code better
- Christian Gripp (core23)
- Christoph Schaefer (cvschaefer)
- Damon Jones (damon__jones)
+ - Cătălin Dan (dancatalin)
- Łukasz Giza (destroyer)
- Daniel Londero (dlondero)
+ - Dmitrii Tarasov (dtarasov)
- Sebastian Landwehr (dword123)
- Adel ELHAIBA (eadel)
- Damián Nohales (eagleoneraptor)
diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php
index 4bbc9f3fcdfb4..c6e4ab3a308ba 100644
--- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php
+++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php
@@ -12,6 +12,7 @@
namespace Symfony\Bridge\Doctrine\Security\RememberMe;
use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
@@ -63,7 +64,7 @@ public function loadTokenBySeries($series)
$paramValues = ['series' => $series];
$paramTypes = ['series' => \PDO::PARAM_STR];
$stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes);
- $row = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch(\PDO::FETCH_ASSOC);
+ $row = $stmt instanceof Result ? $stmt->fetchAssociative() : $stmt->fetch(\PDO::FETCH_ASSOC);
if ($row) {
return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['last_used']));
diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
index 70c2fcdbddcf2..ca41113b373c8 100644
--- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
+++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
@@ -182,6 +182,11 @@
}
}
+ $info += [
+ 'versions' => [],
+ 'requires' => ['php' => '*'],
+ ];
+
if (1 === \count($info['versions'])) {
$passthruOrFail("$COMPOSER create-project --ignore-platform-reqs --no-install --prefer-dist --no-scripts --no-plugins --no-progress --ansi -s dev phpunit/phpunit $PHPUNIT_VERSION_DIR \"$PHPUNIT_VERSION.*\"");
} else {
diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
index 7b4e56521e9de..40644914dcffb 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
@@ -156,7 +156,7 @@ protected function file($file, string $fileName = null, string $disposition = Re
*
* @final
*/
- protected function addFlash(string $type, string $message)
+ protected function addFlash(string $type, $message)
{
if (!$this->container->has('session')) {
throw new \LogicException('You can not use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".');
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
index 403119842e28e..0a7a56f675a65 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
@@ -578,7 +578,7 @@
-
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
index 0dbc388ddffcb..c3eea28b8c2b9 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
@@ -6,7 +6,6 @@
%kernel.cache_dir%/serialization.php
-
@@ -97,7 +96,6 @@
- null
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml
index f2d0e9e2a603b..886132ff4b1c8 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml
@@ -5,7 +5,6 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
- %kernel.cache_dir%/validation.php
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
index 9a8fc0776f0af..3e6591a75b8e7 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
@@ -1204,7 +1204,6 @@ public function testSerializerEnabled()
$this->assertCount(2, $argument);
$this->assertEquals('Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', $argument[0]->getClass());
- $this->assertNull($container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1));
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1));
$this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3));
$this->assertArrayHasKey('circular_reference_handler', $container->getDefinition('serializer.normalizer.object')->getArgument(6));
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig
index c65e278021a0f..58f3867c6f2f1 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig
@@ -131,13 +131,6 @@
removeClass(ajaxToolbarPanel, 'sf-ajax-request-loading');
removeClass(ajaxToolbarPanel, 'sf-toolbar-status-red');
}
-
- addEventListener(document.querySelector('.sf-toolbar-ajax-clear'), 'click', function() {
- requestStack = [];
- renderAjaxRequests();
- successStreak = 4;
- document.querySelector('.sf-toolbar-ajax-request-list').innerHTML = '';
- });
};
var startAjaxRequest = function(index) {
@@ -506,6 +499,12 @@
setPreference('toolbar/displayState', 'block');
});
renderAjaxRequests();
+ addEventListener(document.querySelector('.sf-toolbar-ajax-clear'), 'click', function() {
+ requestStack = [];
+ renderAjaxRequests();
+ successStreak = 4;
+ document.querySelector('.sf-toolbar-ajax-request-list').innerHTML = '';
+ });
addEventListener(document.querySelector('.sf-toolbar-block-ajax'), 'mouseenter', function (event) {
var elem = document.querySelector('.sf-toolbar-block-ajax .sf-toolbar-info');
elem.scrollTop = elem.scrollHeight;
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php
index adcb44d680277..8fcdfc0e71a40 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php
+++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php
@@ -2,6 +2,7 @@
namespace Symfony\Bundle\WebProfilerBundle\Tests\Functional;
+use Psr\Log\NullLogger;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Bundle\TwigBundle\TwigBundle;
@@ -66,6 +67,11 @@ public function getLogDir()
return sys_get_temp_dir().'/log-'.spl_object_hash($this);
}
+ protected function build(ContainerBuilder $container)
+ {
+ $container->register('logger', NullLogger::class);
+ }
+
public function homepageController()
{
return new Response('Homepage Controller.');
diff --git a/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php b/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php
index 05a88e086c3cf..53d0ccc5db44b 100644
--- a/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php
+++ b/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Cache\Tests\Traits;
+use Doctrine\DBAL\Driver\Result;
+
trait PdoPruneableTrait
{
protected function isPruned($cache, string $name): bool
@@ -27,8 +29,8 @@ protected function isPruned($cache, string $name): bool
/** @var \Doctrine\DBAL\Statement|\PDOStatement $select */
$select = $getPdoConn->invoke($cache)->prepare('SELECT 1 FROM cache_items WHERE item_id LIKE :id');
$select->bindValue(':id', sprintf('%%%s', $name));
- $select->execute();
+ $result = $select->execute();
- return 1 !== (int) (method_exists($select, 'fetchOne') ? $select->fetchOne() : $select->fetch(\PDO::FETCH_COLUMN));
+ return 1 !== (int) ($result instanceof Result ? $result->fetchOne() : $select->fetch(\PDO::FETCH_COLUMN));
}
}
diff --git a/src/Symfony/Component/Cache/Traits/PdoTrait.php b/src/Symfony/Component/Cache/Traits/PdoTrait.php
index 36cd1912d10a1..bec497feedf90 100644
--- a/src/Symfony/Component/Cache/Traits/PdoTrait.php
+++ b/src/Symfony/Component/Cache/Traits/PdoTrait.php
@@ -11,8 +11,10 @@
namespace Symfony\Component\Cache\Traits;
+use Doctrine\DBAL\Abstraction\Result;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception\TableNotFoundException;
@@ -192,15 +194,16 @@ protected function doFetch(array $ids)
foreach ($ids as $id) {
$stmt->bindValue(++$i, $id);
}
- $stmt->execute();
+ $result = $stmt->execute();
- if (method_exists($stmt, 'iterateNumeric')) {
- $stmt = $stmt->iterateNumeric();
+ if ($result instanceof Result) {
+ $result = $result->iterateNumeric();
} else {
$stmt->setFetchMode(\PDO::FETCH_NUM);
+ $result = $stmt;
}
- foreach ($stmt as $row) {
+ foreach ($result as $row) {
if (null === $row[1]) {
$expired[] = $row[0];
} else {
@@ -230,9 +233,9 @@ protected function doHave($id)
$stmt->bindValue(':id', $id);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
- $stmt->execute();
+ $result = $stmt->execute();
- return (bool) (method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn());
+ return (bool) ($result instanceof DriverResult ? $result->fetchOne() : $stmt->fetchColumn());
}
/**
@@ -362,19 +365,19 @@ protected function doSave(array $values, int $lifetime)
foreach ($values as $id => $data) {
try {
- $stmt->execute();
+ $result = $stmt->execute();
} catch (TableNotFoundException $e) {
if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
$this->createTable();
}
- $stmt->execute();
+ $result = $stmt->execute();
} catch (\PDOException $e) {
if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
$this->createTable();
}
- $stmt->execute();
+ $result = $stmt->execute();
}
- if (null === $driver && !$stmt->rowCount()) {
+ if (null === $driver && !($result instanceof DriverResult ? $result : $stmt)->rowCount()) {
try {
$insertStmt->execute();
} catch (DBALException $e) {
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
index bad2624529138..cf255640c2258 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php
@@ -198,9 +198,10 @@ private function checkType(Definition $checkedDefinition, $value, \ReflectionPar
}
} elseif (\is_string($value)) {
if ('%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) {
- // Only array parameters are not inlined when dumped.
- $value = [];
- } elseif ($envPlaceholderUniquePrefix && false !== strpos($value, 'env_')) {
+ $value = $this->container->getParameter(substr($value, 1, -1));
+ }
+
+ if ($envPlaceholderUniquePrefix && \is_string($value) && false !== strpos($value, 'env_')) {
// If the value is an env placeholder that is either mixed with a string or with another env placeholder, then its resolved value will always be a string, so we don't need to resolve it.
// We don't need to change the value because it is already a string.
if ('' === preg_replace('/'.$envPlaceholderUniquePrefix.'_\w+_[a-f0-9]{32}/U', '', $value, -1, $c) && 1 === $c) {
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
index 9524c7a4f61bd..0842f2687968a 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php
@@ -612,20 +612,6 @@ public function testProcessThrowsOnIterableTypeWhenScalarPassed()
$this->assertInstanceOf(\stdClass::class, $container->get('bar')->foo);
}
- public function testProcessResolveArrayParameters()
- {
- $container = new ContainerBuilder();
- $container->setParameter('ccc', ['foobar']);
-
- $container
- ->register('foobar', BarMethodCall::class)
- ->addMethodCall('setArray', ['%ccc%']);
-
- (new CheckTypeDeclarationsPass(true))->process($container);
-
- $this->addToAssertionCount(1);
- }
-
public function testProcessResolveExpressions()
{
$container = new ContainerBuilder();
@@ -791,4 +777,30 @@ public function testExpressionLanguageWithSyntheticService()
$this->addToAssertionCount(1);
}
+
+ public function testProcessResolveParameters()
+ {
+ putenv('ARRAY={"foo":"bar"}');
+
+ $container = new ContainerBuilder(new EnvPlaceholderParameterBag([
+ 'env_array_param' => '%env(json:ARRAY)%',
+ ]));
+ $container->setParameter('array_param', ['foobar']);
+ $container->setParameter('string_param', 'ccc');
+
+ $definition = $container->register('foobar', BarMethodCall::class);
+ $definition
+ ->addMethodCall('setArray', ['%array_param%'])
+ ->addMethodCall('setString', ['%string_param%']);
+
+ (new ResolveParameterPlaceHoldersPass())->process($container);
+
+ $definition->addMethodCall('setArray', ['%env_array_param%']);
+
+ (new CheckTypeDeclarationsPass(true))->process($container);
+
+ $this->addToAssertionCount(1);
+
+ putenv('ARRAY=');
+ }
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php
index 69f1a693a4c5b..65437a63ec743 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php
@@ -44,4 +44,8 @@ public function setCallable(callable $callable): void
public function setClosure(\Closure $closure): void
{
}
+
+ public function setString(string $string)
+ {
+ }
}
diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php
index a92aeccab0306..2ac749dfff01d 100644
--- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php
+++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php
@@ -72,7 +72,6 @@ public function validate($form, Constraint $formConstraint)
if ($groups instanceof GroupSequence) {
// Validate the data, the form AND nested fields in sequence
$violationsCount = $this->context->getViolations()->count();
- $fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s';
foreach ($groups->groups as $group) {
if ($validateDataGraph) {
@@ -91,7 +90,8 @@ public function validate($form, Constraint $formConstraint)
// in different steps without breaking early enough
$this->resolvedGroups[$field] = (array) $group;
$fieldFormConstraint = new Form();
- $validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint);
+ $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath());
+ $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint);
}
}
@@ -100,8 +100,6 @@ public function validate($form, Constraint $formConstraint)
}
}
} else {
- $fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s';
-
if ($validateDataGraph) {
$validator->atPath('data')->validate($data, null, $groups);
}
@@ -138,7 +136,8 @@ public function validate($form, Constraint $formConstraint)
if ($field->isSubmitted()) {
$this->resolvedGroups[$field] = $groups;
$fieldFormConstraint = new Form();
- $validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint);
+ $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath());
+ $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint);
}
}
}
diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php
new file mode 100644
index 0000000000000..75ab408b3f701
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php
@@ -0,0 +1,365 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Tests\Extension\Validator\Constraints;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\FormType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\FormFactoryBuilder;
+use Symfony\Component\Form\Test\ForwardCompatTestTrait;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Validator\Constraints\Collection;
+use Symfony\Component\Validator\Constraints\Expression;
+use Symfony\Component\Validator\Constraints\GroupSequence;
+use Symfony\Component\Validator\Constraints\Length;
+use Symfony\Component\Validator\Constraints\NotBlank;
+use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
+use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader;
+use Symfony\Component\Validator\Validation;
+
+class FormValidatorFunctionalTest extends TestCase
+{
+ use ForwardCompatTestTrait;
+
+ private $validator;
+ private $formFactory;
+
+ private function doSetUp()
+ {
+ $this->validator = Validation::createValidatorBuilder()
+ ->setMetadataFactory(new LazyLoadingMetadataFactory(new StaticMethodLoader()))
+ ->getValidator();
+ $this->formFactory = (new FormFactoryBuilder())
+ ->addExtension(new ValidatorExtension($this->validator))
+ ->getFormFactory();
+ }
+
+ public function testDataConstraintsInvalidateFormEvenIfFieldIsNotSubmitted()
+ {
+ $form = $this->formFactory->create(FooType::class);
+ $form->submit(['baz' => 'foobar'], false);
+
+ $this->assertTrue($form->isSubmitted());
+ $this->assertFalse($form->isValid());
+ $this->assertFalse($form->get('bar')->isSubmitted());
+ $this->assertCount(1, $form->get('bar')->getErrors());
+ }
+
+ public function testFieldConstraintsDoNotInvalidateFormIfFieldIsNotSubmitted()
+ {
+ $form = $this->formFactory->create(FooType::class);
+ $form->submit(['bar' => 'foobar'], false);
+
+ $this->assertTrue($form->isSubmitted());
+ $this->assertTrue($form->isValid());
+ }
+
+ public function testFieldConstraintsInvalidateFormIfFieldIsSubmitted()
+ {
+ $form = $this->formFactory->create(FooType::class);
+ $form->submit(['bar' => 'foobar', 'baz' => ''], false);
+
+ $this->assertTrue($form->isSubmitted());
+ $this->assertFalse($form->isValid());
+ $this->assertTrue($form->get('bar')->isSubmitted());
+ $this->assertTrue($form->get('bar')->isValid());
+ $this->assertTrue($form->get('baz')->isSubmitted());
+ $this->assertFalse($form->get('baz')->isValid());
+ }
+
+ public function testNonCompositeConstraintValidatedOnce()
+ {
+ $form = $this->formFactory->create(TextType::class, null, [
+ 'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])],
+ 'validation_groups' => ['foo', 'bar'],
+ ]);
+ $form->submit('');
+
+ $violations = $this->validator->validate($form);
+
+ $this->assertCount(1, $violations);
+ $this->assertSame('This value should not be blank.', $violations[0]->getMessage());
+ $this->assertSame('data', $violations[0]->getPropertyPath());
+ }
+
+ public function testCompositeConstraintValidatedInEachGroup()
+ {
+ $form = $this->formFactory->create(FormType::class, null, [
+ 'constraints' => [
+ new Collection([
+ 'field1' => new NotBlank([
+ 'groups' => ['field1'],
+ ]),
+ 'field2' => new NotBlank([
+ 'groups' => ['field2'],
+ ]),
+ ]),
+ ],
+ 'validation_groups' => ['field1', 'field2'],
+ ]);
+ $form->add('field1');
+ $form->add('field2');
+ $form->submit([
+ 'field1' => '',
+ 'field2' => '',
+ ]);
+
+ $violations = $this->validator->validate($form);
+
+ $this->assertCount(2, $violations);
+ $this->assertSame('This value should not be blank.', $violations[0]->getMessage());
+ $this->assertSame('data[field1]', $violations[0]->getPropertyPath());
+ $this->assertSame('This value should not be blank.', $violations[1]->getMessage());
+ $this->assertSame('data[field2]', $violations[1]->getPropertyPath());
+ }
+
+ public function testCompositeConstraintValidatedInSequence()
+ {
+ $form = $this->formFactory->create(FormType::class, null, [
+ 'constraints' => [
+ new Collection([
+ 'field1' => new NotBlank([
+ 'groups' => ['field1'],
+ ]),
+ 'field2' => new NotBlank([
+ 'groups' => ['field2'],
+ ]),
+ ]),
+ ],
+ 'validation_groups' => new GroupSequence(['field1', 'field2']),
+ ]);
+ $form->add('field1');
+ $form->add('field2');
+
+ $form->submit([
+ 'field1' => '',
+ 'field2' => '',
+ ]);
+
+ $violations = $this->validator->validate($form);
+
+ $this->assertCount(1, $violations);
+ $this->assertSame('This value should not be blank.', $violations[0]->getMessage());
+ $this->assertSame('data[field1]', $violations[0]->getPropertyPath());
+ }
+
+ public function testFieldsValidateInSequence()
+ {
+ $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
+
+ $form = $this->formFactory->create(FormType::class, null, [
+ 'validation_groups' => new GroupSequence(['group1', 'group2']),
+ ])
+ ->add('foo', TextType::class, [
+ 'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)],
+ ])
+ ->add('bar', TextType::class, [
+ 'constraints' => [new NotBlank(['groups' => ['group2']])],
+ ])
+ ;
+
+ $form->submit(['foo' => 'invalid', 'bar' => null]);
+
+ $errors = $form->getErrors(true);
+
+ $this->assertCount(1, $errors);
+ $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
+ }
+
+ public function testFieldsValidateInSequenceWithNestedGroupsArray()
+ {
+ $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
+
+ $form = $this->formFactory->create(FormType::class, null, [
+ 'validation_groups' => new GroupSequence([['group1', 'group2'], 'group3']),
+ ])
+ ->add('foo', TextType::class, [
+ 'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)],
+ ])
+ ->add('bar', TextType::class, [
+ 'constraints' => [new Length(['min' => 10, 'groups' => ['group2']] + $allowEmptyString)],
+ ])
+ ->add('baz', TextType::class, [
+ 'constraints' => [new NotBlank(['groups' => ['group3']])],
+ ])
+ ;
+
+ $form->submit(['foo' => 'invalid', 'bar' => 'invalid', 'baz' => null]);
+
+ $errors = $form->getErrors(true);
+
+ $this->assertCount(2, $errors);
+ $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
+ $this->assertInstanceOf(Length::class, $errors[1]->getCause()->getConstraint());
+ }
+
+ public function testConstraintsInDifferentGroupsOnSingleField()
+ {
+ $form = $this->formFactory->create(FormType::class, null, [
+ 'validation_groups' => new GroupSequence(['group1', 'group2']),
+ ])
+ ->add('foo', TextType::class, [
+ 'constraints' => [
+ new NotBlank([
+ 'groups' => ['group1'],
+ ]),
+ new Length([
+ 'groups' => ['group2'],
+ 'max' => 3,
+ ]),
+ ],
+ ]);
+ $form->submit([
+ 'foo' => 'test@example.com',
+ ]);
+
+ $errors = $form->getErrors(true);
+
+ $this->assertFalse($form->isValid());
+ $this->assertCount(1, $errors);
+ $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
+ }
+
+ public function testCascadeValidationToChildFormsUsingPropertyPaths()
+ {
+ $form = $this->formFactory->create(FormType::class, null, [
+ 'validation_groups' => ['group1', 'group2'],
+ ])
+ ->add('field1', null, [
+ 'constraints' => [new NotBlank(['groups' => 'group1'])],
+ 'property_path' => '[foo]',
+ ])
+ ->add('field2', null, [
+ 'constraints' => [new NotBlank(['groups' => 'group2'])],
+ 'property_path' => '[bar]',
+ ])
+ ;
+
+ $form->submit([
+ 'field1' => '',
+ 'field2' => '',
+ ]);
+
+ $violations = $this->validator->validate($form);
+
+ $this->assertCount(2, $violations);
+ $this->assertSame('This value should not be blank.', $violations[0]->getMessage());
+ $this->assertSame('children[field1].data', $violations[0]->getPropertyPath());
+ $this->assertSame('This value should not be blank.', $violations[1]->getMessage());
+ $this->assertSame('children[field2].data', $violations[1]->getPropertyPath());
+ }
+
+ public function testCascadeValidationToChildFormsUsingPropertyPathsValidatedInSequence()
+ {
+ $form = $this->formFactory->create(FormType::class, null, [
+ 'validation_groups' => new GroupSequence(['group1', 'group2']),
+ ])
+ ->add('field1', null, [
+ 'constraints' => [new NotBlank(['groups' => 'group1'])],
+ 'property_path' => '[foo]',
+ ])
+ ->add('field2', null, [
+ 'constraints' => [new NotBlank(['groups' => 'group2'])],
+ 'property_path' => '[bar]',
+ ])
+ ;
+
+ $form->submit([
+ 'field1' => '',
+ 'field2' => '',
+ ]);
+
+ $violations = $this->validator->validate($form);
+
+ $this->assertCount(1, $violations);
+ $this->assertSame('This value should not be blank.', $violations[0]->getMessage());
+ $this->assertSame('children[field1].data', $violations[0]->getPropertyPath());
+ }
+
+ public function testContextIsPopulatedWithFormBeingValidated()
+ {
+ $form = $this->formFactory->create(FormType::class)
+ ->add('field1', null, [
+ 'constraints' => [new Expression([
+ 'expression' => '!this.getParent().get("field2").getData()',
+ ])],
+ ])
+ ->add('field2')
+ ;
+
+ $form->submit([
+ 'field1' => '',
+ 'field2' => '',
+ ]);
+
+ $violations = $this->validator->validate($form);
+
+ $this->assertCount(0, $violations);
+ }
+
+ public function testContextIsPopulatedWithFormBeingValidatedUsingGroupSequence()
+ {
+ $form = $this->formFactory->create(FormType::class, null, [
+ 'validation_groups' => new GroupSequence(['group1']),
+ ])
+ ->add('field1', null, [
+ 'constraints' => [new Expression([
+ 'expression' => '!this.getParent().get("field2").getData()',
+ 'groups' => ['group1'],
+ ])],
+ ])
+ ->add('field2')
+ ;
+
+ $form->submit([
+ 'field1' => '',
+ 'field2' => '',
+ ]);
+
+ $violations = $this->validator->validate($form);
+
+ $this->assertCount(0, $violations);
+ }
+}
+
+class Foo
+{
+ public $bar;
+ public $baz;
+
+ public static function loadValidatorMetadata(ClassMetadata $metadata)
+ {
+ $metadata->addPropertyConstraint('bar', new NotBlank());
+ }
+}
+
+class FooType extends AbstractType
+{
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ ->add('bar')
+ ->add('baz', null, [
+ 'constraints' => [new NotBlank()],
+ ])
+ ;
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefault('data_class', Foo::class);
+ }
+}
diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php
index 811df69f840cb..c3709a41ed7a5 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php
@@ -18,13 +18,13 @@
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
use Symfony\Component\Form\Extension\Validator\Constraints\FormValidator;
+use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormFactoryBuilder;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\SubmitButtonBuilder;
use Symfony\Component\Translation\IdentityTranslator;
-use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
@@ -52,7 +52,9 @@ class FormValidatorTest extends ConstraintValidatorTestCase
protected function setUp(): void
{
$this->dispatcher = new EventDispatcher();
- $this->factory = (new FormFactoryBuilder())->getFormFactory();
+ $this->factory = (new FormFactoryBuilder())
+ ->addExtension(new ValidatorExtension(Validation::createValidator()))
+ ->getFormFactory();
parent::setUp();
@@ -744,96 +746,6 @@ public function testCauseForNotAllowedExtraFieldsIsTheFormConstraint()
$this->assertSame($constraint, $context->getViolations()->get(0)->getConstraint());
}
- public function testNonCompositeConstraintValidatedOnce()
- {
- $form = $this
- ->getBuilder('form', null, [
- 'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])],
- 'validation_groups' => ['foo', 'bar'],
- ])
- ->setCompound(false)
- ->getForm();
- $form->submit('');
-
- $context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
- $this->validator->initialize($context);
- $this->validator->validate($form, new Form());
-
- $this->assertCount(1, $context->getViolations());
- $this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
- $this->assertSame('data', $context->getViolations()[0]->getPropertyPath());
- }
-
- public function testCompositeConstraintValidatedInEachGroup()
- {
- $form = $this->getBuilder('form', null, [
- 'constraints' => [
- new Collection([
- 'field1' => new NotBlank([
- 'groups' => ['field1'],
- ]),
- 'field2' => new NotBlank([
- 'groups' => ['field2'],
- ]),
- ]),
- ],
- 'validation_groups' => ['field1', 'field2'],
- ])
- ->setData([])
- ->setCompound(true)
- ->setDataMapper(new PropertyPathMapper())
- ->getForm();
- $form->add($this->getForm('field1'));
- $form->add($this->getForm('field2'));
- $form->submit([
- 'field1' => '',
- 'field2' => '',
- ]);
-
- $context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
- $this->validator->initialize($context);
- $this->validator->validate($form, new Form());
-
- $this->assertCount(2, $context->getViolations());
- $this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
- $this->assertSame('data[field1]', $context->getViolations()[0]->getPropertyPath());
- $this->assertSame('This value should not be blank.', $context->getViolations()[1]->getMessage());
- $this->assertSame('data[field2]', $context->getViolations()[1]->getPropertyPath());
- }
-
- public function testCompositeConstraintValidatedInSequence()
- {
- $form = $this->getCompoundForm([], [
- 'constraints' => [
- new Collection([
- 'field1' => new NotBlank([
- 'groups' => ['field1'],
- ]),
- 'field2' => new NotBlank([
- 'groups' => ['field2'],
- ]),
- ]),
- ],
- 'validation_groups' => new GroupSequence(['field1', 'field2']),
- ])
- ->add($this->getForm('field1'))
- ->add($this->getForm('field2'))
- ;
-
- $form->submit([
- 'field1' => '',
- 'field2' => '',
- ]);
-
- $context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
- $this->validator->initialize($context);
- $this->validator->validate($form, new Form());
-
- $this->assertCount(1, $context->getViolations());
- $this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
- $this->assertSame('data[field1]', $context->getViolations()[0]->getPropertyPath());
- }
-
protected function createValidator()
{
return new FormValidator();
diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php
index 4324541cb7ffc..4a12acf4126b4 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php
@@ -12,23 +12,12 @@
namespace Symfony\Component\Form\Tests\Extension\Validator;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\Form\AbstractType;
-use Symfony\Component\Form\Extension\Core\Type\FormType;
-use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Validator\Constraints\Form as FormConstraint;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser;
use Symfony\Component\Form\Form;
-use Symfony\Component\Form\FormBuilderInterface;
-use Symfony\Component\Form\FormFactoryBuilder;
-use Symfony\Component\OptionsResolver\OptionsResolver;
-use Symfony\Component\Validator\Constraints\GroupSequence;
-use Symfony\Component\Validator\Constraints\Length;
-use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Mapping\CascadingStrategy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
-use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
-use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader;
use Symfony\Component\Validator\Mapping\TraversalStrategy;
use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
use Symfony\Component\Validator\Validation;
@@ -57,155 +46,4 @@ public function test2Dot5ValidationApi()
$this->assertSame(TraversalStrategy::NONE, $metadata->traversalStrategy);
$this->assertCount(0, $metadata->getPropertyMetadata('children'));
}
-
- public function testDataConstraintsInvalidateFormEvenIfFieldIsNotSubmitted()
- {
- $form = $this->createForm(FooType::class);
- $form->submit(['baz' => 'foobar'], false);
-
- $this->assertTrue($form->isSubmitted());
- $this->assertFalse($form->isValid());
- $this->assertFalse($form->get('bar')->isSubmitted());
- $this->assertCount(1, $form->get('bar')->getErrors());
- }
-
- public function testFieldConstraintsDoNotInvalidateFormIfFieldIsNotSubmitted()
- {
- $form = $this->createForm(FooType::class);
- $form->submit(['bar' => 'foobar'], false);
-
- $this->assertTrue($form->isSubmitted());
- $this->assertTrue($form->isValid());
- }
-
- public function testFieldConstraintsInvalidateFormIfFieldIsSubmitted()
- {
- $form = $this->createForm(FooType::class);
- $form->submit(['bar' => 'foobar', 'baz' => ''], false);
-
- $this->assertTrue($form->isSubmitted());
- $this->assertFalse($form->isValid());
- $this->assertTrue($form->get('bar')->isSubmitted());
- $this->assertTrue($form->get('bar')->isValid());
- $this->assertTrue($form->get('baz')->isSubmitted());
- $this->assertFalse($form->get('baz')->isValid());
- }
-
- public function testFieldsValidateInSequence()
- {
- $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
-
- $form = $this->createForm(FormType::class, null, [
- 'validation_groups' => new GroupSequence(['group1', 'group2']),
- ])
- ->add('foo', TextType::class, [
- 'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)],
- ])
- ->add('bar', TextType::class, [
- 'constraints' => [new NotBlank(['groups' => ['group2']])],
- ])
- ;
-
- $form->submit(['foo' => 'invalid', 'bar' => null]);
-
- $errors = $form->getErrors(true);
-
- $this->assertCount(1, $errors);
- $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
- }
-
- public function testFieldsValidateInSequenceWithNestedGroupsArray()
- {
- $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
-
- $form = $this->createForm(FormType::class, null, [
- 'validation_groups' => new GroupSequence([['group1', 'group2'], 'group3']),
- ])
- ->add('foo', TextType::class, [
- 'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)],
- ])
- ->add('bar', TextType::class, [
- 'constraints' => [new Length(['min' => 10, 'groups' => ['group2']] + $allowEmptyString)],
- ])
- ->add('baz', TextType::class, [
- 'constraints' => [new NotBlank(['groups' => ['group3']])],
- ])
- ;
-
- $form->submit(['foo' => 'invalid', 'bar' => 'invalid', 'baz' => null]);
-
- $errors = $form->getErrors(true);
-
- $this->assertCount(2, $errors);
- $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
- $this->assertInstanceOf(Length::class, $errors[1]->getCause()->getConstraint());
- }
-
- public function testConstraintsInDifferentGroupsOnSingleField()
- {
- $form = $this->createForm(FormType::class, null, [
- 'validation_groups' => new GroupSequence(['group1', 'group2']),
- ])
- ->add('foo', TextType::class, [
- 'constraints' => [
- new NotBlank([
- 'groups' => ['group1'],
- ]),
- new Length([
- 'groups' => ['group2'],
- 'max' => 3,
- ]),
- ],
- ]);
- $form->submit([
- 'foo' => 'test@example.com',
- ]);
-
- $errors = $form->getErrors(true);
-
- $this->assertFalse($form->isValid());
- $this->assertCount(1, $errors);
- $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
- }
-
- private function createForm($type, $data = null, array $options = [])
- {
- $validator = Validation::createValidatorBuilder()
- ->setMetadataFactory(new LazyLoadingMetadataFactory(new StaticMethodLoader()))
- ->getValidator();
- $formFactoryBuilder = new FormFactoryBuilder();
- $formFactoryBuilder->addExtension(new ValidatorExtension($validator));
- $formFactory = $formFactoryBuilder->getFormFactory();
-
- return $formFactory->create($type, $data, $options);
- }
-}
-
-class Foo
-{
- public $bar;
- public $baz;
-
- public static function loadValidatorMetadata(ClassMetadata $metadata)
- {
- $metadata->addPropertyConstraint('bar', new NotBlank());
- }
-}
-
-class FooType extends AbstractType
-{
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder
- ->add('bar')
- ->add('baz', null, [
- 'constraints' => [new NotBlank()],
- ])
- ;
- }
-
- public function configureOptions(OptionsResolver $resolver)
- {
- $resolver->setDefault('data_class', Foo::class);
- }
}
diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json
index ca855983927ab..283fff0c10812 100644
--- a/src/Symfony/Component/Form/composer.json
+++ b/src/Symfony/Component/Form/composer.json
@@ -29,6 +29,7 @@
"doctrine/collections": "~1.0",
"symfony/validator": "^3.4.31|^4.3.4|^5.0",
"symfony/dependency-injection": "^3.4|^4.0|^5.0",
+ "symfony/expression-language": "^3.4|^4.0|^5.0",
"symfony/config": "^3.4|^4.0|^5.0",
"symfony/console": "^4.3|^5.0",
"symfony/http-foundation": "^3.4|^4.0|^5.0",
diff --git a/src/Symfony/Component/HttpClient/Internal/NativeClientState.php b/src/Symfony/Component/HttpClient/Internal/NativeClientState.php
index 6578929dc5466..2a47dbcca0ec0 100644
--- a/src/Symfony/Component/HttpClient/Internal/NativeClientState.php
+++ b/src/Symfony/Component/HttpClient/Internal/NativeClientState.php
@@ -28,8 +28,6 @@ final class NativeClientState extends ClientState
public $responseCount = 0;
/** @var string[] */
public $dnsCache = [];
- /** @var resource[] */
- public $handles = [];
/** @var bool */
public $sleep = false;
diff --git a/src/Symfony/Component/HttpClient/Response/NativeResponse.php b/src/Symfony/Component/HttpClient/Response/NativeResponse.php
index 639957415f0dd..2ba1b010e41fb 100644
--- a/src/Symfony/Component/HttpClient/Response/NativeResponse.php
+++ b/src/Symfony/Component/HttpClient/Response/NativeResponse.php
@@ -220,11 +220,6 @@ private static function schedule(self $response, array &$runningResponses): void
*/
private static function perform(ClientState $multi, array &$responses = null): void
{
- // List of native handles for stream_select()
- if (null !== $responses) {
- $multi->handles = [];
- }
-
foreach ($multi->openHandles as $i => [$h, $buffer, $onProgress]) {
$hasActivity = false;
$remaining = &$multi->openHandles[$i][3];
@@ -291,8 +286,6 @@ private static function perform(ClientState $multi, array &$responses = null): v
$multi->handlesActivity[$i][] = $e;
unset($multi->openHandles[$i]);
$multi->sleep = false;
- } elseif (null !== $responses) {
- $multi->handles[] = $h;
}
}
@@ -307,7 +300,7 @@ private static function perform(ClientState $multi, array &$responses = null): v
}
}
- if (\count($multi->handles) >= $multi->maxHostConnections) {
+ if (\count($multi->openHandles) >= $multi->maxHostConnections) {
return;
}
@@ -318,10 +311,6 @@ private static function perform(ClientState $multi, array &$responses = null): v
$multi->sleep = false;
self::perform($multi);
- if (null !== $response->handle) {
- $multi->handles[] = $response->handle;
- }
-
break;
}
}
@@ -335,7 +324,8 @@ private static function perform(ClientState $multi, array &$responses = null): v
private static function select(ClientState $multi, float $timeout): int
{
$_ = [];
+ $handles = array_column($multi->openHandles, 0);
- return (!$multi->sleep = !$multi->sleep) ? -1 : stream_select($multi->handles, $_, $_, (int) $timeout, (int) (1E6 * ($timeout - (int) $timeout)));
+ return (!$multi->sleep = !$multi->sleep) ? -1 : stream_select($handles, $_, $_, (int) $timeout, (int) (1E6 * ($timeout - (int) $timeout)));
}
}
diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php
index 786e995b16ffd..8d4fe59ae6872 100644
--- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php
+++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php
@@ -137,7 +137,7 @@ public function getContent(bool $throw = true): string
public function toArray(bool $throw = true): array
{
if ('' === $content = $this->getContent($throw)) {
- throw new TransportException('Response body is empty.');
+ throw new JsonException('Response body is empty.');
}
if (null !== $this->jsonData) {
@@ -316,7 +316,7 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
}
$lastActivity = microtime(true);
- $isTimeout = false;
+ $enlapsedTimeout = 0;
while (true) {
$hasActivity = false;
@@ -338,7 +338,7 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
} elseif (!isset($multi->openHandles[$j])) {
unset($responses[$j]);
continue;
- } elseif ($isTimeout) {
+ } elseif ($enlapsedTimeout >= $timeoutMax) {
$multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))];
} else {
continue;
@@ -346,7 +346,7 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
while ($multi->handlesActivity[$j] ?? false) {
$hasActivity = true;
- $isTimeout = false;
+ $enlapsedTimeout = 0;
if (\is_string($chunk = array_shift($multi->handlesActivity[$j]))) {
if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate, $chunk)) {
@@ -359,8 +359,9 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
continue;
}
- $response->offset += \strlen($chunk);
+ $chunkLen = \strlen($chunk);
$chunk = new DataChunk($response->offset, $chunk);
+ $response->offset += $chunkLen;
} elseif (null === $chunk) {
$e = $multi->handlesActivity[$j][0];
unset($responses[$j], $multi->handlesActivity[$j]);
@@ -379,7 +380,7 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
}
} elseif ($chunk instanceof ErrorChunk) {
unset($responses[$j]);
- $isTimeout = true;
+ $enlapsedTimeout = $timeoutMax;
} elseif ($chunk instanceof FirstChunk) {
if ($response->logger) {
$info = $response->getInfo();
@@ -447,10 +448,11 @@ public static function stream(iterable $responses, float $timeout = null): \Gene
continue;
}
- switch (self::select($multi, $timeoutMin)) {
- case -1: usleep(min(500, 1E6 * $timeoutMin)); break;
- case 0: $isTimeout = microtime(true) - $lastActivity > $timeoutMax; break;
+ if (-1 === self::select($multi, min($timeoutMin, $timeoutMax - $enlapsedTimeout))) {
+ usleep(min(500, 1E6 * $timeoutMin));
}
+
+ $enlapsedTimeout = microtime(true) - $lastActivity;
}
}
}
diff --git a/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php
index f83edf91b6f39..9c18ba1cb2752 100644
--- a/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php
@@ -112,6 +112,15 @@ public function testHttp2PushVulcainWithUnusedResponse()
$this->assertSame($expected, $logger->logs);
}
+ public function testTimeoutIsNotAFatalError()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Too transient on Windows');
+ }
+
+ parent::testTimeoutIsNotAFatalError();
+ }
+
private function getVulcainClient(): CurlHttpClient
{
if (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304) {
diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php
index 7cfe26e9dfe82..94b35470e338f 100644
--- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php
+++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php
@@ -72,9 +72,9 @@ public function testToStream404()
$this->assertSame($response, stream_get_meta_data($stream)['wrapper_data']->getResponse());
$this->assertSame(404, $response->getStatusCode());
- $this->expectException(ClientException::class);
$response = $client->request('GET', 'http://localhost:8057/404');
- $stream = $response->toStream();
+ $this->expectException(ClientException::class);
+ $response->toStream();
}
public function testNonBlockingStream()
@@ -82,6 +82,7 @@ public function testNonBlockingStream()
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-body');
$stream = $response->toStream();
+ usleep(10000);
$this->assertTrue(stream_set_blocking($stream, false));
$this->assertSame('<1>', fread($stream, 8192));
@@ -99,6 +100,7 @@ public function testTimeoutIsNotAFatalError()
$response = $client->request('GET', 'http://localhost:8057/timeout-body', [
'timeout' => 0.25,
]);
+ $this->assertSame(200, $response->getStatusCode());
try {
$response->getContent();
diff --git a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php
index b8125e6716cfd..f81322e0bb0b3 100644
--- a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php
@@ -69,6 +69,10 @@ protected function getHttpClient(string $testCase): HttpClientInterface
$this->markTestSkipped("MockHttpClient doesn't unzip");
break;
+ case 'testTimeoutWithActiveConcurrentStream':
+ $this->markTestSkipped('Real transport required');
+ break;
+
case 'testDestruct':
$this->markTestSkipped("MockHttpClient doesn't timeout on destruct");
break;
diff --git a/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php b/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php
index 1b3ab16a843aa..a531da353597d 100644
--- a/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php
@@ -35,6 +35,12 @@ public function testToArrayError($content, $responseHeaders, $message)
public function toArrayErrors()
{
+ yield [
+ 'content' => '',
+ 'responseHeaders' => [],
+ 'message' => 'Response body is empty.',
+ ];
+
yield [
'content' => '{}',
'responseHeaders' => ['content-type' => 'plain/text'],
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php
index dec63d4524f76..0a5d457332e89 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php
@@ -150,8 +150,8 @@ public function lookup(Request $request)
}
$headers = $match[1];
- if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) {
- return $this->restoreResponse($headers, $body);
+ if (file_exists($path = $this->getPath($headers['x-content-digest'][0]))) {
+ return $this->restoreResponse($headers, $path);
}
// TODO the metaStore referenced an entity that doesn't exist in
@@ -175,15 +175,28 @@ public function write(Request $request, Response $response)
$key = $this->getCacheKey($request);
$storedEnv = $this->persistRequest($request);
- $digest = $this->generateContentDigest($response);
- $response->headers->set('X-Content-Digest', $digest);
+ if ($response->headers->has('X-Body-File')) {
+ // Assume the response came from disk, but at least perform some safeguard checks
+ if (!$response->headers->has('X-Content-Digest')) {
+ throw new \RuntimeException('A restored response must have the X-Content-Digest header.');
+ }
- if (!$this->save($digest, $response->getContent(), false)) {
- throw new \RuntimeException('Unable to store the entity.');
- }
+ $digest = $response->headers->get('X-Content-Digest');
+ if ($this->getPath($digest) !== $response->headers->get('X-Body-File')) {
+ throw new \RuntimeException('X-Body-File and X-Content-Digest do not match.');
+ }
+ // Everything seems ok, omit writing content to disk
+ } else {
+ $digest = $this->generateContentDigest($response);
+ $response->headers->set('X-Content-Digest', $digest);
- if (!$response->headers->has('Transfer-Encoding')) {
- $response->headers->set('Content-Length', \strlen($response->getContent()));
+ if (!$this->save($digest, $response->getContent(), false)) {
+ throw new \RuntimeException('Unable to store the entity.');
+ }
+
+ if (!$response->headers->has('Transfer-Encoding')) {
+ $response->headers->set('Content-Length', \strlen($response->getContent()));
+ }
}
// read existing cache entries, remove non-varying, and add this one to the list
@@ -448,15 +461,15 @@ private function persistResponse(Response $response): array
/**
* Restores a Response from the HTTP headers and body.
*/
- private function restoreResponse(array $headers, string $body = null): Response
+ private function restoreResponse(array $headers, string $path = null): Response
{
$status = $headers['X-Status'][0];
unset($headers['X-Status']);
- if (null !== $body) {
- $headers['X-Body-File'] = [$body];
+ if (null !== $path) {
+ $headers['X-Body-File'] = [$path];
}
- return new Response($body, $status, $headers);
+ return new Response($path, $status, $headers);
}
}
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index 49c7ab594a98f..25e8202cdb997 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -76,11 +76,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
private static $freshCache = [];
- const VERSION = '4.4.9';
- const VERSION_ID = 40409;
+ const VERSION = '4.4.10';
+ const VERSION_ID = 40410;
const MAJOR_VERSION = 4;
const MINOR_VERSION = 4;
- const RELEASE_VERSION = 9;
+ const RELEASE_VERSION = 10;
const EXTRA_VERSION = '';
const END_OF_MAINTENANCE = '11/2022';
diff --git a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php
index e1c0ff11926be..b8337dc737e4e 100644
--- a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php
+++ b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php
@@ -65,7 +65,7 @@
- You're seeing this page because you haven't configured any homepage URL.
+ You're seeing this page because you haven't configured any homepage URL and debug mode is enabled.
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php
index b17cc0a44f9e8..1f5f472802e7a 100644
--- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php
@@ -118,6 +118,39 @@ public function testWritesResponseEvenIfXContentDigestIsPresent()
$this->assertNotNull($response);
}
+ public function testWritingARestoredResponseDoesNotCorruptCache()
+ {
+ /*
+ * This covers the regression reported in https://github.com/symfony/symfony/issues/37174.
+ *
+ * A restored response does *not* load the body, but only keep the file path in a special X-Body-File
+ * header. For reasons (?), the file path was also used as the restored response body.
+ * It would be up to others (HttpCache...?) to honor this header and actually load the response content
+ * from there.
+ *
+ * When a restored response was stored again, the Store itself would ignore the header. In the first
+ * step, this would compute a new Content Digest based on the file path in the restored response body;
+ * this is covered by "Checkpoint 1" below. But, since the X-Body-File header was left untouched (Checkpoint 2), downstream
+ * code (HttpCache...) would not immediately notice.
+ *
+ * Only upon performing the lookup for a second time, we'd get a Response where the (wrong) Content Digest
+ * is also reflected in the X-Body-File header, this time also producing wrong content when the downstream
+ * evaluates it.
+ */
+ $this->store->write($this->request, $this->response);
+ $digest = $this->response->headers->get('X-Content-Digest');
+ $path = $this->getStorePath($digest);
+
+ $response = $this->store->lookup($this->request);
+ $this->store->write($this->request, $response);
+ $this->assertEquals($digest, $response->headers->get('X-Content-Digest')); // Checkpoint 1
+ $this->assertEquals($path, $response->headers->get('X-Body-File')); // Checkpoint 2
+
+ $response = $this->store->lookup($this->request);
+ $this->assertEquals($digest, $response->headers->get('X-Content-Digest'));
+ $this->assertEquals($path, $response->headers->get('X-Body-File'));
+ }
+
public function testFindsAStoredEntryWithLookup()
{
$this->storeSimpleEntry();
diff --git a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php
index fa37e6d7baa3a..cf7e9b39f2bec 100644
--- a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php
+++ b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php
@@ -108,7 +108,7 @@ public function refreshUser(UserInterface $user)
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
}
- return new LdapUser($user->getEntry(), $user->getUsername(), $user->getPassword(), $user->getRoles());
+ return new LdapUser($user->getEntry(), $user->getUsername(), $user->getPassword(), $user->getRoles(), $user->getExtraFields());
}
/**
diff --git a/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php b/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php
index 8d0a7a3517584..a2e888077cde8 100644
--- a/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php
+++ b/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php
@@ -330,4 +330,14 @@ public function testLoadUserByUsernameIsSuccessfulWithPasswordAttribute()
$provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword', ['email']);
$this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo'));
}
+
+ public function testRefreshUserShouldReturnUserWithSameProperties()
+ {
+ $ldap = $this->createMock(LdapInterface::class);
+ $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword', ['email']);
+
+ $user = new LdapUser(new Entry('foo'), 'foo', 'bar', ['ROLE_DUMMY'], ['email' => 'foo@symfony.com']);
+
+ $this->assertEquals($user, $provider->refreshUser($user));
+ }
}
diff --git a/src/Symfony/Component/Lock/Store/PdoStore.php b/src/Symfony/Component/Lock/Store/PdoStore.php
index 7af7f3870fad2..a3273c0cbc75d 100644
--- a/src/Symfony/Component/Lock/Store/PdoStore.php
+++ b/src/Symfony/Component/Lock/Store/PdoStore.php
@@ -13,6 +13,7 @@
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\Lock\Exception\InvalidArgumentException;
@@ -168,10 +169,10 @@ public function putOffExpiration(Key $key, $ttl)
$stmt->bindValue(':id', $this->getHashedKey($key));
$stmt->bindValue(':token1', $uniqueToken);
$stmt->bindValue(':token2', $uniqueToken);
- $stmt->execute();
+ $result = $stmt->execute();
// If this method is called twice in the same second, the row wouldn't be updated. We have to call exists to know if we are the owner
- if (!$stmt->rowCount() && !$this->exists($key)) {
+ if (!($result instanceof Result ? $result : $stmt)->rowCount() && !$this->exists($key)) {
throw new LockConflictedException();
}
@@ -201,9 +202,9 @@ public function exists(Key $key)
$stmt->bindValue(':id', $this->getHashedKey($key));
$stmt->bindValue(':token', $this->getUniqueToken($key));
- $stmt->execute();
+ $result = $stmt->execute();
- return (bool) (method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn());
+ return (bool) ($result instanceof Result ? $result->fetchOne() : $stmt->fetchColumn());
}
/**
diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php
index 7295fdf79a5a9..0bd5627ac9bd1 100644
--- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php
+++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php
@@ -113,6 +113,9 @@ private function getPayload(Email $email, Envelope $envelope): array
if ($email->getHtmlBody()) {
$payload['Message.Body.Html.Data'] = $email->getHtmlBody();
}
+ if ($email->getReplyTo()) {
+ $payload['ReplyToAddresses.member'] = $this->stringifyAddresses($email->getReplyTo());
+ }
return $payload;
}
diff --git a/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php b/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php
index 0472c36b6209a..b06ac839c64f7 100644
--- a/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php
+++ b/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php
@@ -22,9 +22,6 @@ class SendEmailMessage
private $message;
private $envelope;
- /**
- * @internal
- */
public function __construct(RawMessage $message, Envelope $envelope = null)
{
$this->message = $message;
diff --git a/src/Symfony/Component/Mailer/Tests/TransportTest.php b/src/Symfony/Component/Mailer/Tests/TransportTest.php
index 95eb5b6ebf03a..dfd8d1926e61a 100644
--- a/src/Symfony/Component/Mailer/Tests/TransportTest.php
+++ b/src/Symfony/Component/Mailer/Tests/TransportTest.php
@@ -60,6 +60,22 @@ public function fromStringProvider(): iterable
];
}
+ /**
+ * @dataProvider fromDsnProvider
+ */
+ public function testFromDsn(string $dsn, TransportInterface $transport): void
+ {
+ $this->assertEquals($transport, Transport::fromDsn($dsn));
+ }
+
+ public function fromDsnProvider(): iterable
+ {
+ yield 'multiple transports' => [
+ 'failover(smtp://a smtp://b)',
+ new FailoverTransport([new Transport\Smtp\EsmtpTransport('a'), new Transport\Smtp\EsmtpTransport('b')]),
+ ];
+ }
+
/**
* @dataProvider fromWrongStringProvider
*/
diff --git a/src/Symfony/Component/Mailer/Transport.php b/src/Symfony/Component/Mailer/Transport.php
index a8f159dc7c491..c0cd0c5fb9dad 100644
--- a/src/Symfony/Component/Mailer/Transport.php
+++ b/src/Symfony/Component/Mailer/Transport.php
@@ -51,7 +51,7 @@ class Transport
public static function fromDsn(string $dsn, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): TransportInterface
{
- $factory = new self(self::getDefaultFactories($dispatcher, $client, $logger));
+ $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger)));
return $factory->fromString($dsn);
}
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php
index d34e45d2ce518..b4348d4958a48 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php
+++ b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php
@@ -11,14 +11,14 @@
namespace Symfony\Component\Messenger\Tests\Transport\Doctrine;
+use Doctrine\DBAL\Abstraction\Result;
use Doctrine\DBAL\DBALException;
-use Doctrine\DBAL\Driver\ResultStatement;
-use Doctrine\DBAL\ForwardCompatibility\Driver\ResultStatement as ForwardCompatibleResultStatement;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\SchemaConfig;
use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer;
+use Doctrine\DBAL\Statement;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Transport\Doctrine\Connection;
@@ -30,7 +30,7 @@ public function testGetAMessageWillChangeItsStatus()
$queryBuilder = $this->getQueryBuilderMock();
$driverConnection = $this->getDBALConnectionMock();
$schemaSynchronizer = $this->getSchemaSynchronizerMock();
- $stmt = $this->getStatementMock([
+ $stmt = $this->getResultMock([
'id' => 1,
'body' => '{"message":"Hi"}',
'headers' => json_encode(['type' => DummyMessage::class]),
@@ -64,7 +64,7 @@ public function testGetWithNoPendingMessageWillReturnNull()
$queryBuilder = $this->getQueryBuilderMock();
$driverConnection = $this->getDBALConnectionMock();
$schemaSynchronizer = $this->getSchemaSynchronizerMock();
- $stmt = $this->getStatementMock(false);
+ $stmt = $this->getResultMock(false);
$queryBuilder
->method('getParameters')
@@ -143,16 +143,12 @@ private function getQueryBuilderMock()
return $queryBuilder;
}
- private function getStatementMock($expectedResult): ResultStatement
+ private function getResultMock($expectedResult)
{
- $mockedInterface = interface_exists(ForwardCompatibleResultStatement::class)
- ? ForwardCompatibleResultStatement::class
- : ResultStatement::class;
-
- $stmt = $this->createMock($mockedInterface);
+ $stmt = $this->createMock(interface_exists(Result::class) ? Result::class : Statement::class);
$stmt->expects($this->once())
- ->method(method_exists($mockedInterface, 'fetchAssociative') ? 'fetchAssociative' : 'fetch')
+ ->method(interface_exists(Result::class) ? 'fetchAssociative' : 'fetch')
->willReturn($expectedResult);
return $stmt;
@@ -267,7 +263,7 @@ public function testFind()
$driverConnection = $this->getDBALConnectionMock();
$schemaSynchronizer = $this->getSchemaSynchronizerMock();
$id = 1;
- $stmt = $this->getStatementMock([
+ $stmt = $this->getResultMock([
'id' => $id,
'body' => '{"message":"Hi"}',
'headers' => json_encode(['type' => DummyMessage::class]),
@@ -312,12 +308,9 @@ public function testFindAll()
'headers' => json_encode(['type' => DummyMessage::class]),
];
- $mockedInterface = interface_exists(ForwardCompatibleResultStatement::class)
- ? ForwardCompatibleResultStatement::class
- : ResultStatement::class;
- $stmt = $this->createMock($mockedInterface);
+ $stmt = $this->createMock(interface_exists(Result::class) ? Result::class : Statement::class);
$stmt->expects($this->once())
- ->method(method_exists($mockedInterface, 'fetchAllAssociative') ? 'fetchAllAssociative' : 'fetchAll')
+ ->method(interface_exists(Result::class) ? 'fetchAllAssociative' : 'fetchAll')
->willReturn([$message1, $message2]);
$driverConnection
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineIntegrationTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineIntegrationTest.php
index 45ca64b77106a..37a38b95d485e 100644
--- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineIntegrationTest.php
+++ b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineIntegrationTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Messenger\Tests\Transport\Doctrine;
+use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Version;
use PHPUnit\Framework\TestCase;
@@ -71,7 +72,7 @@ public function testSendWithDelay()
->setParameter(':body', '{"message": "Hi i am delayed"}')
->execute();
- $available_at = new \DateTime(method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn());
+ $available_at = new \DateTime($stmt instanceof Result ? $stmt->fetchOne() : $stmt->fetchColumn());
$now = new \DateTime();
$now->modify('+60 seconds');
diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php
index 4f5b43c4ac8d9..19141bd8d94c3 100644
--- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php
+++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php
@@ -13,7 +13,7 @@
use Doctrine\DBAL\Connection as DBALConnection;
use Doctrine\DBAL\DBALException;
-use Doctrine\DBAL\Driver\ResultStatement;
+use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Schema\Schema;
@@ -164,7 +164,7 @@ public function get(): ?array
$query->getParameters(),
$query->getParameterTypes()
);
- $doctrineEnvelope = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch();
+ $doctrineEnvelope = $stmt instanceof Result ? $stmt->fetchAssociative() : $stmt->fetch();
if (false === $doctrineEnvelope) {
$this->driverConnection->commit();
@@ -252,7 +252,7 @@ public function getMessageCount(): int
$stmt = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes());
- return method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn();
+ return $stmt instanceof Result ? $stmt->fetchOne() : $stmt->fetchColumn();
}
public function findAll(int $limit = null): array
@@ -263,7 +263,7 @@ public function findAll(int $limit = null): array
}
$stmt = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes());
- $data = method_exists($stmt, 'fetchAllAssociative') ? $stmt->fetchAllAssociative() : $stmt->fetchAll();
+ $data = $stmt instanceof Result ? $stmt->fetchAllAssociative() : $stmt->fetchAll();
return array_map(function ($doctrineEnvelope) {
return $this->decodeEnvelopeHeaders($doctrineEnvelope);
@@ -276,7 +276,7 @@ public function find($id): ?array
->where('m.id = ?');
$stmt = $this->executeQuery($queryBuilder->getSQL(), [$id]);
- $data = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch();
+ $data = $stmt instanceof Result ? $stmt->fetchAssociative() : $stmt->fetch();
return false === $data ? null : $this->decodeEnvelopeHeaders($data);
}
@@ -310,7 +310,7 @@ private function createQueryBuilder(): QueryBuilder
->from($this->configuration['table_name'], 'm');
}
- private function executeQuery(string $sql, array $parameters = [], array $types = []): ResultStatement
+ private function executeQuery(string $sql, array $parameters = [], array $types = [])
{
try {
$stmt = $this->driverConnection->executeQuery($sql, $parameters, $types);
diff --git a/src/Symfony/Component/Mime/Address.php b/src/Symfony/Component/Mime/Address.php
index b0dcbd0880f66..53f682c9c2bb7 100644
--- a/src/Symfony/Component/Mime/Address.php
+++ b/src/Symfony/Component/Mime/Address.php
@@ -89,7 +89,7 @@ public static function create($address): self
return $address;
}
if (\is_string($address)) {
- return new self($address);
+ return self::fromString($address);
}
throw new InvalidArgumentException(sprintf('An address can be an instance of Address or a string ("%s") given).', \is_object($address) ? \get_class($address) : \gettype($address)));
diff --git a/src/Symfony/Component/Mime/Crypto/SMimeSigner.php b/src/Symfony/Component/Mime/Crypto/SMimeSigner.php
index 243aaf10da060..1b555375ce908 100644
--- a/src/Symfony/Component/Mime/Crypto/SMimeSigner.php
+++ b/src/Symfony/Component/Mime/Crypto/SMimeSigner.php
@@ -24,11 +24,6 @@ final class SMimeSigner extends SMime
private $signOptions;
private $extraCerts;
- /**
- * @var string|null
- */
- private $privateKeyPassphrase;
-
/**
* @param string $certificate The path of the file containing the signing certificate (in PEM format)
* @param string $privateKey The path of the file containing the private key (in PEM format)
@@ -52,7 +47,6 @@ public function __construct(string $certificate, string $privateKey, string $pri
$this->signOptions = $signOptions ?? PKCS7_DETACHED;
$this->extraCerts = $extraCerts ? realpath($extraCerts) : null;
- $this->privateKeyPassphrase = $privateKeyPassphrase;
}
public function sign(Message $message): Message
diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php
index 8e92abe29cdcf..470e9ed0010af 100644
--- a/src/Symfony/Component/Serializer/Serializer.php
+++ b/src/Symfony/Component/Serializer/Serializer.php
@@ -20,6 +20,7 @@
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
+use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
@@ -157,7 +158,7 @@ public function normalize($data, $format = null, array $context = [])
}
if (\is_array($data) || $data instanceof \Traversable) {
- if ($data instanceof \Countable && 0 === $data->count()) {
+ if (($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) === true && $data instanceof \Countable && 0 === $data->count()) {
return $data;
}
diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php
index fe8f8de929692..dce864abfcf47 100644
--- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php
@@ -491,6 +491,27 @@ public function testNotNormalizableValueExceptionMessageForAResource()
(new Serializer())->normalize(tmpfile());
}
+ public function testNormalizeTransformEmptyArrayObjectToArray()
+ {
+ $serializer = new Serializer(
+ [
+ new PropertyNormalizer(),
+ new ObjectNormalizer(),
+ new ArrayDenormalizer(),
+ ],
+ [
+ 'json' => new JsonEncoder(),
+ ]
+ );
+
+ $object = [];
+ $object['foo'] = new \ArrayObject();
+ $object['bar'] = new \ArrayObject(['notempty']);
+ $object['baz'] = new \ArrayObject(['nested' => new \ArrayObject()]);
+
+ $this->assertSame('{"foo":[],"bar":["notempty"],"baz":{"nested":[]}}', $serializer->serialize($object, 'json'));
+ }
+
public function testNormalizePreserveEmptyArrayObject()
{
$serializer = new Serializer(
diff --git a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php
index 2dfb09d2ffc1f..8dbcc06e8ddc6 100644
--- a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php
+++ b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php
@@ -92,7 +92,7 @@ public function testExport(string $testName, $value, bool $staticValueExpected =
} elseif (\PHP_VERSION_ID < 70400) {
$fixtureFile = __DIR__.'/Fixtures/'.$testName.'-legacy.php';
} else {
- $this->markAsSkipped('PHP >= 7.4.6 required.');
+ $this->markTestSkipped('PHP >= 7.4.6 required.');
}
$this->assertStringEqualsFile($fixtureFile, $dump);
diff --git a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
index 8ef46a9a90eeb..c1ad3ffec5014 100644
--- a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
+++ b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
@@ -786,6 +786,30 @@ public function testUncheckedTimeoutThrows()
}
}
+ public function testTimeoutWithActiveConcurrentStream()
+ {
+ $p1 = TestHttpServer::start(8067);
+ $p2 = TestHttpServer::start(8077);
+
+ $client = $this->getHttpClient(__FUNCTION__);
+ $streamingResponse = $client->request('GET', 'http://localhost:8067/max-duration');
+ $blockingResponse = $client->request('GET', 'http://localhost:8077/timeout-body', [
+ 'timeout' => 0.25,
+ ]);
+
+ $this->assertSame(200, $streamingResponse->getStatusCode());
+ $this->assertSame(200, $blockingResponse->getStatusCode());
+
+ $this->expectException(TransportExceptionInterface::class);
+
+ try {
+ $blockingResponse->getContent();
+ } finally {
+ $p1->stop();
+ $p2->stop();
+ }
+ }
+
public function testDestruct()
{
$client = $this->getHttpClient(__FUNCTION__);
diff --git a/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php b/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php
index cfc100d80ce6c..06a11444e35e4 100644
--- a/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php
+++ b/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php
@@ -19,23 +19,28 @@
*/
class TestHttpServer
{
- private static $process;
+ private static $process = [];
- public static function start()
+ public static function start(int $port = 8057)
{
- if (self::$process) {
- self::$process->stop();
+ if (isset(self::$process[$port])) {
+ self::$process[$port]->stop();
+ } else {
+ register_shutdown_function(static function () use ($port) {
+ self::$process[$port]->stop();
+ });
}
$finder = new PhpExecutableFinder();
- $process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:8057']));
+ $process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:'.$port]));
$process->setWorkingDirectory(__DIR__.'/Fixtures/web');
$process->start();
+ self::$process[$port] = $process;
do {
usleep(50000);
- } while (!@fopen('http://127.0.0.1:8057/', 'r'));
+ } while (!@fopen('http://127.0.0.1:'.$port, 'r'));
- self::$process = $process;
+ return $process;
}
}