diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 58caff2209f37..00a686580d01f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,8 +14,9 @@ This will help reviewers and should be a good start for the documentation. Additionally (see https://symfony.com/releases): - Always add tests and ensure they pass. - 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.) + (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. - 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/CHANGELOG-4.4.md b/CHANGELOG-4.4.md index 9e6451685ab5c..b3498d37227df 100644 --- a/CHANGELOG-4.4.md +++ b/CHANGELOG-4.4.md @@ -7,6 +7,24 @@ in 4.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.4.0...v4.4.1 +* 4.4.45 (2022-08-26) + + * bug #47358 Fix broken request stack state if throwable is thrown. (Warxcell) + * bug #47304 [Serializer] Fix caching context-aware encoders/decoders in ChainEncoder/ChainDecoder (Guite) + * bug #47329 Email image parts: regex for single closing quote (rr-it) + * bug #47200 [Form] ignore missing keys when mapping DateTime objects to uninitialized arrays (xabbuh) + * bug #47189 [Validator] Add additional hint when `egulias/email-validator` needs to be installed (mpdude) + * bug #47195 [FrameworkBundle] fix writes to static $kernel property (xabbuh) + * bug #47175 [DowCrawler] Fix locale-sensitivity of whitespace normalization (nicolas-grekas) + * bug #47171 [TwigBridge] suggest to install the Twig bundle when the required component is already installed (xabbuh) + * bug #47161 [Mailer] Fix logic (fabpot) + * bug #47157 [Messenger] Fix Doctrine transport on MySQL (nicolas-grekas) + * bug #46190 [Translation] Fix translator overlapse (Xavier RENAUDIN) + * bug #47142 [Mailer] Fix error message in case of an STMP error (fabpot) + * bug #47145 [HttpClient] Fix shared connections not being freed on PHP < 8 (nicolas-grekas) + * bug #47143 [HttpClient] Fix memory leak when using StreamWrapper (nicolas-grekas) + * bug #47130 [HttpFoundation] Fix invalid ID not regenerated with native PHP file sessions (BrokenSourceCode) + * 4.4.44 (2022-07-29) * bug #47069 [Security] Allow redirect after login to absolute URLs (Tim Ward) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 99d8c121d9fce..167d559dc9e9f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -24,8 +24,8 @@ The Symfony Connect username in parenthesis allows to get more information - Yonel Ceruto (yonelceruto) - Tobias Nyholm (tobias) - Oskar Stark (oskarstark) - - Ryan Weaver (weaverryan) - Javier Eguiluz (javier.eguiluz) + - Ryan Weaver (weaverryan) - Johannes S (johannes) - Jakub Zalas (jakubzalas) - Kris Wallsmith (kriswallsmith) @@ -43,9 +43,9 @@ The Symfony Connect username in parenthesis allows to get more information - Martin Hasoň (hason) - Jérôme Tamarelle (gromnan) - Jeremy Mikola (jmikola) + - Kevin Bond (kbond) - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) - - Kevin Bond (kbond) - Igor Wiedler - Valentin Udaltsov (vudaltsov) - Vasilij Duško (staff) @@ -84,8 +84,8 @@ The Symfony Connect username in parenthesis allows to get more information - Sarah Khalil (saro0h) - Konstantin Kudryashov (everzet) - Vincent Langlet (deviling) - - Bilal Amarni (bamarni) - Tomas Norkūnas (norkunas) + - Bilal Amarni (bamarni) - Eriksen Costa - Florin Patan (florinpatan) - Peter Rehm (rpet) @@ -95,14 +95,14 @@ The Symfony Connect username in parenthesis allows to get more information - Julien Falque (julienfalque) - Massimiliano Arione (garak) - Douglas Greenshields (shieldo) - - Christian Raue - David Buchmann (dbu) + - Christian Raue + - Jáchym Toušek (enumag) - Graham Campbell (graham) - Michel Weimerskirch (mweimerskirch) - Eric Clemmons (ericclemmons) - Issei Murasawa (issei_m) - Fran Moreno (franmomu) - - Jáchym Toušek (enumag) - Malte Schlüter (maltemaltesich) - Mathias Arlaud (mtarld) - Vasilij Dusko @@ -114,8 +114,8 @@ The Symfony Connect username in parenthesis allows to get more information - Dariusz Górecki (canni) - Maxime Helias (maxhelias) - Ener-Getick - - Sebastiaan Stok (sstok) - Ruud Kamphuis (ruudk) + - Sebastiaan Stok (sstok) - Jérôme Vasseur (jvasseur) - Ion Bazan (ionbazan) - Lee McDermott @@ -134,6 +134,7 @@ The Symfony Connect username in parenthesis allows to get more information - Konstantin.Myakshin - Rokas Mikalkėnas (rokasm) - Arman Hosseini (arman) + - Antoine Lamirault - Arnaud Le Blanc (arnaud-lb) - Maxime STEINHAUSSER - Peter Kokot (maastermedia) @@ -146,10 +147,10 @@ The Symfony Connect username in parenthesis allows to get more information - YaFou - Gary PEGEOT (gary-p) - Chris Wilkinson (thewilkybarkid) + - Mathieu Lechat (mat_the_cat) - Brice BERNARD (brikou) - Roman Martinuk (a2a4) - Gregor Harlan (gharlan) - - Antoine Lamirault - Baptiste Clavié (talus) - Adrien Brault (adrienbrault) - Michal Piotrowski @@ -161,7 +162,6 @@ The Symfony Connect username in parenthesis allows to get more information - Włodzimierz Gajda (gajdaw) - Christian Scheb - Guillaume (guill) - - Mathieu Lechat (mat_the_cat) - Tugdual Saunier (tucksaun) - Jacob Dreesen (jdreesen) - Joel Wurtz (brouznouf) @@ -173,6 +173,7 @@ The Symfony Connect username in parenthesis allows to get more information - Javier Spagnoletti (phansys) - excelwebzone - Jérôme Parmentier (lctrs) + - Jeroen Spee (jeroens) - HeahDude - Richard van Laak (rvanlaak) - Paráda József (paradajozsef) @@ -180,7 +181,6 @@ The Symfony Connect username in parenthesis allows to get more information - Alexander Schwenn (xelaris) - Fabien Pennequin (fabienpennequin) - Gordon Franke (gimler) - - Jeroen Spee (jeroens) - Christopher Hertel (chertel) - Gabriel Caruso - Anthony GRASSIOT (antograssiot) @@ -207,6 +207,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jhonny Lidfors (jhonne) - Martin Hujer (martinhujer) - Wouter J + - Guilliam Xavier - Timo Bakx (timobakx) - Juti Noppornpitak (shiroyuki) - Joe Bennett (kralos) @@ -215,6 +216,7 @@ The Symfony Connect username in parenthesis allows to get more information - Colin O'Dell (colinodell) - Sebastian Hörl (blogsh) - Ben Davies (bendavies) + - Andreas Schempp (aschempp) - François-Xavier de Guillebon (de-gui_f) - Daniel Gomes (danielcsgomes) - Michael Käfer (michael_kaefer) @@ -223,11 +225,10 @@ The Symfony Connect username in parenthesis allows to get more information - Arnaud Kleinpeter (nanocom) - Guilherme Blanco (guilhermeblanco) - Chi-teck - - Guilliam Xavier + - Antonio Pauletich (x-coder264) - Nate Wiebe (natewiebe13) - Michael Voříšek - SpacePossum - - Andreas Schempp (aschempp) - Pablo Godel (pgodel) - Romaric Drigon (romaricdrigon) - Andréia Bohner (andreia) @@ -239,7 +240,6 @@ The Symfony Connect username in parenthesis allows to get more information - jwdeitch - Jurica Vlahoviček (vjurica) - David Prévot - - Antonio Pauletich (x-coder264) - Vincent Touzet (vincenttouzet) - Fabien Bourigault (fbourigault) - Farhad Safarov (safarov) @@ -373,9 +373,11 @@ The Symfony Connect username in parenthesis allows to get more information - Emanuele Panzeri (thepanz) - Matthew Smeets - François Zaninotto (fzaninotto) + - Alexis Lefebvre - Dustin Whittle (dustinwhittle) - jeff - John Kary (johnkary) + - Bob van de Vijver (bobvandevijver) - smoench - Michele Orselli (orso) - Sven Paulus (subsven) @@ -397,8 +399,10 @@ The Symfony Connect username in parenthesis allows to get more information - Fabien S (bafs) - Victor Bocharsky (bocharsky_bw) - Sébastien Alfaiate (seb33300) + - Jan Sorgalla (jsor) - henrikbjorn - Alex Bowers + - Simon Podlipsky (simpod) - Marcel Beerta (mazen) - Phil Taylor (prazgod) - flack (flack) @@ -426,7 +430,6 @@ The Symfony Connect username in parenthesis allows to get more information - Iker Ibarguren (ikerib) - Manuel Reinhard (sprain) - Johann Pardanaud - - Alexis Lefebvre - Indra Gunawan (indragunawan) - Tim Goudriaan (codedmonkey) - Harm van Tilborg (hvt) @@ -444,7 +447,6 @@ The Symfony Connect username in parenthesis allows to get more information - Xavier Montaña Carreras (xmontana) - Tarmo Leppänen (tarlepp) - AnneKir - - Bob van de Vijver (bobvandevijver) - Tobias Weichart - Miro Michalicka - M. Vondano @@ -473,12 +475,10 @@ The Symfony Connect username in parenthesis allows to get more information - Félix Labrecque (woodspire) - GordonsLondon - Roman Anasal - - Jan Sorgalla (jsor) - Piotr Kugla (piku235) - Quynh Xuan Nguyen (seriquynh) - Ray - Philipp Cordes (corphi) - - Simon Podlipsky (simpod) - Chekote - bhavin (bhavin4u) - Pavel Popov (metaer) @@ -495,12 +495,14 @@ The Symfony Connect username in parenthesis allows to get more information - Thomas Schulz (king2500) - Benjamin Morel - Bernd Stellwag + - Romain Monteil (ker0x) - Frank de Jonge - Chris Tanaskoski - julien57 - Loïc Frémont (loic425) - Ben Ramsey (ramsey) - Matthieu Auger (matthieuauger) + - Kévin THERAGE (kevin_therage) - Josip Kruslin (jkruslin) - Giorgio Premi - renanbr @@ -526,6 +528,7 @@ The Symfony Connect username in parenthesis allows to get more information - Michael Holm (hollo) - Giso Stallenberg (gisostallenberg) - Blanchon Vincent (blanchonvincent) + - William Arslett (warslett) - Christian Schmidt - Gonzalo Vilaseca (gonzalovilaseca) - Vadim Borodavko (javer) @@ -575,7 +578,6 @@ The Symfony Connect username in parenthesis allows to get more information - Marko Kaznovac (kaznovac) - Emanuele Gaspari (inmarelibero) - Dariusz Rumiński - - Romain Monteil (ker0x) - Terje Bråten - Gennadi Janzen - James Hemery @@ -599,7 +601,6 @@ The Symfony Connect username in parenthesis allows to get more information - Fractal Zombie - Gunnstein Lye (glye) - Thomas Talbot (ioni) - - Kévin THERAGE (kevin_therage) - Noémi Salaün (noemi-salaun) - Michel Hunziker - Krystian Marcisz (simivar) @@ -623,6 +624,7 @@ The Symfony Connect username in parenthesis allows to get more information - Thomas Royer (cydonia7) - Gildas Quéméner (gquemener) - Nicolas LEFEVRE (nicoweb) + - Asmir Mustafic (goetas) - Martins Sipenko - Guilherme Augusto Henschel - Mardari Dorel (dorumd) @@ -662,7 +664,6 @@ The Symfony Connect username in parenthesis allows to get more information - “Filip - Simon Watiau (simonwatiau) - Ruben Jacobs (rubenj) - - William Arslett - Arkadius Stefanski (arkadius) - Jérémy M (th3mouk) - Terje Bråten @@ -700,6 +701,7 @@ The Symfony Connect username in parenthesis allows to get more information - Philipp Kräutli (pkraeutli) - Carl Casbolt (carlcasbolt) - battye + - BrokenSourceCode - Grzegorz (Greg) Zdanowski (kiler129) - Kirill chEbba Chebunin - kylekatarnls (kylekatarnls) @@ -850,7 +852,6 @@ The Symfony Connect username in parenthesis allows to get more information - Arturs Vonda - Xavier Briand (xavierbriand) - Daniel Badura - - Asmir Mustafic (goetas) - vagrant - Asier Illarramendi (doup) - AKeeman (akeeman) @@ -861,6 +862,7 @@ The Symfony Connect username in parenthesis allows to get more information - Chris Sedlmayr (catchamonkey) - Kamil Kokot (pamil) - Seb Koelen + - FORT Pierre-Louis (plfort) - Christoph Mewes (xrstf) - Vitaliy Tverdokhlib (vitaliytv) - Ariel Ferrandini (aferrandini) @@ -900,7 +902,6 @@ The Symfony Connect username in parenthesis allows to get more information - Pablo Díez (pablodip) - Damien Fa - Kevin McBride - - BrokenSourceCode - Sergio Santoro - Philipp Rieber (bicpi) - Dennis Væversted (srnzitcom) @@ -988,6 +989,7 @@ The Symfony Connect username in parenthesis allows to get more information - Ziumin - Matthias Schmidt - Lenar Lõhmus + - Samaël Villette (samadu61) - Zach Badgett (zachbadgett) - Loïc Faugeron - Aurélien Fredouelle @@ -1049,6 +1051,7 @@ The Symfony Connect username in parenthesis allows to get more information - Simon DELICATA - Thibault Buathier (gwemox) - vitaliytv + - Andreas Hennings - Arnaud Frézet - Nicolas Martin (cocorambo) - luffy1727 @@ -1136,6 +1139,7 @@ The Symfony Connect username in parenthesis allows to get more information - David Fuhr - Evgeny Anisiforov - TristanPouliquen + - Gwendolen Lynch - mwos - Aurimas Niekis (gcds) - Volker Killesreiter (ol0lll) @@ -1189,7 +1193,6 @@ The Symfony Connect username in parenthesis allows to get more information - Chris Heng (gigablah) - Oleksii Svitiashchuk - Tristan Bessoussa (sf_tristanb) - - FORT Pierre-Louis (plfort) - Richard Bradley - Nathanaël Martel (nathanaelmartel) - Nicolas Jourdan (nicolasjc) @@ -1259,6 +1262,7 @@ The Symfony Connect username in parenthesis allows to get more information - Aleksandr Dankovtsev - Maciej Zgadzaj - David Legatt (dlegatt) + - Maarten de Boer (mdeboer) - Cameron Porter - Hossein Bukhamsin - Oliver Hoff @@ -1463,6 +1467,7 @@ The Symfony Connect username in parenthesis allows to get more information - bill moll - PaoRuby - Bizley + - Edvin Hultberg - Dominik Piekarski (dompie) - Rares Sebastian Moldovan (raresmldvn) - Felds Liscia (felds) @@ -1773,7 +1778,6 @@ The Symfony Connect username in parenthesis allows to get more information - Pierre-Olivier Vares (povares) - Ronny López (ronnylt) - Julius (sakalys) - - Samaël Villette (samadu61) - abdul malik ikhsan (samsonasik) - Dmitry (staratel) - Tito Miguel Costa (titomiguelcosta) @@ -1956,6 +1960,7 @@ 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) @@ -2053,6 +2058,7 @@ The Symfony Connect username in parenthesis allows to get more information - Pablo Borowicz - Máximo Cuadros (mcuadros) - Lukas Mencl + - EXT - THERAGE Kevin - tamirvs - gauss - julien.galenski @@ -2067,6 +2073,7 @@ The Symfony Connect username in parenthesis allows to get more information - Goran Juric - Laurent G. (laurentg) - Nicolas Macherey + - Asil Barkin Elik (asilelik) - Bhujagendra Ishaya - Guido Donnari - Mert Simsek (mrtsmsk0) @@ -2097,6 +2104,7 @@ The Symfony Connect username in parenthesis allows to get more information - Dan Finnie - Ken Marfilla (marfillaster) - Max Grigorian (maxakawizard) + - allison guilhem - benatespina (benatespina) - Denis Kop - Jean-Guilhem Rouel (jean-gui) @@ -2130,6 +2138,7 @@ The Symfony Connect username in parenthesis allows to get more information - Tadas Gliaubicas (tadcka) - Thanos Polymeneas (thanos) - Benoit Garret + - HellFirePvP - Maximilian Ruta (deltachaos) - Jakub Sacha - Olaf Klischat @@ -2194,6 +2203,7 @@ The Symfony Connect username in parenthesis allows to get more information - Philipp Kretzschmar - Ilya Vertakov - Brooks Boyd + - Axel Venet - Roger Webb - Dmitriy Simushev - Pawel Smolinski @@ -2201,7 +2211,6 @@ The Symfony Connect username in parenthesis allows to get more information - Oxan van Leeuwen - pkowalczyk - Soner Sayakci - - Andreas Hennings - Max Voloshin (maxvoloshin) - Nicolas Fabre (nfabre) - Raul Rodriguez (raul782) @@ -2238,6 +2247,7 @@ The Symfony Connect username in parenthesis allows to get more information - Dmitri Petmanson - heccjj - Alexandre Melard + - AlbinoDrought - Jay Klehr - Sergey Yuferev - Tobias Stöckler @@ -2247,6 +2257,7 @@ The Symfony Connect username in parenthesis allows to get more information - cilefen (cilefen) - Mo Di (modi) - Pablo Schläpfer + - Xavier RENAUDIN - Christian Wahler (christian) - Jelte Steijaert (jelte) - David Négrier (moufmouf) @@ -2268,6 +2279,7 @@ The Symfony Connect username in parenthesis allows to get more information - Malaney J. Hill - Patryk Kozłowski - Alexandre Pavy + - Tim Ward - Christian Flach (cmfcmf) - Lars Ambrosius Wallenborn (larsborn) - Oriol Mangas Abellan (oriolman) @@ -2282,6 +2294,7 @@ The Symfony Connect username in parenthesis allows to get more information - Mihai Nica (redecs) - Andrei Igna - azine + - Wojciech Zimoń - Pierre Tachoire - Dawid Sajdak - Ludek Stepan @@ -2363,6 +2376,7 @@ The Symfony Connect username in parenthesis allows to get more information - Daniel Kay (danielkay-cp) - Matt Daum (daum) - Alberto Pirovano (geezmo) + - Pascal Woerde (pascalwoerde) - Pete Mitchell (peterjmit) - Tom Corrigan (tomcorrigan) - Luis Galeas @@ -2479,6 +2493,7 @@ The Symfony Connect username in parenthesis allows to get more information - Keith Maika - Mephistofeles - Hoffmann András + - Cédric Anne - LubenZA - Flavian Sierk - Michael Bessolov @@ -2508,7 +2523,6 @@ The Symfony Connect username in parenthesis allows to get more information - Alex Teterin (errogaht) - Gunnar Lium (gunnarlium) - Malte Wunsch (maltewunsch) - - Maarten de Boer (mdeboer) - Tiago Garcia (tiagojsag) - Artiom - Jakub Simon @@ -2635,6 +2649,7 @@ The Symfony Connect username in parenthesis allows to get more information - Andy Stanberry - Felix Marezki - Normunds + - Walter Doekes - Thomas Rothe - Troy Crawford - nietonfir @@ -2952,7 +2967,6 @@ The Symfony Connect username in parenthesis allows to get more information - temperatur - Paul Andrieux - Cas - - Gwendolen Lynch - ghazy ben ahmed - Karolis - Myke79 @@ -3051,6 +3065,7 @@ The Symfony Connect username in parenthesis allows to get more information - Shude - Ondřej Führer - Sema + - Ayke Halder - Thorsten Hallwas - Brian Freytag - Alex Nostadt @@ -3062,7 +3077,6 @@ The Symfony Connect username in parenthesis allows to get more information - Yuriy Potemkin - Emilie Lorenzo - enomotodev - - Edvin Hultberg - Vincent - Benjamin Long - Ben Miller diff --git a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php index 16381fddac6c8..43aff010e3c48 100644 --- a/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php +++ b/src/Symfony/Bridge/Twig/UndefinedCallableHandler.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Twig; +use Composer\InstalledVersions; use Symfony\Bundle\FullStack; use Twig\Error\SyntaxError; @@ -93,6 +94,12 @@ private static function onUndefined(string $name, string $type, string $componen throw new SyntaxError(sprintf('Did you forget to %s? Unknown %s "%s".', self::FULL_STACK_ENABLE[$component], $type, $name)); } - throw new SyntaxError(sprintf('Did you forget to run "composer require symfony/%s"? Unknown %s "%s".', $component, $type, $name)); + $missingPackage = 'symfony/'.$component; + + if (class_exists(InstalledVersions::class) && InstalledVersions::isInstalled($missingPackage)) { + $missingPackage = 'symfony/twig-bundle'; + } + + throw new SyntaxError(sprintf('Did you forget to run "composer require %s"? Unknown %s "%s".', $missingPackage, $type, $name)); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php index 33a214ea01aa5..50b51f90734c1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php @@ -53,11 +53,6 @@ protected function configure() Before running this command, the cache must be empty. -This command does not generate the classes cache (as when executing this -command, too many classes that should be part of the cache are already loaded -in memory). Use curl or any other similar tool to warm up -the classes cache if you want. - EOF ) ; diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php index 1ec6548be2738..5df1cc87df7af 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -77,7 +77,7 @@ protected static function bootKernel(array $options = []) $kernel = static::createKernel($options); $kernel->boot(); - self::$kernel = $kernel; + static::$kernel = $kernel; static::$booted = true; $container = static::$kernel->getContainer(); diff --git a/src/Symfony/Component/Console/Descriptor/ApplicationDescription.php b/src/Symfony/Component/Console/Descriptor/ApplicationDescription.php index 3970b90007369..91b18460582fb 100644 --- a/src/Symfony/Component/Console/Descriptor/ApplicationDescription.php +++ b/src/Symfony/Component/Console/Descriptor/ApplicationDescription.php @@ -131,7 +131,7 @@ private function sortCommands(array $commands): array } if ($namespacedCommands) { - ksort($namespacedCommands); + ksort($namespacedCommands, \SORT_STRING); foreach ($namespacedCommands as $key => $commandsSet) { ksort($commandsSet); $sortedCommands[$key] = $commandsSet; diff --git a/src/Symfony/Component/Console/Tests/Descriptor/ApplicationDescriptionTest.php b/src/Symfony/Component/Console/Tests/Descriptor/ApplicationDescriptionTest.php index 33d5c3840f3e3..f1408d087d5e7 100644 --- a/src/Symfony/Component/Console/Tests/Descriptor/ApplicationDescriptionTest.php +++ b/src/Symfony/Component/Console/Tests/Descriptor/ApplicationDescriptionTest.php @@ -36,7 +36,7 @@ public function getNamespacesProvider() return [ [['_global'], ['foobar']], [['a', 'b'], ['b:foo', 'a:foo', 'b:bar']], - [['_global', 'b', 'z', 22, 33], ['z:foo', '1', '33:foo', 'b:foo', '22:foo:bar']], + [['_global', 22, 33, 'b', 'z'], ['z:foo', '1', '33:foo', 'b:foo', '22:foo:bar']], ]; } } diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 4f89eec75a74b..ec8e023ec1d05 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -620,7 +620,7 @@ public function text(/* string $default = null, bool $normalizeWhitespace = true $text = $this->getNode(0)->nodeValue; if (\func_num_args() <= 1) { - if (trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $text)) !== $text) { + if (trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $text), " \n\r\t\x0C") !== $text) { @trigger_error(sprintf('"%s()" will normalize whitespaces by default in Symfony 5.0, set the second "$normalizeWhitespace" argument to false to retrieve the non-normalized version of the text.', __METHOD__), \E_USER_DEPRECATED); } @@ -628,7 +628,7 @@ public function text(/* string $default = null, bool $normalizeWhitespace = true } if (\func_num_args() > 1 && func_get_arg(1)) { - return trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $text)); + return trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $text), " \n\r\t\x0C"); } return $text; diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php index 2a78e03f89715..edd58b83096ec 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php @@ -14,6 +14,7 @@ use Symfony\Component\Form\DataMapperInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\PropertyAccess\Exception\AccessException; +use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -96,6 +97,10 @@ private function getPropertyValue($data, $propertyPath) try { return $this->propertyAccessor->getValue($data, $propertyPath); } catch (AccessException $e) { + if (\is_array($data) && $e instanceof NoSuchIndexException) { + return null; + } + if (!$e instanceof UninitializedPropertyException // For versions without UninitializedPropertyException check the exception message && (class_exists(UninitializedPropertyException::class) || !str_contains($e->getMessage(), 'You should initialize it')) diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index fcfd8fe9e520f..5f6cc60654d62 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -15,6 +15,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Form\Exception\AlreadySubmittedException; use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; +use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; @@ -36,6 +37,7 @@ use Symfony\Component\Form\Tests\Fixtures\Map; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\PropertyAccess\PropertyAccess; class CompoundFormTest extends TestCase { @@ -1076,6 +1078,30 @@ public function testFileUpload() $this->assertNull($this->form->get('bar')->getData()); } + public function testMapDateTimeObjectsWithEmptyArrayData() + { + $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder() + ->enableExceptionOnInvalidIndex() + ->getPropertyAccessor(); + $form = $this->factory->createBuilder() + ->setDataMapper(new PropertyPathMapper($propertyAccessor)) + ->add('date', DateType::class, [ + 'auto_initialize' => false, + 'format' => 'dd/MM/yyyy', + 'html5' => false, + 'model_timezone' => 'UTC', + 'view_timezone' => 'UTC', + 'widget' => 'single_text', + ]) + ->getForm(); + + $form->submit([ + 'date' => '04/08/2022', + ]); + + $this->assertEquals(['date' => new \DateTime('2022-08-04', new \DateTimeZone('UTC'))], $form->getData()); + } + private function createForm(string $name = 'name', bool $compound = true): FormInterface { $builder = $this->getBuilder($name); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php index 76d936d9789a6..5d729eff971ec 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataMapper/PropertyPathMapperTest.php @@ -15,8 +15,10 @@ use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; +use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Form; use Symfony\Component\Form\FormConfigBuilder; +use Symfony\Component\Form\FormFactoryBuilder; use Symfony\Component\Form\Tests\Fixtures\TypehintedPropertiesCar; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -362,6 +364,30 @@ public function provideDate() [new \DateTimeImmutable()], ]; } + + public function testMapFormsToDataMapsDateTimeInstanceToArrayIfNotSetBefore() + { + $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder() + ->enableExceptionOnInvalidIndex() + ->getPropertyAccessor(); + $form = (new FormFactoryBuilder())->getFormFactory()->createBuilder() + ->setDataMapper(new PropertyPathMapper($propertyAccessor)) + ->add('date', DateType::class, [ + 'auto_initialize' => false, + 'format' => 'dd/MM/yyyy', + 'html5' => false, + 'model_timezone' => 'UTC', + 'view_timezone' => 'UTC', + 'widget' => 'single_text', + ]) + ->getForm(); + + $form->submit([ + 'date' => '04/08/2022', + ]); + + $this->assertEquals(['date' => new \DateTime('2022-08-04', new \DateTimeZone('UTC'))], $form->getData()); + } } class SubmittedForm extends Form diff --git a/src/Symfony/Component/HttpClient/Internal/CurlClientState.php b/src/Symfony/Component/HttpClient/Internal/CurlClientState.php index 5821f67a6f700..904cc47b90669 100644 --- a/src/Symfony/Component/HttpClient/Internal/CurlClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/CurlClientState.php @@ -96,7 +96,7 @@ public function reset() curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS); curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION); - if (\defined('CURL_LOCK_DATA_CONNECT')) { + if (\defined('CURL_LOCK_DATA_CONNECT') && \PHP_VERSION_ID >= 80000) { curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_CONNECT); } } diff --git a/src/Symfony/Component/HttpClient/Response/StreamWrapper.php b/src/Symfony/Component/HttpClient/Response/StreamWrapper.php index 644f2ee190f57..6bb31687b949c 100644 --- a/src/Symfony/Component/HttpClient/Response/StreamWrapper.php +++ b/src/Symfony/Component/HttpClient/Response/StreamWrapper.php @@ -53,20 +53,18 @@ public static function createResource(ResponseInterface $response, HttpClientInt throw new \InvalidArgumentException(sprintf('Providing a client to "%s()" is required when the response doesn\'t have any "stream()" method.', __CLASS__)); } - if (false === stream_wrapper_register('symfony', __CLASS__)) { + static $registered = false; + + if (!$registered = $registered || stream_wrapper_register(strtr(__CLASS__, '\\', '-'), __CLASS__)) { throw new \RuntimeException(error_get_last()['message'] ?? 'Registering the "symfony" stream wrapper failed.'); } - try { - $context = [ - 'client' => $client ?? $response, - 'response' => $response, - ]; - - return fopen('symfony://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context])) ?: null; - } finally { - stream_wrapper_unregister('symfony'); - } + $context = [ + 'client' => $client ?? $response, + 'response' => $response, + ]; + + return fopen(strtr(__CLASS__, '\\', '-').'://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context])); } public function getResponse(): ResponseInterface diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php index 627bcfa1dfa84..7e5b5c019cea3 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php @@ -30,6 +30,16 @@ public function __construct(\SessionHandlerInterface $handler) $this->handler = $handler; } + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @internal + */ + public function isWrapper(): bool + { + return $this->handler instanceof \SessionHandler; + } + /** * @return bool */ diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php index 6539acf989387..0defa4a7ab19a 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; + /** * @author Drak */ @@ -22,7 +24,7 @@ public function __construct(\SessionHandlerInterface $handler) { $this->handler = $handler; $this->wrapper = $handler instanceof \SessionHandler; - $this->saveHandlerName = $this->wrapper ? \ini_get('session.save_handler') : 'user'; + $this->saveHandlerName = $this->wrapper || ($handler instanceof StrictSessionHandler && $handler->isWrapper()) ? \ini_get('session.save_handler') : 'user'; } /** diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_max_age.expected b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_max_age.expected index 6870a27728bbe..c4b31d4f3e7af 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_max_age.expected +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_max_age.expected @@ -6,6 +6,6 @@ Array [0] => Content-Type: text/plain; charset=utf-8 [1] => Cache-Control: no-cache, private [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT - [3] => Set-Cookie: foo=bar; expires=Sat, 01-Jan-10000 02:46:40 GMT; Max-Age=%d; path=/ + [3] => Set-Cookie: foo=bar; expires=Sat, 01 Jan 10000 02:46:40 GMT; Max-Age=%d; path=/ ) shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php index 471455d708753..aa24291eda5dc 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php @@ -48,6 +48,7 @@ public function testCookie($fixture) } $result = file_get_contents(sprintf('http://localhost:8054/%s.php', $fixture)); + $result = preg_replace_callback('/expires=[^;]++/', function ($m) { return str_replace('-', ' ', $m[0]); }, $result); $this->assertStringMatchesFormatFile(__DIR__.sprintf('/Fixtures/response-functional/%s.expected', $fixture), $result); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php index f6417720d27aa..aca2bfd882b20 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php @@ -46,6 +46,7 @@ public function testSession($fixture) $context = ['http' => ['header' => "Cookie: sid=123abc\r\n"]]; $context = stream_context_create($context); $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context); + $result = preg_replace_callback('/expires=[^;]++/', function ($m) { return str_replace('-', ' ', $m[0]); }, $result); $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected index 8203714740752..06a118888aba9 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected @@ -12,6 +12,6 @@ Array ( [0] => Content-Type: text/plain; charset=utf-8 [1] => Cache-Control: max-age=10800, private, must-revalidate - [2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly + [2] => Set-Cookie: sid=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly ) shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.expected index 05a5d5d0b090f..549c6847f11da 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.expected +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.expected @@ -16,6 +16,6 @@ Array ( [0] => Content-Type: text/plain; charset=utf-8 [1] => Cache-Control: max-age=0, private, must-revalidate - [2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly + [2] => Set-Cookie: sid=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly ) shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected index 63078228df139..ac8ec061f0310 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected @@ -20,6 +20,6 @@ Array [0] => Content-Type: text/plain; charset=utf-8 [1] => Cache-Control: max-age=10800, private, must-revalidate [2] => Set-Cookie: abc=def - [3] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly + [3] => Set-Cookie: sid=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly ) shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php index 972a2745132e1..a4f45fec68708 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; /** @@ -159,6 +161,23 @@ public function testUpdateTimestamp() $this->proxy->updateTimestamp('id', 'data'); } + + /** + * @dataProvider provideNativeSessionStorageHandler + */ + public function testNativeSessionStorageSaveHandlerName($handler) + { + $this->assertSame('files', (new NativeSessionStorage([], $handler))->getSaveHandler()->getSaveHandlerName()); + } + + public function provideNativeSessionStorageHandler() + { + return [ + [new \SessionHandler()], + [new StrictSessionHandler(new \SessionHandler())], + [new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()))], + ]; + } } abstract class TestSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 0ed82d777b1c4..d53b80665b467 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -76,6 +76,7 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ { $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); + $this->requestStack->push($request); try { return $this->handleRaw($request, $type); } catch (\Exception $e) { @@ -89,6 +90,8 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ } return $this->handleThrowable($e, $request, $type); + } finally { + $this->requestStack->pop(); } } @@ -127,8 +130,6 @@ public function terminateWithException(\Throwable $exception, Request $request = */ private function handleRaw(Request $request, int $type = self::MASTER_REQUEST): Response { - $this->requestStack->push($request); - // request $event = new RequestEvent($this, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::REQUEST); @@ -205,7 +206,6 @@ private function filterResponse(Response $response, Request $request, int $type) private function finishRequest(Request $request, int $type) { $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); - $this->requestStack->pop(); } /** diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 978324f29fb6a..adb9a94727c5e 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,11 +76,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - public const VERSION = '4.4.44'; - public const VERSION_ID = 40444; + public const VERSION = '4.4.45'; + public const VERSION_ID = 40445; public const MAJOR_VERSION = 4; public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 44; + public const RELEASE_VERSION = 45; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '11/2022'; diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php index 014dc752c32ae..53e5f547d249f 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php @@ -39,6 +39,45 @@ public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrue() $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); } + public function testRequestStackIsNotBrokenWhenControllerThrowsAnExceptionAndCatchIsTrue() + { + $requestStack = new RequestStack(); + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \RuntimeException(); }, $requestStack); + + try { + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + } catch (\Throwable $exception) { + } + + self::assertNull($requestStack->getCurrentRequest()); + } + + public function testRequestStackIsNotBrokenWhenControllerThrowsAnExceptionAndCatchIsFalse() + { + $requestStack = new RequestStack(); + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \RuntimeException(); }, $requestStack); + + try { + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, false); + } catch (\Throwable $exception) { + } + + self::assertNull($requestStack->getCurrentRequest()); + } + + public function testRequestStackIsNotBrokenWhenControllerThrowsAnThrowable() + { + $requestStack = new RequestStack(); + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \Error(); }, $requestStack); + + try { + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + } catch (\Throwable $exception) { + } + + self::assertNull($requestStack->getCurrentRequest()); + } + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsFalseAndNoListenerIsRegistered() { $this->expectException(\RuntimeException::class); diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php index e3c30487ca6c4..25006fbb7d326 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php @@ -94,15 +94,7 @@ public function addAuthenticator(AuthenticatorInterface $authenticator): void protected function doHeloCommand(): void { - try { - $response = $this->executeCommand(sprintf("EHLO %s\r\n", $this->getLocalDomain()), [250]); - } catch (TransportExceptionInterface $e) { - parent::doHeloCommand(); - - return; - } - - $capabilities = $this->getCapabilities($response); + $capabilities = $this->callHeloCommand(); /** @var SocketStream $stream */ $stream = $this->getStream(); @@ -116,14 +108,7 @@ protected function doHeloCommand(): void throw new TransportException('Unable to connect with STARTTLS.'); } - try { - $response = $this->executeCommand(sprintf("EHLO %s\r\n", $this->getLocalDomain()), [250]); - $capabilities = $this->getCapabilities($response); - } catch (TransportExceptionInterface $e) { - parent::doHeloCommand(); - - return; - } + $capabilities = $this->callHeloCommand(); } if (\array_key_exists('AUTH', $capabilities)) { @@ -131,10 +116,24 @@ protected function doHeloCommand(): void } } - private function getCapabilities(string $ehloResponse): array + private function callHeloCommand(): array { + try { + $response = $this->executeCommand(sprintf("EHLO %s\r\n", $this->getLocalDomain()), [250]); + } catch (TransportExceptionInterface $e) { + try { + parent::doHeloCommand(); + } catch (TransportExceptionInterface $ex) { + if (!$ex->getCode()) { + throw $e; + } + + throw $ex; + } + } + $capabilities = []; - $lines = explode("\r\n", trim($ehloResponse)); + $lines = explode("\r\n", trim($response)); array_shift($lines); foreach ($lines as $line) { if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) { diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php index e4c2ec215ed88..3c05e94e376d1 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php @@ -295,15 +295,13 @@ private function assertResponseCode(string $response, array $codes): void throw new LogicException('You must set the expected response code.'); } - if (!$response) { - throw new TransportException(sprintf('Expected response code "%s" but got an empty response.', implode('/', $codes))); - } - [$code] = sscanf($response, '%3d'); $valid = \in_array($code, $codes); - if (!$valid) { - throw new TransportException(sprintf('Expected response code "%s" but got code "%s", with message "%s".', implode('/', $codes), $code, trim($response)), $code); + if (!$valid || !$response) { + $codeStr = $code ? sprintf('code "%s"', $code) : 'empty code'; + $responseStr = $response ? sprintf(', with message "%s"', trim($response)) : ''; + throw new TransportException(sprintf('Expected response code "%s" but got ', implode('/', $codes), $codeStr).$codeStr.$responseStr.'.', $code); } } diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php index 83434752d91ec..57bf5346ef55d 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php @@ -162,7 +162,7 @@ public function get(): ?array { if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { try { - $this->driverConnection->delete($this->configuration['table_name'], ['delivered_at' => '9999-12-31']); + $this->driverConnection->delete($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59']); } catch (DriverException $e) { // Ignore the exception } @@ -252,7 +252,7 @@ public function ack(string $id): bool { try { if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { - return $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31'], ['id' => $id]) > 0; + return $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59'], ['id' => $id]) > 0; } return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0; @@ -265,7 +265,7 @@ public function reject(string $id): bool { try { if ($this->driverConnection->getDatabasePlatform() instanceof MySQLPlatform) { - return $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31'], ['id' => $id]) > 0; + return $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59'], ['id' => $id]) > 0; } return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0; diff --git a/src/Symfony/Component/Mime/Email.php b/src/Symfony/Component/Mime/Email.php index 29da5ef2fdcb9..9cdde13e533c6 100644 --- a/src/Symfony/Component/Mime/Email.php +++ b/src/Symfony/Component/Mime/Email.php @@ -503,7 +503,7 @@ private function prepareParts(): ?array $html = stream_get_contents($html); } $htmlPart = new TextPart($html, $this->htmlCharset, 'html'); - preg_match_all('(]*src\s*=\s*(?:([\'"])cid:([^"]+)\\1|cid:([^>\s]+)))i', $html, $names); + preg_match_all('(]*src\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+)))i', $html, $names); $names = array_filter(array_unique(array_merge($names[2], $names[3]))); } diff --git a/src/Symfony/Component/Serializer/Encoder/ChainDecoder.php b/src/Symfony/Component/Serializer/Encoder/ChainDecoder.php index 2a55d93a6066f..29c656397f1f0 100644 --- a/src/Symfony/Component/Serializer/Encoder/ChainDecoder.php +++ b/src/Symfony/Component/Serializer/Encoder/ChainDecoder.php @@ -67,9 +67,13 @@ private function getDecoder(string $format, array $context): DecoderInterface return $this->decoders[$this->decoderByFormat[$format]]; } + $cache = true; foreach ($this->decoders as $i => $decoder) { + $cache = $cache && !$decoder instanceof ContextAwareDecoderInterface; if ($decoder->supportsDecoding($format, $context)) { - $this->decoderByFormat[$format] = $i; + if ($cache) { + $this->decoderByFormat[$format] = $i; + } return $decoder; } diff --git a/src/Symfony/Component/Serializer/Encoder/ChainEncoder.php b/src/Symfony/Component/Serializer/Encoder/ChainEncoder.php index b13333e88aabb..c2c9e3c8ad2e2 100644 --- a/src/Symfony/Component/Serializer/Encoder/ChainEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/ChainEncoder.php @@ -85,9 +85,13 @@ private function getEncoder(string $format, array $context): EncoderInterface return $this->encoders[$this->encoderByFormat[$format]]; } + $cache = true; foreach ($this->encoders as $i => $encoder) { + $cache = $cache && !$encoder instanceof ContextAwareEncoderInterface; if ($encoder->supportsEncoding($format, $context)) { - $this->encoderByFormat[$format] = $i; + if ($cache) { + $this->encoderByFormat[$format] = $i; + } return $encoder; } diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/ChainDecoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/ChainDecoderTest.php index 5cac8d99a5270..8f433ce0fa15a 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/ChainDecoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/ChainDecoderTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Encoder\ChainDecoder; +use Symfony\Component\Serializer\Encoder\ContextAwareDecoderInterface; use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Exception\RuntimeException; @@ -28,7 +29,7 @@ class ChainDecoderTest extends TestCase protected function setUp(): void { - $this->decoder1 = $this->createMock(DecoderInterface::class); + $this->decoder1 = $this->createMock(ContextAwareDecoderInterface::class); $this->decoder1 ->method('supportsDecoding') ->willReturnMap([ @@ -36,6 +37,7 @@ protected function setUp(): void [self::FORMAT_2, [], false], [self::FORMAT_3, [], false], [self::FORMAT_3, ['foo' => 'bar'], true], + [self::FORMAT_3, ['foo' => 'bar2'], false], ]); $this->decoder2 = $this->createMock(DecoderInterface::class); @@ -45,6 +47,8 @@ protected function setUp(): void [self::FORMAT_1, [], false], [self::FORMAT_2, [], true], [self::FORMAT_3, [], false], + [self::FORMAT_3, ['foo' => 'bar'], false], + [self::FORMAT_3, ['foo' => 'bar2'], true], ]); $this->chainDecoder = new ChainDecoder([$this->decoder1, $this->decoder2]); @@ -52,10 +56,26 @@ protected function setUp(): void public function testSupportsDecoding() { + $this->decoder1 + ->method('decode') + ->willReturn('result1'); + $this->decoder2 + ->method('decode') + ->willReturn('result2'); + $this->assertTrue($this->chainDecoder->supportsDecoding(self::FORMAT_1)); + $this->assertEquals('result1', $this->chainDecoder->decode('', self::FORMAT_1, [])); + $this->assertTrue($this->chainDecoder->supportsDecoding(self::FORMAT_2)); + $this->assertEquals('result2', $this->chainDecoder->decode('', self::FORMAT_2, [])); + $this->assertFalse($this->chainDecoder->supportsDecoding(self::FORMAT_3)); + $this->assertTrue($this->chainDecoder->supportsDecoding(self::FORMAT_3, ['foo' => 'bar'])); + $this->assertEquals('result1', $this->chainDecoder->decode('', self::FORMAT_3, ['foo' => 'bar'])); + + $this->assertTrue($this->chainDecoder->supportsDecoding(self::FORMAT_3, ['foo' => 'bar2'])); + $this->assertEquals('result2', $this->chainDecoder->decode('', self::FORMAT_3, ['foo' => 'bar2'])); } public function testDecode() diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/ChainEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/ChainEncoderTest.php index e80dc4f1843a6..8ae9e38c1337a 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/ChainEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/ChainEncoderTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Encoder\ChainEncoder; +use Symfony\Component\Serializer\Encoder\ContextAwareEncoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Encoder\NormalizationAwareInterface; use Symfony\Component\Serializer\Exception\RuntimeException; @@ -29,7 +30,7 @@ class ChainEncoderTest extends TestCase protected function setUp(): void { - $this->encoder1 = $this->createMock(EncoderInterface::class); + $this->encoder1 = $this->createMock(ContextAwareEncoderInterface::class); $this->encoder1 ->method('supportsEncoding') ->willReturnMap([ @@ -37,6 +38,7 @@ protected function setUp(): void [self::FORMAT_2, [], false], [self::FORMAT_3, [], false], [self::FORMAT_3, ['foo' => 'bar'], true], + [self::FORMAT_3, ['foo' => 'bar2'], false], ]); $this->encoder2 = $this->createMock(EncoderInterface::class); @@ -46,6 +48,8 @@ protected function setUp(): void [self::FORMAT_1, [], false], [self::FORMAT_2, [], true], [self::FORMAT_3, [], false], + [self::FORMAT_3, ['foo' => 'bar'], false], + [self::FORMAT_3, ['foo' => 'bar2'], true], ]); $this->chainEncoder = new ChainEncoder([$this->encoder1, $this->encoder2]); @@ -53,10 +57,26 @@ protected function setUp(): void public function testSupportsEncoding() { + $this->encoder1 + ->method('encode') + ->willReturn('result1'); + $this->encoder2 + ->method('encode') + ->willReturn('result2'); + $this->assertTrue($this->chainEncoder->supportsEncoding(self::FORMAT_1)); + $this->assertEquals('result1', $this->chainEncoder->encode('', self::FORMAT_1, [])); + $this->assertTrue($this->chainEncoder->supportsEncoding(self::FORMAT_2)); + $this->assertEquals('result2', $this->chainEncoder->encode('', self::FORMAT_2, [])); + $this->assertFalse($this->chainEncoder->supportsEncoding(self::FORMAT_3)); + $this->assertTrue($this->chainEncoder->supportsEncoding(self::FORMAT_3, ['foo' => 'bar'])); + $this->assertEquals('result1', $this->chainEncoder->encode('', self::FORMAT_3, ['foo' => 'bar'])); + + $this->assertTrue($this->chainEncoder->supportsEncoding(self::FORMAT_3, ['foo' => 'bar2'])); + $this->assertEquals('result2', $this->chainEncoder->encode('', self::FORMAT_3, ['foo' => 'bar2'])); } public function testEncode() diff --git a/src/Symfony/Component/Translation/MessageCatalogue.php b/src/Symfony/Component/Translation/MessageCatalogue.php index 6e6b9fe0eaa95..b43b22d6370f3 100644 --- a/src/Symfony/Component/Translation/MessageCatalogue.php +++ b/src/Symfony/Component/Translation/MessageCatalogue.php @@ -159,19 +159,14 @@ public function replace($messages, $domain = 'messages') */ public function add($messages, $domain = 'messages') { - if (!isset($this->messages[$domain])) { - $this->messages[$domain] = []; - } - $intlDomain = $domain; - if (!str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) { - $intlDomain .= self::INTL_DOMAIN_SUFFIX; - } + $altDomain = str_ends_with($domain, self::INTL_DOMAIN_SUFFIX) ? substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)) : $domain.self::INTL_DOMAIN_SUFFIX; foreach ($messages as $id => $message) { - if (isset($this->messages[$intlDomain]) && \array_key_exists($id, $this->messages[$intlDomain])) { - $this->messages[$intlDomain][$id] = $message; - } else { - $this->messages[$domain][$id] = $message; - } + unset($this->messages[$altDomain][$id]); + $this->messages[$domain][$id] = $message; + } + + if ([] === ($this->messages[$altDomain] ?? null)) { + unset($this->messages[$altDomain]); } } diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php index 073f2255fe7f4..6c9bc7a176664 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -15,6 +15,10 @@ use Symfony\Component\Translation\Exception\InvalidArgumentException; use Symfony\Component\Translation\Exception\NotFoundResourceException; use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\Formatter\IntlFormatter; +use Symfony\Component\Translation\Formatter\IntlFormatterInterface; +use Symfony\Component\Translation\Formatter\MessageFormatter; +use Symfony\Component\Translation\Formatter\MessageFormatterInterface; use Symfony\Component\Translation\Loader\ArrayLoader; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Translator; @@ -683,6 +687,26 @@ public function testIntlFormattedDomain() $this->assertSame('Hi Bob', $translator->trans('some_message', ['%name%' => 'Bob'])); } + public function testIntlDomainOverlapseWithIntlResourceBefore() + { + $intlFormatterMock = $this->createMock(IntlFormatterInterface::class); + $intlFormatterMock->expects($this->once())->method('formatIntl')->with('hello intl', 'en', [])->willReturn('hello intl'); + + $messageFormatter = new MessageFormatter(null, $intlFormatterMock); + + $translator = new Translator('en', $messageFormatter); + $translator->addLoader('array', new ArrayLoader()); + + $translator->addResource('array', ['some_message' => 'hello intl'], 'en', 'messages+intl-icu'); + $translator->addResource('array', ['some_message' => 'hello'], 'en', 'messages'); + + $this->assertSame('hello', $translator->trans('some_message', [], 'messages')); + + $translator->addResource('array', ['some_message' => 'hello intl'], 'en', 'messages+intl-icu'); + + $this->assertSame('hello intl', $translator->trans('some_message', [], 'messages')); + } + /** * @group legacy */ diff --git a/src/Symfony/Component/Validator/Constraints/EmailValidator.php b/src/Symfony/Component/Validator/Constraints/EmailValidator.php index c5a4b21ef21b3..8ea5cee68a2c8 100644 --- a/src/Symfony/Component/Validator/Constraints/EmailValidator.php +++ b/src/Symfony/Component/Validator/Constraints/EmailValidator.php @@ -100,6 +100,10 @@ public function validate($value, Constraint $constraint) } if (null === $constraint->mode) { + if (Email::VALIDATION_MODE_STRICT === $this->defaultMode && !class_exists(EguliasEmailValidator::class)) { + throw new LogicException(sprintf('The "egulias/email-validator" component is required to make the "%s" constraint default to strict mode.', EguliasEmailValidator::class)); + } + $constraint->mode = $this->defaultMode; } diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index 2ebbbd047313c..59a47a8130f31 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Yaml\Dumper; use Symfony\Component\Yaml\Exception\DumpException; +use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Parser; use Symfony\Component\Yaml\Tag\TaggedValue; use Symfony\Component\Yaml\Yaml; @@ -78,7 +79,8 @@ public function testIndentationInConstructor() - foo EOF; - $this->assertEquals($expected, $dumper->dump($this->array, 4, 0)); + $this->assertSame($expected, $dumper->dump($this->array, 4, 0)); + $this->assertSameData($this->array, $this->parser->parse($expected)); } public function testSpecifications() @@ -94,14 +96,17 @@ public function testSpecifications() } $test = $this->parser->parse($yaml); - if (isset($test['dump_skip']) && $test['dump_skip']) { + if ($test['dump_skip'] ?? false) { continue; - } elseif (isset($test['todo']) && $test['todo']) { + } + + if ($test['todo'] ?? false) { // TODO - } else { - eval('$expected = '.trim($test['php']).';'); - $this->assertSame($expected, $this->parser->parse($this->dumper->dump($expected, 10)), $test['test']); + continue; } + + $expected = eval('return '.trim($test['php']).';'); + $this->assertSame($expected, $this->parser->parse($this->dumper->dump($expected, 10)), $test['test']); } } } @@ -111,8 +116,9 @@ public function testInlineLevel() $expected = <<<'EOF' { '': bar, foo: '#bar', 'foo''bar': { }, bar: [1, foo, { a: A }], foobar: { foo: bar, bar: [1, foo], foobar: { foo: bar, bar: [1, foo] } } } EOF; - $this->assertEquals($expected, $this->dumper->dump($this->array, -10), '->dump() takes an inline level argument'); - $this->assertEquals($expected, $this->dumper->dump($this->array, 0), '->dump() takes an inline level argument'); + $this->assertSame($expected, $this->dumper->dump($this->array, -10), '->dump() takes an inline level argument'); + $this->assertSame($expected, $this->dumper->dump($this->array, 0), '->dump() takes an inline level argument'); + $this->assertSameData($this->array, $this->parser->parse($expected)); $expected = <<<'EOF' '': bar @@ -122,7 +128,8 @@ public function testInlineLevel() foobar: { foo: bar, bar: [1, foo], foobar: { foo: bar, bar: [1, foo] } } EOF; - $this->assertEquals($expected, $this->dumper->dump($this->array, 1), '->dump() takes an inline level argument'); + $this->assertSame($expected, $this->dumper->dump($this->array, 1), '->dump() takes an inline level argument'); + $this->assertSameData($this->array, $this->parser->parse($expected)); $expected = <<<'EOF' '': bar @@ -138,7 +145,8 @@ public function testInlineLevel() foobar: { foo: bar, bar: [1, foo] } EOF; - $this->assertEquals($expected, $this->dumper->dump($this->array, 2), '->dump() takes an inline level argument'); + $this->assertSame($expected, $this->dumper->dump($this->array, 2), '->dump() takes an inline level argument'); + $this->assertSameData($this->array, $this->parser->parse($expected)); $expected = <<<'EOF' '': bar @@ -159,7 +167,8 @@ public function testInlineLevel() bar: [1, foo] EOF; - $this->assertEquals($expected, $this->dumper->dump($this->array, 3), '->dump() takes an inline level argument'); + $this->assertSame($expected, $this->dumper->dump($this->array, 3), '->dump() takes an inline level argument'); + $this->assertSameData($this->array, $this->parser->parse($expected)); $expected = <<<'EOF' '': bar @@ -182,22 +191,23 @@ public function testInlineLevel() - foo EOF; - $this->assertEquals($expected, $this->dumper->dump($this->array, 4), '->dump() takes an inline level argument'); - $this->assertEquals($expected, $this->dumper->dump($this->array, 10), '->dump() takes an inline level argument'); + $this->assertSame($expected, $this->dumper->dump($this->array, 4), '->dump() takes an inline level argument'); + $this->assertSame($expected, $this->dumper->dump($this->array, 10), '->dump() takes an inline level argument'); + $this->assertSameData($this->array, $this->parser->parse($expected)); } public function testObjectSupportEnabled() { $dump = $this->dumper->dump(['foo' => new A(), 'bar' => 1], 0, 0, Yaml::DUMP_OBJECT); - $this->assertEquals('{ foo: !php/object \'O:30:"Symfony\Component\Yaml\Tests\A":1:{s:1:"a";s:3:"foo";}\', bar: 1 }', $dump, '->dump() is able to dump objects'); + $this->assertSame('{ foo: !php/object \'O:30:"Symfony\Component\Yaml\Tests\A":1:{s:1:"a";s:3:"foo";}\', bar: 1 }', $dump, '->dump() is able to dump objects'); } public function testObjectSupportDisabledButNoExceptions() { $dump = $this->dumper->dump(['foo' => new A(), 'bar' => 1]); - $this->assertEquals('{ foo: null, bar: 1 }', $dump, '->dump() does not dump objects when disabled'); + $this->assertSame('{ foo: null, bar: 1 }', $dump, '->dump() does not dump objects when disabled'); } public function testObjectSupportDisabledWithExceptions() @@ -211,7 +221,8 @@ public function testObjectSupportDisabledWithExceptions() */ public function testEscapedEscapeSequencesInQuotedScalar($input, $expected) { - $this->assertEquals($expected, $this->dumper->dump($input)); + $this->assertSame($expected, $this->dumper->dump($input)); + $this->assertSameData($input, $this->parser->parse($expected)); } public function getEscapeSequences() @@ -261,7 +272,7 @@ public function testDumpObjectAsMap($object, $expected) { $yaml = $this->dumper->dump($object, 0, 0, Yaml::DUMP_OBJECT_AS_MAP); - $this->assertEquals($expected, Yaml::parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP)); + $this->assertSameData($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP)); } public function objectAsMapProvider() @@ -339,7 +350,7 @@ public function testDumpingArrayObjectInstancesWithNumericKeysRespectsInlineLeve 2: { 0: d, 1: e } YAML; - $this->assertEquals($expected, $yaml); + $this->assertSame($expected, $yaml); } public function testDumpEmptyArrayObjectInstanceAsMap() @@ -378,6 +389,7 @@ public function testDumpingStdClassInstancesRespectsInlineLevel() YAML; $this->assertSame($expected, $yaml); + $this->assertSameData($outer, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP)); } public function testDumpingTaggedValueSequenceRespectsInlineLevel() @@ -403,6 +415,59 @@ public function testDumpingTaggedValueSequenceRespectsInlineLevel() YAML; $this->assertSame($expected, $yaml); + $this->assertSameData($data, $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS)); + } + + public function testDumpingTaggedValueTopLevelScalar() + { + $data = new TaggedValue('user', 'jane'); + + $yaml = $this->dumper->dump($data); + + $expected = '!user jane'; + $this->assertSame($expected, $yaml); + $this->assertSameData($data, $this->parser->parse($yaml, Yaml::PARSE_CUSTOM_TAGS)); + } + + public function testDumpingTaggedValueTopLevelAssocInline() + { + $data = new TaggedValue('user', ['name' => 'jane']); + + $yaml = $this->dumper->dump($data); + + $expected = '!user { name: jane }'; + $this->assertSame($expected, $yaml); + $this->assertSameData($data, $this->parser->parse($yaml, Yaml::PARSE_CUSTOM_TAGS)); + } + + public function testDumpingTaggedValueTopLevelAssoc() + { + $data = new TaggedValue('user', ['name' => 'jane']); + + // @todo Fix the dumper, the output should not be ''. + $expected = ''; + $yaml = $this->dumper->dump($data, 2); + $this->assertSame($expected, $yaml); + } + + 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)); + } + + public function testDumpingTaggedValueSpecialCharsInTag() + { + // @todo Validate the tag name in the TaggedValue constructor. + $data = new TaggedValue('a b @ c', 5); + $expected = '!a b @ c 5'; + $this->assertSame($expected, $this->dumper->dump($data)); + // The data changes after a round trip, due to the illegal tag name. + $data = new TaggedValue('a', 'b @ c 5'); + $this->assertSameData($data, $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS)); } public function testDumpingTaggedValueSequenceWithInlinedTagValues() @@ -415,6 +480,7 @@ public function testDumpingTaggedValueSequenceWithInlinedTagValues() 'john', 'claire', ]), + new TaggedValue('number', 5), ]; $yaml = $this->dumper->dump($data, 1); @@ -422,9 +488,13 @@ public function testDumpingTaggedValueSequenceWithInlinedTagValues() $expected = <<assertSame($expected, $yaml); + // @todo Fix the parser, preserve numbers. + $data[2] = new TaggedValue('number', '5'); + $this->assertSameData($data, $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS)); } public function testDumpingTaggedValueMapRespectsInlineLevel() @@ -437,6 +507,7 @@ public function testDumpingTaggedValueMapRespectsInlineLevel() 'john', 'claire', ]), + 'count' => new TaggedValue('number', 5), ]; $yaml = $this->dumper->dump($data, 2); @@ -447,9 +518,13 @@ public function testDumpingTaggedValueMapRespectsInlineLevel() names1: !names - john - claire +count: !number 5 YAML; $this->assertSame($expected, $yaml); + // @todo Fix the parser, preserve numbers. + $data['count'] = new TaggedValue('number', '5'); + $this->assertSameData($data, $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS)); } public function testDumpingTaggedValueMapWithInlinedTagValues() @@ -472,6 +547,7 @@ public function testDumpingTaggedValueMapWithInlinedTagValues() YAML; $this->assertSame($expected, $yaml); + $this->assertSameData($data, $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS)); } public function testDumpingNotInlinedScalarTaggedValue() @@ -487,6 +563,7 @@ public function testDumpingNotInlinedScalarTaggedValue() YAML; $this->assertSame($expected, $this->dumper->dump($data, 2)); + $this->assertSameData($data, $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS)); } public function testDumpingNotInlinedNullTaggedValue() @@ -500,6 +577,10 @@ public function testDumpingNotInlinedNullTaggedValue() YAML; $this->assertSame($expected, $this->dumper->dump($data, 2)); + + // @todo Fix the parser, don't stringify null. + $data['foo'] = new TaggedValue('bar', 'null'); + $this->assertSameData($data, $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS | Yaml::PARSE_CONSTANT)); } public function testDumpingMultiLineStringAsScalarBlockTaggedValue() @@ -519,6 +600,53 @@ public function testDumpingMultiLineStringAsScalarBlockTaggedValue() ' baz'; $this->assertSame($expected, $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + $this->assertSameData($data, $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS)); + } + + public function testDumpingTaggedMultiLineInList() + { + $data = [ + new TaggedValue('bar', "a\nb"), + ]; + $expected = "- !bar |\n a\n b"; + $this->assertSame($expected, $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + + // @todo Fix the parser, eliminate these exceptions. + $this->expectException(ParseException::class); + $this->expectExceptionMessage('Unable to parse at line 3 (near "!bar |").'); + + $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS); + } + + public function testDumpingTaggedMultiLineTrailingNewlinesInMap() + { + $data = [ + 'foo' => new TaggedValue('bar', "a\nb\n\n\n"), + ]; + $expected = "foo: !bar |\n a\n b\n \n \n "; + $this->assertSame($expected, $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + + // @todo Fix the parser, the result should be identical to $data. + $this->assertSameData( + [ + 'foo' => new TaggedValue('bar', "a\nb\n"), + ], + $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS)); + } + + public function testDumpingTaggedMultiLineTrailingNewlinesInList() + { + $data = [ + new TaggedValue('bar', "a\nb\n\n\n"), + ]; + $expected = "- !bar |\n a\n b\n \n \n "; + $this->assertSame($expected, $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + + // @todo Fix the parser, eliminate these exceptions. + $this->expectException(ParseException::class); + $this->expectExceptionMessage('Unable to parse at line 6 (near "!bar |").'); + + $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS); } public function testDumpingInlinedMultiLineIfRnBreakLineInTaggedValue() @@ -528,8 +656,14 @@ public function testDumpingInlinedMultiLineIfRnBreakLineInTaggedValue() 'foo' => new TaggedValue('bar', "foo\r\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz"), ], ]; + $expected = <<<'YAML' +data: + foo: !bar "foo\r\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz" - $this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block_for_tagged_values.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); +YAML; + $yml = $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + $this->assertSame($expected, $yml); + $this->assertSameData($data, $this->parser->parse($expected, Yaml::PARSE_CUSTOM_TAGS)); } public function testDumpMultiLineStringAsScalarBlock() @@ -544,8 +678,27 @@ public function testDumpMultiLineStringAsScalarBlock() ], ], ]; - - $this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + $yml = $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + $expected = str_replace("@\n", "\n", <<<'YAML' +data: + single_line: 'foo bar baz' + multi_line: |- + foo + line with trailing spaces: + @ + bar + integer like line: + 123456789 + empty line: + + baz + multi_line_with_carriage_return: "foo\nbar\r\nbaz" + nested_inlined_multi_line_string: { inlined_multi_line: "foo\nbar\r\nempty line:\n\nbaz" } + +YAML +); + $this->assertSame($expected, $yml); + $this->assertSame($data, $this->parser->parse($yml)); } public function testDumpMultiLineStringAsScalarBlockWhenFirstLineHasLeadingSpace() @@ -558,27 +711,33 @@ public function testDumpMultiLineStringAsScalarBlockWhenFirstLineHasLeadingSpace $expected = "data:\n multi_line: |4-\n the first line has leading spaces\n The second line does not."; - $this->assertSame($expected, $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + $yml = $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + $this->assertSame($expected, $yml); + $this->assertSame($data, $this->parser->parse($yml)); } public function testCarriageReturnFollowedByNewlineIsMaintainedWhenDumpingAsMultiLineLiteralBlock() { - $this->assertSame("- \"a\\r\\nb\\nc\"\n", $this->dumper->dump(["a\r\nb\nc"], 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + $data = ["a\r\nb\nc"]; + $expected = "- \"a\\r\\nb\\nc\"\n"; + $this->assertSame($expected, $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + $this->assertSame($data, $this->parser->parse($expected)); } public function testCarriageReturnNotFollowedByNewlineIsPreservedWhenDumpingAsMultiLineLiteralBlock() { + $data = [ + 'parent' => [ + 'foo' => "bar\n\rbaz: qux", + ], + ]; $expected = <<<'YAML' parent: foo: "bar\n\rbaz: qux" YAML; - - $this->assertSame($expected, $this->dumper->dump([ - 'parent' => [ - 'foo' => "bar\n\rbaz: qux", - ], - ], 4, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + $this->assertSame($expected, $this->dumper->dump($data, 4, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + $this->assertSame($data, $this->parser->parse($expected)); } public function testNoExtraTrailingNewlineWhenDumpingAsMultiLineLiteralBlock() @@ -590,7 +749,15 @@ public function testNoExtraTrailingNewlineWhenDumpingAsMultiLineLiteralBlock() $yaml = $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); $this->assertSame("- |-\n a\n b\n- |-\n c\n d", $yaml); - $this->assertSame($data, Yaml::parse($yaml)); + $this->assertSame($data, $this->parser->parse($yaml)); + } + + public function testTopLevelMultiLineStringLiteral() + { + $data = "a\nb\n"; + $yaml = $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + $this->assertSame('"a\nb\n"', $yaml); + $this->assertSame($data, $this->parser->parse($yaml)); } public function testDumpTrailingNewlineInMultiLineLiteralBlocks() @@ -600,6 +767,7 @@ public function testDumpTrailingNewlineInMultiLineLiteralBlocks() 'clip 2' => "one\ntwo\n", 'keep 1' => "one\ntwo\n", 'keep 2' => "one\ntwo\n\n", + 'keep 3' => "one\ntwo\n\n\n", 'strip 1' => "one\ntwo", 'strip 2' => "one\ntwo", ]; @@ -619,6 +787,11 @@ public function testDumpTrailingNewlineInMultiLineLiteralBlocks() one two +'keep 3': |+ + one + two + + 'strip 1': |- one two @@ -628,7 +801,7 @@ public function testDumpTrailingNewlineInMultiLineLiteralBlocks() YAML; $this->assertSame($expected, $yaml); - $this->assertSame($data, Yaml::parse($yaml)); + $this->assertSame($data, $this->parser->parse($yaml)); } public function testZeroIndentationThrowsException() @@ -664,6 +837,15 @@ public function testDumpIdeographicSpaces() 'regular_space' => 'a b', ], 2)); } + + private function assertSameData($expected, $actual) + { + $this->assertEquals($expected, $actual); + $this->assertSame( + var_export($expected, true), + var_export($actual, true) + ); + } } class A diff --git a/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block.yml b/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block.yml deleted file mode 100644 index 1f61eb1216a52..0000000000000 --- a/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block.yml +++ /dev/null @@ -1,14 +0,0 @@ -data: - single_line: 'foo bar baz' - multi_line: |- - foo - line with trailing spaces: - - bar - integer like line: - 123456789 - empty line: - - baz - multi_line_with_carriage_return: "foo\nbar\r\nbaz" - nested_inlined_multi_line_string: { inlined_multi_line: "foo\nbar\r\nempty line:\n\nbaz" } diff --git a/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_for_tagged_values.yml b/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_for_tagged_values.yml deleted file mode 100644 index f8c9112fd52a5..0000000000000 --- a/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_for_tagged_values.yml +++ /dev/null @@ -1,2 +0,0 @@ -data: - foo: !bar "foo\r\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz" diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 9a14422e434fe..6bcc937d6f49e 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -34,6 +34,90 @@ protected function tearDown(): void chmod(__DIR__.'/Fixtures/not_readable.yml', 0644); } + public function testTopLevelNumber() + { + $yml = '5'; + $data = $this->parser->parse($yml); + $expected = 5; + $this->assertSameData($expected, $data); + } + + public function testTopLevelNull() + { + $yml = 'null'; + $data = $this->parser->parse($yml); + $expected = null; + $this->assertSameData($expected, $data); + } + + public function testTaggedValueTopLevelNumber() + { + $yml = '!number 5'; + $data = $this->parser->parse($yml, Yaml::PARSE_CUSTOM_TAGS); + // @todo Preserve the number, don't turn into string. + $expected = new TaggedValue('number', '5'); + $this->assertSameData($expected, $data); + } + + public function testTaggedValueTopLevelNull() + { + $yml = '!tag null'; + $data = $this->parser->parse($yml, Yaml::PARSE_CUSTOM_TAGS); + // @todo Preserve literal null, don't turn into string. + $expected = new TaggedValue('tag', 'null'); + $this->assertSameData($expected, $data); + } + + public function testTaggedValueTopLevelString() + { + $yml = '!user barbara'; + $data = $this->parser->parse($yml, Yaml::PARSE_CUSTOM_TAGS); + $expected = new TaggedValue('user', 'barbara'); + $this->assertSameData($expected, $data); + } + + public function testTaggedValueTopLevelAssocInline() + { + $yml = '!user { name: barbara }'; + $data = $this->parser->parse($yml, Yaml::PARSE_CUSTOM_TAGS); + $expected = new TaggedValue('user', ['name' => 'barbara']); + $this->assertSameData($expected, $data); + } + + public function testTaggedValueTopLevelAssoc() + { + $yml = <<<'YAML' +!user +name: barbara +YAML; + $data = $this->parser->parse($yml, Yaml::PARSE_CUSTOM_TAGS); + $expected = new TaggedValue('user', ['name' => 'barbara']); + $this->assertSameData($expected, $data); + } + + public function testTaggedValueTopLevelList() + { + $yml = <<<'YAML' +!users +- barbara +YAML; + $data = $this->parser->parse($yml, Yaml::PARSE_CUSTOM_TAGS); + $expected = new TaggedValue('users', ['barbara']); + $this->assertSameData($expected, $data); + } + + public function testTaggedTextAsListItem() + { + $yml = <<<'YAML' +- !text | + first line +YAML; + // @todo Fix the parser, eliminate this exception. + $this->expectException(ParseException::class); + $this->expectExceptionMessage('Unable to parse at line 2 (near "!text |").'); + $this->parser->parse($yml, Yaml::PARSE_CUSTOM_TAGS); + } + /** * @dataProvider getDataFormSpecifications */ @@ -104,7 +188,7 @@ public function testParserIsStateless() public function testValidTokenSeparation(string $given, array $expected) { $actual = $this->parser->parse($given); - $this->assertEquals($expected, $actual); + $this->assertSameData($expected, $actual); } public function validTokenSeparators(): array @@ -482,7 +566,7 @@ public function testObjectSupportEnabled() foo: !php/object O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";} bar: 1 EOF; - $this->assertEquals(['foo' => new B(), 'bar' => 1], $this->parser->parse($input, Yaml::PARSE_OBJECT), '->parse() is able to parse objects'); + $this->assertSameData(['foo' => new B(), 'bar' => 1], $this->parser->parse($input, Yaml::PARSE_OBJECT), '->parse() is able to parse objects'); } public function testObjectSupportDisabledButNoExceptions() @@ -491,7 +575,7 @@ public function testObjectSupportDisabledButNoExceptions() foo: !php/object O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";} bar: 1 EOF; - $this->assertEquals(['foo' => null, 'bar' => 1], $this->parser->parse($input), '->parse() does not parse objects'); + $this->assertSameData(['foo' => null, 'bar' => 1], $this->parser->parse($input), '->parse() does not parse objects'); } /** @@ -501,7 +585,7 @@ public function testObjectForMap($yaml, $expected) { $flags = Yaml::PARSE_OBJECT_FOR_MAP; - $this->assertEquals($expected, $this->parser->parse($yaml, $flags)); + $this->assertSameData($expected, $this->parser->parse($yaml, $flags)); } public function getObjectForMapTests() @@ -957,12 +1041,12 @@ public function testEmptyValue() hash: EOF; - $this->assertEquals(['hash' => null], Yaml::parse($input)); + $this->assertSame(['hash' => null], Yaml::parse($input)); } public function testCommentAtTheRootIndent() { - $this->assertEquals([ + $this->assertSame([ 'services' => [ 'app.foo_service' => [ 'class' => 'Foo', @@ -988,7 +1072,7 @@ class: Bar public function testStringBlockWithComments() { - $this->assertEquals(['content' => <<<'EOT' + $this->assertSame(['content' => <<<'EOT' # comment 1 header @@ -1016,7 +1100,7 @@ public function testStringBlockWithComments() public function testFoldedStringBlockWithComments() { - $this->assertEquals([['content' => <<<'EOT' + $this->assertSame([['content' => <<<'EOT' # comment 1 header @@ -1045,7 +1129,7 @@ public function testFoldedStringBlockWithComments() public function testNestedFoldedStringBlockWithComments() { - $this->assertEquals([[ + $this->assertSame([[ 'title' => 'some title', 'content' => <<<'EOT' # comment 1 @@ -1077,7 +1161,7 @@ public function testNestedFoldedStringBlockWithComments() public function testReferenceResolvingInInlineStrings() { - $this->assertEquals([ + $this->assertSame([ 'var' => 'var-value', 'scalar' => 'var-value', 'list' => ['var-value'], @@ -1117,7 +1201,7 @@ public function testYamlDirective() foo: 1 bar: 2 EOF; - $this->assertEquals(['foo' => 1, 'bar' => 2], $this->parser->parse($yaml)); + $this->assertSame(['foo' => 1, 'bar' => 2], $this->parser->parse($yaml)); } public function testFloatKeys() @@ -1167,7 +1251,7 @@ public function testExplicitStringCasting() '~' => 'null', ]; - $this->assertEquals($expected, $this->parser->parse($yaml)); + $this->assertSame($expected, $this->parser->parse($yaml)); } public function testColonInMappingValueException() @@ -1468,7 +1552,7 @@ public function testParseDateAsMappingValue() $expectedDate->setDate(2002, 12, 14); $expectedDate->setTime(0, 0, 0); - $this->assertEquals(['date' => $expectedDate], $this->parser->parse($yaml, Yaml::PARSE_DATETIME)); + $this->assertSameData(['date' => $expectedDate], $this->parser->parse($yaml, Yaml::PARSE_DATETIME)); } /** @@ -1688,7 +1772,7 @@ public function testBackslashInSingleQuotedString() public function testParseMultiLineString() { - $this->assertEquals("foo bar\nbaz", $this->parser->parse("foo\nbar\n\nbaz")); + $this->assertSame("foo bar\nbaz", $this->parser->parse("foo\nbar\n\nbaz")); } /** @@ -1696,7 +1780,7 @@ public function testParseMultiLineString() */ public function testParseMultiLineMappingValue($yaml, $expected, $parseError) { - $this->assertEquals($expected, $this->parser->parse($yaml)); + $this->assertSame($expected, $this->parser->parse($yaml)); } public function multiLineDataProvider() @@ -1763,7 +1847,7 @@ public function multiLineDataProvider() */ public function testInlineNotationSpanningMultipleLines($expected, string $yaml) { - $this->assertEquals($expected, $this->parser->parse($yaml)); + $this->assertSame($expected, $this->parser->parse($yaml)); } public function inlineNotationSpanningMultipleLinesProvider(): array @@ -2137,7 +2221,7 @@ public function testRootLevelInlineMappingFollowedByMoreContentIsInvalid() public function testTaggedInlineMapping() { - $this->assertEquals(new TaggedValue('foo', ['foo' => 'bar']), $this->parser->parse('!foo {foo: bar}', Yaml::PARSE_CUSTOM_TAGS)); + $this->assertSameData(new TaggedValue('foo', ['foo' => 'bar']), $this->parser->parse('!foo {foo: bar}', Yaml::PARSE_CUSTOM_TAGS)); } public function testInvalidInlineSequenceContainingStringWithEscapedQuotationCharacter() @@ -2152,7 +2236,7 @@ public function testInvalidInlineSequenceContainingStringWithEscapedQuotationCha */ public function testCustomTagSupport($expected, $yaml) { - $this->assertEquals($expected, $this->parser->parse($yaml, Yaml::PARSE_CUSTOM_TAGS)); + $this->assertSameData($expected, $this->parser->parse($yaml, Yaml::PARSE_CUSTOM_TAGS)); } public function taggedValuesProvider() @@ -2348,7 +2432,7 @@ public function testCanParseVeryLongValue() $yamlString = Yaml::dump($trickyVal); $arrayFromYaml = $this->parser->parse($yamlString); - $this->assertEquals($trickyVal, $arrayFromYaml); + $this->assertSame($trickyVal, $arrayFromYaml); } public function testParserCleansUpReferencesBetweenRuns() @@ -2463,7 +2547,7 @@ public function testMergeKeysWhenMappingsAreParsedAsObjects() ], ]; - $this->assertEquals($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP)); + $this->assertSameData($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP)); } public function testFilenamesAreParsedAsStringsWithoutFlag() @@ -2556,7 +2640,7 @@ public function testParseReferencesOnMergeKeysWithMappingsParsedAsObjects() ], ]; - $this->assertEquals($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP)); + $this->assertSameData($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP)); } public function testEvalRefException() @@ -2831,6 +2915,15 @@ public function testParseIdeographicSpaces() 'regular_space' => 'a b', ], $this->parser->parse($expected)); } + + private function assertSameData($expected, $actual) + { + $this->assertEquals($expected, $actual); + $this->assertSame( + var_export($expected, true), + var_export($actual, true) + ); + } } class B pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy