diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 30408a440624e..6a3604dff7aad 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ | Q | A | ------------- | --- -| Branch? | 6.3 for features / 4.4, 5.4, 6.0, 6.1, or 6.2 for bug fixes +| Branch? | 6.3 for features / 5.4, 6.0, 6.1, or 6.2 for bug fixes | Bug fix? | yes/no | New feature? | yes/no | Deprecations? | yes/no @@ -16,7 +16,7 @@ Additionally (see https://symfony.com/releases): - Bug fixes must be submitted against the lowest maintained branch where they apply (lowest branches are regularly merged to upper ones so they get the fixes too). - Features and deprecations must be submitted against the latest branch. - - For new features, provide some code snippets to help understand usage. + - For new features, provide some code snippets to help understand usage. - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry - Never break backward compatibility (see https://symfony.com/bc). --> diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index f244dd2542d8a..7dc6fb938da19 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -48,7 +48,7 @@ jobs: ports: - 16379:6379 redis-cluster: - image: grokzen/redis-cluster:5.0.4 + image: grokzen/redis-cluster:latest ports: - 7000:7000 - 7001:7001 diff --git a/CHANGELOG-5.4.md b/CHANGELOG-5.4.md index 378f31473a4c5..ace18524fa969 100644 --- a/CHANGELOG-5.4.md +++ b/CHANGELOG-5.4.md @@ -7,6 +7,41 @@ in 5.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.4.0...v5.4.1 +* 5.4.17 (2022-12-28) + + * bug #48787 [PhpUnitBridge] Use verbose deprecation output for quiet types only when it reaches the threshold (ogizanagi) + * bug #48784 [Console] Correctly overwrite progressbars with different line count per step (ncharalampidis) + * bug #48801 [Form] Make `ButtonType` handle `form_attr` option (MatTheCat) + * bug #48791 [DependencyInjection] Fix deduplicating service instances in circular graphs (nicolas-grekas) + * bug #48771 [CssSelector] Fix escape patterns (fancyweb) + * bug #48711 [Cache] RedisTrait::createConnection does not pass auth value from redis sentinel cluster DSN (evgkord) + * bug #48724 [VarExporter] Fix exporting classes with __unserialize() but not __serialize() (fancyweb) + * bug #48746 [Validator] Fix IBAN format for Tunisia and Mauritania (smelesh) + * bug #48738 [Workflow] Allow spaces in place names so the PUML dump doesn't break (Kamil Musial) + * bug #48718 Compatibility with doctrine/annotations 2 (derrabus) + * bug #48651 [HttpKernel] AbstractSessionListener should not override the cache lifetime for private responses (rodmen) + * bug #48591 [DependencyInjection] Shared private services becomes public after a public service is accessed (alexpott) + * bug #48126 [Mailer] Include all transports' debug messages in RoundRobin transport exception (mixdf) + * bug #48635 [HttpFoundation] Use relative timestamps with MemcachedSessionHandler (tvlooy) + * bug #47979 [Cache] Fix dealing with ext-redis' multi/exec returning a bool (João Nogueira) + * bug #48612 [Messenger] [Amqp] Added missing rpc_timeout option (lyrixx) + * bug #48233 [Serializer] Prevent `GetSetMethodNormalizer` from creating invalid magic method call (klaussilveira) + * bug #48628 [HttpFoundation] Fix dumping array cookies (nicolas-grekas) + * bug #48048 [WebProfilerBundle] Fix dump header not being displayed (HypeMC) + * bug #47836 [HttpClient] TraceableHttpClient: increase decorator's priority (adpeyre) + * bug #48259 [FrameworkBundle] Allow configuring `framework.exceptions` with a config builder (MatTheCat) + * bug #48314 [Mime] Fix MessagePart serialization (Amunak) + * bug #48331 [Yaml] fix dumping top-level tagged values (xabbuh) + * bug #48615 Fix getting the name of closures on PHP 8.1.11+ (nicolas-grekas) + * bug #48618 [ErrorHandler] [DebugClassLoader] Fix some new return types support (fancyweb) + * bug #48421 [HttpFoundation] IPv4-mapped IPv6 addresses incorrectly rejected (bonroyage) + * bug #48501 [RateLimiter] Add `int` to `Reservation::wait()` (DaRealFreak) + * bug #48359 [VarDumper] Ignore \Error in __debugInfo() (fancyweb) + * bug #48482 [DependencyInjection] Revert "bug #48027 Don't autoconfigure tag when it's already set with attributes" (nicolas-grekas) + * bug #48335 [TwigBridge] Amend `MoneyType` twig to include a space (mogilvie) + * bug #48046 [WebProfilerBundle] Remove redundant code from logger template (HypeMC) + * bug #48292 [Security] [LoginLink] Throw InvalidLoginLinkException on missing parameter (MatTheCat) + * 5.4.16 (2022-11-28) * bug #48333 [Yaml] parse unquoted digits in tag values as integers (xabbuh) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 72ef68321528f..94862ab99db42 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -23,8 +23,8 @@ The Symfony Connect username in parenthesis allows to get more information - Victor Berchet (victor) - Yonel Ceruto (yonelceruto) - Tobias Nyholm (tobias) - - Javier Eguiluz (javier.eguiluz) - Oskar Stark (oskarstark) + - Javier Eguiluz (javier.eguiluz) - Ryan Weaver (weaverryan) - Johannes S (johannes) - Jakub Zalas (jakubzalas) @@ -38,12 +38,12 @@ The Symfony Connect username in parenthesis allows to get more information - Joseph Bielawski (stloyd) - Drak (drak) - Abdellatif Ait boudad (aitboudad) - - Lukas Kahwe Smith (lsmith) - Jan Schädlich (jschaedl) - - Martin Hasoň (hason) + - Lukas Kahwe Smith (lsmith) - Jérôme Tamarelle (gromnan) - - Jeremy Mikola (jmikola) + - Martin Hasoň (hason) - Kevin Bond (kbond) + - Jeremy Mikola (jmikola) - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) - Igor Wiedler @@ -57,8 +57,9 @@ The Symfony Connect username in parenthesis allows to get more information - Grégoire Paris (greg0ire) - Gabriel Ostrolucký (gadelat) - Jonathan Wage (jwage) - - David Maicher (dmaicher) - Titouan Galopin (tgalopin) + - David Maicher (dmaicher) + - Alexandre Daubois (alexandre-daubois) - Alexandre Salomé (alexandresalome) - William DURAND - Alexander Schranz (alexander-schranz) @@ -71,16 +72,17 @@ The Symfony Connect username in parenthesis allows to get more information - Alexander Mols (asm89) - Gábor Egyed (1ed) - Francis Besset (francisbesset) - - Alexandre Daubois (alexandre-daubois) - Vasilij Dusko | CREATION - Bulat Shakirzyanov (avalanche123) - Iltar van der Berg - Miha Vrhovnik (mvrhov) - Mathieu Piot (mpiot) - Saša Stamenković (umpirsky) + - Antoine Lamirault - Alex Pott - - Guilhem N (guilhemn) - Vincent Langlet (deviling) + - Mathieu Lechat (mat_the_cat) + - Guilhem N (guilhemn) - Vladimir Reznichenko (kalessil) - Sarah Khalil (saro0h) - Konstantin Kudryashov (everzet) @@ -112,11 +114,9 @@ The Symfony Connect username in parenthesis allows to get more information - Przemysław Bogusz (przemyslaw-bogusz) - Henrik Westphal (snc) - Dariusz Górecki (canni) - - Mathieu Lechat (mat_the_cat) - Maxime Helias (maxhelias) - Ener-Getick - Ruud Kamphuis (ruudk) - - Antoine Lamirault - Sebastiaan Stok (sstok) - Jérôme Vasseur (jvasseur) - Ion Bazan (ionbazan) @@ -136,10 +136,11 @@ The Symfony Connect username in parenthesis allows to get more information - Konstantin.Myakshin - Rokas Mikalkėnas (rokasm) - Arman Hosseini (arman) + - Saif Eddin Gmati (azjezz) - Arnaud Le Blanc (arnaud-lb) - Maxime STEINHAUSSER - Peter Kokot (maastermedia) - - Saif Eddin Gmati (azjezz) + - jeremyFreeAgent (jeremyfreeagent) - Ahmed TAILOULOUTE (ahmedtai) - Simon Berger - Tim Nagel (merk) @@ -158,26 +159,27 @@ The Symfony Connect username in parenthesis allows to get more information - lenar - Jesse Rushlow (geeshoe) - Théo FIDRY - - jeremyFreeAgent (jeremyfreeagent) + - Jeroen Spee (jeroens) - Michael Babker (mbabker) - Włodzimierz Gajda (gajdaw) - Christian Scheb - Guillaume (guill) - Tugdual Saunier (tucksaun) - Jacob Dreesen (jdreesen) - - Jeroen Spee (jeroens) - Joel Wurtz (brouznouf) - Olivier Dolbeau (odolbeau) - Florian Voutzinos (florianv) - zairig imad (zairigimad) + - Hugo Alliaume (kocal) - Colin Frei + - Christopher Hertel (chertel) - Javier Spagnoletti (phansys) - excelwebzone + - Phil Taylor (prazgod) - Jérôme Parmentier (lctrs) - HeahDude - Richard van Laak (rvanlaak) - Paráda József (paradajozsef) - - Christopher Hertel (chertel) - Alessandro Lai (jean85) - Alexander Schwenn (xelaris) - Fabien Pennequin (fabienpennequin) @@ -209,13 +211,13 @@ The Symfony Connect username in parenthesis allows to get more information - Jhonny Lidfors (jhonne) - Martin Hujer (martinhujer) - Wouter J + - Chi-teck - Guilliam Xavier - Antonio Pauletich (x-coder264) - Timo Bakx (timobakx) - Juti Noppornpitak (shiroyuki) - Joe Bennett (kralos) - Nate Wiebe (natewiebe13) - - Hugo Alliaume (kocal) - Anthony MARTIN - Colin O'Dell (colinodell) - Sebastian Hörl (blogsh) @@ -227,11 +229,12 @@ The Symfony Connect username in parenthesis allows to get more information - Albert Casademont (acasademont) - Arnaud Kleinpeter (nanocom) - Guilherme Blanco (guilhermeblanco) - - Chi-teck - Michael Voříšek - Farhad Safarov (safarov) - SpacePossum + - Nicolas Philippe (nikophil) - Pablo Godel (pgodel) + - Denis Brumann (dbrumann) - Romaric Drigon (romaricdrigon) - Andréia Bohner (andreia) - Jannik Zschiesche @@ -244,25 +247,25 @@ The Symfony Connect username in parenthesis allows to get more information - David Prévot - Vincent Touzet (vincenttouzet) - Fabien Bourigault (fbourigault) + - soyuka + - Sergey (upyx) - Jérémy Derussé - - Nicolas Philippe (nikophil) - Hubert Lenoir (hubert_lenoir) - Florent Mata (fmata) - mcfedr (mcfedr) - - Denis Brumann (dbrumann) - Maciej Malarz (malarzm) - Soner Sayakci - Artem Lopata - Sokolov Evgeniy (ewgraf) - Stadly - Justin Hileman (bobthecow) + - Tom Van Looy (tvlooy) - Niels Keurentjes (curry684) - Vyacheslav Pavlov - Richard Shank (iampersistent) + - Thomas Landauer (thomas-landauer) - Andre Rømcke (andrerom) - Dmitrii Poddubnyi (karser) - - soyuka - - Sergey (upyx) - Rouven Weßling (realityking) - BoShurik - Zmey @@ -274,7 +277,6 @@ The Symfony Connect username in parenthesis allows to get more information - Ben Hakim - Sylvain Fabre (sylfabre) - Filippo Tessarotto (slamdunk) - - Tom Van Looy (tvlooy) - 77web - Bohan Yang (brentybh) - Bastien Jaillot (bastnic) @@ -287,8 +289,6 @@ The Symfony Connect username in parenthesis allows to get more information - Jonathan Ingram - Artur Kotyrba - Tyson Andre - - Thomas Landauer (thomas-landauer) - - Phil Taylor (prazgod) - GDIBass - Samuel NELA (snela) - dFayet @@ -324,6 +324,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jonathan Scheiber (jmsche) - DQNEO - Andrii Bodnar + - gnito-org - Artem (artemgenvald) - ivan - Sergey Belyshkin (sbelyshkin) @@ -351,6 +352,7 @@ The Symfony Connect username in parenthesis allows to get more information - Clara van Miert - Martin Auswöger - Alexander Menshchikov + - Marcin Sikoń (marphi) - Stepan Anchugov (kix) - bronze1man - sun (sun) @@ -368,6 +370,7 @@ The Symfony Connect username in parenthesis allows to get more information - Pierre Minnieur (pminnieur) - Kyle - Dominique Bongiraud + - Romain Monteil (ker0x) - Hidde Wieringa (hiddewie) - Christopher Davis (chrisguitarguy) - Lukáš Holeczy (holicz) @@ -387,7 +390,7 @@ The Symfony Connect username in parenthesis allows to get more information - Daniel STANCU - Markus Fasselt (digilist) - Maxime Veber (nek-) - - Marcin Sikoń (marphi) + - Oleksiy (alexndlm) - Sullivan SENECHAL (soullivaneuh) - Rui Marinho (ruimarinho) - Marc Weistroff (futurecat) @@ -409,7 +412,6 @@ The Symfony Connect username in parenthesis allows to get more information - Craig Duncan (duncan3dc) - Mantis Development - Pablo Lozano (arkadis) - - Romain Monteil (ker0x) - quentin neyrat (qneyrat) - Antonio Jose Cerezo (ajcerezo) - Marcin Szepczynski (czepol) @@ -480,6 +482,7 @@ The Symfony Connect username in parenthesis allows to get more information - Quynh Xuan Nguyen (seriquynh) - Ray - Philipp Cordes (corphi) + - Yannick Ihmels (ihmels) - Andrii Dembitskyi - Chekote - bhavin (bhavin4u) @@ -508,6 +511,7 @@ The Symfony Connect username in parenthesis allows to get more information - Josip Kruslin (jkruslin) - Giorgio Premi - renanbr + - Maxim Dovydenok (shiftby) - Sébastien Lavoie (lavoiesl) - Alex Rock (pierstoval) - Wodor Wodorski @@ -543,6 +547,7 @@ The Symfony Connect username in parenthesis allows to get more information - Chris Smith (cs278) - Florian Klein (docteurklein) - Bilge + - Cătălin Dan (dancatalin) - Rhodri Pugh (rodnaph) - Manuel Kiessling (manuelkiessling) - Patrick Reimers (preimers) @@ -562,7 +567,6 @@ The Symfony Connect username in parenthesis allows to get more information - Grégoire Passault (gregwar) - Jerzy Zawadzki (jzawadzki) - Ismael Ambrosi (iambrosi) - - Yannick Ihmels (ihmels) - Saif Eddin G - Emmanuel BORGES (eborges78) - siganushka (siganushka) @@ -570,6 +574,7 @@ The Symfony Connect username in parenthesis allows to get more information - Evert Harmeling (evertharmeling) - Jan Decavele (jandc) - Gustavo Piltcher + - Shakhobiddin - Grenier Kévin (mcsky_biig) - Stepan Tanasiychuk (stfalcon) - Tiago Ribeiro (fixe) @@ -602,6 +607,7 @@ The Symfony Connect username in parenthesis allows to get more information - Tri Pham (phamuyentri) - marie - Erkhembayar Gantulga (erheme318) + - Philippe SEGATORI (tigitz) - Fractal Zombie - Gunnstein Lye (glye) - Thomas Talbot (ioni) @@ -620,7 +626,6 @@ The Symfony Connect username in parenthesis allows to get more information - Ricard Clau (ricardclau) - Dmitrii Tarasov (dtarasov) - Philipp Kolesnikov - - Maxim Dovydenok (shiftby) - Carlos Pereira De Amorim (epitre) - Rodrigo Aguilera - Roumen Damianoff @@ -701,7 +706,6 @@ The Symfony Connect username in parenthesis allows to get more information - ShinDarth - Arun Philip - Stéphane PY (steph_py) - - Cătălin Dan (dancatalin) - Philipp Kräutli (pkraeutli) - Carl Casbolt (carlcasbolt) - battye @@ -770,7 +774,6 @@ The Symfony Connect username in parenthesis allows to get more information - Tomasz Ignatiuk - Joachim Løvgaard (loevgaard) - vladimir.reznichenko - - Shakhobiddin - Kai - Lee Rowlands - Alain Hippolyte (aloneh) @@ -811,6 +814,7 @@ The Symfony Connect username in parenthesis allows to get more information - Rimas Kudelis - Ben Scott (bpscott) - Andrii Dembitskyi + - a.dmitryuk - Pavol Tuka - Paulo Ribeiro (paulo) - Marc Laporte @@ -825,6 +829,7 @@ The Symfony Connect username in parenthesis allows to get more information - Steffen Roßkamp - Alexandru Furculita (afurculita) - Michel Salib (michelsalib) + - Quentin Dequippe (qdequippe) - Valentin Jonovs - geoffrey - Bastien DURAND (deamon) @@ -837,12 +842,12 @@ The Symfony Connect username in parenthesis allows to get more information - Tobias Bönner - Berny Cantos (xphere81) - Mátyás Somfai (smatyas) + - Simon Leblanc (leblanc_simon) - Jan Schumann - Matheo Daninos (mathdns) - Niklas Fiekas - Mark Challoner (markchalloner) - Markus Bachmann (baachi) - - Philippe SEGATORI (tigitz) - Roger Guasch (rogerguasch) - Luis Tacón (lutacon) - Alex Hofbauer (alexhofbauer) @@ -856,6 +861,7 @@ The Symfony Connect username in parenthesis allows to get more information - Arturs Vonda - Xavier Briand (xavierbriand) - Daniel Badura + - Angelov Dejan (angelov) - vagrant - Asier Illarramendi (doup) - AKeeman (akeeman) @@ -871,6 +877,7 @@ The Symfony Connect username in parenthesis allows to get more information - Vitaliy Tverdokhlib (vitaliytv) - Ariel Ferrandini (aferrandini) - Niklas Keller + - BASAK Semih (itsemih) - Dirk Pahl (dirkaholic) - Cédric Lombardot (cedriclombardot) - Jonas Flodén (flojon) @@ -898,7 +905,9 @@ The Symfony Connect username in parenthesis allows to get more information - ReenExe - Fabian Lange (codingfabian) - Yoshio HANAWA + - Sergey Melesh (sergex) - Toon Verwerft (veewee) + - Jiri Barous - Gert de Pagter - Sebastian Bergmann - Miroslav Šustek (sustmi) @@ -1001,6 +1010,7 @@ The Symfony Connect username in parenthesis allows to get more information - Zach Badgett (zachbadgett) - Loïc Faugeron - Aurélien Fredouelle + - Jordane VASPARD (elementaire) - Pavel Campr (pcampr) - Forfarle (forfarle) - Johnny Robeson (johnny) @@ -1014,10 +1024,10 @@ The Symfony Connect username in parenthesis allows to get more information - Elan Ruusamäe (glen) - Brad Jones - Nicolas de Marqué (nicola) - - a.dmitryuk - Jannik Zschiesche - Jan Ole Behrens (deegital) - Mantas Var (mvar) + - Florent Morselli (spomky_) - Yann LUCAS (drixs6o9) - Sebastian Krebs - Htun Htun Htet (ryanhhh91) @@ -1039,7 +1049,6 @@ The Symfony Connect username in parenthesis allows to get more information - Aurélien MARTIN - Malte Schlüter - Jules Matsounga (hyoa) - - Quentin Dequippe (qdequippe) - Yewhen Khoptynskyi (khoptynskyi) - Jérôme Nadaud (jnadaud) - wuchen90 @@ -1159,6 +1168,7 @@ The Symfony Connect username in parenthesis allows to get more information - RevZer0 (rav) - remieuronews - Marek Binkowski + - Benjamin Schoch (bschoch) - Rostyslav Kinash - Andrey Lebedev (alebedev) - Cristoforo Cervino (cristoforocervino) @@ -1179,7 +1189,6 @@ The Symfony Connect username in parenthesis allows to get more information - Quentin Moreau (sheitak) - Stefan Warman (warmans) - Bert Ramakers - - Angelov Dejan (angelov) - Tristan Maindron (tmaindron) - Behnoush Norouzali (behnoush) - Marc Duboc (icemad) @@ -1188,6 +1197,7 @@ The Symfony Connect username in parenthesis allows to get more information - Timothée BARRAY - Nilmar Sanchez Muguercia - Ivo Bathke (ivoba) + - Lukas Mencl - Strate - Anton A. Sumin - Atthaphon Urairat @@ -1220,7 +1230,6 @@ The Symfony Connect username in parenthesis allows to get more information - Evgeny Efimov (edefimov) - John VanDeWeghe - Oleg Mifle - - gnito-org - Michael Devery (mickadoo) - Loïc Ovigne (oviglo) - Antoine Corcy @@ -1248,6 +1257,7 @@ The Symfony Connect username in parenthesis allows to get more information - Benjamin Zikarsky (bzikarsky) - Jason Schilling (chapterjason) - Nathan PAGE (nathix) + - Rodrigo Méndez (rodmen) - sl_toto (sl_toto) - Marek Pietrzak (mheki) - Dmitrii Lozhkin @@ -1289,6 +1299,7 @@ The Symfony Connect username in parenthesis allows to get more information - Konstantin Bogomolov - Mark Spink - Cesar Scur (cesarscur) + - Maximilian Beckers (maxbeckers) - Kevin (oxfouzer) - Paweł Wacławczyk (pwc) - Sagrario Meneses @@ -1312,7 +1323,6 @@ The Symfony Connect username in parenthesis allows to get more information - rtek - Maxime AILLOUD (mailloud) - Richard van den Brand (ricbra) - - Sergey Melesh (sergex) - mohammadreza honarkhah - develop - flip111 @@ -1336,7 +1346,6 @@ The Symfony Connect username in parenthesis allows to get more information - Massimiliano Braglia (massimilianobraglia) - Swen van Zanten (swenvanzanten) - Frankie Wittevrongel - - Oleksiy (alexndlm) - Richard Quadling - James Hudson (mrthehud) - Adam Prickett @@ -1358,6 +1367,7 @@ The Symfony Connect username in parenthesis allows to get more information - Harald Tollefsen - Arend-Jan Tetteroo - Mbechezi Nawo + - Klaus Silveira (klaussilveira) - Andre Eckardt (korve) - Michael Piecko (michael.piecko) - Osayawe Ogbemudia Terry (terdia) @@ -1390,7 +1400,6 @@ The Symfony Connect username in parenthesis allows to get more information - Serhiy Lunak (slunak) - Wojciech Błoszyk (wbloszyk) - Jeroen van den Enden (endroid) - - Jiri Barous - abunch - tamcy - Mikko Pesari @@ -1570,6 +1579,7 @@ The Symfony Connect username in parenthesis allows to get more information - Stefano Degenkamp (steef) - James Michael DuPont - kor3k kor3k (kor3k) + - Rustam Bakeev (nommyde) - Eric Schildkamp - agaktr - Vincent CHALAMON @@ -1773,7 +1783,6 @@ The Symfony Connect username in parenthesis allows to get more information - Shin Ohno (ganchiku) - Jaap van Otterdijk (jaapio) - Kubicki Kamil (kubik) - - Simon Leblanc (leblanc_simon) - Vladislav Nikolayev (luxemate) - Martin Mandl (m2mtech) - Maxime Pinot (maximepinot) @@ -1918,7 +1927,6 @@ The Symfony Connect username in parenthesis allows to get more information - Ronny (big-r) - Anton (bonio) - Alexandre Fiocre (demos77) - - Jordane VASPARD (elementaire) - Erwan Nader (ernadoo) - Faizan Akram Dar (faizanakram) - Greg Szczotka (greg606) @@ -1970,7 +1978,6 @@ The Symfony Connect username in parenthesis allows to get more information - Daniel Alejandro Castro Arellano (lexcast) - Aleksandar Dimitrov (netbull) - Gary Houbre (thegarious) - - Florent Morselli - Thomas Jarrand - Baptiste Leduc (bleduc) - Antoine Bluchet (soyuka) @@ -1992,6 +1999,7 @@ The Symfony Connect username in parenthesis allows to get more information - The Whole Life to Learn - Mikkel Paulson - ergiegonzaga + - kurozumi (kurozumi) - Liverbool (liverbool) - Dalibor Karlović - Sam Malone @@ -1999,6 +2007,7 @@ The Symfony Connect username in parenthesis allows to get more information - Chris Jones (leek) - neghmurken - stefan.r + - Allison Guilhem (a_guilhem) - xaav - Jean-Christophe Cuvelier [Artack] - Mahmoud Mostafa (mahmoud) @@ -2044,6 +2053,7 @@ The Symfony Connect username in parenthesis allows to get more information - Zachary Tong (polyfractal) - Ashura - Hryhorii Hrebiniuk + - Alex Plekhanov - johnstevenson - hamza - dantleech @@ -2069,7 +2079,6 @@ The Symfony Connect username in parenthesis allows to get more information - Pablo Borowicz - Ondřej Frei - Máximo Cuadros (mcuadros) - - Lukas Mencl - EXT - THERAGE Kevin - tamirvs - gauss @@ -2091,6 +2100,7 @@ The Symfony Connect username in parenthesis allows to get more information - Mert Simsek (mrtsmsk0) - Lin Clark - Jeremy David (jeremy.david) + - Michał Marcin Brzuchalski (brzuchal) - Jordi Rejas - Troy McCabe - Ville Mattila @@ -2137,6 +2147,7 @@ The Symfony Connect username in parenthesis allows to get more information - Hugo Fonseca (fonsecas72) - Martynas Narbutas - Bailey Parker + - curlycarla2004 - Antanas Arvasevicius - Kris Kelly - Eddie Abou-Jaoude (eddiejaoude) @@ -2156,6 +2167,7 @@ The Symfony Connect username in parenthesis allows to get more information - HellFirePvP - Maximilian Ruta (deltachaos) - Jakub Sacha + - Kamil Musial - Olaf Klischat - orlovv - Claude Dioudonnat @@ -2173,6 +2185,7 @@ The Symfony Connect username in parenthesis allows to get more information - Rodrigo Díez Villamuera (rodrigodiez) - Stephen Clouse - e-ivanov + - Abderrahman DAIF (death_maker) - Yann Rabiller (einenlum) - Jochen Bayer (jocl) - Patrick Carlo-Hickman @@ -2182,6 +2195,7 @@ The Symfony Connect username in parenthesis allows to get more information - Gordienko Vladislav - Ener-Getick - Viacheslav Sychov + - Nicolas Sauveur (baishu) - Helmut Hummel (helhum) - Matt Brunt - Carlos Ortega Huetos @@ -2199,6 +2213,7 @@ The Symfony Connect username in parenthesis allows to get more information - Artem Kolesnikov (tyomo4ka) - Gustavo Adrian - Yannick + - Kuzia - Vladimir Luchaninov (luchaninov) - spdionis - rchoquet @@ -2206,6 +2221,7 @@ The Symfony Connect username in parenthesis allows to get more information - gitlost - Taras Girnyk - Sergio + - Mehrdad - Eduardo García Sanz (coma) - fduch (fduch) - David de Boer (ddeboer) @@ -2252,6 +2268,7 @@ The Symfony Connect username in parenthesis allows to get more information - Alain Flaus (halundra) - tsufeki - Philipp Strube + - Petar Obradović - Clement Herreman (clemherreman) - Dan Ionut Dumitriu (danionut90) - Evgeny (disparity) @@ -2270,6 +2287,7 @@ The Symfony Connect username in parenthesis allows to get more information - AlbinoDrought - Jay Klehr - Sergey Yuferev + - Monet Emilien - Tobias Stöckler - Mario Young - martkop26 @@ -2278,6 +2296,7 @@ The Symfony Connect username in parenthesis allows to get more information - cilefen (cilefen) - Mo Di (modi) - Pablo Schläpfer + - Robert Meijers - Xavier RENAUDIN - Christian Wahler (christian) - Jelte Steijaert (jelte) @@ -2469,7 +2488,6 @@ The Symfony Connect username in parenthesis allows to get more information - Nouhail AL FIDI (alfidi) - Fabian Steiner (fabstei) - Felipy Amorim (felipyamorim) - - Klaus Silveira (klaussilveira) - Michael Lively (mlivelyjr) - Abderrahim (phydev) - Attila Bukor (r1pp3rj4ck) @@ -2484,7 +2502,6 @@ The Symfony Connect username in parenthesis allows to get more information - AnrDaemon - Charly Terrier (charlypoppins) - Emre Akinci (emre) - - Rustam Bakeev (nommyde) - psampaz (psampaz) - Maxwell Vandervelde - kaywalker @@ -2557,6 +2574,7 @@ The Symfony Connect username in parenthesis allows to get more information - Tiago Garcia (tiagojsag) - Artiom - Jakub Simon + - Brandon Antonio Lorenzo - Bouke Haarsma - mlievertz - Enrico Schultz @@ -2574,6 +2592,7 @@ The Symfony Connect username in parenthesis allows to get more information - Anton Sukhachev (mrsuh) - Marcel Siegert - ryunosuke + - Roy de Vos Burchart - Francisco Facioni (fran6co) - Iwan van Staveren (istaveren) - Povilas S. (povilas) @@ -2631,6 +2650,7 @@ The Symfony Connect username in parenthesis allows to get more information - Bert Hekman - Luis Muñoz - Matthew Donadio + - Kris Buist - Houziaux mike - Phobetor - Markus @@ -2655,6 +2675,7 @@ The Symfony Connect username in parenthesis allows to get more information - Mike Francis - Nil Borodulia - Almog Baku (almogbaku) + - Arrakis (arrakis) - Benjamin Schultz (bschultz) - Gerd Christian Kunze (derdu) - Ionel Scutelnicu (ionelscutelnicu) @@ -2728,8 +2749,10 @@ The Symfony Connect username in parenthesis allows to get more information - Rénald Casagraude (rcasagraude) - Robin Duval (robin-duval) - Mohammad Ali Sarbanha (sarbanha) + - Steeve Titeca (stiteca) - Artem Lopata (bumz) - alex + - evgkord - Roman Orlov - Andreas Allacher - VolCh @@ -2749,6 +2772,7 @@ The Symfony Connect username in parenthesis allows to get more information - Julien Moulin (lizjulien) - Raito Akehanareru (raito) - Mauro Foti (skler) + - skmedix (skmedix) - Yannick Warnier (ywarnier) - Jörn Lang - Kevin Decherf @@ -2764,6 +2788,7 @@ The Symfony Connect username in parenthesis allows to get more information - Sam Ward - Hans N. Hjort - Walther Lalk + - victor-prdh - Adam - Ivo - Sören Bernstein @@ -2825,6 +2850,7 @@ The Symfony Connect username in parenthesis allows to get more information - pf - Zoli Konta - Vincent Chalnot + - Roeland Jago Douma - Patrizio Bekerle - Tom Maguire - Mateusz Lerczak @@ -2849,6 +2875,8 @@ The Symfony Connect username in parenthesis allows to get more information - Omar Yepez (oyepez003) - Jonny Schmid (schmidjon) - Götz Gottwald + - Adrien Peyre + - Christoph Krapp - Nick Chiu - Robert Campbell - Matt Lehner @@ -2976,6 +3004,7 @@ The Symfony Connect username in parenthesis allows to get more information - Gabriel Moreira - Alexey Popkov - ChS + - Jannik Zschiesche - Alexis MARQUIS - Joseph Deray - Damian Sromek @@ -3009,6 +3038,7 @@ The Symfony Connect username in parenthesis allows to get more information - Mantas Urnieža - temperatur - Paul Andrieux + - Sezil - Cas - ghazy ben ahmed - Karolis @@ -3024,6 +3054,7 @@ The Symfony Connect username in parenthesis allows to get more information - znerol - Christian Eikermann - Sergei Shitikov + - Steffen Keuper - Antonio Angelino - Pavel Golovin - Matt Fields @@ -3076,6 +3107,7 @@ The Symfony Connect username in parenthesis allows to get more information - Markus Tacker - Kasperki - Tammy D + - Adrien Foulon - Ryan Rud - Ondrej Slinták - vlechemin @@ -3122,6 +3154,7 @@ The Symfony Connect username in parenthesis allows to get more information - zorn - Yuriy Potemkin - Emilie Lorenzo + - prudhomme victor - enomotodev - Vincent - Benjamin Long @@ -3166,6 +3199,7 @@ The Symfony Connect username in parenthesis allows to get more information - Lin Lu - arduanov - sualko + - Fabien - Martin Komischke - ADmad - Nicolas Roudaire @@ -3226,6 +3260,7 @@ The Symfony Connect username in parenthesis allows to get more information - Moritz Borgmann (mborgmann) - Matt Drollette (mdrollette) - Adam Monsen (meonkeys) + - Steffen Persch (n3o77) - Ala Eddine Khefifi (nayzo) - emilienbouard (neime) - Nicholas Byfleet (nickbyfleet) @@ -3253,6 +3288,7 @@ The Symfony Connect username in parenthesis allows to get more information - Schuyler Jager (sjager) - Volker (skydiablo) - Julien Sanchez (sumbobyboys) + - Sylvain BEISSIER (sylvain-beissier) - Ron Gähler (t-ronx) - Guillermo Gisinger (t3chn0r) - Tom Newby (tomnewbyau) @@ -3291,6 +3327,7 @@ The Symfony Connect username in parenthesis allows to get more information - Mark Topper - Romain - Xavier REN + - Kevin Meijer - max - Ahmad Mayahi (ahmadmayahi) - Mohamed Karnichi (amiral) diff --git a/composer.json b/composer.json index 600df1683b5a1..c322ed90f60dd 100644 --- a/composer.json +++ b/composer.json @@ -124,7 +124,7 @@ "async-aws/sqs": "^1.0", "async-aws/sns": "^1.0", "cache/integration-tests": "dev-master", - "doctrine/annotations": "^1.13.1", + "doctrine/annotations": "^1.13.1|^2", "doctrine/cache": "^1.11|^2.0", "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", @@ -158,7 +158,7 @@ "egulias/email-validator": "~3.0.0", "masterminds/html5": "<2.6", "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/type-resolver": "<1.4.0|>=1.7.0", "ocramius/proxy-manager": "<2.1", "phpunit/phpunit": "<5.4.3" }, diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index 729adf8554a26..42a8d6560eb08 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -15,6 +15,7 @@ use Doctrine\DBAL\Types\Type as DBALType; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\ORMSetup; use Doctrine\ORM\Tools\Setup; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; @@ -33,7 +34,9 @@ class DoctrineExtractorTest extends TestCase { private function createExtractor() { - $config = Setup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true); + $config = class_exists(ORMSetup::class) + ? ORMSetup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true) + : Setup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true); $entityManager = EntityManager::create(['driver' => 'pdo_sqlite'], $config); if (!DBALType::hasType('foo')) { diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 3c10df7d6b2bf..923ee2dac2aae 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -43,7 +43,7 @@ "symfony/validator": "^5.2|^6.0", "symfony/translation": "^4.4|^5.0|^6.0", "symfony/var-dumper": "^4.4|^5.0|^6.0", - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "^1.10.4|^2", "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13.1|^3.0", diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/HttpCodeActivationStrategyTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/HttpCodeActivationStrategyTest.php index 073f3eee1f86f..0afb8eb4cfbe8 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/HttpCodeActivationStrategyTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/HttpCodeActivationStrategyTest.php @@ -101,19 +101,19 @@ public function isActivatedProvider(): array { return [ ['/test', ['level' => Logger::ERROR], true], - ['/400', ['level' => Logger::ERROR, 'context' => $this->getContextException(400)], true], - ['/400/a', ['level' => Logger::ERROR, 'context' => $this->getContextException(400)], false], - ['/400/b', ['level' => Logger::ERROR, 'context' => $this->getContextException(400)], false], - ['/400/c', ['level' => Logger::ERROR, 'context' => $this->getContextException(400)], true], - ['/401', ['level' => Logger::ERROR, 'context' => $this->getContextException(401)], true], - ['/403', ['level' => Logger::ERROR, 'context' => $this->getContextException(403)], false], - ['/404', ['level' => Logger::ERROR, 'context' => $this->getContextException(404)], false], - ['/405', ['level' => Logger::ERROR, 'context' => $this->getContextException(405)], false], - ['/500', ['level' => Logger::ERROR, 'context' => $this->getContextException(500)], true], + ['/400', ['level' => Logger::ERROR, 'context' => self::getContextException(400)], true], + ['/400/a', ['level' => Logger::ERROR, 'context' => self::getContextException(400)], false], + ['/400/b', ['level' => Logger::ERROR, 'context' => self::getContextException(400)], false], + ['/400/c', ['level' => Logger::ERROR, 'context' => self::getContextException(400)], true], + ['/401', ['level' => Logger::ERROR, 'context' => self::getContextException(401)], true], + ['/403', ['level' => Logger::ERROR, 'context' => self::getContextException(403)], false], + ['/404', ['level' => Logger::ERROR, 'context' => self::getContextException(404)], false], + ['/405', ['level' => Logger::ERROR, 'context' => self::getContextException(405)], false], + ['/500', ['level' => Logger::ERROR, 'context' => self::getContextException(500)], true], ]; } - private function getContextException(int $code): array + private static function getContextException(int $code): array { return ['exception' => new HttpException($code)]; } diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php index a60cc450c7236..09e71e0b332ef 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php @@ -53,18 +53,18 @@ public function isActivatedProvider(): array { return [ ['/test', ['level' => Logger::DEBUG], false], - ['/foo', ['level' => Logger::DEBUG, 'context' => $this->getContextException(404)], false], - ['/baz/bar', ['level' => Logger::ERROR, 'context' => $this->getContextException(404)], false], - ['/foo', ['level' => Logger::ERROR, 'context' => $this->getContextException(404)], false], - ['/foo', ['level' => Logger::ERROR, 'context' => $this->getContextException(500)], true], + ['/foo', ['level' => Logger::DEBUG, 'context' => self::getContextException(404)], false], + ['/baz/bar', ['level' => Logger::ERROR, 'context' => self::getContextException(404)], false], + ['/foo', ['level' => Logger::ERROR, 'context' => self::getContextException(404)], false], + ['/foo', ['level' => Logger::ERROR, 'context' => self::getContextException(500)], true], ['/test', ['level' => Logger::ERROR], true], - ['/baz', ['level' => Logger::ERROR, 'context' => $this->getContextException(404)], true], - ['/baz', ['level' => Logger::ERROR, 'context' => $this->getContextException(500)], true], + ['/baz', ['level' => Logger::ERROR, 'context' => self::getContextException(404)], true], + ['/baz', ['level' => Logger::ERROR, 'context' => self::getContextException(500)], true], ]; } - protected function getContextException(int $code): array + protected static function getContextException(int $code): array { return ['exception' => new HttpException($code)]; } diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php index c576462d0abfe..d01ca9f83ea1d 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php @@ -34,7 +34,7 @@ public function testDatetimeFormat(array $record, $expectedTimestamp) public function providerDatetimeFormatTests(): array { - $record = $this->getRecord(); + $record = self::getRecord(); return [ [array_merge($record, ['datetime' => new \DateTime('2019-01-01T00:01:00+00:00')]), 1546300860], @@ -58,7 +58,7 @@ public function testDatetimeRfc3339Format(array $record, $expectedTimestamp) public function providerDatetimeRfc3339FormatTests(): array { - $record = $this->getRecord(); + $record = self::getRecord(); return [ [array_merge($record, ['datetime' => new \DateTime('2019-01-01T00:01:00+00:00')]), '2019-01-01T00:01:00.000+00:00'], @@ -70,8 +70,8 @@ public function providerDatetimeRfc3339FormatTests(): array public function testDebugProcessor() { $processor = new DebugProcessor(); - $processor($this->getRecord()); - $processor($this->getRecord(Logger::ERROR)); + $processor(self::getRecord()); + $processor(self::getRecord(Logger::ERROR)); $this->assertCount(2, $processor->getLogs()); $this->assertSame(1, $processor->countErrors()); @@ -89,8 +89,8 @@ public function testWithRequestStack() { $stack = new RequestStack(); $processor = new DebugProcessor($stack); - $processor($this->getRecord()); - $processor($this->getRecord(Logger::ERROR)); + $processor(self::getRecord()); + $processor(self::getRecord(Logger::ERROR)); $this->assertCount(2, $processor->getLogs()); $this->assertSame(1, $processor->countErrors()); @@ -98,8 +98,8 @@ public function testWithRequestStack() $request = new Request(); $stack->push($request); - $processor($this->getRecord()); - $processor($this->getRecord(Logger::ERROR)); + $processor(self::getRecord()); + $processor(self::getRecord(Logger::ERROR)); $this->assertCount(4, $processor->getLogs()); $this->assertSame(2, $processor->countErrors()); @@ -123,7 +123,7 @@ public function testInheritedClassCallCountErrorsWithoutArgument() $this->assertEquals(0, $debugProcessorChild->countErrors()); } - private function getRecord($level = Logger::WARNING, $message = 'test'): array + private static function getRecord($level = Logger::WARNING, $message = 'test'): array { return [ 'message' => $message, diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index d72d0b9888e01..00453b796ed1f 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -200,7 +200,7 @@ public function shutdown() // store failing status $isFailing = !$configuration->tolerates($this->deprecationGroups); - $this->displayDeprecations($groups, $configuration, $isFailing); + $this->displayDeprecations($groups, $configuration); $this->resetDeprecationGroups(); @@ -213,7 +213,7 @@ public function shutdown() } $isFailingAtShutdown = !$configuration->tolerates($this->deprecationGroups); - $this->displayDeprecations($groups, $configuration, $isFailingAtShutdown); + $this->displayDeprecations($groups, $configuration); if ($configuration->isGeneratingBaseline()) { $configuration->writeBaseline(); @@ -289,11 +289,10 @@ private static function colorize($str, $red) /** * @param string[] $groups * @param Configuration $configuration - * @param bool $isFailing * * @throws \InvalidArgumentException */ - private function displayDeprecations($groups, $configuration, $isFailing) + private function displayDeprecations($groups, $configuration) { $cmp = function ($a, $b) { return $b->count() - $a->count(); @@ -320,7 +319,8 @@ private function displayDeprecations($groups, $configuration, $isFailing) fwrite($handle, "\n".self::colorize($deprecationGroupMessage, 'legacy' !== $group && 'indirect' !== $group)."\n"); } - if ('legacy' !== $group && !$configuration->verboseOutput($group) && !$isFailing) { + // Skip the verbose output if the group is quiet and not failing according to its threshold: + if ('legacy' !== $group && !$configuration->verboseOutput($group) && $configuration->toleratesForGroup($group, $this->deprecationGroups)) { continue; } $notices = $this->deprecationGroups[$group]->notices(); diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php index bc46e4f447912..6e9f0e485a693 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Configuration.php @@ -165,6 +165,32 @@ public function tolerates(array $deprecationGroups) return true; } + /** + * @param array $deprecationGroups + * + * @return bool true if the threshold is not reached for the deprecation type nor for the total + */ + public function toleratesForGroup(string $groupName, array $deprecationGroups): bool + { + $grandTotal = 0; + + foreach ($deprecationGroups as $type => $group) { + if ('legacy' !== $type) { + $grandTotal += $group->count(); + } + } + + if ($grandTotal > $this->thresholds['total']) { + return false; + } + + if (\in_array($groupName, ['self', 'direct', 'indirect'], true) && $deprecationGroups[$groupName]->count() > $this->thresholds[$groupName]) { + return false; + } + + return true; + } + /** * @return bool */ diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index 0fc2f2d623358..a623edbbf15de 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -138,7 +138,7 @@ public function startTestSuite($suite) if (!class_exists(AnnotationRegistry::class, false) && class_exists(AnnotationRegistry::class)) { if (method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { AnnotationRegistry::registerUniqueLoader('class_exists'); - } else { + } elseif (method_exists(AnnotationRegistry::class, 'registerLoader')) { AnnotationRegistry::registerLoader('class_exists'); } } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php index 5d36a43bff54f..3e3a831308a43 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php @@ -234,6 +234,103 @@ public function testOutputIsNotVerboseInWeakMode() $this->assertFalse($configuration->verboseOutput('other')); } + /** + * @dataProvider provideDataForToleratesForGroup + */ + public function testToleratesForIndividualGroups(string $deprecationsHelper, array $deprecationsPerType, array $expected) + { + $configuration = Configuration::fromUrlEncodedString($deprecationsHelper); + + $groups = $this->buildGroups($deprecationsPerType); + + foreach ($expected as $groupName => $tolerates) { + $this->assertSame($tolerates, $configuration->toleratesForGroup($groupName, $groups), sprintf('Deprecation type "%s" is %s', $groupName, $tolerates ? 'tolerated' : 'not tolerated')); + } + } + + public function provideDataForToleratesForGroup() { + + yield 'total threshold not reached' => ['max[total]=1', [ + 'unsilenced' => 0, + 'self' => 0, + 'legacy' => 1, // Legacy group is ignored in total threshold + 'other' => 0, + 'direct' => 1, + 'indirect' => 0, + ], [ + 'unsilenced' => true, + 'self' => true, + 'legacy' => true, + 'other' => true, + 'direct' => true, + 'indirect' => true, + ]]; + + yield 'total threshold reached' => ['max[total]=1', [ + 'unsilenced' => 0, + 'self' => 0, + 'legacy' => 1, + 'other' => 0, + 'direct' => 1, + 'indirect' => 1, + ], [ + 'unsilenced' => false, + 'self' => false, + 'legacy' => false, + 'other' => false, + 'direct' => false, + 'indirect' => false, + ]]; + + yield 'direct threshold reached' => ['max[total]=99&max[direct]=0', [ + 'unsilenced' => 0, + 'self' => 0, + 'legacy' => 1, + 'other' => 0, + 'direct' => 1, + 'indirect' => 1, + ], [ + 'unsilenced' => true, + 'self' => true, + 'legacy' => true, + 'other' => true, + 'direct' => false, + 'indirect' => true, + ]]; + + yield 'indirect & self threshold reached' => ['max[total]=99&max[direct]=0&max[self]=0', [ + 'unsilenced' => 0, + 'self' => 1, + 'legacy' => 1, + 'other' => 1, + 'direct' => 1, + 'indirect' => 1, + ], [ + 'unsilenced' => true, + 'self' => false, + 'legacy' => true, + 'other' => true, + 'direct' => false, + 'indirect' => true, + ]]; + + yield 'indirect & self threshold not reached' => ['max[total]=99&max[direct]=2&max[self]=2', [ + 'unsilenced' => 0, + 'self' => 1, + 'legacy' => 1, + 'other' => 1, + 'direct' => 1, + 'indirect' => 1, + ], [ + 'unsilenced' => true, + 'self' => true, + 'legacy' => true, + 'other' => true, + 'direct' => true, + 'indirect' => true, + ]]; + } + private function buildGroups($counts) { $groups = []; diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/partially_quiet2.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/partially_quiet2.phpt new file mode 100644 index 0000000000000..4d0d6c3d55794 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/partially_quiet2.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test DeprecationErrorHandler quiet on everything but self/direct deprecations +--FILE-- + +--EXPECTF-- +Unsilenced deprecation notices (3) + +Remaining direct deprecation notices (2) + + 1x: root deprecation + + 1x: silenced bar deprecation + 1x in FooTestCase::testNonLegacyBar + +Remaining indirect deprecation notices (1) + +Legacy deprecation notices (2) diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php index e07c8d6cf5de8..f2064368f41a3 100644 --- a/src/Symfony/Bridge/PhpUnit/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php @@ -30,7 +30,7 @@ if (!class_exists(AnnotationRegistry::class, false) && class_exists(AnnotationRegistry::class)) { if (method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { AnnotationRegistry::registerUniqueLoader('class_exists'); - } else { + } elseif (method_exists(AnnotationRegistry::class, 'registerLoader')) { AnnotationRegistry::registerLoader('class_exists'); } } diff --git a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php index f1726914b490b..b214ae8a743c2 100644 --- a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php @@ -35,6 +35,12 @@ public function toName(): string return $this->message->getTo()[0]->getName(); } + /** + * @param string $image A Twig path to the image file. It's recommended to define + * some Twig namespace for email images (e.g. '@email/images/logo.png'). + * @param string|null $contentType The media type (i.e. MIME type) of the image file (e.g. 'image/png'). + * Some email clients require this to display embedded images. + */ public function image(string $image, string $contentType = null): string { $file = $this->twig->getLoader()->getSourceContext($image); @@ -47,6 +53,13 @@ public function image(string $image, string $contentType = null): string return 'cid:'.$image; } + /** + * @param string $file A Twig path to the file. It's recommended to define + * some Twig namespace for email files (e.g. '@email/files/contract.pdf'). + * @param string|null $name A custom file name that overrides the original name of the attached file. + * @param string|null $contentType The media type (i.e. MIME type) of the file (e.g. 'application/pdf'). + * Some email clients require this to display attached files. + */ public function attach(string $file, string $name = null, string $contentType = null): void { $file = $this->twig->getLoader()->getSourceContext($file); diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig index 0e80840541fa1..9aa6081e7e323 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig @@ -6,7 +6,7 @@ {%- set prepend = not (money_pattern starts with '{{') -%} {%- set append = not (money_pattern ends with '}}') -%} {%- if prepend or append -%} -
+
{%- if prepend -%}
{{ money_pattern|form_encode_currency }} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig index 54903a5713082..22555ed88f4a8 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_5_layout.html.twig @@ -6,7 +6,7 @@ {%- set prepend = not (money_pattern starts with '{{') -%} {%- set append = not (money_pattern ends with '}}') -%} {%- if prepend or append -%} -
+
{%- if prepend -%} {{ money_pattern|form_encode_currency }} {%- endif -%} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_base_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_base_layout.html.twig index b8cb8c44aa832..e8b9318b3a8bf 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_base_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_base_layout.html.twig @@ -11,7 +11,7 @@ {% set prepend = not (money_pattern starts with '{{') %} {% set append = not (money_pattern ends with '}}') %} {% if prepend or append %} -
+
{% if prepend %} {{ money_pattern|form_encode_currency }} {% endif %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php index 36f8a50d00f13..8689df830b290 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php @@ -1165,7 +1165,7 @@ public function testMoney() $this->assertWidgetMatchesXpath($form->createView(), ['id' => 'my&id', 'attr' => ['class' => 'my&class']], '/div - [@class="input-group"] + [@class="input-group "] [ ./div [@class="input-group-prepend"] diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php index ff35789a564cd..ebeacf7045afc 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap5LayoutTest.php @@ -1450,7 +1450,7 @@ public function testMoney() $this->assertWidgetMatchesXpath($form->createView(), ['id' => 'my&id', 'attr' => ['class' => 'my&class']], '/div - [@class="input-group"] + [@class="input-group "] [ ./span [@class="input-group-text"] diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php index b98cb3a73ab4f..00fee10edb2fc 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php @@ -102,7 +102,7 @@ public function testMoneyWidgetInIso() ; $this->assertSame(<<<'HTML' -
+
HTML diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php index 8c0e54744f964..ed69ca81c3375 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php @@ -104,7 +104,7 @@ public function testMoneyWidgetInIso() ->createView(); self::assertSame(<<<'HTML' -
+
HTML , trim($this->renderWidget($view))); } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 63b072605582d..52aeba8f8cb08 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -22,7 +22,7 @@ "twig/twig": "^2.13|^3.0.4" }, "require-dev": { - "doctrine/annotations": "^1.12", + "doctrine/annotations": "^1.12|^2", "egulias/email-validator": "^2.1.10|^3", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/asset": "^4.4|^5.0|^6.0", diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index 7a56ec5abed48..63e6496bd1bcc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -69,7 +69,7 @@ protected function configure() For dumping a specific option, add its path as second argument (only available for the yaml format): - php %command.full_name% framework profiler.matcher + php %command.full_name% framework http_client.default_options EOF ) @@ -99,7 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $errorIo->comment([ 'Provide the name of a bundle as the first argument of this command to dump its default configuration. (e.g. config:dump-reference FrameworkBundle)', - 'For dumping a specific option, add its path as the second argument of this command. (e.g. config:dump-reference FrameworkBundle profiler.matcher to dump the framework.profiler.matcher configuration)', + 'For dumping a specific option, add its path as the second argument of this command. (e.g. config:dump-reference FrameworkBundle http_client.default_options to dump the framework.http_client.default_options configuration)', ]); return 0; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index 162a1fb806c9e..0ad063343f78c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -361,7 +361,7 @@ private function getCallableData($callable): array } $data['name'] = $r->name; - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $data['class'] = $class->name; if (!$r->getClosureThis()) { $data['static'] = true; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index cf70089405419..a3fbabc6d2bf9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -377,7 +377,7 @@ protected function describeCallable($callable, array $options = []) } $string .= "\n".sprintf('- Name: `%s`', $r->name); - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $string .= "\n".sprintf('- Class: `%s`', $class->name); if (!$r->getClosureThis()) { $string .= "\n- Static: yes"; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index a76d7f20744bc..e7eb18762de86 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -614,7 +614,7 @@ private function formatCallable($callable): string if (str_contains($r->name, '{closure}')) { return 'Closure()'; } - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { return sprintf('%s::%s()', $class->name, $r->name); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index ef59fe3dc4fa3..350452f33cee9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -548,7 +548,7 @@ private function getCallableDocument($callable): \DOMDocument } $callableXML->setAttribute('name', $r->name); - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $callableXML->setAttribute('class', $class->name); if (!$r->getClosureThis()) { $callableXML->setAttribute('static', 'true'); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index a86ec0561d3bc..c70a07635843a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1194,35 +1194,31 @@ private function addExceptionsSection(ArrayNodeDefinition $rootNode) $logLevels = (new \ReflectionClass(LogLevel::class))->getConstants(); $rootNode + ->fixXmlConfig('exception') ->children() ->arrayNode('exceptions') ->info('Exception handling configuration') + ->useAttributeAsKey('class') ->beforeNormalization() + // Handle legacy XML configuration ->ifArray() ->then(function (array $v): array { if (!\array_key_exists('exception', $v)) { return $v; } - // Fix XML normalization - $data = isset($v['exception'][0]) ? $v['exception'] : [$v['exception']]; - $exceptions = []; - foreach ($data as $exception) { - $config = []; - if (\array_key_exists('log-level', $exception)) { - $config['log_level'] = $exception['log-level']; - } - if (\array_key_exists('status-code', $exception)) { - $config['status_code'] = $exception['status-code']; - } - $exceptions[$exception['name']] = $config; + $v = $v['exception']; + unset($v['exception']); + + foreach ($v as &$exception) { + $exception['class'] = $exception['name']; + unset($exception['name']); } - return $exceptions; + return $v; }) ->end() ->prototype('array') - ->fixXmlConfig('exception') ->children() ->scalarNode('log_level') ->info('The level of log message. Null to let Symfony decide.') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index a20f2a3ddb4d5..b69254687c6d4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1632,8 +1632,12 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde $loader->load('annotations.php'); if (!method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { - $container->getDefinition('annotations.dummy_registry') - ->setMethodCalls([['registerLoader', ['class_exists']]]); + if (method_exists(AnnotationRegistry::class, 'registerLoader')) { + $container->getDefinition('annotations.dummy_registry') + ->setMethodCalls([['registerLoader', ['class_exists']]]); + } else { + $container->removeDefinition('annotations.dummy_registry'); + } } if ('none' === $config['cache']) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php index 64e3f4e31dff6..8bb408e2aba65 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.php @@ -26,7 +26,7 @@ ->set('annotations.reader', AnnotationReader::class) ->call('addGlobalIgnoredName', [ 'required', - service('annotations.dummy_registry'), // dummy arg to register class_exists as annotation loader only when required + service('annotations.dummy_registry')->ignoreOnInvalid(), // dummy arg to register class_exists as annotation loader only when required ]) ->set('annotations.dummy_registry', AnnotationRegistry::class) 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 d5f369cb1e815..d4be9fb6f2b7f 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 @@ -30,6 +30,7 @@ + @@ -361,14 +362,29 @@ - + - - + - + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions.xml index 49878fc118b50..35c787867a93a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions.xml @@ -6,11 +6,25 @@ http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - - - - - - + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions_legacy.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions_legacy.xml new file mode 100644 index 0000000000000..49878fc118b50 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/exceptions_legacy.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index abbe2d9a3e4c1..f212a6db89ffa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -547,24 +547,34 @@ public function testExceptionsConfig() { $container = $this->createContainerFromFile('exceptions'); + $configuration = $container->getDefinition('exception_listener')->getArgument(3); + $this->assertSame([ - \Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class => [ - 'log_level' => 'info', - 'status_code' => 422, - ], - \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class => [ - 'log_level' => 'info', - 'status_code' => null, - ], - \Symfony\Component\HttpKernel\Exception\ConflictHttpException::class => [ - 'log_level' => 'info', - 'status_code' => null, - ], - \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class => [ - 'log_level' => null, - 'status_code' => 500, - ], - ], $container->getDefinition('exception_listener')->getArgument(3)); + \Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class, + \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class, + \Symfony\Component\HttpKernel\Exception\ConflictHttpException::class, + \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class, + ], array_keys($configuration)); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => 422, + ], $configuration[\Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => null, + ], $configuration[\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => null, + ], $configuration[\Symfony\Component\HttpKernel\Exception\ConflictHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => null, + 'status_code' => 500, + ], $configuration[\Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class]); } public function testRouter() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php index ebc37d93bed84..131bb07f0c657 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php @@ -32,4 +32,38 @@ public function testMessengerMiddlewareFactoryErroneousFormat() { $this->markTestSkipped('XML configuration will not allow erroneous format.'); } + + public function testLegacyExceptionsConfig() + { + $container = $this->createContainerFromFile('exceptions_legacy'); + + $configuration = $container->getDefinition('exception_listener')->getArgument(3); + + $this->assertSame([ + \Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class, + \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class, + \Symfony\Component\HttpKernel\Exception\ConflictHttpException::class, + \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class, + ], array_keys($configuration)); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => 422, + ], $configuration[\Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => null, + ], $configuration[\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => 'info', + 'status_code' => null, + ], $configuration[\Symfony\Component\HttpKernel\Exception\ConflictHttpException::class]); + + $this->assertEqualsCanonicalizing([ + 'log_level' => null, + 'status_code' => 500, + ], $configuration[\Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class]); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index c9482a7676d0c..089e2e0827fea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -34,7 +34,7 @@ "symfony/routing": "^5.3|^6.0" }, "require-dev": { - "doctrine/annotations": "^1.13.1", + "doctrine/annotations": "^1.13.1|^2", "doctrine/cache": "^1.11|^2.0", "doctrine/persistence": "^1.3|^2|^3", "symfony/asset": "^5.3|^6.0", diff --git a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php index b5fe209944ce9..3757c5657ae4a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/DebugFirewallCommand.php @@ -255,7 +255,7 @@ private function formatCallable($callable): string if (false !== strpos($r->name, '{closure}')) { return 'Closure()'; } - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { return sprintf('%s::%s()', $class->name, $r->name); } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index ee3e6e3e90008..cdc814910b819 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -32,7 +32,7 @@ "symfony/security-http": "^5.4|^6.0" }, "require-dev": { - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "^1.10.4|^2", "symfony/asset": "^4.4|^5.0|^6.0", "symfony/browser-kit": "^4.4|^5.0|^6.0", "symfony/console": "^4.4|^5.0|^6.0", diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 5635bb430d8c5..a92b08930a111 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -37,7 +37,7 @@ "symfony/yaml": "^4.4|^5.0|^6.0", "symfony/framework-bundle": "^5.0|^6.0", "symfony/web-link": "^4.4|^5.0|^6.0", - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "^1.10.4|^2", "doctrine/cache": "^1.0|^2.0" }, "conflict": { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index df2679d79c9ee..b1642d4e19d2e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -253,10 +253,6 @@ {% if has_trace %} {% set trace_id = 'trace-' ~ category ~ '-' ~ log_index %} - -
- {{ profiler_dump(log.context.exception.trace, maxDepth=1) }} -
{% endif %} {% if has_context %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php index fbaf2f7965d05..c345b5fbb89c8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php @@ -66,10 +66,10 @@ public function provideRequestAndResponses() ]; return [ - [$nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], $this->createRequest(), $this->createResponse()], - [$nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], $this->createRequest($requestNonceHeaders), $this->createResponse($responseNonceHeaders)], - [$nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], $this->createRequest($requestNonceHeaders), $this->createResponse()], - [$nonce, ['csp_script_nonce' => $responseScriptNonce, 'csp_style_nonce' => $responseStyleNonce], $this->createRequest(), $this->createResponse($responseNonceHeaders)], + [$nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], self::createRequest(), self::createResponse()], + [$nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], self::createRequest($requestNonceHeaders), self::createResponse($responseNonceHeaders)], + [$nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], self::createRequest($requestNonceHeaders), self::createResponse()], + [$nonce, ['csp_script_nonce' => $responseScriptNonce, 'csp_style_nonce' => $responseStyleNonce], self::createRequest(), self::createResponse($responseNonceHeaders)], ]; } @@ -96,104 +96,104 @@ public function provideRequestAndResponsesForOnKernelResponse() [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(), + self::createRequest(), + self::createResponse(), ['Content-Security-Policy' => null, 'Content-Security-Policy-Report-Only' => null, 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], - $this->createRequest($requestNonceHeaders), - $this->createResponse($responseNonceHeaders), + self::createRequest($requestNonceHeaders), + self::createResponse($responseNonceHeaders), ['Content-Security-Policy' => null, 'Content-Security-Policy-Report-Only' => null, 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $requestScriptNonce, 'csp_style_nonce' => $requestStyleNonce], - $this->createRequest($requestNonceHeaders), - $this->createResponse(), + self::createRequest($requestNonceHeaders), + self::createResponse(), ['Content-Security-Policy' => null, 'Content-Security-Policy-Report-Only' => null, 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $responseScriptNonce, 'csp_style_nonce' => $responseStyleNonce], - $this->createRequest(), - $this->createResponse($responseNonceHeaders), + self::createRequest(), + self::createResponse($responseNonceHeaders), ['Content-Security-Policy' => null, 'Content-Security-Policy-Report-Only' => null, 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'frame-ancestors https: ; form-action: https:', 'Content-Security-Policy-Report-Only' => 'frame-ancestors http: ; form-action: http:']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'frame-ancestors https: ; form-action: https:', 'Content-Security-Policy-Report-Only' => 'frame-ancestors http: ; form-action: http:']), ['Content-Security-Policy' => 'frame-ancestors https: ; form-action: https:', 'Content-Security-Policy-Report-Only' => 'frame-ancestors http: ; form-action: http:', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'']), ['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; style-src \'self\' domain.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; style-src \'self\' domain-report-only.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\'']), ['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' \'unsafe-inline\'; style-src-elem \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'default-src \'none\'', 'Content-Security-Policy-Report-Only' => 'default-src \'none\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'default-src \'none\'', 'Content-Security-Policy-Report-Only' => 'default-src \'none\'']), ['Content-Security-Policy' => 'default-src \'none\'; script-src \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy-Report-Only' => 'default-src \'none\'; script-src \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'']), ['Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'script-src \'self\'; style-src \'self\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'script-src \'self\'; style-src \'self\'']), ['Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'']), + self::createRequest(), + self::createResponse(['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'']), ['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'', 'Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['X-Content-Security-Policy' => 'script-src \'self\'']), + self::createRequest(), + self::createResponse(['X-Content-Security-Policy' => 'script-src \'self\'']), ['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'sha384-LALALALALAAL\'']), + self::createRequest(), + self::createResponse(['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'sha384-LALALALALAAL\'']), ['X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'sha384-LALALALALAAL\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy' => null], ], [ $nonce, ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], - $this->createRequest(), - $this->createResponse(['Content-Security-Policy' => 'script-src \'self\'; style-src \'self\'', 'X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'; style-src \'self\'']), + self::createRequest(), + self::createResponse(['Content-Security-Policy' => 'script-src \'self\'; style-src \'self\'', 'X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'; style-src \'self\'']), ['Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => 'script-src \'self\' \'unsafe-inline\'; style-src \'self\' \'unsafe-inline\' \'nonce-'.$nonce.'\''], ], ]; } - private function createRequest(array $headers = []) + private static function createRequest(array $headers = []) { $request = new Request(); $request->headers->add($headers); @@ -201,7 +201,7 @@ private function createRequest(array $headers = []) return $request; } - private function createResponse(array $headers = []) + private static function createResponse(array $headers = []) { $response = new Response(); $response->headers->add($headers); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Twig/WebProfilerExtensionTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Twig/WebProfilerExtensionTest.php new file mode 100644 index 0000000000000..6b026bcc53385 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Twig/WebProfilerExtensionTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Tests\Twig; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\WebProfilerBundle\Twig\WebProfilerExtension; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Twig\Environment; +use Twig\Extension\CoreExtension; +use Twig\Extension\EscaperExtension; + +class WebProfilerExtensionTest extends TestCase +{ + /** + * @dataProvider provideMessages + */ + public function testDumpHeaderIsDisplayed(string $message, array $context, bool $dump1HasHeader, bool $dump2HasHeader) + { + class_exists(CoreExtension::class); // Load twig_convert_encoding() + class_exists(EscaperExtension::class); // Load twig_escape_filter() + + $twigEnvironment = $this->mockTwigEnvironment(); + $varCloner = new VarCloner(); + + $webProfilerExtension = new WebProfilerExtension(); + + $needle = 'window.Sfdump'; + + $dump1 = $webProfilerExtension->dumpLog($twigEnvironment, $message, $varCloner->cloneVar($context)); + self::assertSame($dump1HasHeader, str_contains($dump1, $needle)); + + $dump2 = $webProfilerExtension->dumpData($twigEnvironment, $varCloner->cloneVar([])); + self::assertSame($dump2HasHeader, str_contains($dump2, $needle)); + } + + public function provideMessages(): iterable + { + yield ['Some message', ['foo' => 'foo', 'bar' => 'bar'], false, true]; + yield ['Some message {@see some text}', ['foo' => 'foo', 'bar' => 'bar'], false, true]; + yield ['Some message {foo}', ['foo' => 'foo', 'bar' => 'bar'], true, false]; + yield ['Some message {foo}', ['bar' => 'bar'], false, true]; + } + + private function mockTwigEnvironment() + { + $twigEnvironment = $this->createMock(Environment::class); + + $twigEnvironment->expects($this->any())->method('getCharset')->willReturn('UTF-8'); + + return $twigEnvironment; + } +} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php b/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php index 8a8721a3a1516..b5f0f3cad2479 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php @@ -90,13 +90,19 @@ public function dumpLog(Environment $env, string $message, Data $context = null) $message = twig_escape_filter($env, $message); $message = preg_replace('/"(.*?)"/', '"$1"', $message); - if (null === $context || !str_contains($message, '{')) { + $replacements = []; + foreach ($context ?? [] as $k => $v) { + $k = '{'.twig_escape_filter($env, $k).'}'; + if (str_contains($message, $k)) { + $replacements[$k] = $v; + } + } + + if (!$replacements) { return ''.$message.''; } - $replacements = []; - foreach ($context as $k => $v) { - $k = '{'.twig_escape_filter($env, $k).'}'; + foreach ($replacements as $k => $v) { $replacements['"'.$k.'"'] = $replacements['"'.$k.'"'] = $replacements[$k] = $this->dumpData($env, $v); } diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index accee44bc8e2a..8e26cfc1f23e5 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -207,7 +207,7 @@ public static function createConnection(string $dsn, array $options = []) break; } - $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout']); + $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::OPT_NULL_MULTIBULK_AS_NULL') ? [$params['auth'] ?? ''] : []); if ($address = $sentinel->getMasterAddrByName($params['redis_sentinel'])) { [$host, $port] = $address; @@ -219,7 +219,10 @@ public static function createConnection(string $dsn, array $options = []) } try { - @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [['stream' => $params['ssl'] ?? null]] : []); + @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [[ + 'auth' => $params['auth'] ?? '', + 'stream' => $params['ssl'] ?? null, + ]] : []); set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); try { @@ -567,7 +570,11 @@ private function pipeline(\Closure $generator, object $redis = null): \Generator if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) { $e = new \RedisException($redis->getLastError()); - $results = array_map(function ($v) use ($e) { return false === $v ? $e : $v; }, $results); + $results = array_map(function ($v) use ($e) { return false === $v ? $e : $v; }, (array) $results); + } + + if (\is_bool($results)) { + return; } foreach ($ids as $k => $id) { diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index ddc5870aaf1f4..eb6aacb1a4018 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -53,7 +53,6 @@ final class ProgressBar private $startTime; private $stepWidth; private $percent = 0.0; - private $formatLineCount; private $messages = []; private $overwrite = true; private $terminal; @@ -446,8 +445,6 @@ private function setRealFormat(string $format) } else { $this->format = $format; } - - $this->formatLineCount = substr_count($this->format, "\n"); } /** @@ -464,7 +461,7 @@ private function overwrite(string $message): void if ($this->overwrite) { if (null !== $this->previousMessage) { if ($this->output instanceof ConsoleSectionOutput) { - $messageLines = explode("\n", $message); + $messageLines = explode("\n", $this->previousMessage); $lineCount = \count($messageLines); foreach ($messageLines as $messageLine) { $messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine)); @@ -474,13 +471,11 @@ private function overwrite(string $message): void } $this->output->clear($lineCount); } else { - if ('' !== $this->previousMessage) { - // only clear upper lines when last call was not a clear - for ($i = 0; $i < $this->formatLineCount; ++$i) { - $this->cursor->moveToColumn(1); - $this->cursor->clearLine(); - $this->cursor->moveUp(); - } + $lineCount = substr_count($this->previousMessage, "\n"); + for ($i = 0; $i < $lineCount; ++$i) { + $this->cursor->moveToColumn(1); + $this->cursor->clearLine(); + $this->cursor->moveUp(); } $this->cursor->moveToColumn(1); diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 9f85975ad8dd0..d438c3fc95852 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -1969,6 +1969,10 @@ public function testSetSignalsToDispatchEvent() public function testSignalableCommandInterfaceWithoutSignals() { + if (!\defined('SIGUSR1')) { + $this->markTestSkipped('SIGUSR1 not available'); + } + $command = new SignableCommand(false); $dispatcher = new EventDispatcher(); diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index 17401b887ec5f..c9b9c9d535956 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -361,8 +361,8 @@ public function testOverwriteWithAnsiSectionOutput() rewind($output->getStream()); $this->assertSame( " \033[44;37m 0/50\033[0m [>---------------------------] 0%".\PHP_EOL. - "\x1b[1A\x1b[0J"." \033[44;37m 1/50\033[0m [>---------------------------] 2%".\PHP_EOL. - "\x1b[1A\x1b[0J"." \033[44;37m 2/50\033[0m [=>--------------------------] 4%".\PHP_EOL, + "\x1b[1A\x1b[0J \033[44;37m 1/50\033[0m [>---------------------------] 2%".\PHP_EOL. + "\x1b[1A\x1b[0J \033[44;37m 2/50\033[0m [=>--------------------------] 4%".\PHP_EOL, stream_get_contents($output->getStream()) ); putenv('COLUMNS=120'); @@ -397,6 +397,28 @@ public function testOverwriteMultipleProgressBarsWithSectionOutputs() ); } + public function testOverwritWithNewlinesInMessage() + { + ProgressBar::setFormatDefinition('test', '%current%/%max% [%bar%] %percent:3s%% %message% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.'); + + $bar = new ProgressBar($output = $this->getOutputStream(), 50, 0); + $bar->setFormat('test'); + $bar->start(); + $bar->display(); + $bar->setMessage("Twas brillig, and the slithy toves. Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.\nBeware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!"); + $bar->advance(); + $bar->setMessage("He took his vorpal sword in hand; Long time the manxome foe he sought— So rested he by the Tumtum tree And stood awhile in thought.\nAnd, as in uffish thought he stood, The Jabberwock, with eyes of flame, Came whiffling through the tulgey wood, And burbled as it came!"); + $bar->advance(); + + rewind($output->getStream()); + $this->assertEquals( + " 0/50 [>] 0% %message% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.\x1b[1G\x1b[2K 1/50 [>] 2% Twas brillig, and the slithy toves. Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe. +Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch! Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.\x1b[1G\x1b[2K\x1b[1A\x1b[1G\x1b[2K 2/50 [>] 4% He took his vorpal sword in hand; Long time the manxome foe he sought— So rested he by the Tumtum tree And stood awhile in thought. +And, as in uffish thought he stood, The Jabberwock, with eyes of flame, Came whiffling through the tulgey wood, And burbled as it came! Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.", + stream_get_contents($output->getStream()) + ); + } + public function testOverwriteWithSectionOutputWithNewlinesInMessage() { $sections = []; @@ -417,7 +439,7 @@ public function testOverwriteWithSectionOutputWithNewlinesInMessage() rewind($output->getStream()); $this->assertEquals( ' 0/50 [>] 0% %message% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.'.\PHP_EOL. - "\x1b[6A\x1b[0J 1/50 [>] 2% Twas brillig, and the slithy toves. Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe. + "\x1b[3A\x1b[0J 1/50 [>] 2% Twas brillig, and the slithy toves. Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe. Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch! Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.".\PHP_EOL. "\x1b[6A\x1b[0J 2/50 [>] 4% He took his vorpal sword in hand; Long time the manxome foe he sought— So rested he by the Tumtum tree And stood awhile in thought. And, as in uffish thought he stood, The Jabberwock, with eyes of flame, Came whiffling through the tulgey wood, And burbled as it came! Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.".\PHP_EOL, diff --git a/src/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php b/src/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php index 5f16ac48f8aa5..2e78a1c4d8bbb 100644 --- a/src/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php +++ b/src/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php @@ -49,22 +49,22 @@ public function __construct() $this->identifierPattern = '-?(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*'; $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)'; $this->numberPattern = '[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)'; - $this->quotedStringPattern = '([^\n\r\f%s]|'.$this->stringEscapePattern.')*'; + $this->quotedStringPattern = '([^\n\r\f\\\\%s]|'.$this->stringEscapePattern.')*'; } public function getNewLineEscapePattern(): string { - return '~^'.$this->newLineEscapePattern.'~'; + return '~'.$this->newLineEscapePattern.'~'; } public function getSimpleEscapePattern(): string { - return '~^'.$this->simpleEscapePattern.'~'; + return '~'.$this->simpleEscapePattern.'~'; } public function getUnicodeEscapePattern(): string { - return '~^'.$this->unicodeEscapePattern.'~i'; + return '~'.$this->unicodeEscapePattern.'~i'; } public function getIdentifierPattern(): string diff --git a/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php b/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php index 48a67f5ab6678..77ce5d58258a2 100644 --- a/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php +++ b/src/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php @@ -138,6 +138,16 @@ public function getParserTestData() ['div:not(div.foo)', ['Negation[Element[div]:not(Class[Element[div].foo])]']], ['td ~ th', ['CombinedSelector[Element[td] ~ Element[th]]']], ['.foo[data-bar][data-baz=0]', ["Attribute[Attribute[Class[Element[*].foo][data-bar]][data-baz = '0']]"]], + ['div#foo\.bar', ['Hash[Element[div]#foo.bar]']], + ['div.w-1\/3', ['Class[Element[div].w-1/3]']], + ['#test\:colon', ['Hash[Element[*]#test:colon]']], + [".a\xc1b", ["Class[Element[*].a\xc1b]"]], + // unicode escape: \22 == " + ['*[aval="\'\22\'"]', ['Attribute[Element[*][aval = \'\'"\'\']]']], + ['*[aval="\'\22 2\'"]', ['Attribute[Element[*][aval = \'\'"2\'\']]']], + // unicode escape: \20 == (space) + ['*[aval="\'\20 \'"]', ['Attribute[Element[*][aval = \'\' \'\']]']], + ["*[aval=\"'\\20\r\n '\"]", ['Attribute[Element[*][aval = \'\' \'\']]']], ]; } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php index 426fe651a6ada..b211b84e1336d 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php @@ -129,7 +129,7 @@ private function processDefinition(ContainerBuilder $container, string $id, Defi foreach ($instanceofTags[$i] as $k => $v) { if (null === $definition->getDecoratedService() || \in_array($k, $tagsToKeep, true)) { foreach ($v as $v) { - if ($definition->hasTag($k) && (!$v || \in_array($v, $definition->getTag($k)))) { + if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k))) { continue; } $definition->addTag($k, $v); diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 485630a748764..db1ea84c2cf73 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -546,8 +546,8 @@ public function has(string $id) */ public function get(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { - if ($this->isCompiled() && isset($this->removedIds[$id]) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior) { - return parent::get($id); + if ($this->isCompiled() && isset($this->removedIds[$id])) { + return ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior ? parent::get($id) : null; } return $this->doGet($id, $invalidBehavior); @@ -564,9 +564,9 @@ private function doGet(string $id, int $invalidBehavior = ContainerInterface::EX } try { if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) { - return parent::get($id, $invalidBehavior); + return $this->privates[$id] ?? parent::get($id, $invalidBehavior); } - if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { + if (null !== $service = $this->privates[$id] ?? parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { return $service; } } catch (ServiceCircularReferenceException $e) { @@ -1085,8 +1085,8 @@ private function createService(Definition $definition, array &$inlineServices, b } } - if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) { - return $this->services[$id]; + if (null !== $id && $definition->isShared() && (isset($this->services[$id]) || isset($this->privates[$id])) && ($tryProxy || !$definition->isLazy())) { + return $this->services[$id] ?? $this->privates[$id]; } if (null !== $factory) { @@ -1665,7 +1665,11 @@ private function shareService(Definition $definition, $service, ?string $id, arr $inlineServices[$id ?? spl_object_hash($definition)] = $service; if (null !== $id && $definition->isShared()) { - $this->services[$id] = $service; + if ($definition->isPrivate() && $this->isCompiled()) { + $this->privates[$id] = $service; + } else { + $this->services[$id] = $service; + } unset($this->loading[$id]); } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index bd63d0689e63a..e4a7ae55d5a64 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -32,7 +32,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\ExpressionLanguage; -use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; use Symfony\Component\DependencyInjection\Loader\FileLoader; use Symfony\Component\DependencyInjection\Parameter; @@ -95,9 +95,10 @@ class PhpDumper extends Dumper private $baseClass; /** - * @var ProxyDumper + * @var DumperInterface */ private $proxyDumper; + private $hasProxyDumper = false; /** * {@inheritdoc} @@ -114,9 +115,10 @@ public function __construct(ContainerBuilder $container) /** * Sets the dumper to be used when dumping proxies in the generated container. */ - public function setProxyDumper(ProxyDumper $proxyDumper) + public function setProxyDumper(DumperInterface $proxyDumper) { $this->proxyDumper = $proxyDumper; + $this->hasProxyDumper = !$proxyDumper instanceof NullDumper; } /** @@ -174,7 +176,7 @@ public function dump(array $options = []) $this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass); - if ($this->getProxyDumper() instanceof NullDumper) { + if (!$this->hasProxyDumper) { (new AnalyzeServiceReferencesPass(true, false))->process($this->container); try { (new CheckCircularReferencesPass())->process($this->container); @@ -407,7 +409,7 @@ class %s extends {$options['class']} /** * Retrieves the currently set proxy dumper or instantiates one. */ - private function getProxyDumper(): ProxyDumper + private function getProxyDumper(): DumperInterface { if (!$this->proxyDumper) { $this->proxyDumper = new NullDumper(); @@ -418,7 +420,7 @@ private function getProxyDumper(): ProxyDumper private function analyzeReferences() { - (new AnalyzeServiceReferencesPass(false, !$this->getProxyDumper() instanceof NullDumper))->process($this->container); + (new AnalyzeServiceReferencesPass(false, $this->hasProxyDumper))->process($this->container); $checkedNodes = []; $this->circularReferences = []; $this->singleUsePrivateIds = []; @@ -445,13 +447,13 @@ private function collectCircularReferences(string $sourceId, array $edges, array foreach ($edges as $edge) { $node = $edge->getDestNode(); $id = $node->getId(); - if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isLazy() || $edge->isWeak()) { + if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isWeak()) { continue; } if (isset($path[$id])) { $loop = null; - $loopByConstructor = $edge->isReferencedByConstructor(); + $loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy(); $pathInLoop = [$id, []]; foreach ($path as $k => $pathByConstructor) { if (null !== $loop) { @@ -465,7 +467,7 @@ private function collectCircularReferences(string $sourceId, array $edges, array } $this->addCircularReferences($id, $loop, $loopByConstructor); } elseif (!isset($checkedNodes[$id])) { - $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor()); + $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor() && !$edge->isLazy()); } elseif (isset($loops[$id])) { // we already had detected loops for this edge // let's check if we have a common ancestor in one of the detected loops @@ -486,7 +488,7 @@ private function collectCircularReferences(string $sourceId, array $edges, array // we can now build the loop $loop = null; - $loopByConstructor = $edge->isReferencedByConstructor(); + $loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy(); foreach ($fillPath as $k => $pathByConstructor) { if (null !== $loop) { $loop[] = $k; @@ -988,7 +990,7 @@ private function addInlineReference(string $id, Definition $definition, string $ return ''; } - $hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]); + $hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]) && !($this->hasProxyDumper && $definition->isLazy()); if ($hasSelfRef && !$forConstructor && !$forConstructor = !$this->circularReferences[$id][$targetId]) { $code = $this->addInlineService($id, $definition, $definition); @@ -1031,7 +1033,7 @@ private function addInlineService(string $id, Definition $definition, Definition if ($isSimpleInstance = $isRootInstance = null === $inlineDef) { foreach ($this->serviceCalls as $targetId => [$callCount, $behavior, $byConstructor]) { - if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId]) { + if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId] && !($this->hasProxyDumper && $definition->isLazy())) { $code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor); } } diff --git a/src/Symfony/Component/DependencyInjection/ReverseContainer.php b/src/Symfony/Component/DependencyInjection/ReverseContainer.php index 280e9e2dd5a63..e5207e56dcf71 100644 --- a/src/Symfony/Component/DependencyInjection/ReverseContainer.php +++ b/src/Symfony/Component/DependencyInjection/ReverseContainer.php @@ -63,10 +63,6 @@ public function getId(object $service): ?string */ public function getService(string $id): object { - if ($this->serviceContainer->has($id)) { - return $this->serviceContainer->get($id); - } - if ($this->reversibleLocator->has($id)) { return $this->reversibleLocator->get($id); } @@ -75,7 +71,6 @@ public function getService(string $id): object throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is private and cannot be accessed by reference. You should either make it public, or tag it as "%s".', $id, $this->tagName)); } - // will throw a ServiceNotFoundException - $this->serviceContainer->get($id); + return $this->serviceContainer->get($id); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index 89ad99d987603..0c1fe4d141958 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -883,7 +883,6 @@ static function (ChildDefinition $definition, CustomAutoconfiguration $attribute $definition->addTag('app.custom_tag', get_object_vars($attribute) + ['class' => $reflector->getName()]); } ); - $container->registerForAutoconfiguration(TaggedService1::class)->addTag('app.custom_tag'); $container->register('one', TaggedService1::class) ->setPublic(true) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php index ead1df25a2123..96215c6d14883 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php @@ -184,19 +184,19 @@ public function testIndexedByServiceIdWithDecoration() $container->setDefinition(Service::class, $service); - $decorated = new Definition(Decorated::class); + $decorated = new Definition(DecoratedService::class); $decorated->setPublic(true); $decorated->setDecoratedService(Service::class); - $container->setDefinition(Decorated::class, $decorated); + $container->setDefinition(DecoratedService::class, $decorated); $container->compile(); /** @var ServiceLocator $locator */ $locator = $container->get(Locator::class)->locator; static::assertTrue($locator->has(Service::class)); - static::assertFalse($locator->has(Decorated::class)); - static::assertInstanceOf(Decorated::class, $locator->get(Service::class)); + static::assertFalse($locator->has(DecoratedService::class)); + static::assertInstanceOf(DecoratedService::class, $locator->get(Service::class)); } public function testDefinitionOrderIsTheSame() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php b/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php index 08ff5c797e185..3563a313814db 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php @@ -51,7 +51,7 @@ public function testIsFresh(callable $mockContainer, $expected) $this->assertSame($expected, $this->resourceChecker->isFresh($this->resource, time())); } - public function isFreshProvider() + public static function isFreshProvider() { yield 'not fresh on missing parameter' => [function (MockObject $container) { $container->method('hasParameter')->with('locales')->willReturn(false); @@ -62,11 +62,11 @@ public function isFreshProvider() }, false]; yield 'fresh on every identical parameters' => [function (MockObject $container) { - $container->expects($this->exactly(2))->method('hasParameter')->willReturn(true); - $container->expects($this->exactly(2))->method('getParameter') + $container->expects(self::exactly(2))->method('hasParameter')->willReturn(true); + $container->expects(self::exactly(2))->method('getParameter') ->withConsecutive( - [$this->equalTo('locales')], - [$this->equalTo('default_locale')] + [self::equalTo('locales')], + [self::equalTo('default_locale')] ) ->willReturnMap([ ['locales', ['fr', 'en']], diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index e491e2c790171..1dfd8f86eaf45 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1105,10 +1105,19 @@ public function testPrivateServiceUser() $container->addDefinitions([ 'bar' => $fooDefinition, 'bar_user' => $fooUserDefinition->setPublic(true), + 'bar_user2' => $fooUserDefinition->setPublic(true), ]); $container->compile(); + $this->assertNull($container->get('bar', $container::NULL_ON_INVALID_REFERENCE)); $this->assertInstanceOf(\BarClass::class, $container->get('bar_user')->bar); + + // Ensure that accessing a public service with a shared private service + // does not make the private service available. + $this->assertNull($container->get('bar', $container::NULL_ON_INVALID_REFERENCE)); + + // Ensure the private service is still shared. + $this->assertSame($container->get('bar_user')->bar, $container->get('bar_user2')->bar); } public function testThrowsExceptionWhenSetServiceOnACompiledContainer() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index f20be40568b0b..1dbcdb10ed01c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -491,7 +491,13 @@ protected function getBar6Service() */ protected function getDoctrine_ListenerService() { - return $this->privates['doctrine.listener'] = new \stdClass(($this->services['doctrine.entity_manager'] ?? $this->getDoctrine_EntityManagerService())); + $a = ($this->services['doctrine.entity_manager'] ?? $this->getDoctrine_EntityManagerService()); + + if (isset($this->privates['doctrine.listener'])) { + return $this->privates['doctrine.listener']; + } + + return $this->privates['doctrine.listener'] = new \stdClass($a); } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index 666ac0a876995..496e6b847290c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -285,11 +285,16 @@ protected function getDoctrine_EntityListenerResolverService() */ protected function getDoctrine_EntityManagerService() { - $a = new \stdClass(); - $a->resolver = ($this->services['doctrine.entity_listener_resolver'] ?? $this->getDoctrine_EntityListenerResolverService()); - $a->flag = 'ok'; + $a = ($this->services['doctrine.entity_listener_resolver'] ?? $this->getDoctrine_EntityListenerResolverService()); + + if (isset($this->services['doctrine.entity_manager'])) { + return $this->services['doctrine.entity_manager']; + } + $b = new \stdClass(); + $b->resolver = $a; + $b->flag = 'ok'; - return $this->services['doctrine.entity_manager'] = \FactoryChecker::create($a); + return $this->services['doctrine.entity_manager'] = \FactoryChecker::create($b); } /** @@ -299,7 +304,13 @@ protected function getDoctrine_EntityManagerService() */ protected function getDoctrine_ListenerService() { - return $this->services['doctrine.listener'] = new \stdClass(($this->services['doctrine.entity_manager'] ?? $this->getDoctrine_EntityManagerService())); + $a = ($this->services['doctrine.entity_manager'] ?? $this->getDoctrine_EntityManagerService()); + + if (isset($this->services['doctrine.listener'])) { + return $this->services['doctrine.listener']; + } + + return $this->services['doctrine.listener'] = new \stdClass($a); } /** @@ -495,7 +506,13 @@ protected function getLoggerService() */ protected function getMailer_TransportService() { - return $this->services['mailer.transport'] = ($this->services['mailer.transport_factory'] ?? $this->getMailer_TransportFactoryService())->create(); + $a = ($this->services['mailer.transport_factory'] ?? $this->getMailer_TransportFactoryService()); + + if (isset($this->services['mailer.transport'])) { + return $this->services['mailer.transport']; + } + + return $this->services['mailer.transport'] = $a->create(); } /** @@ -518,7 +535,13 @@ protected function getMailer_TransportFactoryService() */ protected function getMailer_TransportFactory_AmazonService() { - return $this->services['mailer.transport_factory.amazon'] = new \stdClass(($this->services['monolog.logger_2'] ?? $this->getMonolog_Logger2Service())); + $a = ($this->services['monolog.logger_2'] ?? $this->getMonolog_Logger2Service()); + + if (isset($this->services['mailer.transport_factory.amazon'])) { + return $this->services['mailer.transport_factory.amazon']; + } + + return $this->services['mailer.transport_factory.amazon'] = new \stdClass($a); } /** diff --git a/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php index da9a7033fd56c..a816650c26827 100644 --- a/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTest.php @@ -22,7 +22,7 @@ abstract class AbstractCrawlerTest extends TestCase { use ExpectDeprecationTrait; - abstract public function getDoctype(): string; + abstract public static function getDoctype(): string; protected function createCrawler($node = null, string $uri = null, string $baseHref = null) { diff --git a/src/Symfony/Component/DomCrawler/Tests/Html5ParserCrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/Html5ParserCrawlerTest.php index 806bc2e181032..05d1bc76a9f1a 100644 --- a/src/Symfony/Component/DomCrawler/Tests/Html5ParserCrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/Html5ParserCrawlerTest.php @@ -13,7 +13,7 @@ class Html5ParserCrawlerTest extends AbstractCrawlerTest { - public function getDoctype(): string + public static function getDoctype(): string { return ''; } diff --git a/src/Symfony/Component/DomCrawler/Tests/NativeParserCrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/NativeParserCrawlerTest.php index a17562f735580..c0cac9e8b603f 100644 --- a/src/Symfony/Component/DomCrawler/Tests/NativeParserCrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/NativeParserCrawlerTest.php @@ -13,7 +13,7 @@ class NativeParserCrawlerTest extends AbstractCrawlerTest { - public function getDoctype(): string + public static function getDoctype(): string { return ''; } diff --git a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php index 6e4adeab05509..746e8d44a548e 100644 --- a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php +++ b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php @@ -56,7 +56,7 @@ class DebugClassLoader 'null' => 'null', 'resource' => 'resource', 'boolean' => 'bool', - 'true' => 'bool', + 'true' => 'true', 'false' => 'false', 'integer' => 'int', 'array' => 'array', @@ -74,6 +74,7 @@ class DebugClassLoader '$this' => 'static', 'list' => 'array', 'class-string' => 'string', + 'never' => 'never', ]; private const BUILTIN_RETURN_TYPES = [ @@ -91,6 +92,9 @@ class DebugClassLoader 'parent' => true, 'mixed' => true, 'static' => true, + 'null' => true, + 'true' => true, + 'never' => true, ]; private const MAGIC_METHODS = [ @@ -762,6 +766,12 @@ private function setReturnType(string $types, string $class, string $method, str return; } + if ('null' === $types) { + self::$returnTypes[$class][$method] = ['null', 'null', $class, $filename]; + + return; + } + if ($nullable = 0 === strpos($types, 'null|')) { $types = substr($types, 5); } elseif ($nullable = '|null' === substr($types, -5)) { diff --git a/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php b/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php index 2168a1c075816..1e8afe39b9a4e 100644 --- a/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php +++ b/src/Symfony/Component/ErrorHandler/Internal/TentativeTypes.php @@ -753,6 +753,7 @@ class TentativeTypes 'isVariadic' => 'bool', 'isStatic' => 'bool', 'getClosureThis' => '?object', + 'getClosureCalledClass' => '?ReflectionClass', 'getClosureScopeClass' => '?ReflectionClass', 'getDocComment' => 'string|false', 'getEndLine' => 'int|false', diff --git a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php index 92ff96fdf83eb..83d7793d89592 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php @@ -402,6 +402,10 @@ class_exists('Test\\'.ReturnType::class, true); 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::mixed()" might add "mixed" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::nullableMixed()" might add "mixed" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::static()" might add "static" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', + 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::false()" might add "false" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', + 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::true()" might add "true" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', + 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::never()" might add "never" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', + 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::null()" might add "null" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', ]), $deprecations); } } diff --git a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnType.php b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnType.php index 21c4d2012f663..1b8138001de78 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnType.php +++ b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnType.php @@ -46,5 +46,9 @@ public function this() { } public function mixed() { } public function nullableMixed() { } public function static() { } + public function false() { } + public function true() { } + public function never() { } + public function null() { } public function outsideMethod() { } } diff --git a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnTypeParent.php b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnTypeParent.php index c7b0b6f0fca0c..d42c7c8c02167 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnTypeParent.php +++ b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/ReturnTypeParent.php @@ -220,6 +220,34 @@ public function static() { } + /** + * @return false + */ + public function false() + { + } + + /** + * @return true + */ + public function true() + { + } + + /** + * @return never + */ + public function never() + { + } + + /** + * @return null + */ + public function null() + { + } + /** * @return int */ diff --git a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php index 3916716ec0bc7..86d3854b22c4e 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php +++ b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php @@ -49,7 +49,7 @@ public function __construct($listener, ?string $name, Stopwatch $stopwatch, Even $r = new \ReflectionFunction($listener); if (str_contains($r->name, '{closure}')) { $this->pretty = $this->name = 'closure'; - } elseif ($class = $r->getClosureScopeClass()) { + } elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $this->name = $class->name; $this->pretty = $this->name.'::'.$r->name; } else { diff --git a/src/Symfony/Component/Finder/Tests/GitignoreTest.php b/src/Symfony/Component/Finder/Tests/GitignoreTest.php index 63f3b76cd5f75..65b52057937b9 100644 --- a/src/Symfony/Component/Finder/Tests/GitignoreTest.php +++ b/src/Symfony/Component/Finder/Tests/GitignoreTest.php @@ -55,7 +55,7 @@ public function testToRegex(array $gitignoreLines, array $matchingCases, array $ } } - public function provider(): array + public static function provider(): array { $cases = [ [ @@ -394,7 +394,7 @@ public function provider(): array public function providerExtended(): array { - $basicCases = $this->provider(); + $basicCases = self::provider(); $cases = []; foreach ($basicCases as $case) { diff --git a/src/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php index 129d565d55b8c..25a6b8a2d75a2 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php @@ -41,7 +41,7 @@ public function getAcceptData() ]; return [ - [[new NumberComparator('< 1K'), new NumberComparator('> 0.5K')], $this->toAbsolute($lessThan1KGreaterThan05K)], + [[new NumberComparator('< 1K'), new NumberComparator('> 0.5K')], self::toAbsolute($lessThan1KGreaterThan05K)], ]; } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php index 4ac58bd2acd05..55efb652daec7 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php @@ -13,6 +13,7 @@ use Symfony\Component\Form\AbstractRendererEngine; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; @@ -70,8 +71,16 @@ public function buildView(FormView $view, FormInterface $form, array $options) if (!$labelFormat) { $labelFormat = $view->parent->vars['label_format']; } + + $rootFormAttrOption = $form->getRoot()->getConfig()->getOption('form_attr'); + if ($options['form_attr'] || $rootFormAttrOption) { + $options['attr']['form'] = \is_string($rootFormAttrOption) ? $rootFormAttrOption : $form->getRoot()->getName(); + if (empty($options['attr']['form'])) { + throw new LogicException('"form_attr" option must be a string identifier on root form when it has no id.'); + } + } } else { - $id = $name; + $id = \is_string($options['form_attr']) ? $options['form_attr'] : $name; $fullName = $name; $uniqueBlockPrefix = '_'.$blockName; @@ -137,6 +146,7 @@ public function configureOptions(OptionsResolver $resolver) 'translation_domain' => null, 'auto_initialize' => true, 'priority' => 0, + 'form_attr' => false, ]); $resolver->setAllowedTypes('block_prefix', ['null', 'string']); @@ -144,6 +154,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('row_attr', 'array'); $resolver->setAllowedTypes('label_html', 'bool'); $resolver->setAllowedTypes('priority', 'int'); + $resolver->setAllowedTypes('form_attr', ['bool', 'string']); $resolver->setInfo('priority', 'The form rendering priority (higher priorities will be rendered first)'); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index f3bfd4d09519b..bd8ba13a3e7a5 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -97,16 +97,6 @@ public function buildView(FormView $view, FormInterface $form, array $options) } $helpTranslationParameters = array_merge($view->parent->vars['help_translation_parameters'], $helpTranslationParameters); - - $rootFormAttrOption = $form->getRoot()->getConfig()->getOption('form_attr'); - if ($options['form_attr'] || $rootFormAttrOption) { - $view->vars['attr']['form'] = \is_string($rootFormAttrOption) ? $rootFormAttrOption : $form->getRoot()->getName(); - if (empty($view->vars['attr']['form'])) { - throw new LogicException('"form_attr" option must be a string identifier on root form when it has no id.'); - } - } - } elseif (\is_string($options['form_attr'])) { - $view->vars['id'] = $options['form_attr']; } $formConfig = $form->getConfig(); @@ -221,7 +211,6 @@ public function configureOptions(OptionsResolver $resolver) 'is_empty_callback' => null, 'getter' => null, 'setter' => null, - 'form_attr' => false, ]); $resolver->setAllowedTypes('label_attr', 'array'); @@ -233,7 +222,6 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setAllowedTypes('is_empty_callback', ['null', 'callable']); $resolver->setAllowedTypes('getter', ['null', 'callable']); $resolver->setAllowedTypes('setter', ['null', 'callable']); - $resolver->setAllowedTypes('form_attr', ['bool', 'string']); $resolver->setInfo('getter', 'A callable that accepts two arguments (the view data and the current form field) and must return a value.'); $resolver->setInfo('setter', 'A callable that accepts three arguments (a reference to the view data, the submitted value and the current form field).'); diff --git a/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php index 61b8dc379148a..1d33451d45293 100644 --- a/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php @@ -65,7 +65,7 @@ public function getNormalizedIniPostMaxSize(): string $this->request = null; } - public function methodExceptGetProvider() + public static function methodExceptGetProvider() { return [ ['POST'], @@ -79,7 +79,7 @@ public function methodProvider() { return array_merge([ ['GET'], - ], $this->methodExceptGetProvider()); + ], self::methodExceptGetProvider()); } /** diff --git a/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php index 561eea5aa35a0..73750380385f8 100644 --- a/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php @@ -215,7 +215,7 @@ public function provideCompletionSuggestions(): iterable yield 'form_type' => [ [''], - $this->getCoreTypes(), + self::getCoreTypes(), ]; yield 'option for FQCN' => [ @@ -234,6 +234,7 @@ public function provideCompletionSuggestions(): iterable 'translation_domain', 'auto_initialize', 'priority', + 'form_attr', ], ]; @@ -253,6 +254,7 @@ public function provideCompletionSuggestions(): iterable 'translation_domain', 'auto_initialize', 'priority', + 'form_attr', ], ]; @@ -267,7 +269,7 @@ public function provideCompletionSuggestions(): iterable ]; } - private function getCoreTypes(): array + private static function getCoreTypes(): array { $coreExtension = new CoreExtension(); $loadTypesRefMethod = (new \ReflectionObject($coreExtension))->getMethod('loadTypes'); diff --git a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php index af94dd7e32487..6c73abe370d7e 100644 --- a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php +++ b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php @@ -285,15 +285,15 @@ public function privateTaggedServicesProvider() function (ContainerBuilder $container) { $formTypes = $container->getDefinition('form.extension')->getArgument(0); - $this->assertInstanceOf(Reference::class, $formTypes); + self::assertInstanceOf(Reference::class, $formTypes); $locator = $container->getDefinition((string) $formTypes); $expectedLocatorMap = [ 'stdClass' => new ServiceClosureArgument(new Reference('my.type')), ]; - $this->assertInstanceOf(Definition::class, $locator); - $this->assertEquals($expectedLocatorMap, $locator->getArgument(0)); + self::assertInstanceOf(Definition::class, $locator); + self::assertEquals($expectedLocatorMap, $locator->getArgument(0)); }, ], [ @@ -301,7 +301,7 @@ function (ContainerBuilder $container) { Type1TypeExtension::class, 'form.type_extension', function (ContainerBuilder $container) { - $this->assertEquals( + self::assertEquals( ['Symfony\Component\Form\Extension\Core\Type\FormType' => new IteratorArgument([new Reference('my.type_extension')])], $container->getDefinition('form.extension')->getArgument(1) ); @@ -309,7 +309,7 @@ function (ContainerBuilder $container) { ['extended_type' => 'Symfony\Component\Form\Extension\Core\Type\FormType'], ], ['my.guesser', 'stdClass', 'form.type_guesser', function (ContainerBuilder $container) { - $this->assertEquals(new IteratorArgument([new Reference('my.guesser')]), $container->getDefinition('form.extension')->getArgument(2)); + self::assertEquals(new IteratorArgument([new Reference('my.guesser')]), $container->getDefinition('form.extension')->getArgument(2)); }], ]; } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php index eccaa22a136f3..0412dc321a0f6 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php @@ -37,7 +37,7 @@ protected function tearDown(): void $this->dateTimeWithoutSeconds = null; } - public function allProvider() + public static function allProvider() { return [ ['UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06Z'], @@ -51,12 +51,12 @@ public function allProvider() public function transformProvider() { - return $this->allProvider(); + return self::allProvider(); } public function reverseTransformProvider() { - return array_merge($this->allProvider(), [ + return array_merge(self::allProvider(), [ // format without seconds, as appears in some browsers ['UTC', 'UTC', '2010-02-03 04:05:00 UTC', '2010-02-03T04:05Z'], ['America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:00 America/New_York', '2010-02-03T17:05+08:00'], diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php index 654e04649e9f1..dcff028026482 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php @@ -13,6 +13,8 @@ use Symfony\Component\Form\Button; use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Extension\Core\Type\FormType; /** * @author Bernhard Schussek @@ -36,4 +38,65 @@ public function testSubmitNullUsesDefaultEmptyData($emptyData = 'empty', $expect $this->expectExceptionMessage('Buttons do not support empty data.'); parent::testSubmitNullUsesDefaultEmptyData($emptyData, $expectedData); } + + public function testFormAttrOnRoot() + { + $view = $this->factory + ->createNamedBuilder('parent', FormType::class, null, [ + 'form_attr' => true, + ]) + ->add('child1', $this->getTestedType()) + ->add('child2', $this->getTestedType()) + ->getForm() + ->createView(); + $this->assertArrayNotHasKey('form', $view->vars['attr']); + $this->assertSame($view->vars['id'], $view['child1']->vars['attr']['form']); + $this->assertSame($view->vars['id'], $view['child2']->vars['attr']['form']); + } + + public function testFormAttrOnChild() + { + $view = $this->factory + ->createNamedBuilder('parent') + ->add('child1', $this->getTestedType(), [ + 'form_attr' => true, + ]) + ->add('child2', $this->getTestedType()) + ->getForm() + ->createView(); + $this->assertArrayNotHasKey('form', $view->vars['attr']); + $this->assertSame($view->vars['id'], $view['child1']->vars['attr']['form']); + $this->assertArrayNotHasKey('form', $view['child2']->vars['attr']); + } + + public function testFormAttrAsBoolWithNoId() + { + $this->expectException(LogicException::class); + $this->expectErrorMessage('form_attr'); + $this->factory + ->createNamedBuilder('', FormType::class, null, [ + 'form_attr' => true, + ]) + ->add('child1', $this->getTestedType()) + ->add('child2', $this->getTestedType()) + ->getForm() + ->createView(); + } + + public function testFormAttrAsStringWithNoId() + { + $stringId = 'custom-identifier'; + $view = $this->factory + ->createNamedBuilder('', FormType::class, null, [ + 'form_attr' => $stringId, + ]) + ->add('child1', $this->getTestedType()) + ->add('child2', $this->getTestedType()) + ->getForm() + ->createView(); + $this->assertArrayNotHasKey('form', $view->vars['attr']); + $this->assertSame($stringId, $view->vars['id']); + $this->assertSame($view->vars['id'], $view['child1']->vars['attr']['form']); + $this->assertSame($view->vars['id'], $view['child2']->vars['attr']['form']); + } } diff --git a/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php b/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php index 73f88651345d3..8f3c3c5347026 100644 --- a/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php +++ b/src/Symfony/Component/HttpClient/DependencyInjection/HttpClientPass.php @@ -43,7 +43,7 @@ public function process(ContainerBuilder $container) $container->register('.debug.'.$id, TraceableHttpClient::class) ->setArguments([new Reference('.debug.'.$id.'.inner'), new Reference('debug.stopwatch', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]) ->addTag('kernel.reset', ['method' => 'reset']) - ->setDecoratedService($id); + ->setDecoratedService($id, null, 5); $container->getDefinition('data_collector.http_client') ->addMethodCall('registerClient', [$id, new Reference('.debug.'.$id)]); } diff --git a/src/Symfony/Component/HttpClient/Tests/DependencyInjection/HttpClientPassTest.php b/src/Symfony/Component/HttpClient/Tests/DependencyInjection/HttpClientPassTest.php index eb04f88226d1f..c6dcf4fcf7902 100755 --- a/src/Symfony/Component/HttpClient/Tests/DependencyInjection/HttpClientPassTest.php +++ b/src/Symfony/Component/HttpClient/Tests/DependencyInjection/HttpClientPassTest.php @@ -38,7 +38,7 @@ public function testItDecoratesHttpClientWithTraceableHttpClient() $sut->process($container); $this->assertTrue($container->hasDefinition('.debug.foo')); $this->assertSame(TraceableHttpClient::class, $container->getDefinition('.debug.foo')->getClass()); - $this->assertSame(['foo', null, 0], $container->getDefinition('.debug.foo')->getDecoratedService()); + $this->assertSame(['foo', null, 5], $container->getDefinition('.debug.foo')->getDecoratedService()); } public function testItRegistersDebugHttpClientToCollector() diff --git a/src/Symfony/Component/HttpFoundation/Exception/SessionNotFoundException.php b/src/Symfony/Component/HttpFoundation/Exception/SessionNotFoundException.php index 9c719aa041be3..94b0cb69aae1f 100644 --- a/src/Symfony/Component/HttpFoundation/Exception/SessionNotFoundException.php +++ b/src/Symfony/Component/HttpFoundation/Exception/SessionNotFoundException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\HttpFoundation\Exception; /** - * Raised when a session does not exists. This happens in the following cases: + * Raised when a session does not exist. This happens in the following cases: * - the session is not enabled * - attempt to read a session outside a request context (ie. cli script). * diff --git a/src/Symfony/Component/HttpFoundation/IpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php index c13c1e47db22d..9a1afa7bf498a 100644 --- a/src/Symfony/Component/HttpFoundation/IpUtils.php +++ b/src/Symfony/Component/HttpFoundation/IpUtils.php @@ -136,10 +136,6 @@ public static function checkIp6(?string $requestIp, string $ip) } // Check to see if we were given a IP4 $requestIp or $ip by mistake - if (str_contains($requestIp, '.') || str_contains($ip, '.')) { - return self::$checkedIps[$cacheKey] = false; - } - if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { return self::$checkedIps[$cacheKey] = false; } @@ -147,6 +143,10 @@ public static function checkIp6(?string $requestIp, string $ip) if (str_contains($ip, '/')) { [$address, $netmask] = explode('/', $ip, 2); + if (!filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return self::$checkedIps[$cacheKey] = false; + } + if ('0' === $netmask) { return (bool) unpack('n*', @inet_pton($address)); } @@ -155,6 +155,10 @@ public static function checkIp6(?string $requestIp, string $ip) return self::$checkedIps[$cacheKey] = false; } } else { + if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return self::$checkedIps[$cacheKey] = false; + } + $address = $ip; $netmask = 128; } diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 2bf52ce4339df..10f779d279ec0 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -522,10 +522,10 @@ public function __toString() $cookies = []; foreach ($this->cookies as $k => $v) { - $cookies[] = $k.'='.$v; + $cookies[] = \is_array($v) ? http_build_query([$k => $v], '', '; ', \PHP_QUERY_RFC3986) : "$k=$v"; } - if (!empty($cookies)) { + if ($cookies) { $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n"; } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php index 9cb841ae0d775..e0ec4d2d9984d 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -77,7 +77,7 @@ protected function doRead(string $sessionId) #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { - $this->memcached->touch($this->prefix.$sessionId, time() + (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'))); + $this->memcached->touch($this->prefix.$sessionId, $this->getCompatibleTtl()); return true; } @@ -87,7 +87,20 @@ public function updateTimestamp($sessionId, $data) */ protected function doWrite(string $sessionId, string $data) { - return $this->memcached->set($this->prefix.$sessionId, $data, time() + (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'))); + return $this->memcached->set($this->prefix.$sessionId, $data, $this->getCompatibleTtl()); + } + + private function getCompatibleTtl(): int + { + $ttl = (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')); + + // If the relative TTL that is used exceeds 30 days, memcached will treat the value as Unix time. + // We have to convert it to an absolute Unix time at this point, to make sure the TTL is correct. + if ($ttl > 60 * 60 * 24 * 30) { + $ttl += time(); + } + + return $ttl; } /** diff --git a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php index ccbc8275f62aa..d6d3728db1401 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php @@ -78,6 +78,7 @@ public function getIpv6Data() [false, '0.0.0.0/8', '::1'], [false, '::1', '127.0.0.1'], [false, '::1', '0.0.0.0/8'], + [true, '::ffff:10.126.42.2', '::ffff:10.0.0.0/0'], ]; } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index df55628c6d15d..058b5419a87f9 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1702,6 +1702,12 @@ public function testToString() $asString = (string) $request; $this->assertStringContainsString('Cookie: Foo=Bar; Another=Cookie', $asString); + + $request->cookies->set('foo.bar', [1, 2]); + + $asString = (string) $request; + + $this->assertStringContainsString('foo.bar%5B0%5D=1; foo.bar%5B1%5D=2', $asString); } public function testIsMethod() diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php index d404b74c6a5f4..6abdf4eb05f5c 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php @@ -16,6 +16,7 @@ /** * @requires extension memcached + * * @group time-sensitive */ class MemcachedSessionHandlerTest extends TestCase @@ -92,13 +93,30 @@ public function testWriteSession() $this->memcached ->expects($this->once()) ->method('set') - ->with(self::PREFIX.'id', 'data', $this->equalTo(time() + self::TTL, 2)) + ->with(self::PREFIX.'id', 'data', $this->equalTo(self::TTL, 2)) ->willReturn(true) ; $this->assertTrue($this->storage->write('id', 'data')); } + public function testWriteSessionWithLargeTTL() + { + $this->memcached + ->expects($this->once()) + ->method('set') + ->with(self::PREFIX.'id', 'data', $this->equalTo(time() + self::TTL + 60 * 60 * 24 * 30, 2)) + ->willReturn(true) + ; + + $storage = new MemcachedSessionHandler( + $this->memcached, + ['prefix' => self::PREFIX, 'expiretime' => self::TTL + 60 * 60 * 24 * 30] + ); + + $this->assertTrue($storage->write('id', 'data')); + } + public function testDestroySession() { $this->memcached diff --git a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php index e3835c057a4a3..85bb805f34bb6 100644 --- a/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php +++ b/src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -33,7 +33,7 @@ public function createArgumentMetadata($controller): array $class = $reflection->class; } else { $reflection = new \ReflectionFunction($controller); - if ($class = str_contains($reflection->name, '{closure}') ? null : $reflection->getClosureScopeClass()) { + if ($class = str_contains($reflection->name, '{closure}') ? null : (\PHP_VERSION_ID >= 80111 ? $reflection->getClosureCalledClass() : $reflection->getClosureScopeClass())) { $class = $class->name; } } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index 951860a225668..5717000f292c7 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -479,7 +479,7 @@ private function parseController($controller) } $controller['method'] = $r->name; - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $controller['class'] = $class->name; } else { return $r->name; diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php index 4603052e933df..27749b24b2504 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php @@ -200,10 +200,11 @@ public function onKernelResponse(ResponseEvent $event) } if ($autoCacheControl) { + $maxAge = $response->headers->hasCacheControlDirective('public') ? 0 : (int) $response->getMaxAge(); $response - ->setExpires(new \DateTime()) + ->setExpires(new \DateTimeImmutable('+'.$maxAge.' seconds')) ->setPrivate() - ->setMaxAge(0) + ->setMaxAge($maxAge) ->headers->addCacheControlDirective('must-revalidate'); } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 94735c2f08dc2..b7d516e41363f 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -78,11 +78,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static $freshCache = []; - public const VERSION = '5.4.16'; - public const VERSION_ID = 50416; + public const VERSION = '5.4.17'; + public const VERSION_ID = 50417; public const MAJOR_VERSION = 5; public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 16; + public const RELEASE_VERSION = 17; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '11/2024'; diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php index 5e11875bb1db8..96f66354ba7e4 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php @@ -39,6 +39,7 @@ class SessionListenerTest extends TestCase { /** * @dataProvider provideSessionOptions + * * @runInSeparateProcess */ public function testSessionCookieOptions(array $phpSessionOptions, array $sessionOptions, array $expectedSessionOptions) @@ -531,6 +532,64 @@ public function testUninitializedSessionWithoutInitializedSession() $this->assertSame('60', $response->headers->getCacheControlDirective('s-maxage')); } + public function testResponseHeadersMaxAgeAndExpiresNotBeOverridenIfSessionStarted() + { + $session = $this->createMock(Session::class); + $session->expects($this->exactly(2))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1)); + + $container = new Container(); + $container->set('initialized_session', $session); + + $listener = new SessionListener($container); + $kernel = $this->createMock(HttpKernelInterface::class); + + $request = new Request(); + $listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST)); + + $response = new Response(); + $response->setPrivate(); + $expiresHeader = gmdate('D, d M Y H:i:s', time() + 600).' GMT'; + $response->setMaxAge(600); + $listener->onKernelResponse(new ResponseEvent($kernel, new Request(), HttpKernelInterface::MAIN_REQUEST, $response)); + + $this->assertTrue($response->headers->has('expires')); + $this->assertSame($expiresHeader, $response->headers->get('expires')); + $this->assertFalse($response->headers->has('max-age')); + $this->assertSame('600', $response->headers->getCacheControlDirective('max-age')); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + $this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($response->headers->has(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER)); + } + + public function testResponseHeadersMaxAgeAndExpiresDefaultValuesIfSessionStarted() + { + $session = $this->createMock(Session::class); + $session->expects($this->exactly(2))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1)); + + $container = new Container(); + $container->set('initialized_session', $session); + + $listener = new SessionListener($container); + $kernel = $this->createMock(HttpKernelInterface::class); + + $request = new Request(); + $listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST)); + + $response = new Response(); + $expiresHeader = gmdate('D, d M Y H:i:s', time()).' GMT'; + $listener->onKernelResponse(new ResponseEvent($kernel, new Request(), HttpKernelInterface::MAIN_REQUEST, $response)); + + $this->assertTrue($response->headers->has('expires')); + $this->assertSame($expiresHeader, $response->headers->get('expires')); + $this->assertFalse($response->headers->has('max-age')); + $this->assertSame('0', $response->headers->getCacheControlDirective('max-age')); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + $this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($response->headers->has(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER)); + } + public function testSurrogateMainRequestIsPublic() { $session = $this->createMock(Session::class); diff --git a/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php index fff75af64b457..08edb245a0df9 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mailer\Exception\TransportException; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use Symfony\Component\Mailer\Transport\RoundRobinTransport; use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Mime\RawMessage; @@ -60,10 +61,21 @@ public function testSendAllDead() $t2 = $this->createMock(TransportInterface::class); $t2->expects($this->once())->method('send')->will($this->throwException(new TransportException())); $t = new RoundRobinTransport([$t1, $t2]); - $this->expectException(TransportException::class); - $this->expectExceptionMessage('All transports failed.'); - $t->send(new RawMessage('')); - $this->assertTransports($t, 1, [$t1, $t2]); + $p = new \ReflectionProperty($t, 'cursor'); + $p->setAccessible(true); + $p->setValue($t, 0); + + try { + $t->send(new RawMessage('')); + } catch (\Exception $e) { + $this->assertInstanceOf(TransportException::class, $e); + $this->assertStringContainsString('All transports failed.', $e->getMessage()); + $this->assertTransports($t, 0, [$t1, $t2]); + + return; + } + + $this->fail('The expected exception was not thrown.'); } public function testSendOneDead() @@ -127,6 +139,34 @@ public function testSendOneDeadAndRecoveryWithinRetryPeriod() $this->assertTransports($t, 1, []); } + public function testFailureDebugInformation() + { + $t1 = $this->createMock(TransportInterface::class); + $e1 = new TransportException(); + $e1->appendDebug('Debug message 1'); + $t1->expects($this->once())->method('send')->will($this->throwException($e1)); + $t1->expects($this->once())->method('__toString')->willReturn('t1'); + + $t2 = $this->createMock(TransportInterface::class); + $e2 = new TransportException(); + $e2->appendDebug('Debug message 2'); + $t2->expects($this->once())->method('send')->will($this->throwException($e2)); + $t2->expects($this->once())->method('__toString')->willReturn('t2'); + + $t = new RoundRobinTransport([$t1, $t2]); + + try { + $t->send(new RawMessage('')); + } catch (TransportExceptionInterface $e) { + $this->assertStringContainsString('Transport "t1": Debug message 1', $e->getDebug()); + $this->assertStringContainsString('Transport "t2": Debug message 2', $e->getDebug()); + + return; + } + + $this->fail('Expected exception was not thrown!'); + } + private function assertTransports(RoundRobinTransport $transport, int $cursor, array $deadTransports) { $p = new \ReflectionProperty($transport, 'cursor'); diff --git a/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php b/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php index 2f351389fa1f2..761b57f188b75 100644 --- a/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php +++ b/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php @@ -48,15 +48,19 @@ public function __construct(array $transports, int $retryPeriod = 60) public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage { + $exception = null; + while ($transport = $this->getNextTransport()) { try { return $transport->send($message, $envelope); } catch (TransportExceptionInterface $e) { + $exception = $exception ?? new TransportException('All transports failed.'); + $exception->appendDebug(sprintf("Transport \"%s\": %s\n", $transport, $e->getDebug())); $this->deadTransports[$transport] = microtime(true); } } - throw new TransportException('All transports failed.'); + throw $exception ?? new TransportException('No transports found.'); } public function __toString(): string diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php index 35fa87ca54a5a..0595b573e8776 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php @@ -32,6 +32,9 @@ class Connection 'x-message-ttl', ]; + /** + * @see https://github.com/php-amqp/php-amqp/blob/master/amqp_connection_resource.h + */ private const AVAILABLE_OPTIONS = [ 'host', 'port', @@ -53,6 +56,7 @@ class Connection 'write_timeout', 'confirm_timeout', 'connect_timeout', + 'rpc_timeout', 'cacert', 'cert', 'key', diff --git a/src/Symfony/Component/Messenger/Handler/HandlerDescriptor.php b/src/Symfony/Component/Messenger/Handler/HandlerDescriptor.php index 6acb2c2f377bf..5957e3f13823b 100644 --- a/src/Symfony/Component/Messenger/Handler/HandlerDescriptor.php +++ b/src/Symfony/Component/Messenger/Handler/HandlerDescriptor.php @@ -37,7 +37,7 @@ public function __construct(callable $handler, array $options = []) if (str_contains($r->name, '{closure}')) { $this->name = 'Closure'; } elseif (!$handler = $r->getClosureThis()) { - $class = $r->getClosureScopeClass(); + $class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass(); $this->name = ($class ? $class->name.'::' : '').$r->name; } else { diff --git a/src/Symfony/Component/Mime/Part/MessagePart.php b/src/Symfony/Component/Mime/Part/MessagePart.php index 1b5c23e2bc411..00129b4757b40 100644 --- a/src/Symfony/Component/Mime/Part/MessagePart.php +++ b/src/Symfony/Component/Mime/Part/MessagePart.php @@ -59,4 +59,17 @@ public function bodyToIterable(): iterable { return $this->message->toIterable(); } + + /** + * @return array + */ + public function __sleep() + { + return ['message']; + } + + public function __wakeup() + { + $this->__construct($this->message); + } } diff --git a/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php b/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php index 2713d5bc079c7..c01958a4b94b8 100644 --- a/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php +++ b/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php @@ -39,4 +39,14 @@ public function testHeaders() new ParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'Subject.eml', 'filename' => 'Subject.eml']) ), $p->getPreparedHeaders()); } + + public function testSerialize() + { + $email = (new Email())->from('fabien@symfony.com')->to('you@example.com')->text('content'); + $email->getHeaders()->addIdHeader('Message-ID', $email->generateMessageId()); + + $p = new MessagePart($email); + $expected = clone $p; + $this->assertEquals($expected->toString(), unserialize(serialize($p))->toString()); + } } diff --git a/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md b/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md index 3c32a8eef1d00..3dbcbb1546247 100644 --- a/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md +++ b/src/Symfony/Component/Notifier/Bridge/AmazonSns/README.md @@ -13,7 +13,7 @@ AMAZON_SNS_DSN=sns://ACCESS_ID:ACCESS_KEY@default?region=REGION Resources --------- - * [Contributing](https://symfony.com/doc/current/contributing/index.html) - * [Report issues](https://github.com/symfony/symfony/issues) and - [send Pull Requests](https://github.com/symfony/symfony/pulls) - in the [main Symfony repository](https://github.com/symfony/symfony) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/Clickatell/composer.json b/src/Symfony/Component/Notifier/Bridge/Clickatell/composer.json index a176c0c3611ea..2d7d60a68e1e5 100644 --- a/src/Symfony/Component/Notifier/Bridge/Clickatell/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Clickatell/composer.json @@ -24,9 +24,6 @@ "symfony/http-client": "^4.3|^5.0|^6.0", "symfony/notifier": "^5.3|^6.0" }, - "require-dev": { - "symfony/event-dispatcher": "^4.3|^5.0|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Clickatell\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/Discord/composer.json b/src/Symfony/Component/Notifier/Bridge/Discord/composer.json index a67dbd48299ec..63bc7997575f3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Discord/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Discord/composer.json @@ -21,9 +21,6 @@ "symfony/notifier": "^5.3|^6.0", "symfony/polyfill-mbstring": "^1.0" }, - "require-dev": { - "symfony/event-dispatcher": "^4.3|^5.0|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Discord\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json b/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json index 0d2a4792a0010..905f54b2dd7a8 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/FakeChat/composer.json @@ -24,7 +24,6 @@ "php": ">=7.2.5", "symfony/http-client": "^4.4|^5.2|^6.0", "symfony/notifier": "^5.3|^6.0", - "symfony/event-dispatcher-contracts": "^2|^3", "symfony/mailer": "^5.2|^6.0" }, "autoload": { diff --git a/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json b/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json index e7199872f3629..7008743675e13 100644 --- a/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/FakeSms/composer.json @@ -24,7 +24,6 @@ "php": ">=7.2.5", "symfony/http-client": "^4.4|^5.2|^6.0", "symfony/notifier": "^5.3|^6.0", - "symfony/event-dispatcher-contracts": "^2|^3", "symfony/mailer": "^5.2|^6.0" }, "autoload": { diff --git a/src/Symfony/Component/Notifier/Bridge/MessageMedia/composer.json b/src/Symfony/Component/Notifier/Bridge/MessageMedia/composer.json index 845ec9f6cd2f2..2410b712a7402 100644 --- a/src/Symfony/Component/Notifier/Bridge/MessageMedia/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/MessageMedia/composer.json @@ -1,30 +1,30 @@ { - "name": "symfony/message-media-notifier", - "type": "symfony-notifier-bridge", - "description": "Symfony MessageMedia Notifier Bridge", - "keywords": ["sms", "messagemedia", "notifier"], - "homepage": "https://symfony.com", - "license": "MIT", - "authors": [ - { - "name": "Adrian Nguyen", - "email": "vuphuong87@gmail.com" + "name": "symfony/message-media-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony MessageMedia Notifier Bridge", + "keywords": ["sms", "messagemedia", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Adrian Nguyen", + "email": "vuphuong87@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/http-client": "^4.4|^5.2|^6.0", + "symfony/notifier": "^5.3|^6.0" }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "require": { - "php": ">=7.2.5", - "symfony/http-client": "^4.4|^5.2|^6.0", - "symfony/notifier": "^5.3|^6.0" - }, - "autoload": { - "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\MessageMedia\\": "" }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "minimum-stability": "dev" + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\MessageMedia\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" } diff --git a/src/Symfony/Component/Notifier/Bridge/OneSignal/composer.json b/src/Symfony/Component/Notifier/Bridge/OneSignal/composer.json index 12a97c870b0c9..af19fde7917d4 100644 --- a/src/Symfony/Component/Notifier/Bridge/OneSignal/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/OneSignal/composer.json @@ -20,9 +20,6 @@ "symfony/http-client": "^4.4|^5.2|^6.0", "symfony/notifier": "^5.4|^6.0" }, - "require-dev": { - "symfony/event-dispatcher": "^4.3|^5.0|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\OneSignal\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/composer.json b/src/Symfony/Component/Notifier/Bridge/Slack/composer.json index f5c7b522b2e1d..70cd75bdc354b 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Slack/composer.json @@ -21,9 +21,6 @@ "symfony/http-client": "^4.3|^5.0|^6.0", "symfony/notifier": "^5.3|^6.0" }, - "require-dev": { - "symfony/event-dispatcher": "^4.3|^5.0|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Slack\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json b/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json index 0ba70d6e549cf..610f06c97195f 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Telegram/composer.json @@ -20,9 +20,6 @@ "symfony/http-client": "^4.3|^5.0|^6.0", "symfony/notifier": "^5.3|^6.0" }, - "require-dev": { - "symfony/event-dispatcher": "^4.3|^5.0|^6.0" - }, "autoload": { "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Telegram\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Notifier/Bridge/Telnyx/README.md b/src/Symfony/Component/Notifier/Bridge/Telnyx/README.md index e279e11167a35..36d1aa7a21efd 100644 --- a/src/Symfony/Component/Notifier/Bridge/Telnyx/README.md +++ b/src/Symfony/Component/Notifier/Bridge/Telnyx/README.md @@ -18,7 +18,7 @@ where: Resources --------- - * [Contributing](https://symfony.com/doc/current/contributing/index.html) - * [Report issues](https://github.com/symfony/symfony/issues) and - [send Pull Requests](https://github.com/symfony/symfony/pulls) - in the [main Symfony repository](https://github.com/symfony/symfony) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index 21020415ef58b..dec09cc70b7b4 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -58,8 +58,8 @@ public function invalidTypesProvider() return [ 'pub' => ['pub', null, null], 'stat' => ['stat', null, null], - 'foo' => ['foo', $this->isPhpDocumentorV5() ? 'Foo.' : null, null], - 'bar' => ['bar', $this->isPhpDocumentorV5() ? 'Bar.' : null, null], + 'foo' => ['foo', self::isPhpDocumentorV5() ? 'Foo.' : null, null], + 'bar' => ['bar', self::isPhpDocumentorV5() ? 'Bar.' : null, null], ]; } @@ -125,10 +125,10 @@ public function typesProvider() ['donotexist', null, null, null], ['staticGetter', null, null, null], ['staticSetter', null, null, null], - ['emptyVar', null, $this->isPhpDocumentorV5() ? 'This should not be removed.' : null, null], + ['emptyVar', null, self::isPhpDocumentorV5() ? 'This should not be removed.' : null, null], [ 'arrayWithKeys', - $this->isPhpDocumentorV5() ? [ + self::isPhpDocumentorV5() ? [ new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_STRING)), ] : null, null, @@ -136,7 +136,7 @@ public function typesProvider() ], [ 'arrayOfMixed', - $this->isPhpDocumentorV5() ? [ + self::isPhpDocumentorV5() ? [ new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_STRING), null), ] : null, null, @@ -144,7 +144,7 @@ public function typesProvider() ], [ 'listOfStrings', - $this->isPhpDocumentorV5() ? [ + self::isPhpDocumentorV5() ? [ new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)), ] : null, null, @@ -417,7 +417,7 @@ public function testUnknownPseudoType() $this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'scalar')], $this->extractor->getTypes(PseudoTypeDummy::class, 'unknownPseudoType')); } - protected function isPhpDocumentorV5() + protected static function isPhpDocumentorV5() { if (class_exists(InvalidTag::class)) { return true; diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json index e6a29c069e3e7..30d77f6209c8d 100644 --- a/src/Symfony/Component/PropertyInfo/composer.json +++ b/src/Symfony/Component/PropertyInfo/composer.json @@ -34,7 +34,7 @@ "symfony/dependency-injection": "^4.4|^5.0|^6.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "phpstan/phpdoc-parser": "^1.0", - "doctrine/annotations": "^1.10.4" + "doctrine/annotations": "^1.10.4|^2" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", diff --git a/src/Symfony/Component/RateLimiter/Reservation.php b/src/Symfony/Component/RateLimiter/Reservation.php index 0a21310513809..108d672574991 100644 --- a/src/Symfony/Component/RateLimiter/Reservation.php +++ b/src/Symfony/Component/RateLimiter/Reservation.php @@ -45,6 +45,6 @@ public function getRateLimit(): RateLimit public function wait(): void { - usleep($this->getWaitDuration() * 1e6); + usleep((int) ($this->getWaitDuration() * 1e6)); } } diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php index b7399df353ef0..e2843a0a39843 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php @@ -26,7 +26,9 @@ protected function configureRoute(Route $route, \ReflectionClass $class, \Reflec { } }; - AnnotationRegistry::registerLoader('class_exists'); + if (method_exists(AnnotationRegistry::class, 'registerLoader')) { + AnnotationRegistry::registerLoader('class_exists'); + } } public function testDefaultRouteName() diff --git a/src/Symfony/Component/Routing/composer.json b/src/Symfony/Component/Routing/composer.json index b978c06263f27..b21ad5f2929f3 100644 --- a/src/Symfony/Component/Routing/composer.json +++ b/src/Symfony/Component/Routing/composer.json @@ -26,7 +26,7 @@ "symfony/yaml": "^4.4|^5.0|^6.0", "symfony/expression-language": "^4.4|^5.0|^6.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "doctrine/annotations": "^1.12", + "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3" }, "conflict": { diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php index e245ad5f8bd3c..74c6c7e74fb33 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php @@ -97,8 +97,12 @@ public function consumeLoginLink(Request $request): UserInterface throw new InvalidLoginLinkException('User not found.', 0, $exception); } - $hash = $request->get('hash'); - $expires = $request->get('expires'); + if (!$hash = $request->get('hash')) { + throw new InvalidLoginLinkException('Missing "hash" parameter.'); + } + if (!$expires = $request->get('expires')) { + throw new InvalidLoginLinkException('Missing "expires" parameter.'); + } try { $this->signatureHasher->verifySignatureHash($user, $expires, $hash); diff --git a/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php b/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php index 697584d28b6d7..c29bb9a85fbde 100644 --- a/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php @@ -182,6 +182,30 @@ public function testConsumeLoginLinkExceedsMaxUsage() $linker->consumeLoginLink($request); } + public function testConsumeLoginLinkWithMissingHash() + { + $user = new TestLoginLinkHandlerUser('weaverryan', 'ryan@symfonycasts.com', 'pwhash'); + $this->userProvider->createUser($user); + + $this->expectException(InvalidLoginLinkException::class); + $request = Request::create('/login/verify?user=weaverryan&expires=10000'); + + $linker = $this->createLinker(); + $linker->consumeLoginLink($request); + } + + public function testConsumeLoginLinkWithMissingExpiration() + { + $user = new TestLoginLinkHandlerUser('weaverryan', 'ryan@symfonycasts.com', 'pwhash'); + $this->userProvider->createUser($user); + + $this->expectException(InvalidLoginLinkException::class); + $request = Request::create('/login/verify?user=weaverryan&hash=thehash'); + + $linker = $this->createLinker(); + $linker->consumeLoginLink($request); + } + private function createSignatureHash(string $username, int $expires, array $extraFields): string { $fields = [base64_encode($username), $expires]; diff --git a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php index b38a5f6a419ba..962bf6a5b9b5e 100644 --- a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php @@ -126,17 +126,17 @@ protected function getAttributeValue(object $object, string $attribute, string $ $ucfirsted = ucfirst($attribute); $getter = 'get'.$ucfirsted; - if (\is_callable([$object, $getter])) { + if (method_exists($object, $getter) && \is_callable([$object, $getter])) { return $object->$getter(); } $isser = 'is'.$ucfirsted; - if (\is_callable([$object, $isser])) { + if (method_exists($object, $isser) && \is_callable([$object, $isser])) { return $object->$isser(); } $haser = 'has'.$ucfirsted; - if (\is_callable([$object, $haser])) { + if (method_exists($object, $haser) && \is_callable([$object, $haser])) { return $object->$haser(); } @@ -152,7 +152,14 @@ protected function setAttributeValue(object $object, string $attribute, $value, $key = \get_class($object).':'.$setter; if (!isset(self::$setterAccessibleCache[$key])) { - self::$setterAccessibleCache[$key] = \is_callable([$object, $setter]) && !(new \ReflectionMethod($object, $setter))->isStatic(); + try { + // We have to use is_callable() here since method_exists() + // does not "see" protected/private methods + self::$setterAccessibleCache[$key] = \is_callable([$object, $setter]) && !(new \ReflectionMethod($object, $setter))->isStatic(); + } catch (\ReflectionException $e) { + // Method does not exist in the class, probably a magic method + self::$setterAccessibleCache[$key] = false; + } } if (self::$setterAccessibleCache[$key]) { diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php index 6fd430bb47a43..e6f8396fe9d15 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -453,6 +453,22 @@ public function testHasGetterNormalize() ); } + public function testCallMagicMethodDenormalize() + { + $obj = $this->normalizer->denormalize(['active' => true], ObjectWithMagicMethod::class); + $this->assertTrue($obj->isActive()); + } + + public function testCallMagicMethodNormalize() + { + $obj = new ObjectWithMagicMethod(); + + $this->assertSame( + ['active' => true], + $this->normalizer->normalize($obj, 'any') + ); + } + protected function getObjectCollectionWithExpectedArray(): array { return [[ @@ -722,3 +738,18 @@ public function hasFoo() return $this->foo; } } + +class ObjectWithMagicMethod +{ + private $active = true; + + public function isActive() + { + return $this->active; + } + + public function __call($key, $value) + { + throw new \RuntimeException('__call should not be called. Called with: '.$key); + } +} diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index ebd23c3e3dfdf..ce0eeb6042947 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -22,7 +22,7 @@ "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "doctrine/annotations": "^1.12", + "doctrine/annotations": "^1.12|^2", "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", "symfony/cache": "^4.4|^5.0|^6.0", "symfony/config": "^4.4|^5.0|^6.0", @@ -44,7 +44,7 @@ "conflict": { "doctrine/annotations": "<1.12", "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/type-resolver": "<1.4.0|>=1.7.0", "symfony/dependency-injection": "<4.4", "symfony/property-access": "<5.4", "symfony/property-info": "<5.3.13", diff --git a/src/Symfony/Component/String/LazyString.php b/src/Symfony/Component/String/LazyString.php index 3b10595f3e677..9c7a9c58b659b 100644 --- a/src/Symfony/Component/String/LazyString.php +++ b/src/Symfony/Component/String/LazyString.php @@ -148,7 +148,7 @@ private static function getPrettyName(callable $callback): string } elseif ($callback instanceof \Closure) { $r = new \ReflectionFunction($callback); - if (false !== strpos($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) { + if (false !== strpos($r->name, '{closure}') || !$class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { return $r->name; } diff --git a/src/Symfony/Component/Validator/Constraints/IbanValidator.php b/src/Symfony/Component/Validator/Constraints/IbanValidator.php index 6db31e5359dbb..215eb16f174fc 100644 --- a/src/Symfony/Component/Validator/Constraints/IbanValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IbanValidator.php @@ -102,7 +102,7 @@ class IbanValidator extends ConstraintValidator 'MK' => 'MK\d{2}\d{3}[\dA-Z]{10}\d{2}', // Macedonia, Former Yugoslav Republic of 'ML' => 'ML\d{2}[A-Z]{1}\d{23}', // Mali 'MQ' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Martinique - 'MR' => 'MR13\d{5}\d{5}\d{11}\d{2}', // Mauritania + 'MR' => 'MR\d{2}\d{5}\d{5}\d{11}\d{2}', // Mauritania 'MT' => 'MT\d{2}[A-Z]{4}\d{5}[\dA-Z]{18}', // Malta 'MU' => 'MU\d{2}[A-Z]{4}\d{2}\d{2}\d{12}\d{3}[A-Z]{3}', // Mauritius 'MZ' => 'MZ\d{2}\d{21}', // Mozambique @@ -127,7 +127,7 @@ class IbanValidator extends ConstraintValidator 'SN' => 'SN\d{2}[A-Z]{1}\d{23}', // Senegal 'TF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // French Southern Territories 'TL' => 'TL\d{2}\d{3}\d{14}\d{2}', // Timor-Leste - 'TN' => 'TN59\d{2}\d{3}\d{13}\d{2}', // Tunisia + 'TN' => 'TN\d{2}\d{2}\d{3}\d{13}\d{2}', // Tunisia 'TR' => 'TR\d{2}\d{5}[\dA-Z]{1}[\dA-Z]{16}', // Turkey 'UA' => 'UA\d{2}\d{6}[\dA-Z]{19}', // Ukraine 'VA' => 'VA\d{2}\d{3}\d{15}', // Vatican City State diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index 0ce4d0d2a4318..4799c7be301dd 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -41,7 +41,7 @@ "symfony/property-access": "^4.4|^5.0|^6.0", "symfony/property-info": "^5.3|^6.0", "symfony/translation": "^4.4|^5.0|^6.0", - "doctrine/annotations": "^1.13", + "doctrine/annotations": "^1.13|^2", "doctrine/cache": "^1.11|^2.0", "egulias/email-validator": "^2.1.10|^3" }, diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php index 53f4461d0df80..890f531063760 100644 --- a/src/Symfony/Component/VarDumper/Caster/Caster.php +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -47,7 +47,7 @@ public static function castObject(object $obj, string $class, bool $hasDebugInfo if ($hasDebugInfo) { try { $debugInfo = $obj->__debugInfo(); - } catch (\Exception $e) { + } catch (\Throwable $e) { // ignore failing __debugInfo() $hasDebugInfo = false; } diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index 274ee0d98f7da..5c644053ad136 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -197,7 +197,7 @@ public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, arra self::addMap($a, $c, [ 'returnsReference' => 'returnsReference', 'returnType' => 'getReturnType', - 'class' => 'getClosureScopeClass', + 'class' => \PHP_VERSION_ID >= 80111 ? 'getClosureCalledClass' : 'getClosureScopeClass', 'this' => 'getClosureThis', ]); diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php index c39e82cf6adb0..66cd5fbeda660 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/CasterTest.php @@ -22,7 +22,7 @@ class CasterTest extends TestCase { use VarDumperTestTrait; - private $referenceArray = [ + private static $referenceArray = [ 'null' => null, 'empty' => false, 'public' => 'pub', @@ -38,12 +38,12 @@ class CasterTest extends TestCase public function testFilter($filter, $expectedDiff, $listedProperties = null) { if (null === $listedProperties) { - $filteredArray = Caster::filter($this->referenceArray, $filter); + $filteredArray = Caster::filter(self::$referenceArray, $filter); } else { - $filteredArray = Caster::filter($this->referenceArray, $filter, $listedProperties); + $filteredArray = Caster::filter(self::$referenceArray, $filter, $listedProperties); } - $this->assertSame($expectedDiff, array_diff_assoc($this->referenceArray, $filteredArray)); + $this->assertSame($expectedDiff, array_diff_assoc(self::$referenceArray, $filteredArray)); } public function provideFilter() @@ -126,7 +126,7 @@ public function provideFilter() ], [ Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_VERBOSE, - $this->referenceArray, + self::$referenceArray, ['public', "\0*\0protected"], ], [ @@ -175,4 +175,14 @@ public function testAnonymousClass() , $c ); } + + public function testTypeErrorInDebugInfo() + { + $this->assertDumpMatchesFormat('class@anonymous {}', new class() { + public function __debugInfo(): array + { + return ['class' => \get_class(null)]; + } + }); + } } diff --git a/src/Symfony/Component/VarExporter/Internal/Exporter.php b/src/Symfony/Component/VarExporter/Internal/Exporter.php index a034dddb989b4..f4e5746f15e47 100644 --- a/src/Symfony/Component/VarExporter/Internal/Exporter.php +++ b/src/Symfony/Component/VarExporter/Internal/Exporter.php @@ -149,6 +149,7 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount } if (null !== $sleep) { if (!isset($sleep[$n]) || ($i && $c !== $class)) { + unset($arrayValue[$name]); continue; } $sleep[$n] = false; @@ -164,6 +165,9 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount } } } + if (method_exists($class, '__unserialize')) { + $properties = $arrayValue; + } prepare_value: $objectsPool[$value] = [$id = \count($objectsPool)]; diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/__unserialize-but-no-__serialize.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/__unserialize-but-no-__serialize.php new file mode 100644 index 0000000000000..987999b8d2cfa --- /dev/null +++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/__unserialize-but-no-__serialize.php @@ -0,0 +1,15 @@ + 'ccc', + ], + ] +); diff --git a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php index f90737da2e8cf..a4ea1a9221d3c 100644 --- a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php +++ b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php @@ -245,6 +245,8 @@ public function provideExport() yield ['php74-serializable', new Php74Serializable()]; + yield ['__unserialize-but-no-__serialize', new __UnserializeButNo__Serialize()]; + if (\PHP_VERSION_ID < 80100) { return; } @@ -453,3 +455,18 @@ public function unserialize($ser) class ArrayObject extends \ArrayObject { } + +class __UnserializeButNo__Serialize +{ + public $foo; + + public function __construct() + { + $this->foo = 'ccc'; + } + + public function __unserialize(array $data): void + { + $this->foo = $data['foo']; + } +} diff --git a/src/Symfony/Component/Workflow/Dumper/PlantUmlDumper.php b/src/Symfony/Component/Workflow/Dumper/PlantUmlDumper.php index 6e75c3c820048..43595c6f25394 100644 --- a/src/Symfony/Component/Workflow/Dumper/PlantUmlDumper.php +++ b/src/Symfony/Component/Workflow/Dumper/PlantUmlDumper.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Workflow\Dumper; -use InvalidArgumentException; use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Marking; use Symfony\Component\Workflow\Metadata\MetadataStoreInterface; @@ -57,7 +56,7 @@ class PlantUmlDumper implements DumperInterface public function __construct(string $transitionType = null) { if (!\in_array($transitionType, self::TRANSITION_TYPES, true)) { - throw new InvalidArgumentException("Transition type '$transitionType' does not exist."); + throw new \InvalidArgumentException("Transition type '$transitionType' does not exist."); } $this->transitionType = $transitionType; } @@ -209,9 +208,7 @@ private function getState(string $place, Definition $definition, Marking $markin $description = $workflowMetadata->getMetadata('description', $place); if (null !== $description) { - $output .= ' as '.$place. - \PHP_EOL. - $place.' : '.$description; + $output .= \PHP_EOL.$placeEscaped.' : '.$description; } return $output; diff --git a/src/Symfony/Component/Workflow/Tests/Dumper/PlantUmlDumperTest.php b/src/Symfony/Component/Workflow/Tests/Dumper/PlantUmlDumperTest.php index 85c67969b8488..0c750fc750255 100644 --- a/src/Symfony/Component/Workflow/Tests/Dumper/PlantUmlDumperTest.php +++ b/src/Symfony/Component/Workflow/Tests/Dumper/PlantUmlDumperTest.php @@ -12,9 +12,12 @@ namespace Symfony\Component\Workflow\Tests\Dumper; use PHPUnit\Framework\TestCase; +use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Dumper\PlantUmlDumper; use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; use Symfony\Component\Workflow\Tests\WorkflowBuilderTrait; +use Symfony\Component\Workflow\Transition; class PlantUmlDumperTest extends TestCase { @@ -63,6 +66,34 @@ public function provideStateMachineDefinitionWithoutMarking() yield [$this->createComplexStateMachineDefinition(), $marking, 'complex-state-machine-marking', 'SimpleDiagram']; } + public function testDumpWorkflowWithSpacesInTheStateNamesAndDescription() + { + $dumper = new PlantUmlDumper(PlantUmlDumper::WORKFLOW_TRANSITION); + + // The graph looks like: + // + // +---------+ t 1 +----------+ | + // | place a | -----> | place b | | + // +---------+ +----------+ | + $places = ['place a', 'place b']; + + $transitions = []; + $transition = new Transition('t 1', 'place a', 'place b'); + $transitions[] = $transition; + + $placesMetadata = []; + $placesMetadata['place a'] = [ + 'description' => 'My custom place description', + ]; + $inMemoryMetadataStore = new InMemoryMetadataStore([], $placesMetadata); + $definition = new Definition($places, $transitions, null, $inMemoryMetadataStore); + + $dump = $dumper->dump($definition, null, ['title' => 'SimpleDiagram']); + $dump = str_replace(\PHP_EOL, "\n", $dump.\PHP_EOL); + $file = $this->getFixturePath('simple-workflow-with-spaces', PlantUmlDumper::WORKFLOW_TRANSITION); + $this->assertStringEqualsFile($file, $dump); + } + private function getFixturePath($name, $transitionType) { return __DIR__.'/../fixtures/puml/'.$transitionType.'/'.$name.'.puml'; diff --git a/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-marking.puml b/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-marking.puml index 0ea138f83f725..1e8a2ea0f6b86 100644 --- a/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-marking.puml +++ b/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-marking.puml @@ -17,8 +17,8 @@ skinparam agent { } state "a" <> state "b" <> -state "c" <> as c -c : My custom place description +state "c" <> +"c" : My custom place description agent "t1" agent "t2" "a" -[#Purple]-> "t1": "My custom transition label 2" diff --git a/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-nomarking.puml b/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-nomarking.puml index 02e7f396eacb3..b57dc5b1fab43 100644 --- a/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-nomarking.puml +++ b/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-nomarking.puml @@ -17,8 +17,8 @@ skinparam agent { } state "a" <> state "b" -state "c" <> as c -c : My custom place description +state "c" <> +"c" : My custom place description agent "t1" agent "t2" "a" -[#Purple]-> "t1": "My custom transition label 2" diff --git a/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml b/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml new file mode 100644 index 0000000000000..0e20d27198024 --- /dev/null +++ b/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml @@ -0,0 +1,23 @@ +@startuml +allow_mixing +title SimpleDiagram +skinparam titleBorderRoundCorner 15 +skinparam titleBorderThickness 2 +skinparam state { + BackgroundColor<> #87b741 + BackgroundColor<> #3887C6 + BorderColor #3887C6 + BorderColor<> Black + FontColor<> White +} +skinparam agent { + BackgroundColor #ffffff + BorderColor #3887C6 +} +state "place a" <> +"place a" : My custom place description +state "place b" +agent "t 1" +"place a" --> "t 1" +"t 1" --> "place b" +@enduml diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php index 711840c109ec0..679d533cb77aa 100644 --- a/src/Symfony/Component/Yaml/Dumper.php +++ b/src/Symfony/Component/Yaml/Dumper.php @@ -58,6 +58,8 @@ public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) { $output .= $prefix.Inline::dump($input, $flags); + } elseif ($input instanceof TaggedValue) { + $output .= $this->dumpTaggedValue($input, $inline, $indent, $flags, $prefix); } else { $dumpAsMap = Inline::isHash($input); @@ -137,4 +139,28 @@ public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): return $output; } + + private function dumpTaggedValue(TaggedValue $value, int $inline, int $indent, int $flags, string $prefix): string + { + $output = sprintf('%s!%s', $prefix ? $prefix.' ' : '', $value->getTag()); + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : ''; + $output .= sprintf(' |%s', $blockIndentationIndicator); + + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + + return $output; + } + + if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { + return $output.' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } + + return $output."\n".$this->dump($value->getValue(), $inline - 1, $indent, $flags); + } } diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index 4b9c74c2609de..16551f187136c 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -444,8 +444,11 @@ public function testDumpingTaggedValueTopLevelAssoc() { $data = new TaggedValue('user', ['name' => 'jane']); - // @todo Fix the dumper, the output should not be ''. - $expected = ''; + $expected = <<<'YAML' +!user +name: jane + +YAML; $yaml = $this->dumper->dump($data, 2); $this->assertSame($expected, $yaml); } @@ -454,9 +457,7 @@ public function testDumpingTaggedValueTopLevelMultiLine() { $data = new TaggedValue('text', "a\nb\n"); - // @todo Fix the dumper, the output should not be ''. - $expected = ''; - $this->assertSame($expected, $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + $this->assertSame("!text |\n a\n b\n ", $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); } public function testDumpingTaggedValueSpecialCharsInTag() diff --git a/src/Symfony/Contracts/Translation/Test/TranslatorTest.php b/src/Symfony/Contracts/Translation/Test/TranslatorTest.php index 98f0267b7512a..68587f12d9817 100644 --- a/src/Symfony/Contracts/Translation/Test/TranslatorTest.php +++ b/src/Symfony/Contracts/Translation/Test/TranslatorTest.php @@ -140,7 +140,7 @@ public function getTransChoiceTests() } /** - * @dataProvider getInternal + * @dataProvider getInterval */ public function testInterval($expected, $number, $interval) { @@ -149,7 +149,7 @@ public function testInterval($expected, $number, $interval) $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number])); } - public function getInternal() + public function getInterval() { return [ ['foo', 3, '{1,2, 3 ,4}'],