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; } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy