diff --git a/.github/workflows/package-tests.yml b/.github/workflows/package-tests.yml index b6015edf4e00c..40c0e66c573ea 100644 --- a/.github/workflows/package-tests.yml +++ b/.github/workflows/package-tests.yml @@ -21,7 +21,7 @@ jobs: - name: Find packages id: find-packages - run: echo "::set-output name=packages::$(php .github/get-modified-packages.php $(find src/Symfony -mindepth 2 -type f -name composer.json -printf '%h\n' | jq -R -s -c 'split("\n")[:-1]') $(git diff --name-only origin/${{ github.base_ref }} HEAD | grep src/ | jq -R -s -c 'split("\n")[:-1]'))" + run: echo "::set-output name=packages::$(php .github/get-modified-packages.php $(find src/Symfony -mindepth 2 -maxdepth 6 -type f -name composer.json -printf '%h\n' | jq -R -s -c 'split("\n")[:-1]') $(git diff --name-only origin/${{ github.base_ref }} HEAD | grep src/ | jq -R -s -c 'split("\n")[:-1]'))" - name: Verify meta files are correct run: | diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index b1cb7a268cf8b..6d49e81916ac6 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -44,11 +44,6 @@ jobs: with: fetch-depth: 2 - - name: Configure for PHP >= 8.2 - if: "matrix.php >= '8.2'" - run: | - composer config platform.php 8.1.99 - - name: Setup PHP uses: shivammathur/setup-php@v2 with: @@ -70,7 +65,7 @@ jobs: echo COLUMNS=120 >> $GITHUB_ENV echo PHPUNIT="$(pwd)/phpunit --exclude-group tty,benchmark,intl-data,integration" >> $GITHUB_ENV - echo COMPOSER_UP='composer update --no-progress --ansi' >> $GITHUB_ENV + echo COMPOSER_UP='composer update --no-progress --ansi'$([[ "${{ matrix.php }}" = "8.2" ]] && echo ' --ignore-platform-req=php+') >> $GITHUB_ENV SYMFONY_VERSIONS=$(git ls-remote -q --heads | cut -f2 | grep -o '/[1-9][0-9]*\.[0-9].*' | sort -V) SYMFONY_VERSION=$(grep ' VERSION = ' src/Symfony/Component/HttpKernel/Kernel.php | cut -d "'" -f2 | cut -d '.' -f 1-2) @@ -99,7 +94,7 @@ jobs: echo SYMFONY_DEPRECATIONS_HELPER=weak >> $GITHUB_ENV cp composer.json composer.json.orig echo -e '{\n"require":{'"$(grep phpunit-bridge composer.json)"'"php":"*"},"minimum-stability":"dev"}' > composer.json - php .github/build-packages.php HEAD^ $SYMFONY_VERSION $(find src/Symfony -mindepth 2 -type f -name composer.json -printf '%h\n') + php .github/build-packages.php HEAD^ $SYMFONY_VERSION $(find src/Symfony -mindepth 2 -maxdepth 6 -type f -name composer.json -printf '%h\n') mv composer.json composer.json.phpunit mv composer.json.orig composer.json fi diff --git a/CHANGELOG-5.4.md b/CHANGELOG-5.4.md index 184bbd447c979..f4ed8a2960638 100644 --- a/CHANGELOG-5.4.md +++ b/CHANGELOG-5.4.md @@ -7,6 +7,37 @@ 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.13 (2022-09-30) + + * bug #47317 [Security] Fix login url matching when app is not run with url rewriting or from a sub folder (sgehrig) + * bug #47637 [FrameworkBundle] Fix passing `serializer.default_context` option to normalizers (wuchen90) + * bug #47695 [FrameworkBundle] Filter out trans paths that are covered by a parent folder path (natewiebe13) + * bug #45554 [Serializer] Fixed framework.serializer.default_context is not working for JsonEncoder (siganushka) + * bug #47547 [Ldap] Do not run ldap_set_option on failed connection (tatankat) + * bug #47578 [Security] Fix AbstractFormLoginAuthenticator return types (AndrolGenhald) + * bug #47614 [FrameworkBundle] Fix a phpdoc in mailer assertions (HeahDude) + * bug #47516 [HttpFoundation] Prevent BinaryFileResponse::prepare from adding content type if no content is sent (naitsirch) + * bug #47533 [Messenger] decode URL-encoded characters in DSN's usernames/passwords (xabbuh) + * bug #47530 [HttpFoundation] Always return strings from accept headers (ausi) + * bug #47523 [Uid] Ensure ULIDs are monotonic even when the time goes backward (nicolas-grekas) + * bug #47528 [Form] fix UUID tranformer (nicolas-grekas) + * bug #47488 [Security] Fix valid remember-me token exposure to the second consequent request (Ivan Kurnosov) + * bug #47518 [Uid] Fix validating UUID variant bits (nicolas-grekas) + * bug #47441 [HttpClient] [HttpClientBundle] Bugfix for delayed retryableHttpClient (martkop26) + * bug #47499 [Uid][Validator] Stop to first ULID format violation (ogizanagi) + * bug #47491 [HttpKernel] Prevent exception in RequestDataCollector if request stack is empty (aschempp) + * bug #47497 [Bridge] Fix mkdir() race condition in ProxyCacheWarmer (andrey-tech) + * bug #47415 [HttpClient] Psr18Client ignore invalid HTTP headers (nuryagdym) + * bug #47394 [Console] [Completion] Make bash completion run in non interactive mode (Seldaek) + * bug #47455 [Mime] Fix TextPart broken after being serialized (fabpot) + * bug #47423 [String] CamelCase/SnakeCase on uppercase word (mpiot) + * bug #47435 [HttpKernel] lock when writting profiles (nicolas-grekas) + * bug #47417 [WebProfilerBundle] Fix profile search bar link query params (HeahDude) + * bug #47437 [Mime] Fix email rendering when having inlined parts that are not related to the content (fabpot) + * bug #47434 [HttpFoundation] move flushing outside of Response::closeOutputBuffers (nicolas-grekas) + * bug #47351 [FrameworkBundle] Do not throw when describing a factory definition (MatTheCat) + * bug #47403 [Mailer] Fix edge cases in STMP transports (fabpot) + * 5.4.12 (2022-08-26) * bug #47391 [LokaliseBridge] Fix push command --delete-missing options when there are no missing messages (rwionczek) 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/composer.json b/composer.json index 8ba28df968853..ffe1e31ef4341 100644 --- a/composer.json +++ b/composer.json @@ -204,6 +204,15 @@ { "type": "path", "url": "src/Symfony/Component/Runtime" + }, + { + "type": "path", + "url": "src/Symfony/Component/Notifier/Bridge/Mercure/Tests/stella-maris-clock", + "options": { + "versions": { + "stella-maris/clock": "0.1.x-dev" + } + } } ], "minimum-stability": "dev" diff --git a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php index bbee8cea3f3f8..52dbd7285aacd 100644 --- a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php +++ b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php @@ -52,7 +52,7 @@ public function warmUp(string $cacheDir) foreach ($this->registry->getManagers() as $em) { // we need the directory no matter the proxy cache generation strategy if (!is_dir($proxyCacheDir = $em->getConfiguration()->getProxyDir())) { - if (false === @mkdir($proxyCacheDir, 0777, true)) { + if (false === @mkdir($proxyCacheDir, 0777, true) && !is_dir($proxyCacheDir)) { throw new \RuntimeException(sprintf('Unable to create the Doctrine Proxy directory "%s".', $proxyCacheDir)); } } elseif (!is_writable($proxyCacheDir)) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index de9332615eb97..f0e1dc8870aa6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -407,6 +407,7 @@ private function extractMessages(string $locale, array $transPaths, string $pref { $extractedCatalogue = new MessageCatalogue($locale); $this->extractor->setPrefix($prefix); + $transPaths = $this->filterDuplicateTransPaths($transPaths); foreach ($transPaths as $path) { if (is_dir($path) || is_file($path)) { $this->extractor->extract($path, $extractedCatalogue); @@ -416,6 +417,27 @@ private function extractMessages(string $locale, array $transPaths, string $pref return $extractedCatalogue; } + private function filterDuplicateTransPaths(array $transPaths): array + { + $transPaths = array_filter(array_map('realpath', $transPaths)); + + sort($transPaths); + + $filteredPaths = []; + + foreach ($transPaths as $path) { + foreach ($filteredPaths as $filteredPath) { + if (str_starts_with($path, $filteredPath.\DIRECTORY_SEPARATOR)) { + continue 2; + } + } + + $filteredPaths[] = $path; + } + + return $filteredPaths; + } + private function loadCurrentMessages(string $locale, array $transPaths): MessageCatalogue { $currentCatalogue = new MessageCatalogue($locale); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index 3e282ac10ad03..162a1fb806c9e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -245,7 +245,7 @@ private function getContainerDefinitionData(Definition $definition, bool $omitTa if ($factory[0] instanceof Reference) { $data['factory_service'] = (string) $factory[0]; } elseif ($factory[0] instanceof Definition) { - throw new \InvalidArgumentException('Factory is not describable.'); + $data['factory_service'] = sprintf('inline factory service (%s)', $factory[0]->getClass() ?? 'class not configured'); } else { $data['factory_class'] = $factory[0]; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index ad1a804ce7f96..cf70089405419 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -224,7 +224,7 @@ protected function describeContainerDefinition(Definition $definition, array $op if ($factory[0] instanceof Reference) { $output .= "\n".'- Factory Service: `'.$factory[0].'`'; } elseif ($factory[0] instanceof Definition) { - throw new \InvalidArgumentException('Factory is not describable.'); + $output .= "\n".sprintf('- Factory Service: inline factory service (%s)', $factory[0]->getClass() ? sprintf('`%s`', $factory[0]->getClass()) : 'not configured'); } else { $output .= "\n".'- Factory Class: `'.$factory[0].'`'; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index ac22fa731d158..a76d7f20744bc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -315,7 +315,7 @@ protected function describeContainerDefinition(Definition $definition, array $op if ($factory[0] instanceof Reference) { $tableRows[] = ['Factory Service', $factory[0]]; } elseif ($factory[0] instanceof Definition) { - throw new \InvalidArgumentException('Factory is not describable.'); + $tableRows[] = ['Factory Service', sprintf('inline factory service (%s)', $factory[0]->getClass() ?? 'class not configured')]; } else { $tableRows[] = ['Factory Class', $factory[0]]; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index 62e8fa50cde00..ef59fe3dc4fa3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -324,7 +324,7 @@ private function getContainerDefinitionDocument(Definition $definition, string $ if ($factory[0] instanceof Reference) { $factoryXML->setAttribute('service', (string) $factory[0]); } elseif ($factory[0] instanceof Definition) { - throw new \InvalidArgumentException('Factory is not describable.'); + $factoryXML->setAttribute('service', sprintf('inline factory service (%s)', $factory[0]->getClass() ?? 'not configured')); } else { $factoryXML->setAttribute('class', $factory[0]); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php index 6a4508d0d7620..ca0cf9b53612e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php @@ -78,7 +78,8 @@ // Normalizer ->set('serializer.normalizer.constraint_violation_list', ConstraintViolationListNormalizer::class) - ->args([[], service('serializer.name_converter.metadata_aware')]) + ->args([1 => service('serializer.name_converter.metadata_aware')]) + ->autowire(true) ->tag('serializer.normalizer', ['priority' => -915]) ->set('serializer.normalizer.mime_message', MimeMessageNormalizer::class) @@ -124,7 +125,6 @@ service('property_info')->ignoreOnInvalid(), service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(), null, - [], ]) ->tag('serializer.normalizer', ['priority' => -1000]) @@ -137,7 +137,6 @@ service('property_info')->ignoreOnInvalid(), service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(), null, - [], ]) ->alias(PropertyNormalizer::class, 'serializer.normalizer.property') @@ -176,6 +175,7 @@ ->tag('serializer.encoder') ->set('serializer.encoder.json', JsonEncoder::class) + ->args([null, null]) ->tag('serializer.encoder') ->set('serializer.encoder.yaml', YamlEncoder::class) diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php index 875c84d4813da..1a629d6255fbe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/MailerAssertionsTrait.php @@ -91,7 +91,7 @@ public static function assertEmailAddressContains(RawMessage $email, string $hea } /** - * @return MessageEvents[] + * @return MessageEvent[] */ public static function getMailerEvents(string $transport = null): array { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php index 5c6fa8ec35ea2..f883fac0c57ce 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php @@ -140,6 +140,46 @@ public function testWriteMessagesForSpecificDomain() $this->assertMatchesRegularExpression('/Translation files were successfully updated./', $tester->getDisplay()); } + public function testFilterDuplicateTransPaths() + { + $transPaths = [ + $this->translationDir.'/a/test/folder/with/a/subfolder', + $this->translationDir.'/a/test/folder/', + $this->translationDir.'/a/test/folder/with/a/subfolder/and/a/file.txt', + $this->translationDir.'/a/different/test/folder', + ]; + + foreach ($transPaths as $transPath) { + if (realpath($transPath)) { + continue; + } + + if (preg_match('/\.[a-z]+$/', $transPath)) { + if (!realpath(\dirname($transPath))) { + mkdir(\dirname($transPath), 0777, true); + } + + touch($transPath); + } else { + mkdir($transPath, 0777, true); + } + } + + $command = $this->createMock(TranslationUpdateCommand::class); + + $method = new \ReflectionMethod(TranslationUpdateCommand::class, 'filterDuplicateTransPaths'); + $method->setAccessible(true); + + $filteredTransPaths = $method->invoke($command, $transPaths); + + $expectedPaths = [ + realpath($this->translationDir.'/a/different/test/folder'), + realpath($this->translationDir.'/a/test/folder'), + ]; + + $this->assertEquals($expectedPaths, $filteredTransPaths); + } + protected function setUp(): void { $this->fs = new Filesystem(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php index 02c27c1679fdf..d73ed66d761c3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php @@ -142,6 +142,7 @@ public static function getContainerDefinitions() { $definition1 = new Definition('Full\\Qualified\\Class1'); $definition2 = new Definition('Full\\Qualified\\Class2'); + $definition3 = new Definition('Full\\Qualified\\Class3'); return [ 'definition_1' => $definition1 @@ -174,6 +175,9 @@ public static function getContainerDefinitions() ->addTag('tag2') ->addMethodCall('setMailer', [new Reference('mailer')]) ->setFactory([new Reference('factory.service'), 'get']), + '.definition_3' => $definition3 + ->setFile('/path/to/file') + ->setFactory([new Definition('Full\\Qualified\\FactoryClass'), 'get']), 'definition_without_class' => new Definition(), ]; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json index 0eda1932f7a15..401c588c03d42 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json @@ -34,6 +34,20 @@ "parameters": [] } ] + }, + ".definition_3": { + "class": "Full\\Qualified\\Class3", + "public": false, + "synthetic": false, + "lazy": false, + "shared": true, + "abstract": false, + "autowire": false, + "autoconfigure": false, + "file": "\/path\/to\/file", + "factory_service": "inline factory service (Full\\Qualified\\FactoryClass)", + "factory_method": "get", + "tags": [] } }, "aliases": { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md index d793c5900a65a..d6daca9971dc7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md @@ -25,6 +25,20 @@ Definitions - Attr3: val3 - Tag: `tag2` +### .definition_3 + +- Class: `Full\Qualified\Class3` +- Public: no +- Synthetic: no +- Lazy: no +- Shared: yes +- Abstract: no +- Autowired: no +- Autoconfigured: no +- File: `/path/to/file` +- Factory Service: inline factory service (`Full\Qualified\FactoryClass`) +- Factory Method: `get` + Aliases ------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.txt index cdefb65d208dd..daf47ddc39187 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.txt @@ -7,5 +7,6 @@ --------------- ------------------------ .alias_2 alias for ".service_2" .definition_2 Full\Qualified\Class2 - --------------- ------------------------ + .definition_3 Full\Qualified\Class3 + --------------- ------------------------ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml index a311a2e2bb991..b9416fd069d05 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml @@ -17,4 +17,7 @@ + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.json new file mode 100644 index 0000000000000..4bf56746493f8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.json @@ -0,0 +1,14 @@ +{ + "class": "Full\\Qualified\\Class3", + "public": false, + "synthetic": false, + "lazy": false, + "shared": true, + "abstract": false, + "autowire": false, + "autoconfigure": false, + "file": "\/path\/to\/file", + "factory_service": "inline factory service (Full\\Qualified\\FactoryClass)", + "factory_method": "get", + "tags": [] +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.md new file mode 100644 index 0000000000000..68f51634db99f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.md @@ -0,0 +1,11 @@ +- Class: `Full\Qualified\Class3` +- Public: no +- Synthetic: no +- Lazy: no +- Shared: yes +- Abstract: no +- Autowired: no +- Autoconfigured: no +- File: `/path/to/file` +- Factory Service: inline factory service (`Full\Qualified\FactoryClass`) +- Factory Method: `get` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.txt new file mode 100644 index 0000000000000..35ddaf3e452a8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.txt @@ -0,0 +1,18 @@ + ----------------- ------------------------------------------------------ +  Option   Value  + ----------------- ------------------------------------------------------ + Service ID - + Class Full\Qualified\Class3 + Tags - + Public no + Synthetic no + Lazy no + Shared yes + Abstract no + Autowired no + Autoconfigured no + Required File /path/to/file + Factory Service inline factory service (Full\Qualified\FactoryClass) + Factory Method get + ----------------- ------------------------------------------------------ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.xml new file mode 100644 index 0000000000000..e81c77014253f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_3.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.json new file mode 100644 index 0000000000000..94c2fda5402fc --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.json @@ -0,0 +1,15 @@ +{ + "class": "Full\\Qualified\\Class3", + "public": false, + "synthetic": false, + "lazy": false, + "shared": true, + "abstract": false, + "autowire": false, + "autoconfigure": false, + "arguments": [], + "file": "\/path\/to\/file", + "factory_service": "inline factory service (Full\\Qualified\\FactoryClass)", + "factory_method": "get", + "tags": [] +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.md new file mode 100644 index 0000000000000..2ce1f264dfc6c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.md @@ -0,0 +1,12 @@ +- Class: `Full\Qualified\Class3` +- Public: no +- Synthetic: no +- Lazy: no +- Shared: yes +- Abstract: no +- Autowired: no +- Autoconfigured: no +- Arguments: no +- File: `/path/to/file` +- Factory Service: inline factory service (`Full\Qualified\FactoryClass`) +- Factory Method: `get` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.txt new file mode 100644 index 0000000000000..6e400de44e8ff --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.txt @@ -0,0 +1,18 @@ + ----------------- ------------------------------------------------------ +  Option   Value  + ----------------- ------------------------------------------------------ + Service ID - + Class Full\Qualified\Class3 + Tags - + Public no + Synthetic no + Lazy no + Shared yes + Abstract no + Autowired no + Autoconfigured no + Required File /path/to/file + Factory Service inline factory service (Full\Qualified\FactoryClass) + Factory Method get + ----------------- ------------------------------------------------------ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.xml new file mode 100644 index 0000000000000..e81c77014253f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_3.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php index 019aa418901d8..aaf6ad49ccca1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php @@ -32,6 +32,41 @@ public function testDeserializeArrayOfObject() $this->assertEquals($expected, $result); } + + /** + * @dataProvider provideNormalizersAndEncodersWithDefaultContextOption + */ + public function testNormalizersAndEncodersUseDefaultContextConfigOption(string $normalizerId) + { + static::bootKernel(['test_case' => 'Serializer']); + + $normalizer = static::getContainer()->get($normalizerId); + + $reflectionObject = new \ReflectionObject($normalizer); + $property = $reflectionObject->getProperty('defaultContext'); + $property->setAccessible(true); + + $defaultContext = $property->getValue($normalizer); + + self::assertArrayHasKey('fake_context_option', $defaultContext); + self::assertEquals('foo', $defaultContext['fake_context_option']); + } + + public function provideNormalizersAndEncodersWithDefaultContextOption(): array + { + return [ + ['serializer.normalizer.constraint_violation_list.alias'], + ['serializer.normalizer.dateinterval.alias'], + ['serializer.normalizer.datetime.alias'], + ['serializer.normalizer.json_serializable.alias'], + ['serializer.normalizer.problem.alias'], + ['serializer.normalizer.uid.alias'], + ['serializer.normalizer.object.alias'], + ['serializer.encoder.xml.alias'], + ['serializer.encoder.yaml.alias'], + ['serializer.encoder.csv.alias'], + ]; + } } class Foo diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml index 3721de1cac584..e9620ede4d920 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml @@ -6,9 +6,54 @@ framework: enabled: true default_context: enable_max_depth: true + fake_context_option: foo property_info: { enabled: true } services: serializer.alias: alias: serializer public: true + + serializer.normalizer.constraint_violation_list.alias: + alias: serializer.normalizer.constraint_violation_list + public: true + + serializer.normalizer.dateinterval.alias: + alias: serializer.normalizer.dateinterval + public: true + + serializer.normalizer.datetime.alias: + alias: serializer.normalizer.datetime + public: true + + serializer.normalizer.json_serializable.alias: + alias: serializer.normalizer.json_serializable + public: true + + serializer.normalizer.problem.alias: + alias: serializer.normalizer.problem + public: true + + serializer.normalizer.uid.alias: + alias: serializer.normalizer.uid + public: true + + serializer.normalizer.property.alias: + alias: serializer.normalizer.property + public: true + + serializer.normalizer.object.alias: + alias: serializer.normalizer.object + public: true + + serializer.encoder.xml.alias: + alias: serializer.encoder.xml + public: true + + serializer.encoder.yaml.alias: + alias: serializer.encoder.yaml + public: true + + serializer.encoder.csv.alias: + alias: serializer.encoder.csv + public: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php index d47ca5a822139..1aa15d48daa69 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php @@ -101,7 +101,7 @@ public function testSecretLoadedFromExtension() public function testAnonymousMicroKernel() { - $kernel = new class('anonymous_kernel') extends MinimalKernel { + $kernel = $this->kernel = new class('anonymous_kernel') extends MinimalKernel { public function helloAction(): Response { return new Response('Hello World!'); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig index 379653cf93f3c..00c64cadf6aa0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig @@ -108,7 +108,7 @@ {{ include('@WebProfiler/Icon/search.svg') }} Search - {{ render(controller('web_profiler.controller.profiler::searchBarAction', request.query.all)) }} + {{ render(controller('web_profiler.controller.profiler::searchBarAction', query=request.query.all)) }} diff --git a/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php index 3111e557d16bf..1d2fb6fe6774d 100644 --- a/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php +++ b/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php @@ -46,7 +46,7 @@ public function applyVersion(string $path) { $versionized = sprintf($this->format, ltrim($path, '/'), $this->getVersion($path)); - if ($path && '/' == $path[0]) { + if ($path && '/' === $path[0]) { return '/'.$versionized; } diff --git a/src/Symfony/Component/Cache/README.md b/src/Symfony/Component/Cache/README.md index 74052052c8c33..c466d57883c2f 100644 --- a/src/Symfony/Component/Cache/README.md +++ b/src/Symfony/Component/Cache/README.md @@ -1,13 +1,13 @@ Symfony PSR-6 implementation for caching ======================================== -The Cache component provides an extended -[PSR-6](http://www.php-fig.org/psr/psr-6/) implementation for adding cache to +The Cache component provides extended +[PSR-6](https://www.php-fig.org/psr/psr-6/) implementations for adding cache to your applications. It is designed to have a low overhead so that caching is -fastest. It ships with a few caching adapters for the most widespread and -suited to caching backends. It also provides a `doctrine/cache` proxy adapter -to cover more advanced caching needs and a proxy adapter for greater -interoperability between PSR-6 implementations. +fastest. It ships with adapters for the most widespread caching backends. +It also provides a [PSR-16](https://www.php-fig.org/psr/psr-16/) adapter, +and implementations for [symfony/cache-contracts](https://github.com/symfony/cache-contracts)' +`CacheInterface` and `TagAwareCacheInterface`. Resources --------- diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index 1ebcfc9b307e7..91296b0477ba9 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/cache", "type": "library", - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", "keywords": ["caching", "psr6"], "homepage": "https://symfony.com", "license": "MIT", diff --git a/src/Symfony/Component/Console/Resources/completion.bash b/src/Symfony/Component/Console/Resources/completion.bash index fba46070cdebe..bf3edf511b2cb 100644 --- a/src/Symfony/Component/Console/Resources/completion.bash +++ b/src/Symfony/Component/Console/Resources/completion.bash @@ -24,7 +24,7 @@ _sf_{{ COMMAND_NAME }}() { local cur prev words cword _get_comp_words_by_ref -n := cur prev words cword - local completecmd=("$sf_cmd" "_complete" "-sbash" "-c$cword" "-S{{ VERSION }}") + local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-S{{ VERSION }}") for w in ${words[@]}; do w=$(printf -- '%b' "$w") # remove quotes from typed values diff --git a/src/Symfony/Component/DependencyInjection/Attribute/When.php b/src/Symfony/Component/DependencyInjection/Attribute/When.php index 60b7af04b6e21..302b7b0507737 100644 --- a/src/Symfony/Component/DependencyInjection/Attribute/When.php +++ b/src/Symfony/Component/DependencyInjection/Attribute/When.php @@ -12,7 +12,7 @@ namespace Symfony\Component\DependencyInjection\Attribute; /** - * An attribute to tell under which environement this class should be registered as a service. + * An attribute to tell under which environment this class should be registered as a service. * * @author Nicolas Grekas */ diff --git a/src/Symfony/Component/Filesystem/Path.php b/src/Symfony/Component/Filesystem/Path.php index 6d3755e0a6ac3..9aa37355a8555 100644 --- a/src/Symfony/Component/Filesystem/Path.php +++ b/src/Symfony/Component/Filesystem/Path.php @@ -721,7 +721,7 @@ public static function isBasePath(string $basePath, string $ofPath): bool } /** - * @return non-empty-string[] + * @return string[] */ private static function findCanonicalParts(string $root, string $pathWithoutRoot): array { diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php index 0aebb09fb219a..9e680b1c762d3 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php @@ -121,7 +121,7 @@ public function reverseTransform($value) $outputTz = new \DateTimeZone($this->outputTimezone); $dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz); - $lastErrors = \DateTime::getLastErrors(); + $lastErrors = \DateTime::getLastErrors() ?: ['error_count' => 0, 'warning_count' => 0]; if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) { throw new TransformationFailedException(implode(', ', array_merge(array_values($lastErrors['warnings']), array_values($lastErrors['errors'])))); diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/UuidToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/UuidToStringTransformer.php index 1ccf04b223c09..c4775bb12bca5 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/UuidToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/UuidToStringTransformer.php @@ -64,12 +64,14 @@ public function reverseTransform($value) throw new TransformationFailedException('Expected a string.'); } + if (!Uuid::isValid($value)) { + throw new TransformationFailedException(sprintf('The value "%s" is not a valid UUID.', $value)); + } + try { - $uuid = new Uuid($value); + return Uuid::fromString($value); } catch (\InvalidArgumentException $e) { throw new TransformationFailedException(sprintf('The value "%s" is not a valid UUID.', $value), $e->getCode(), $e); } - - return $uuid; } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/PercentToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/PercentToLocalizedStringTransformerTest.php index 708a0acf71db4..c9d89e8e27a2e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/PercentToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/PercentToLocalizedStringTransformerTest.php @@ -82,7 +82,7 @@ public function testReverseTransformWithScaleAndRoundingDisabled() $transformer = new PercentToLocalizedStringTransformer(2, PercentToLocalizedStringTransformer::FRACTIONAL); - $this->assertEquals(0.0123456, $transformer->reverseTransform('1.23456')); + $this->assertEqualsWithDelta(0.0123456, $transformer->reverseTransform('1.23456'), \PHP_FLOAT_EPSILON); } public function testReverseTransform() diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/UuidToStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/UuidToStringTransformerTest.php index f7a93beca8fb9..cb4374535ed70 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/UuidToStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/UuidToStringTransformerTest.php @@ -15,26 +15,15 @@ use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Extension\Core\DataTransformer\UuidToStringTransformer; use Symfony\Component\Uid\Uuid; +use Symfony\Component\Uid\UuidV1; class UuidToStringTransformerTest extends TestCase { - public function provideValidUuid() - { - return [ - ['123e4567-e89b-12d3-a456-426655440000', new Uuid('123e4567-e89b-12d3-a456-426655440000')], - ]; - } - - /** - * @dataProvider provideValidUuid - */ - public function testTransform($output, $input) + public function testTransform() { $transformer = new UuidToStringTransformer(); - $input = new Uuid($input); - - $this->assertEquals($output, $transformer->transform($input)); + $this->assertEquals('123e4567-e89b-12d3-a456-426655440000', $transformer->transform(new UuidV1('123e4567-e89b-12d3-a456-426655440000'))); } public function testTransformEmpty() @@ -53,16 +42,11 @@ public function testTransformExpectsUuid() $transformer->transform('1234'); } - /** - * @dataProvider provideValidUuid - */ - public function testReverseTransform($input, $output) + public function testReverseTransform() { - $reverseTransformer = new UuidToStringTransformer(); - - $output = new Uuid($output); + $transformer = new UuidToStringTransformer(); - $this->assertEquals($output, $reverseTransformer->reverseTransform($input)); + $this->assertEquals(new UuidV1('123e4567-e89b-12d3-a456-426655440000'), $transformer->reverseTransform('123e4567-e89b-12d3-a456-426655440000')); } public function testReverseTransformEmpty() @@ -78,7 +62,7 @@ public function testReverseTransformExpectsString() $this->expectException(TransformationFailedException::class); - $reverseTransformer->reverseTransform(1234); + $reverseTransformer->reverseTransform(Uuid::fromString('123e4567-e89b-12d3-a456-426655440000')->toBase32()); } public function testReverseTransformExpectsValidUuidString() diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/PercentTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/PercentTypeTest.php index 875ac905f7689..9641d529f3e3b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/PercentTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/PercentTypeTest.php @@ -96,7 +96,7 @@ public function testSubmitWithoutRoundingMode() $form->submit('1.23456'); - $this->assertEquals(0.0123456, $form->getData()); + $this->assertEqualsWithDelta(0.0123456, $form->getData(), \PHP_FLOAT_EPSILON); } /** @@ -113,6 +113,6 @@ public function testSubmitWithNullRoundingMode() $form->submit('1.23456'); - $this->assertEquals(0.0123456, $form->getData()); + $this->assertEqualsWithDelta(0.0123456, $form->getData(), \PHP_FLOAT_EPSILON); } } diff --git a/src/Symfony/Component/HttpClient/Psr18Client.php b/src/Symfony/Component/HttpClient/Psr18Client.php index dbd8864a5bd29..c62df84ecd367 100644 --- a/src/Symfony/Component/HttpClient/Psr18Client.php +++ b/src/Symfony/Component/HttpClient/Psr18Client.php @@ -101,7 +101,11 @@ public function sendRequest(RequestInterface $request): ResponseInterface foreach ($response->getHeaders(false) as $name => $values) { foreach ($values as $value) { - $psrResponse = $psrResponse->withAddedHeader($name, $value); + try { + $psrResponse = $psrResponse->withAddedHeader($name, $value); + } catch (\InvalidArgumentException $e) { + // ignore invalid header + } } } diff --git a/src/Symfony/Component/HttpClient/RetryableHttpClient.php b/src/Symfony/Component/HttpClient/RetryableHttpClient.php index 4df466f4ceb31..9a5b503fa25fa 100644 --- a/src/Symfony/Component/HttpClient/RetryableHttpClient.php +++ b/src/Symfony/Component/HttpClient/RetryableHttpClient.php @@ -138,7 +138,7 @@ private function getDelayFromHeader(array $headers): ?int { if (null !== $after = $headers['retry-after'][0] ?? null) { if (is_numeric($after)) { - return (int) $after * 1000; + return (int) ($after * 1000); } if (false !== $time = strtotime($after)) { diff --git a/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php b/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php index 1ef36fc5bd09e..366d555ae03f9 100644 --- a/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/Psr18ClientTest.php @@ -13,10 +13,12 @@ use Nyholm\Psr7\Factory\Psr17Factory; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\NativeHttpClient; use Symfony\Component\HttpClient\Psr18Client; use Symfony\Component\HttpClient\Psr18NetworkException; use Symfony\Component\HttpClient\Psr18RequestException; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Contracts\HttpClient\Test\TestHttpServer; class Psr18ClientTest extends TestCase @@ -81,4 +83,22 @@ public function test404() $response = $client->sendRequest($factory->createRequest('GET', 'http://localhost:8057/404')); $this->assertSame(404, $response->getStatusCode()); } + + public function testInvalidHeaderResponse() + { + $responseHeaders = [ + // space in header name not allowed in RFC 7230 + ' X-XSS-Protection' => '0', + 'Cache-Control' => 'no-cache', + ]; + $response = new MockResponse('body', ['response_headers' => $responseHeaders]); + $this->assertArrayHasKey(' x-xss-protection', $response->getHeaders()); + + $client = new Psr18Client(new MockHttpClient($response)); + $request = $client->createRequest('POST', 'http://localhost:8057/post') + ->withBody($client->createStream('foo=0123456789')); + + $resultResponse = $client->sendRequest($request); + $this->assertCount(1, $resultResponse->getHeaders()); + } } diff --git a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php index 6bd9a1f15e788..85a03fd225183 100644 --- a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php @@ -187,4 +187,42 @@ public function testCancelOnTimeout() $response->cancel(); } } + + public function testRetryWithDelay() + { + $retryAfter = '0.46'; + + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', [ + 'http_code' => 503, + 'response_headers' => [ + 'retry-after' => $retryAfter, + ], + ]), + new MockResponse('', [ + 'http_code' => 200, + ]), + ]), + new GenericRetryStrategy(), + 1, + $logger = new class() extends TestLogger { + public $context = []; + + public function log($level, $message, array $context = []): void + { + $this->context = $context; + parent::log($level, $message, $context); + } + } + ); + + $client->request('GET', 'http://example.com/foo-bar')->getContent(); + + $delay = $logger->context['delay'] ?? null; + + $this->assertArrayHasKey('delay', $logger->context); + $this->assertNotNull($delay); + $this->assertSame((int) ($retryAfter * 1000), $delay); + } } diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index beb0eda0a5fb1..aff851956d4bc 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -206,15 +206,17 @@ public function setContentDisposition(string $disposition, string $filename = '' */ public function prepare(Request $request) { - if (!$this->headers->has('Content-Type')) { - $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); - } + parent::prepare($request); - if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) { - $this->setProtocolVersion('1.1'); + if ($this->isInformational() || $this->isEmpty()) { + $this->maxlen = 0; + + return $this; } - $this->ensureIEOverSSLCompatibility($request); + if (!$this->headers->has('Content-Type')) { + $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); + } $this->offset = 0; $this->maxlen = -1; @@ -222,6 +224,7 @@ public function prepare(Request $request) if (false === $fileSize = $this->file->getSize()) { return $this; } + $this->headers->remove('Transfer-Encoding'); $this->headers->set('Content-Length', $fileSize); if (!$this->headers->has('Accept-Ranges')) { @@ -291,6 +294,10 @@ public function prepare(Request $request) } } + if ($request->isMethod('HEAD')) { + $this->maxlen = 0; + } + return $this; } @@ -312,40 +319,42 @@ private function hasValidIfRangeHeader(?string $header): bool */ public function sendContent() { - if (!$this->isSuccessful()) { - return parent::sendContent(); - } + try { + if (!$this->isSuccessful()) { + return parent::sendContent(); + } - if (0 === $this->maxlen) { - return $this; - } + if (0 === $this->maxlen) { + return $this; + } - $out = fopen('php://output', 'w'); - $file = fopen($this->file->getPathname(), 'r'); + $out = fopen('php://output', 'w'); + $file = fopen($this->file->getPathname(), 'r'); - ignore_user_abort(true); + ignore_user_abort(true); - if (0 !== $this->offset) { - fseek($file, $this->offset); - } + if (0 !== $this->offset) { + fseek($file, $this->offset); + } - $length = $this->maxlen; - while ($length && !feof($file)) { - $read = ($length > $this->chunkSize) ? $this->chunkSize : $length; - $length -= $read; + $length = $this->maxlen; + while ($length && !feof($file)) { + $read = ($length > $this->chunkSize) ? $this->chunkSize : $length; + $length -= $read; - stream_copy_to_stream($file, $out, $read); + stream_copy_to_stream($file, $out, $read); - if (connection_aborted()) { - break; + if (connection_aborted()) { + break; + } } - } - fclose($out); - fclose($file); - - if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) { - unlink($this->file->getPathname()); + fclose($out); + fclose($file); + } finally { + if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) { + unlink($this->file->getPathname()); + } } return $this; diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 65f30a1a7fa94..2bf52ce4339df 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1689,7 +1689,8 @@ public function getLanguages() $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); $this->languages = []; - foreach ($languages as $lang => $acceptHeaderItem) { + foreach ($languages as $acceptHeaderItem) { + $lang = $acceptHeaderItem->getValue(); if (str_contains($lang, '-')) { $codes = explode('-', $lang); if ('i' === $codes[0]) { @@ -1727,7 +1728,7 @@ public function getCharsets() return $this->charsets; } - return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()); + return $this->charsets = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all())); } /** @@ -1741,7 +1742,7 @@ public function getEncodings() return $this->encodings; } - return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()); + return $this->encodings = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all())); } /** @@ -1755,7 +1756,7 @@ public function getAcceptableContentTypes() return $this->acceptableContentTypes; } - return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); + return $this->acceptableContentTypes = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all())); } /** diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index 88635bb490340..d5c8cb45cd4be 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -399,6 +399,7 @@ public function send() litespeed_finish_request(); } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { static::closeOutputBuffers(0, true); + flush(); } return $this; @@ -1245,7 +1246,6 @@ public static function closeOutputBuffers(int $targetLevel, bool $flush): void while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { if ($flush) { ob_end_flush(); - flush(); } else { ob_end_clean(); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php index c4db22bef25ae..fa5172ef61e36 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php @@ -396,6 +396,21 @@ public function testStream() $this->assertNull($response->headers->get('Content-Length')); } + public function testPrepareNotAddingContentTypeHeaderIfNoContentResponse() + { + $request = Request::create('/'); + $request->headers->set('If-Modified-Since', date('D, d M Y H:i:s').' GMT'); + + $response = new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']); + $response->setLastModified(new \DateTimeImmutable('-1 day')); + $response->isNotModified($request); + + $response->prepare($request); + + $this->assertSame(BinaryFileResponse::HTTP_NOT_MODIFIED, $response->getStatusCode()); + $this->assertFalse($response->headers->has('Content-Type')); + } + protected function provideResponse() { return new BinaryFileResponse(__DIR__.'/../README.md', 200, ['Content-Type' => 'application/octet-stream']); diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 5ee8c4467bd9e..df55628c6d15d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1610,6 +1610,20 @@ public function testGetLanguages() $this->assertEquals(['zh', 'cherokee'], $request->getLanguages()); } + public function testGetAcceptHeadersReturnString() + { + $request = new Request(); + $request->headers->set('Accept', '123'); + $request->headers->set('Accept-Charset', '123'); + $request->headers->set('Accept-Encoding', '123'); + $request->headers->set('Accept-Language', '123'); + + $this->assertSame(['123'], $request->getAcceptableContentTypes()); + $this->assertSame(['123'], $request->getCharsets()); + $this->assertSame(['123'], $request->getEncodings()); + $this->assertSame(['123'], $request->getLanguages()); + } + public function testGetRequestFormat() { $request = new Request(); diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index 523f5c957f188..951860a225668 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -110,7 +110,7 @@ public function collect(Request $request, Response $response, \Throwable $except 'session_metadata' => $sessionMetadata, 'session_attributes' => $sessionAttributes, 'session_usages' => array_values($this->sessionUsages), - 'stateless_check' => $this->requestStack && $this->requestStack->getMainRequest()->attributes->get('_stateless', false), + 'stateless_check' => $this->requestStack && ($mainRequest = $this->requestStack->getMainRequest()) && $mainRequest->attributes->get('_stateless', false), 'flashes' => $flashes, 'path_info' => $request->getPathInfo(), 'controller' => 'n/a', diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index efedd884ad5e1..93068eda13b69 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.12'; - public const VERSION_ID = 50412; + public const VERSION = '5.4.13'; + public const VERSION_ID = 50413; public const MAJOR_VERSION = 5; public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 12; + public const RELEASE_VERSION = 13; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '11/2024'; diff --git a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php index 3e7b1bd8843c7..0b5a780abd658 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php @@ -115,19 +115,7 @@ public function purge() */ public function read(string $token): ?Profile { - if (!$token || !file_exists($file = $this->getFilename($token))) { - return null; - } - - if (\function_exists('gzcompress')) { - $file = 'compress.zlib://'.$file; - } - - if (!$data = unserialize(file_get_contents($file))) { - return null; - } - - return $this->createProfileFromData($token, $data); + return $this->doRead($token); } /** @@ -169,14 +157,13 @@ public function write(Profile $profile): bool 'status_code' => $profile->getStatusCode(), ]; - $context = stream_context_create(); + $data = serialize($data); - if (\function_exists('gzcompress')) { - $file = 'compress.zlib://'.$file; - stream_context_set_option($context, 'zlib', 'level', 3); + if (\function_exists('gzencode')) { + $data = gzencode($data, 3); } - if (false === file_put_contents($file, serialize($data), 0, $context)) { + if (false === file_put_contents($file, $data, \LOCK_EX)) { return false; } @@ -291,21 +278,34 @@ protected function createProfileFromData(string $token, array $data, Profile $pa } foreach ($data['children'] as $token) { - if (!$token || !file_exists($file = $this->getFilename($token))) { - continue; + if (null !== $childProfile = $this->doRead($token, $profile)) { + $profile->addChild($childProfile); } + } - if (\function_exists('gzcompress')) { - $file = 'compress.zlib://'.$file; - } + return $profile; + } - if (!$childData = unserialize(file_get_contents($file))) { - continue; - } + private function doRead($token, Profile $profile = null): ?Profile + { + if (!$token || !file_exists($file = $this->getFilename($token))) { + return null; + } + + $h = fopen($file, 'r'); + flock($h, \LOCK_SH); + $data = stream_get_contents($h); + flock($h, \LOCK_UN); + fclose($h); - $profile->addChild($this->createProfileFromData($token, $childData, $profile)); + if (\function_exists('gzdecode')) { + $data = @gzdecode($data) ?: $data; } - return $profile; + if (!$data = unserialize($data)) { + return null; + } + + return $this->createProfileFromData($token, $data, $profile); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php index 0c576e00ed4dd..d7c8b302b628a 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php @@ -312,6 +312,15 @@ public function testStatelessCheck() $collector->lateCollect(); $this->assertTrue($collector->getStatelessCheck()); + + $requestStack = new RequestStack(); + $request = $this->createRequest(); + + $collector = new RequestDataCollector($requestStack); + $collector->collect($request, $response = $this->createResponse()); + $collector->lateCollect(); + + $this->assertFalse($collector->getStatelessCheck()); } public function testItHidesPassword() diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php index 4c1c624de2524..824d906340460 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php @@ -120,7 +120,7 @@ public function testRequestLocaleIsNotOverridden() public function testRequestPreferredLocaleFromAcceptLanguageHeader() { $request = Request::create('/'); - $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + $request->headers->set('Accept-Language', 'fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5'); $listener = new LocaleListener($this->requestStack, 'de', null, true, ['de', 'fr']); $event = $this->getEvent($request); @@ -133,7 +133,7 @@ public function testRequestPreferredLocaleFromAcceptLanguageHeader() public function testRequestSecondPreferredLocaleFromAcceptLanguageHeader() { $request = Request::create('/'); - $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + $request->headers->set('Accept-Language', 'fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5'); $listener = new LocaleListener($this->requestStack, 'de', null, true, ['de', 'en']); $event = $this->getEvent($request); @@ -146,7 +146,7 @@ public function testRequestSecondPreferredLocaleFromAcceptLanguageHeader() public function testDontUseAcceptLanguageHeaderIfNotEnabled() { $request = Request::create('/'); - $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + $request->headers->set('Accept-Language', 'fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5'); $listener = new LocaleListener($this->requestStack, 'de', null, false, ['de', 'en']); $event = $this->getEvent($request); @@ -159,7 +159,7 @@ public function testDontUseAcceptLanguageHeaderIfNotEnabled() public function testRequestUnavailablePreferredLocaleFromAcceptLanguageHeader() { $request = Request::create('/'); - $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + $request->headers->set('Accept-Language', 'fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5'); $listener = new LocaleListener($this->requestStack, 'de', null, true, ['de', 'it']); $event = $this->getEvent($request); @@ -172,7 +172,7 @@ public function testRequestUnavailablePreferredLocaleFromAcceptLanguageHeader() public function testRequestNoLocaleFromAcceptLanguageHeader() { $request = Request::create('/'); - $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + $request->headers->set('Accept-Language', 'fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5'); $listener = new LocaleListener($this->requestStack, 'de', null, true); $event = $this->getEvent($request); @@ -186,7 +186,7 @@ public function testRequestAttributeLocaleNotOverridenFromAcceptLanguageHeader() { $request = Request::create('/'); $request->attributes->set('_locale', 'it'); - $request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']); + $request->headers->set('Accept-Language', 'fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5'); $listener = new LocaleListener($this->requestStack, 'de', null, true, ['fr', 'en']); $event = $this->getEvent($request); diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php index f6711365c6fb6..5d87fe1c710af 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php @@ -159,7 +159,11 @@ private function connect() } } - $this->connection = ldap_connect($this->config['connection_string']); + if (false === $connection = ldap_connect($this->config['connection_string'])) { + throw new LdapException('Invalid connection string: '.$this->config['connection_string']); + } else { + $this->connection = $connection; + } foreach ($this->config['options'] as $name => $value) { if (!\in_array(ConnectionOptions::getOption($name), self::PRECONNECT_OPTIONS, true)) { @@ -167,10 +171,6 @@ private function connect() } } - if (false === $this->connection) { - throw new LdapException('Could not connect to Ldap server: '.ldap_error($this->connection)); - } - if ('tls' === $this->config['encryption'] && false === @ldap_start_tls($this->connection)) { throw new LdapException('Could not initiate TLS connection: '.ldap_error($this->connection)); } diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php index 5f8d3ba0d8180..c54b050b92963 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\LogicException; use Symfony\Component\Mailer\Exception\TransportException; use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport; use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream; @@ -133,6 +134,35 @@ public function testWriteEncodedRecipientAndSenderAddresses() $this->assertContains("RCPT TO:\r\n", $stream->getCommands()); $this->assertContains("RCPT TO:\r\n", $stream->getCommands()); } + + public function testAssertResponseCodeNoCodes() + { + $this->expectException(LogicException::class); + $this->invokeAssertResponseCode('response', []); + } + + public function testAssertResponseCodeWithEmptyResponse() + { + $this->expectException(TransportException::class); + $this->expectExceptionMessage('Expected response code "220" but got empty code.'); + $this->invokeAssertResponseCode('', [220]); + } + + public function testAssertResponseCodeWithNotValidCode() + { + $this->expectException(TransportException::class); + $this->expectExceptionMessage('Expected response code "220" but got code "550", with message "550 Access Denied".'); + $this->expectExceptionCode(550); + $this->invokeAssertResponseCode('550 Access Denied', [220]); + } + + private function invokeAssertResponseCode(string $response, array $codes): void + { + $transport = new SmtpTransport($this->getMockForAbstractClass(AbstractStream::class)); + $m = new \ReflectionMethod($transport, 'assertResponseCode'); + $m->setAccessible(true); + $m->invoke($transport, $response, $codes); + } } class DummyStream extends AbstractStream diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php index a597f8321a457..1dcb53f152e7a 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php @@ -100,7 +100,9 @@ public function addAuthenticator(AuthenticatorInterface $authenticator): void protected function doHeloCommand(): void { - $capabilities = $this->callHeloCommand(); + if (!$capabilities = $this->callHeloCommand()) { + return; + } /** @var SocketStream $stream */ $stream = $this->getStream(); @@ -129,6 +131,8 @@ private function callHeloCommand(): array } catch (TransportExceptionInterface $e) { try { parent::doHeloCommand(); + + return []; } catch (TransportExceptionInterface $ex) { if (!$ex->getCode()) { throw $e; diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php index ae8f104098ba8..92af6aaf648af 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php @@ -303,7 +303,8 @@ private function assertResponseCode(string $response, array $codes): void 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); + + throw new TransportException(sprintf('Expected response code "%s" but got ', implode('/', $codes)).$codeStr.$responseStr.'.', $code ?: 0); } } diff --git a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php index a6a628be30628..e5a9d59eaa3bb 100644 --- a/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Amqp/Transport/Connection.php @@ -195,11 +195,11 @@ public static function fromDsn(string $dsn, array $options = [], AmqpFactory $am self::validateOptions($amqpOptions); if (isset($parsedUrl['user'])) { - $amqpOptions['login'] = $parsedUrl['user']; + $amqpOptions['login'] = urldecode($parsedUrl['user']); } if (isset($parsedUrl['pass'])) { - $amqpOptions['password'] = $parsedUrl['pass']; + $amqpOptions['password'] = urldecode($parsedUrl['pass']); } if (!isset($amqpOptions['queues'])) { diff --git a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php index 466d52fe24be1..ffcac3e2a96b2 100644 --- a/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php @@ -238,8 +238,8 @@ public static function fromDsn(string $dsn, array $redisOptions = [], $redis = n ]; if (isset($parsedUrl['host'])) { - $pass = '' !== ($parsedUrl['pass'] ?? '') ? $parsedUrl['pass'] : null; - $user = '' !== ($parsedUrl['user'] ?? '') ? $parsedUrl['user'] : null; + $pass = '' !== ($parsedUrl['pass'] ?? '') ? urldecode($parsedUrl['pass']) : null; + $user = '' !== ($parsedUrl['user'] ?? '') ? urldecode($parsedUrl['user']) : null; $connectionCredentials = [ 'host' => $parsedUrl['host'] ?? '127.0.0.1', 'port' => $parsedUrl['port'] ?? 6379, diff --git a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php index 1c5bb63634ccb..f423889713972 100644 --- a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php +++ b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php @@ -235,15 +235,20 @@ private function guessHandledClasses(\ReflectionClass $handlerClass, string $ser if ($type instanceof \ReflectionUnionType) { $types = []; + $invalidTypes = []; foreach ($type->getTypes() as $type) { if (!$type->isBuiltin()) { $types[] = (string) $type; + } else { + $invalidTypes[] = (string) $type; } } if ($types) { return $types; } + + throw new RuntimeException(sprintf('Invalid handler service "%s": type-hint of argument "$%s" in method "%s::__invoke()" must be a class , "%s" given.', $serviceId, $parameters[0]->getName(), $handlerClass->getName(), implode('|', $invalidTypes))); } if ($type->isBuiltin()) { diff --git a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php index 5f49bbb3dd58e..3d29497d34c1a 100644 --- a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php +++ b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php @@ -38,6 +38,7 @@ use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\StackInterface; +use Symfony\Component\Messenger\Tests\Fixtures\ChildDummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\DummyCommand; use Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; @@ -47,6 +48,8 @@ use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler; use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage; use Symfony\Component\Messenger\Tests\Fixtures\TaggedDummyHandler; +use Symfony\Component\Messenger\Tests\Fixtures\UnionBuiltinTypeArgumentHandler; +use Symfony\Component\Messenger\Tests\Fixtures\UnionTypeArgumentHandler; use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface; class MessengerPassTest extends TestCase @@ -540,6 +543,43 @@ public function testBuiltinArgumentTypeHandler() (new MessengerPass())->process($container); } + /** + * @requires PHP 8 + */ + public function testUnionTypeArgumentsTypeHandler() + { + $container = $this->getContainerBuilder($busId = 'message_bus'); + $container + ->register(UnionTypeArgumentHandler::class, UnionTypeArgumentHandler::class) + ->addTag('messenger.message_handler') + ; + + (new MessengerPass())->process($container); + + $handlersMapping = $container->getDefinition($busId.'.messenger.handlers_locator')->getArgument(0); + + $this->assertArrayHasKey(ChildDummyMessage::class, $handlersMapping); + $this->assertArrayHasKey(DummyMessage::class, $handlersMapping); + $this->assertHandlerDescriptor($container, $handlersMapping, ChildDummyMessage::class, [UnionTypeArgumentHandler::class]); + $this->assertHandlerDescriptor($container, $handlersMapping, DummyMessage::class, [UnionTypeArgumentHandler::class]); + } + + /** + * @requires PHP 8 + */ + public function testUnionBuiltinArgumentTypeHandler() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage(sprintf('Invalid handler service "%s": type-hint of argument "$message" in method "%s::__invoke()" must be a class , "string|int" given.', UnionBuiltinTypeArgumentHandler::class, UnionBuiltinTypeArgumentHandler::class)); + $container = $this->getContainerBuilder(); + $container + ->register(UnionBuiltinTypeArgumentHandler::class, UnionBuiltinTypeArgumentHandler::class) + ->addTag('messenger.message_handler') + ; + + (new MessengerPass())->process($container); + } + public function testNeedsToHandleAtLeastOneMessage() { $this->expectException(RuntimeException::class); diff --git a/src/Symfony/Component/Messenger/Tests/Fixtures/UnionBuiltinTypeArgumentHandler.php b/src/Symfony/Component/Messenger/Tests/Fixtures/UnionBuiltinTypeArgumentHandler.php new file mode 100644 index 0000000000000..6061651de187b --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Fixtures/UnionBuiltinTypeArgumentHandler.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Tests\Fixtures; + +class UnionBuiltinTypeArgumentHandler +{ + public function __invoke(string|int $message): void + { + } +} diff --git a/src/Symfony/Component/Messenger/Tests/Fixtures/UnionTypeArgumentHandler.php b/src/Symfony/Component/Messenger/Tests/Fixtures/UnionTypeArgumentHandler.php new file mode 100644 index 0000000000000..85be3662ac974 --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Fixtures/UnionTypeArgumentHandler.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Tests\Fixtures; + +class UnionTypeArgumentHandler +{ + public function __invoke(ChildDummyMessage|DummyMessage $message): void + { + } +} diff --git a/src/Symfony/Component/Mime/Email.php b/src/Symfony/Component/Mime/Email.php index a33dcb46188a3..bd0a476c4f778 100644 --- a/src/Symfony/Component/Mime/Email.php +++ b/src/Symfony/Component/Mime/Email.php @@ -463,7 +463,7 @@ private function generateBody(): AbstractPart $this->ensureValidity(); - [$htmlPart, $attachmentParts, $inlineParts] = $this->prepareParts(); + [$htmlPart, $otherParts, $relatedParts] = $this->prepareParts(); $part = null === $this->text ? null : new TextPart($this->text, $this->textCharset); if (null !== $htmlPart) { @@ -474,15 +474,15 @@ private function generateBody(): AbstractPart } } - if ($inlineParts) { - $part = new RelatedPart($part, ...$inlineParts); + if ($relatedParts) { + $part = new RelatedPart($part, ...$relatedParts); } - if ($attachmentParts) { + if ($otherParts) { if ($part) { - $part = new MixedPart($part, ...$attachmentParts); + $part = new MixedPart($part, ...$otherParts); } else { - $part = new MixedPart(...$attachmentParts); + $part = new MixedPart(...$otherParts); } } @@ -502,42 +502,44 @@ private function prepareParts(): ?array } // usage of reflection is a temporary workaround for missing getters that will be added in 6.2 - $dispositionRef = new \ReflectionProperty(TextPart::class, 'disposition'); - $dispositionRef->setAccessible(true); $nameRef = new \ReflectionProperty(TextPart::class, 'name'); $nameRef->setAccessible(true); - $attachmentParts = $inlineParts = []; + $otherParts = $relatedParts = []; foreach ($this->attachments as $attachment) { $part = $this->createDataPart($attachment); if (isset($attachment['part'])) { $attachment['name'] = $nameRef->getValue($part); } + $related = false; foreach ($names as $name) { if ($name !== $attachment['name']) { continue; } - if (isset($inlineParts[$name])) { + if (isset($relatedParts[$name])) { continue 2; } $part->setDisposition('inline'); - $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html); + $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count); + if ($count) { + $related = true; + } $part->setName($part->getContentId()); break; } - if ('inline' === $dispositionRef->getValue($part)) { - $inlineParts[$attachment['name']] = $part; + if ($related) { + $relatedParts[$attachment['name']] = $part; } else { - $attachmentParts[] = $part; + $otherParts[] = $part; } } if (null !== $htmlPart) { $htmlPart = new TextPart($html, $this->htmlCharset, 'html'); } - return [$htmlPart, $attachmentParts, array_values($inlineParts)]; + return [$htmlPart, $otherParts, array_values($relatedParts)]; } private function createDataPart(array $attachment): DataPart diff --git a/src/Symfony/Component/Mime/Part/TextPart.php b/src/Symfony/Component/Mime/Part/TextPart.php index 4afb6560aec0a..bfe41c0aab235 100644 --- a/src/Symfony/Component/Mime/Part/TextPart.php +++ b/src/Symfony/Component/Mime/Part/TextPart.php @@ -197,6 +197,7 @@ public function __sleep() // convert resources to strings for serialization if (null !== $this->seekable) { $this->body = $this->getBody(); + $this->seekable = null; } $this->_headers = $this->getHeaders(); diff --git a/src/Symfony/Component/Mime/Tests/EmailTest.php b/src/Symfony/Component/Mime/Tests/EmailTest.php index d16667c781c78..516a589180823 100644 --- a/src/Symfony/Component/Mime/Tests/EmailTest.php +++ b/src/Symfony/Component/Mime/Tests/EmailTest.php @@ -309,6 +309,17 @@ public function testGenerateBodyWithHtmlContentAndAttachedFile() $this->assertEquals(new MixedPart($html, $filePart), $e->getBody()); } + public function testGenerateBodyWithHtmlContentAndInlineImageNotreferenced() + { + [$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts(); + $imagePart = new DataPart($image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r')); + $imagePart->asInline(); + $e = (new Email())->from('me@example.com')->to('you@example.com'); + $e->embed($image); + $e->html('html content'); + $this->assertEquals(new MixedPart($html, $imagePart), $e->getBody()); + } + public function testGenerateBodyWithAttachedFileOnly() { [$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts(); @@ -317,6 +328,24 @@ public function testGenerateBodyWithAttachedFileOnly() $this->assertEquals(new MixedPart($filePart), $e->getBody()); } + public function testGenerateBodyWithInlineImageOnly() + { + $imagePart = new DataPart($image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r')); + $imagePart->asInline(); + $e = (new Email())->from('me@example.com')->to('you@example.com'); + $e->embed($image); + $this->assertEquals(new MixedPart($imagePart), $e->getBody()); + } + + public function testGenerateBodyWithEmbeddedImageOnly() + { + $imagePart = new DataPart($image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r')); + $e = (new Email())->from('me@example.com')->to('you@example.com'); + $e->embed($image); + $imagePart->asInline(); + $this->assertEquals(new MixedPart($imagePart), $e->getBody()); + } + public function testGenerateBodyWithTextAndHtmlContentAndAttachedFile() { [$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts(); diff --git a/src/Symfony/Component/Mime/Tests/Part/TextPartTest.php b/src/Symfony/Component/Mime/Tests/Part/TextPartTest.php index c3818b883d465..ea14fe29f88af 100644 --- a/src/Symfony/Component/Mime/Tests/Part/TextPartTest.php +++ b/src/Symfony/Component/Mime/Tests/Part/TextPartTest.php @@ -87,6 +87,8 @@ public function testSerialize() $p = new TextPart($r); $p->getHeaders()->addTextHeader('foo', 'bar'); $expected = clone $p; - $this->assertEquals($expected->toString(), unserialize(serialize($p))->toString()); + $n = unserialize(serialize($p)); + $this->assertEquals($expected->toString(), $p->toString()); + $this->assertEquals($expected->toString(), $n->toString()); } } diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/stella-maris-clock/ClockInterface.php b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/stella-maris-clock/ClockInterface.php new file mode 100644 index 0000000000000..d8b2e86692260 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/stella-maris-clock/ClockInterface.php @@ -0,0 +1,23 @@ + and ClockInterfaceContributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +namespace StellaMaris\Clock; + +use DateTimeImmutable; + +interface ClockInterface +{ + /** + * Return the current point in time as a DateTimeImmutable object + */ + public function now() : DateTimeImmutable; +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/stella-maris-clock/composer.json b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/stella-maris-clock/composer.json new file mode 100644 index 0000000000000..fb838caed6e88 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/Tests/stella-maris-clock/composer.json @@ -0,0 +1,17 @@ +{ + "name": "stella-maris/clock", + "description": "A local fork to workaround gitlab failing to serve the package reliably", + "homepage": "https://gitlab.com/stella-maris/clock", + "license": "MIT", + "authors": [ + { + "name": "Andreas Heigl", + "role": "Maintainer" + } + ], + "autoload": { + "psr-4": { + "StellaMaris\\Clock\\": "" + } + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json b/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json index ed13323a28166..eb7532353c4c3 100644 --- a/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json +++ b/src/Symfony/Component/Notifier/Bridge/Mercure/composer.json @@ -28,5 +28,16 @@ "/Tests/" ] }, + "repositories": [ + { + "type": "path", + "url": "Tests/stella-maris-clock", + "options": { + "versions": { + "stella-maris/clock": "0.1.x-dev" + } + } + } + ], "minimum-stability": "dev" } diff --git a/src/Symfony/Component/Security/Core/Resources/translations/security.nb.xlf b/src/Symfony/Component/Security/Core/Resources/translations/security.nb.xlf index 2d3a87c793ddf..7e75773798bf3 100644 --- a/src/Symfony/Component/Security/Core/Resources/translations/security.nb.xlf +++ b/src/Symfony/Component/Security/Core/Resources/translations/security.nb.xlf @@ -70,6 +70,14 @@ Invalid or expired login link. Ugyldig eller utløpt påloggingskobling. + + Too many failed login attempts, please try again in %minutes% minute. + For mange mislykkede påloggingsforsøk, prøv igjen om %minutes% minutt. + + + Too many failed login attempts, please try again in %minutes% minutes. + For mange mislykkede påloggingsforsøk, prøv igjen om %minutes% minutter. + diff --git a/src/Symfony/Component/Security/Core/Resources/translations/security.no.xlf b/src/Symfony/Component/Security/Core/Resources/translations/security.no.xlf index 2d3a87c793ddf..7e75773798bf3 100644 --- a/src/Symfony/Component/Security/Core/Resources/translations/security.no.xlf +++ b/src/Symfony/Component/Security/Core/Resources/translations/security.no.xlf @@ -70,6 +70,14 @@ Invalid or expired login link. Ugyldig eller utløpt påloggingskobling. + + Too many failed login attempts, please try again in %minutes% minute. + For mange mislykkede påloggingsforsøk, prøv igjen om %minutes% minutt. + + + Too many failed login attempts, please try again in %minutes% minutes. + For mange mislykkede påloggingsforsøk, prøv igjen om %minutes% minutter. + diff --git a/src/Symfony/Component/Security/Core/Signature/SignatureHasher.php b/src/Symfony/Component/Security/Core/Signature/SignatureHasher.php index b578d77c9b638..e2b5dcb31f655 100644 --- a/src/Symfony/Component/Security/Core/Signature/SignatureHasher.php +++ b/src/Symfony/Component/Security/Core/Signature/SignatureHasher.php @@ -31,9 +31,9 @@ class SignatureHasher private $maxUses; /** - * @param array $signatureProperties properties of the User; the hash is invalidated if these properties change - * @param ExpiredSignatureStorage|null $expiredSignaturesStorage if provided, secures a sequence of hashes that are expired - * @param int|null $maxUses used together with $expiredSignatureStorage to allow a maximum usage of a hash + * @param array $signatureProperties Properties of the User; the hash is invalidated if these properties change + * @param ExpiredSignatureStorage|null $expiredSignaturesStorage If provided, secures a sequence of hashes that are expired + * @param int|null $maxUses Used together with $expiredSignatureStorage to allow a maximum usage of a hash */ public function __construct(PropertyAccessorInterface $propertyAccessor, array $signatureProperties, string $secret, ExpiredSignatureStorage $expiredSignaturesStorage = null, int $maxUses = null) { @@ -47,8 +47,8 @@ public function __construct(PropertyAccessorInterface $propertyAccessor, array $ /** * Verifies the hash using the provided user and expire time. * - * @param int $expires the expiry time as a unix timestamp - * @param string $hash the plaintext hash provided by the request + * @param int $expires The expiry time as a unix timestamp + * @param string $hash The plaintext hash provided by the request * * @throws InvalidSignatureException If the signature does not match the provided parameters * @throws ExpiredSignatureException If the signature is no longer valid @@ -75,7 +75,7 @@ public function verifySignatureHash(UserInterface $user, int $expires, string $h /** * Computes the secure hash for the provided user and expire time. * - * @param int $expires the expiry time as a unix timestamp + * @param int $expires The expiry time as a unix timestamp */ public function computeSignatureHash(UserInterface $user, int $expires): string { diff --git a/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php b/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php index a02fb13b831a8..f31d7a31faab5 100644 --- a/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; @@ -36,7 +37,7 @@ abstract protected function getLoginUrl(); /** * Override to change what happens after a bad username/password is submitted. * - * @return RedirectResponse + * @return Response */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception) { @@ -58,7 +59,7 @@ public function supportsRememberMe() * Override to control what happens when the user hits a secure page * but isn't logged in yet. * - * @return RedirectResponse + * @return Response */ public function start(Request $request, AuthenticationException $authException = null) { diff --git a/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php index 25413b73cbc0f..c234cb4df4868 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php @@ -41,7 +41,7 @@ abstract protected function getLoginUrl(Request $request): string; */ public function supports(Request $request): bool { - return $request->isMethod('POST') && $this->getLoginUrl($request) === $request->getPathInfo(); + return $request->isMethod('POST') && $this->getLoginUrl($request) === $request->getBaseUrl().$request->getPathInfo(); } /** diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php index b55e8aa6becf9..e245ad5f8bd3c 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php @@ -31,13 +31,13 @@ final class LoginLinkHandler implements LoginLinkHandlerInterface private $urlGenerator; private $userProvider; private $options; - private $signatureHashUtil; + private $signatureHasher; - public function __construct(UrlGeneratorInterface $urlGenerator, UserProviderInterface $userProvider, SignatureHasher $signatureHashUtil, array $options) + public function __construct(UrlGeneratorInterface $urlGenerator, UserProviderInterface $userProvider, SignatureHasher $signatureHasher, array $options) { $this->urlGenerator = $urlGenerator; $this->userProvider = $userProvider; - $this->signatureHashUtil = $signatureHashUtil; + $this->signatureHasher = $signatureHasher; $this->options = array_merge([ 'route_name' => null, 'lifetime' => 600, @@ -53,7 +53,7 @@ public function createLoginLink(UserInterface $user, Request $request = null): L // @deprecated since Symfony 5.3, change to $user->getUserIdentifier() in 6.0 'user' => method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), 'expires' => $expires, - 'hash' => $this->signatureHashUtil->computeSignatureHash($user, $expires), + 'hash' => $this->signatureHasher->computeSignatureHash($user, $expires), ]; if ($request) { @@ -101,7 +101,7 @@ public function consumeLoginLink(Request $request): UserInterface $expires = $request->get('expires'); try { - $this->signatureHashUtil->verifySignatureHash($user, $expires, $hash); + $this->signatureHasher->verifySignatureHash($user, $expires, $hash); } catch (ExpiredSignatureException $e) { throw new ExpiredLoginLinkException(ucfirst(str_ireplace('signature', 'login link', $e->getMessage())), 0, $e); } catch (InvalidSignatureException $e) { diff --git a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeHandler.php b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeHandler.php index 97918c86cb8b4..d9c9d8327a197 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeHandler.php +++ b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeHandler.php @@ -53,7 +53,7 @@ public function __construct(UserProviderInterface $userProvider, RequestStack $r * - Create a new remember-me cookie to be sent with the response (using {@see createCookie()}); * - If you store the token somewhere else (e.g. in a database), invalidate the stored token. * - * @throws AuthenticationException throw this exception if the remember me details are not accepted + * @throws AuthenticationException If the remember-me details are not accepted */ abstract protected function processRememberMe(RememberMeDetails $rememberMeDetails, UserInterface $user): void; diff --git a/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php b/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php index 8a5db07e5e8ab..8d9360355282a 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php +++ b/src/Symfony/Component/Security/Http/RememberMe/PersistentRememberMeHandler.php @@ -75,7 +75,6 @@ public function processRememberMe(RememberMeDetails $rememberMeDetails, UserInte if ($this->tokenVerifier) { $isTokenValid = $this->tokenVerifier->verifyToken($persistentToken, $tokenValue); - $tokenValue = $persistentToken->getTokenValue(); } else { $isTokenValid = hash_equals($persistentToken->getTokenValue(), $tokenValue); } @@ -96,9 +95,9 @@ public function processRememberMe(RememberMeDetails $rememberMeDetails, UserInte $this->tokenVerifier->updateExistingToken($persistentToken, $tokenValue, $tokenLastUsed); } $this->tokenProvider->updateToken($series, $tokenValue, $tokenLastUsed); - } - $this->createCookie($rememberMeDetails->withValue($series.':'.$tokenValue)); + $this->createCookie($rememberMeDetails->withValue($series.':'.$tokenValue)); + } } /** diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AbstractLoginFormAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AbstractLoginFormAuthenticatorTest.php new file mode 100644 index 0000000000000..3a50c131cd522 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AbstractLoginFormAuthenticatorTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Tests\Authenticator; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; + +class AbstractLoginFormAuthenticatorTest extends TestCase +{ + /** + * @dataProvider provideSupportsData + */ + public function testSupports(string $loginUrl, Request $request, bool $expected) + { + $authenticator = new ConcreteFormAuthenticator($loginUrl); + $this->assertSame($expected, $authenticator->supports($request)); + } + + public function provideSupportsData(): iterable + { + yield [ + '/login', + Request::create('http://localhost/login', Request::METHOD_POST, [], [], [], [ + 'DOCUMENT_ROOT' => '/var/www/app/public', + 'PHP_SELF' => '/index.php', + 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', + 'SCRIPT_NAME' => '/index.php', + ]), + true, + ]; + yield [ + '/login', + Request::create('http://localhost/somepath', Request::METHOD_POST, [], [], [], [ + 'DOCUMENT_ROOT' => '/var/www/app/public', + 'PHP_SELF' => '/index.php', + 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', + 'SCRIPT_NAME' => '/index.php', + ]), + false, + ]; + yield [ + '/folder/login', + Request::create('http://localhost/folder/login', Request::METHOD_POST, [], [], [], [ + 'DOCUMENT_ROOT' => '/var/www/app/public', + 'PHP_SELF' => '/folder/index.php', + 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', + 'SCRIPT_NAME' => '/folder/index.php', + ]), + true, + ]; + yield [ + '/folder/login', + Request::create('http://localhost/folder/somepath', Request::METHOD_POST, [], [], [], [ + 'DOCUMENT_ROOT' => '/var/www/app/public', + 'PHP_SELF' => '/folder/index.php', + 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', + 'SCRIPT_NAME' => '/folder/index.php', + ]), + false, + ]; + yield [ + '/index.php/login', + Request::create('http://localhost/index.php/login', Request::METHOD_POST, [], [], [], [ + 'DOCUMENT_ROOT' => '/var/www/app/public', + 'PHP_SELF' => '/index.php', + 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', + 'SCRIPT_NAME' => '/index.php', + ]), + true, + ]; + yield [ + '/index.php/login', + Request::create('http://localhost/index.php/somepath', Request::METHOD_POST, [], [], [], [ + 'DOCUMENT_ROOT' => '/var/www/app/public', + 'PHP_SELF' => '/index.php', + 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', + 'SCRIPT_NAME' => '/index.php', + ]), + false, + ]; + } +} + +class ConcreteFormAuthenticator extends AbstractLoginFormAuthenticator +{ + private $loginUrl; + + public function __construct(string $loginUrl) + { + $this->loginUrl = $loginUrl; + } + + protected function getLoginUrl(Request $request): string + { + return $this->loginUrl; + } + + public function authenticate(Request $request) + { + return new SelfValidatingPassport(new UserBadge('dummy')); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } +} diff --git a/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentRememberMeHandlerTest.php b/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentRememberMeHandlerTest.php index 770a1c634abe6..4e2c0980ba0aa 100644 --- a/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentRememberMeHandlerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentRememberMeHandlerTest.php @@ -125,18 +125,7 @@ public function testConsumeRememberMeCookieValidByValidatorWithoutUpdate() $rememberMeDetails = new RememberMeDetails(InMemoryUser::class, 'wouter', 360, 'series1:oldTokenValue'); $handler->consumeRememberMeCookie($rememberMeDetails); - // assert that the cookie has been updated with a new base64 encoded token value - $this->assertTrue($this->request->attributes->has(ResponseListener::COOKIE_ATTR_NAME)); - - /** @var Cookie $cookie */ - $cookie = $this->request->attributes->get(ResponseListener::COOKIE_ATTR_NAME); - - $cookieParts = explode(':', base64_decode($cookie->getValue()), 4); - - $this->assertSame(InMemoryUser::class, $cookieParts[0]); // class - $this->assertSame(base64_encode('wouter'), $cookieParts[1]); // identifier - $this->assertSame('360', $cookieParts[2]); // expire - $this->assertSame('series1:tokenvalue', $cookieParts[3]); // value + $this->assertFalse($this->request->attributes->has(ResponseListener::COOKIE_ATTR_NAME)); } public function testConsumeRememberMeCookieInvalidToken() diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php b/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php index cf4a89ca1ab5f..d460331a3dd51 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php @@ -23,10 +23,15 @@ class JsonEncoder implements EncoderInterface, DecoderInterface protected $encodingImpl; protected $decodingImpl; - public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null) + private $defaultContext = [ + JsonDecode::ASSOCIATIVE => true, + ]; + + public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null, array $defaultContext = []) { - $this->encodingImpl = $encodingImpl ?? new JsonEncode(); - $this->decodingImpl = $decodingImpl ?? new JsonDecode([JsonDecode::ASSOCIATIVE => true]); + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); + $this->encodingImpl = $encodingImpl ?? new JsonEncode($this->defaultContext); + $this->decodingImpl = $decodingImpl ?? new JsonDecode($this->defaultContext); } /** @@ -34,6 +39,8 @@ public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodin */ public function encode($data, string $format, array $context = []) { + $context = array_merge($this->defaultContext, $context); + return $this->encodingImpl->encode($data, self::FORMAT, $context); } @@ -42,6 +49,8 @@ public function encode($data, string $format, array $context = []) */ public function decode(string $data, string $format, array $context = []) { + $context = array_merge($this->defaultContext, $context); + return $this->decodingImpl->decode($data, self::FORMAT, $context); } diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncoderTest.php index c1d7d496cce71..6cd1f82b1ab6c 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncoderTest.php @@ -66,6 +66,22 @@ public function testOptions() $this->assertEquals($expected, $this->serializer->serialize($arr, 'json'), 'Context should not be persistent'); } + public function testWithDefaultContext() + { + $defaultContext = [ + 'json_encode_options' => \JSON_UNESCAPED_UNICODE, + 'json_decode_associative' => false, + ]; + + $encoder = new JsonEncoder(null, null, $defaultContext); + + $data = new \stdClass(); + $data->msg = '你好'; + + $this->assertEquals('{"msg":"你好"}', $json = $encoder->encode($data, 'json')); + $this->assertEquals($data, $encoder->decode($json, 'json')); + } + public function testEncodeNotUtf8WithoutPartialOnError() { $this->expectException(UnexpectedValueException::class); diff --git a/src/Symfony/Component/Stopwatch/Tests/StopwatchPeriodTest.php b/src/Symfony/Component/Stopwatch/Tests/StopwatchPeriodTest.php index e01849d474869..81010a79413fd 100644 --- a/src/Symfony/Component/Stopwatch/Tests/StopwatchPeriodTest.php +++ b/src/Symfony/Component/Stopwatch/Tests/StopwatchPeriodTest.php @@ -40,7 +40,7 @@ public function testGetEndTime($end, $useMorePrecision, $expected) public function testGetDuration($start, $end, $useMorePrecision, $duration) { $period = new StopwatchPeriod($start, $end, $useMorePrecision); - $this->assertSame($duration, $period->getDuration()); + $this->assertEqualsWithDelta($duration, $period->getDuration(), \PHP_FLOAT_EPSILON); } public function provideTimeValues() diff --git a/src/Symfony/Component/String/AbstractUnicodeString.php b/src/Symfony/Component/String/AbstractUnicodeString.php index 6fd418e65afe9..1bc6f88fdac22 100644 --- a/src/Symfony/Component/String/AbstractUnicodeString.php +++ b/src/Symfony/Component/String/AbstractUnicodeString.php @@ -162,7 +162,7 @@ public function ascii(array $rules = []): self public function camel(): parent { $str = clone $this; - $str->string = str_replace(' ', '', preg_replace_callback('/\b./u', static function ($m) use (&$i) { + $str->string = str_replace(' ', '', preg_replace_callback('/\b.(?![A-Z]{2,})/u', static function ($m) use (&$i) { return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); }, preg_replace('/[^\pL0-9]++/u', ' ', $this->string))); diff --git a/src/Symfony/Component/String/ByteString.php b/src/Symfony/Component/String/ByteString.php index d9ee3edb52cb2..626d8c1bb31fe 100644 --- a/src/Symfony/Component/String/ByteString.php +++ b/src/Symfony/Component/String/ByteString.php @@ -103,7 +103,10 @@ public function append(string ...$suffix): parent public function camel(): parent { $str = clone $this; - $str->string = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string)))); + + $parts = explode(' ', trim(ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string)))); + $parts[0] = 1 !== \strlen($parts[0]) && ctype_upper($parts[0]) ? $parts[0] : lcfirst($parts[0]); + $str->string = implode('', $parts); return $str; } diff --git a/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php b/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php index b3c3d9086e1e6..a0cf2068f9476 100644 --- a/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php +++ b/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php @@ -1042,11 +1042,13 @@ public static function provideCamel() return [ ['', ''], ['xY', 'x_y'], + ['xuYo', 'xu_yo'], ['symfonyIsGreat', 'symfony_is_great'], ['symfony5IsGreat', 'symfony_5_is_great'], ['symfonyIsGreat', 'Symfony is great'], ['symfonyIsAGreatFramework', 'Symfony is a great framework'], ['symfonyIsGREAT', '*Symfony* is GREAT!!'], + ['SYMFONY', 'SYMFONY'], ]; } @@ -1066,6 +1068,7 @@ public static function provideSnake() ['', ''], ['x_y', 'x_y'], ['x_y', 'X_Y'], + ['xu_yo', 'xu_yo'], ['symfony_is_great', 'symfonyIsGreat'], ['symfony5_is_great', 'symfony5IsGreat'], ['symfony5is_great', 'symfony5isGreat'], @@ -1073,6 +1076,7 @@ public static function provideSnake() ['symfony_is_a_great_framework', 'symfonyIsAGreatFramework'], ['symfony_is_great', 'symfonyIsGREAT'], ['symfony_is_really_great', 'symfonyIsREALLYGreat'], + ['symfony', 'SYMFONY'], ]; } diff --git a/src/Symfony/Component/Uid/Tests/UlidTest.php b/src/Symfony/Component/Uid/Tests/UlidTest.php index 9a8ee9a78abe0..50801a840c326 100644 --- a/src/Symfony/Component/Uid/Tests/UlidTest.php +++ b/src/Symfony/Component/Uid/Tests/UlidTest.php @@ -26,11 +26,16 @@ public function testGenerate() { $a = new Ulid(); $b = new Ulid(); + usleep(-10000); + $c = new Ulid(); $this->assertSame(0, strncmp($a, $b, 20)); + $this->assertSame(0, strncmp($a, $c, 20)); $a = base_convert(strtr(substr($a, -6), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'), 32, 10); $b = base_convert(strtr(substr($b, -6), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'), 32, 10); + $c = base_convert(strtr(substr($c, -6), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'), 32, 10); $this->assertSame(1, $b - $a); + $this->assertSame(1, $c - $b); } public function testWithInvalidUlid() diff --git a/src/Symfony/Component/Uid/Tests/UuidTest.php b/src/Symfony/Component/Uid/Tests/UuidTest.php index dad559f5a31ce..3e3c36c02ab03 100644 --- a/src/Symfony/Component/Uid/Tests/UuidTest.php +++ b/src/Symfony/Component/Uid/Tests/UuidTest.php @@ -44,6 +44,32 @@ public function provideInvalidUuids(): iterable yield ['these are just thirty-six characters']; } + /** + * @dataProvider provideInvalidVariant + */ + public function testInvalidVariant(string $uuid) + { + $uuid = new Uuid($uuid); + $this->assertFalse(Uuid::isValid($uuid)); + + $uuid = (string) $uuid; + $class = Uuid::class.'V'.$uuid[14]; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid UUIDv'.$uuid[14].': "'.$uuid.'".'); + + new $class($uuid); + } + + public function provideInvalidVariant(): iterable + { + yield ['8dac64d3-937a-1e7c-fa1d-d5d6c06a61f5']; + yield ['8dac64d3-937a-3e7c-fa1d-d5d6c06a61f5']; + yield ['8dac64d3-937a-4e7c-fa1d-d5d6c06a61f5']; + yield ['8dac64d3-937a-5e7c-fa1d-d5d6c06a61f5']; + yield ['8dac64d3-937a-6e7c-fa1d-d5d6c06a61f5']; + } + public function testConstructorWithValidUuid() { $uuid = new UuidV4(self::A_UUID_V4); diff --git a/src/Symfony/Component/Uid/Ulid.php b/src/Symfony/Component/Uid/Ulid.php index 0ed0673ee3183..bda82ef6856c9 100644 --- a/src/Symfony/Component/Uid/Ulid.php +++ b/src/Symfony/Component/Uid/Ulid.php @@ -64,8 +64,8 @@ public static function isValid(string $ulid): bool */ public static function fromString(string $ulid): parent { - if (36 === \strlen($ulid) && Uuid::isValid($ulid)) { - $ulid = (new Uuid($ulid))->toBinary(); + if (36 === \strlen($ulid) && preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di', $ulid)) { + $ulid = uuid_parse($ulid); } elseif (22 === \strlen($ulid) && 22 === strspn($ulid, BinaryUtil::BASE58[''])) { $ulid = str_pad(BinaryUtil::fromBase($ulid, BinaryUtil::BASE58), 16, "\0", \STR_PAD_LEFT); } @@ -137,7 +137,7 @@ public function getDateTime(): \DateTimeImmutable } if (4 > \strlen($time)) { - $time = str_pad($time, 4, '0', \STR_PAD_LEFT); + $time = '000'.$time; } return \DateTimeImmutable::createFromFormat('U.u', substr_replace($time, '.', -3, 0)); @@ -145,25 +145,15 @@ public function getDateTime(): \DateTimeImmutable public static function generate(\DateTimeInterface $time = null): string { - if (null === $time) { - return self::doGenerate(); - } - - if (0 > $time = substr($time->format('Uu'), 0, -3)) { - throw new \InvalidArgumentException('The timestamp must be positive.'); - } - - return self::doGenerate($time); - } - - private static function doGenerate(string $mtime = null): string - { - if (null === $time = $mtime) { + if (null === $mtime = $time) { $time = microtime(false); $time = substr($time, 11).substr($time, 2, 3); + } elseif (0 > $time = $time->format('Uv')) { + throw new \InvalidArgumentException('The timestamp must be positive.'); } - if ($time !== self::$time) { + if ($time > self::$time || (null !== $mtime && $time !== self::$time)) { + randomize: $r = unpack('nr1/nr2/nr3/nr4/nr', random_bytes(10)); $r['r1'] |= ($r['r'] <<= 4) & 0xF0000; $r['r2'] |= ($r['r'] <<= 4) & 0xF0000; @@ -173,19 +163,22 @@ private static function doGenerate(string $mtime = null): string self::$rand = array_values($r); self::$time = $time; } elseif ([0xFFFFF, 0xFFFFF, 0xFFFFF, 0xFFFFF] === self::$rand) { - if (null === $mtime) { - usleep(100); + if (\PHP_INT_SIZE >= 8 || 10 > \strlen($time = self::$time)) { + $time = (string) (1 + $time); + } elseif ('999999999' === $mtime = substr($time, -9)) { + $time = (1 + substr($time, 0, -9)).'000000000'; } else { - self::$rand = [0, 0, 0, 0]; + $time = substr_replace($time, str_pad(++$mtime, 9, '0', \STR_PAD_LEFT), -9); } - return self::doGenerate($mtime); + goto randomize; } else { for ($i = 3; $i >= 0 && 0xFFFFF === self::$rand[$i]; --$i) { self::$rand[$i] = 0; } ++self::$rand[$i]; + $time = self::$time; } if (\PHP_INT_SIZE >= 8) { diff --git a/src/Symfony/Component/Uid/Uuid.php b/src/Symfony/Component/Uid/Uuid.php index 58c2871c49665..a68c5092f09de 100644 --- a/src/Symfony/Component/Uid/Uuid.php +++ b/src/Symfony/Component/Uid/Uuid.php @@ -26,7 +26,7 @@ class Uuid extends AbstractUid protected const TYPE = 0; protected const NIL = '00000000-0000-0000-0000-000000000000'; - public function __construct(string $uuid) + public function __construct(string $uuid, bool $checkVariant = false) { $type = preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di', $uuid) ? (int) $uuid[14] : false; @@ -35,6 +35,10 @@ public function __construct(string $uuid) } $this->uid = strtolower($uuid); + + if ($checkVariant && !\in_array($this->uid[19], ['8', '9', 'a', 'b'], true)) { + throw new \InvalidArgumentException(sprintf('Invalid UUID%s: "%s".', static::TYPE ? 'v'.static::TYPE : '', $uuid)); + } } /** @@ -67,12 +71,14 @@ public static function fromString(string $uuid): parent return new NilUuid(); } - switch ($uuid[14]) { - case UuidV1::TYPE: return new UuidV1($uuid); - case UuidV3::TYPE: return new UuidV3($uuid); - case UuidV4::TYPE: return new UuidV4($uuid); - case UuidV5::TYPE: return new UuidV5($uuid); - case UuidV6::TYPE: return new UuidV6($uuid); + if (\in_array($uuid[19], ['8', '9', 'a', 'b', 'A', 'B'], true)) { + switch ($uuid[14]) { + case UuidV1::TYPE: return new UuidV1($uuid); + case UuidV3::TYPE: return new UuidV3($uuid); + case UuidV4::TYPE: return new UuidV4($uuid); + case UuidV5::TYPE: return new UuidV5($uuid); + case UuidV6::TYPE: return new UuidV6($uuid); + } } return new self($uuid); @@ -111,7 +117,7 @@ final public static function v6(): UuidV6 public static function isValid(string $uuid): bool { - if (!preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di', $uuid)) { + if (!preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){2}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$}Di', $uuid)) { return false; } diff --git a/src/Symfony/Component/Uid/UuidV1.php b/src/Symfony/Component/Uid/UuidV1.php index 7c1fceb9065e8..3b8cd5e3fc87a 100644 --- a/src/Symfony/Component/Uid/UuidV1.php +++ b/src/Symfony/Component/Uid/UuidV1.php @@ -27,7 +27,7 @@ public function __construct(string $uuid = null) if (null === $uuid) { $this->uid = uuid_create(static::TYPE); } else { - parent::__construct($uuid); + parent::__construct($uuid, true); } } diff --git a/src/Symfony/Component/Uid/UuidV3.php b/src/Symfony/Component/Uid/UuidV3.php index f89f2d7bb313b..cc9f016b47192 100644 --- a/src/Symfony/Component/Uid/UuidV3.php +++ b/src/Symfony/Component/Uid/UuidV3.php @@ -21,4 +21,9 @@ class UuidV3 extends Uuid { protected const TYPE = 3; + + public function __construct(string $uuid) + { + parent::__construct($uuid, true); + } } diff --git a/src/Symfony/Component/Uid/UuidV4.php b/src/Symfony/Component/Uid/UuidV4.php index 897e1ba627213..9724b67de2c59 100644 --- a/src/Symfony/Component/Uid/UuidV4.php +++ b/src/Symfony/Component/Uid/UuidV4.php @@ -30,7 +30,7 @@ public function __construct(string $uuid = null) $this->uid = substr($uuid, 0, 8).'-'.substr($uuid, 8, 4).'-'.substr($uuid, 12, 4).'-'.substr($uuid, 16, 4).'-'.substr($uuid, 20, 12); } else { - parent::__construct($uuid); + parent::__construct($uuid, true); } } } diff --git a/src/Symfony/Component/Uid/UuidV5.php b/src/Symfony/Component/Uid/UuidV5.php index f671f41250373..74ab133a296c8 100644 --- a/src/Symfony/Component/Uid/UuidV5.php +++ b/src/Symfony/Component/Uid/UuidV5.php @@ -21,4 +21,9 @@ class UuidV5 extends Uuid { protected const TYPE = 5; + + public function __construct(string $uuid) + { + parent::__construct($uuid, true); + } } diff --git a/src/Symfony/Component/Uid/UuidV6.php b/src/Symfony/Component/Uid/UuidV6.php index 5ba260e82a521..bf307ef41916a 100644 --- a/src/Symfony/Component/Uid/UuidV6.php +++ b/src/Symfony/Component/Uid/UuidV6.php @@ -29,7 +29,7 @@ public function __construct(string $uuid = null) if (null === $uuid) { $this->uid = static::generate(); } else { - parent::__construct($uuid); + parent::__construct($uuid, true); } } diff --git a/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php b/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php index bde54baf78733..6ecefe6214203 100644 --- a/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php @@ -42,7 +42,7 @@ public function validate($value, Constraint $constraint) \DateTime::createFromFormat($constraint->format, $value); - $errors = \DateTime::getLastErrors(); + $errors = \DateTime::getLastErrors() ?: ['error_count' => 0, 'warnings' => []]; if (0 < $errors['error_count']) { $this->context->buildViolation($constraint->message) diff --git a/src/Symfony/Component/Validator/Constraints/UlidValidator.php b/src/Symfony/Component/Validator/Constraints/UlidValidator.php index dedaabcfc5998..ad741244132c8 100644 --- a/src/Symfony/Component/Validator/Constraints/UlidValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UlidValidator.php @@ -48,6 +48,8 @@ public function validate($value, Constraint $constraint) ->setParameter('{{ value }}', $this->formatValue($value)) ->setCode(26 > \strlen($value) ? Ulid::TOO_SHORT_ERROR : Ulid::TOO_LONG_ERROR) ->addViolation(); + + return; } if (\strlen($value) !== strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz')) { @@ -55,6 +57,8 @@ public function validate($value, Constraint $constraint) ->setParameter('{{ value }}', $this->formatValue($value)) ->setCode(Ulid::INVALID_CHARACTERS_ERROR) ->addViolation(); + + return; } // Largest valid ULID is '7ZZZZZZZZZZZZZZZZZZZZZZZZZ' diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.nb.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.nb.xlf index 93132ec57cdfc..5e1ebc189c350 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.nb.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.nb.xlf @@ -386,6 +386,22 @@ This value is not a valid International Securities Identification Number (ISIN). Denne verdien er ikke et gyldig International Securities Identification Number (ISIN). + + This value should be a valid expression. + Denne verdien skal være et gyldig uttrykk. + + + This value is not a valid CSS color. + Denne verdien er ikke en gyldig CSS-farge. + + + This value is not a valid CIDR notation. + Denne verdien er ikke en gyldig CIDR-notasjon. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Verdien på nettmasken skal være mellom {{ min }} og {{ max }}. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.no.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.no.xlf index 93132ec57cdfc..5e1ebc189c350 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.no.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.no.xlf @@ -386,6 +386,22 @@ This value is not a valid International Securities Identification Number (ISIN). Denne verdien er ikke et gyldig International Securities Identification Number (ISIN). + + This value should be a valid expression. + Denne verdien skal være et gyldig uttrykk. + + + This value is not a valid CSS color. + Denne verdien er ikke en gyldig CSS-farge. + + + This value is not a valid CIDR notation. + Denne verdien er ikke en gyldig CIDR-notasjon. + + + The value of the netmask should be between {{ min }} and {{ max }}. + Verdien på nettmasken skal være mellom {{ min }} og {{ max }}. + diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UlidValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UlidValidatorTest.php index 2c97c97604e6e..8948cbabd8d9f 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UlidValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UlidValidatorTest.php @@ -78,6 +78,7 @@ public function getInvalidUlids() ['01ARZ3NDEKTSV4RRFFQ69G5FAVA', Ulid::TOO_LONG_ERROR], ['01ARZ3NDEKTSV4RRFFQ69G5FAO', Ulid::INVALID_CHARACTERS_ERROR], ['Z1ARZ3NDEKTSV4RRFFQ69G5FAV', Ulid::TOO_LARGE_ERROR], + ['not-even-ulid-like', Ulid::TOO_SHORT_ERROR], ]; } diff --git a/src/Symfony/Component/VarDumper/Tests/Dumper/ServerDumperTest.php b/src/Symfony/Component/VarDumper/Tests/Dumper/ServerDumperTest.php index 921cfda456acf..44036295efb68 100644 --- a/src/Symfony/Component/VarDumper/Tests/Dumper/ServerDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Dumper/ServerDumperTest.php @@ -39,6 +39,10 @@ public function testDumpForwardsToWrappedDumperWhenServerIsUnavailable() public function testDump() { + if ('True' === getenv('APPVEYOR')) { + $this->markTestSkipped('Skip transient test on AppVeyor'); + } + $wrappedDumper = $this->createMock(DataDumperInterface::class); $wrappedDumper->expects($this->never())->method('dump'); // test wrapped dumper is not used diff --git a/src/Symfony/Component/VarDumper/Tests/Server/ConnectionTest.php b/src/Symfony/Component/VarDumper/Tests/Server/ConnectionTest.php index 15e7b0760d3b6..e15b8d6acffb2 100644 --- a/src/Symfony/Component/VarDumper/Tests/Server/ConnectionTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Server/ConnectionTest.php @@ -24,6 +24,10 @@ class ConnectionTest extends TestCase public function testDump() { + if ('True' === getenv('APPVEYOR')) { + $this->markTestSkipped('Skip transient test on AppVeyor'); + } + $cloner = new VarCloner(); $data = $cloner->cloneVar('foo'); $connection = new Connection(self::VAR_DUMPER_SERVER, [