diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 84dd98f43ad2b..62662f876fd3a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ | Q | A | ------------- | --- -| Branch? | 5.4 for features / 4.4, 5.2 or 5.3 for bug fixes +| Branch? | 5.4 for features / 4.4 or 5.3 for bug fixes | Bug fix? | yes/no | New feature? | yes/no | Deprecations? | yes/no diff --git a/.github/patch-types.php b/.github/patch-types.php index 4d49778403fc3..a308eda397d51 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -42,6 +42,7 @@ case false !== strpos($file, '/src/Symfony/Component/ErrorHandler/Tests/Fixtures/'): case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php'): case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php'): + case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php81Dummy.php'): case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/ReflectionIntersectionTypeFixture.php'): diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 4074f78d39356..9832c8a9d09a2 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -117,3 +117,13 @@ jobs: # docker run --rm -e COMPOSER_ROOT_VERSION -v $(pwd):/app -v $(which composer):/usr/local/bin/composer -v $(which vulcain):/usr/local/bin/vulcain -w /app php:8.0-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push # sudo rm -rf .phpunit # [ -d .phpunit.bak ] && mv .phpunit.bak .phpunit + + - uses: marceloprado/has-changed-path@v1 + id: changed-translation-files + with: + paths: src/**/Resources/translations/*.xlf + + - name: Check Translation Status + if: steps.changed-translation-files.outputs.changed == 'true' + run: | + php src/Symfony/Component/Translation/Resources/bin/translation-status.php -v diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index a9adb8e7cf532..9c126e2ef2422 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -52,4 +52,5 @@ jobs: - name: Psalm run: | + ./vendor/bin/psalm.phar --no-progress ./vendor/bin/psalm.phar --output-format=github --no-progress diff --git a/CHANGELOG-4.4.md b/CHANGELOG-4.4.md index 40d08340ad617..2067112f91c93 100644 --- a/CHANGELOG-4.4.md +++ b/CHANGELOG-4.4.md @@ -7,6 +7,24 @@ in 4.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.4.0...v4.4.1 +* 4.4.30 (2021-08-30) + + * bug #42753 Cast ini_get to an integer to match expected type (natewiebe13) + * bug #42345 [Messenger] Remove indices in messenger table on MySQL to prevent deadlocks while removing messages when running multiple consumers (jeroennoten) + * bug #40744 allow null for framework.translator.default_path (SimonHeimberg) + * bug #39856 [DomCrawler] improve failure messages of the CrawlerSelectorTextContains constraint (xabbuh) + * bug #40545 [HttpFoundation] Fix isNotModified determination logic (ol0lll) + * bug #42368 [FrameworkBundle] Fall back to default configuration in debug:config and consistently resolve parameter values (herndlm) + * bug #41684 Fix Url Validator false positives (sidz) + * bug #42576 [Translation] Reverse fallback locales (ro0NL) + * bug #42628 [PropertyInfo] Support for the `never` return type (derrabus) + * bug #42585 [ExpressionLanguage] [Lexer] Remove PHP 8.0 polyfill (nigelmann) + * bug #42621 [Security] Don't produce TypeErrors for non-string CSRF tokens (derrabus) + * bug #42365 [Cache] Do not add namespace argument to `NullAdapter` in `CachePoolPass` (olsavmic) + * bug #42331 [HttpKernel] always close open stopwatch section after handling `kernel.request` events (xabbuh) + * bug #42260 Fix return types for PHP 8.1 (derrabus) + * bug #42341 [Validator] Update MIR card scheme (ossinkine) + * 4.4.29 (2021-07-29) * bug #42307 [Mailer] Fixed decode exception when sendgrid response is 202 (rubanooo) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5c9b27862671f..f804602f54457 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -12,10 +12,10 @@ The Symfony Connect username in parenthesis allows to get more information - Tobias Schultze (tobion) - Robin Chalas (chalas_r) - Christophe Coevoet (stof) + - Wouter De Jong (wouterj) - Jérémy DERUSSÉ (jderusse) - Maxime Steinhausser (ogizanagi) - Kévin Dunglas (dunglas) - - Wouter De Jong (wouterj) - Grégoire Pineau (lyrixx) - Jordi Boggiano (seldaek) - Victor Berchet (victor) @@ -48,11 +48,11 @@ The Symfony Connect username in parenthesis allows to get more information - Eriksen Costa (eriksencosta) - Ener-Getick (energetick) - Sarah Khalil (saro0h) - - Pierre du Plessis (pierredup) - Kevin Bond (kbond) + - Pierre du Plessis (pierredup) + - Valentin Udaltsov (vudaltsov) - Iltar van der Berg (kjarli) - Jonathan Wage (jwage) - - Valentin Udaltsov (vudaltsov) - Matthias Pigulla (mpdude) - Vasilij Duško (staff) - Diego Saint Esteben (dosten) @@ -91,6 +91,7 @@ The Symfony Connect username in parenthesis allows to get more information - Issei Murasawa (issei_m) - Eric Clemmons (ericclemmons) - Charles Sarrazin (csarrazi) + - Antoine M (amakdessi) - Vasilij Dusko - Douglas Greenshields (shieldo) - Graham Campbell (graham) @@ -99,7 +100,6 @@ The Symfony Connect username in parenthesis allows to get more information - Deni - Henrik Westphal (snc) - Dariusz Górecki (canni) - - Antoine M (amakdessi) - Fran Moreno (franmomu) - Dariusz Ruminski - Jérôme Vasseur (jvasseur) @@ -112,26 +112,27 @@ The Symfony Connect username in parenthesis allows to get more information - Bart van den Burg (burgov) - Jordan Alliot (jalliot) - John Wards (johnwards) + - Alexander Schranz (alexander-schranz) - Baptiste Clavié (talus) - Antoine Hérault (herzult) - Paráda József (paradajozsef) - - Alexander Schranz (alexander-schranz) - Arnaud Le Blanc (arnaud-lb) - Przemysław Bogusz (przemyslaw-bogusz) + - Vincent Langlet (deviling) - Maxime STEINHAUSSER + - Tomas Norkūnas (norkunas) - Michal Piotrowski (eventhorizon) - Tomáš Votruba (tomas_votruba) - Massimiliano Arione (garak) - Mathias Arlaud (mtarld) - Tim Nagel (merk) - - Vincent Langlet (deviling) - Chris Wilkinson (thewilkybarkid) - - Tomas Norkūnas (norkunas) - Peter Kokot (maastermedia) - Lars Strojny (lstrojny) - Brice BERNARD (brikou) - Ahmed TAILOULOUTE (ahmedtai) - Gregor Harlan (gharlan) + - HypeMC (hypemc) - marc.weistroff - lenar - Alexander Schwenn (xelaris) @@ -144,29 +145,29 @@ The Symfony Connect username in parenthesis allows to get more information - Jacob Dreesen (jdreesen) - Malte Schlüter (maltemaltesich) - Joel Wurtz (brouznouf) + - Théo FIDRY (theofidry) - Florian Voutzinos (florianv) - Teoh Han Hui (teohhanhui) - Colin Frei - Javier Spagnoletti (phansys) - Joshua Thijssen - - HypeMC (hypemc) - Daniel Wehner (dawehner) - Tugdual Saunier (tucksaun) - excelwebzone - Gordon Franke (gimler) - Saif Eddin Gmati (azjezz) + - Alexandre Daubois (alexandre-daubois) - Jesse Rushlow (geeshoe) - Fabien Pennequin (fabienpennequin) - - Théo FIDRY (theofidry) - Olivier Dolbeau (odolbeau) - - Alexandre Daubois (alexandre-daubois) + - Smaine Milianni (ismail1432) + - Richard van Laak (rvanlaak) - Eric GELOEN (gelo) - Matthieu Napoli (mnapoli) - Jannik Zschiesche (apfelbox) - Mathieu Santostefano (welcomattic) - Robert Schönthal (digitalkaoz) - Florian Lonqueu-Brochard (florianlb) - - Richard van Laak (rvanlaak) - Tigran Azatyan (tigranazatyan) - YaFou - Gary PEGEOT (gary-p) @@ -183,10 +184,10 @@ The Symfony Connect username in parenthesis allows to get more information - Hidenori Goto (hidenorigoto) - Jan Rosier (rosier) - Alessandro Chitolina (alekitto) + - Ruud Kamphuis (ruudk) - Albert Casademont (acasademont) - Arnaud Kleinpeter (nanocom) - Guilherme Blanco (guilhermeblanco) - - Smaine Milianni (ismail1432) - SpacePossum - Pablo Godel (pgodel) - Andreas Braun @@ -194,7 +195,6 @@ The Symfony Connect username in parenthesis allows to get more information - François-Xavier de Guillebon (de-gui_f) - Oleg Voronkovich - hacfi (hifi) - - Ruud Kamphuis (ruudk) - Rafael Dohms (rdohms) - George Mponos (gmponos) - jwdeitch @@ -215,6 +215,7 @@ The Symfony Connect username in parenthesis allows to get more information - Timo Bakx (timobakx) - Marco Pivetta (ocramius) - Vincent Touzet (vincenttouzet) + - Nate Wiebe (natewiebe13) - Rouven Weßling (realityking) - Jérôme Parmentier (lctrs) - Ben Davies (bendavies) @@ -248,6 +249,7 @@ The Symfony Connect username in parenthesis allows to get more information - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) - Mathieu Lechat (mat_the_cat) + - Jeroen Noten (jeroennoten) - Marek Štípek (maryo) - Daniel Espendiller - Possum @@ -263,7 +265,6 @@ The Symfony Connect username in parenthesis allows to get more information - Hidde Wieringa (hiddewie) - Antonio Pauletich (x-coder264) - Andre Rømcke (andrerom) - - Nate Wiebe (natewiebe13) - Philippe Segatori - Thibaut Cheymol (tcheymol) - Sebastien Morel (plopix) @@ -287,10 +288,10 @@ The Symfony Connect username in parenthesis allows to get more information - François Pluchino (francoispluchino) - Rokas Mikalkėnas (rokasm) - bronze1man - - Jeroen Noten (jeroennoten) - sun (sun) - Larry Garfield (crell) - Edi Modrić (emodric) + - Gocha Ossinkine (ossinkine) - Roman Martinuk (a2a4) - Leo Feyer (leofeyer) - Nikolay Labinskiy (e-moe) @@ -314,8 +315,10 @@ The Symfony Connect username in parenthesis allows to get more information - Dustin Whittle (dustinwhittle) - jeff - John Kary (johnkary) + - zairig imad (zairigimad) - Justin Hileman (bobthecow) - Blanchon Vincent (blanchonvincent) + - Maciej Malarz (malarzm) - Michele Orselli (orso) - Sven Paulus (subsven) - Daniel STANCU @@ -330,7 +333,6 @@ The Symfony Connect username in parenthesis allows to get more information - Bohan Yang (brentybh) - Pascal Montoya - Julien Brochet (mewt) - - Gocha Ossinkine (ossinkine) - Tristan Darricau (nicofuma) - Victor Bocharsky (bocharsky_bw) - Bozhidar Hristov (warxcell) @@ -360,12 +362,10 @@ The Symfony Connect username in parenthesis allows to get more information - Manuel Reinhard (sprain) - Harm van Tilborg (hvt) - Danny Berger (dpb587) - - zairig imad (zairigimad) - Antonio J. García Lagar (ajgarlag) - Adam Prager (padam87) - Judicaël RUFFIEUX (axanagor) - Benoît Burnichon (bburnichon) - - Maciej Malarz (malarzm) - Roman Marintšenko (inori) - Xavier Montaña Carreras (xmontana) - Mickaël Andrieu (mickaelandrieu) @@ -400,6 +400,7 @@ The Symfony Connect username in parenthesis allows to get more information - Bob den Otter (bopp) - Thomas Schulz (king2500) - Frank de Jonge (frenkynet) + - Artem Henvald (artemgenvald) - Lescot Edouard (idetox) - Nikita Konstantinov - Wodor Wodorski @@ -422,10 +423,12 @@ The Symfony Connect username in parenthesis allows to get more information - Roumen Damianoff (roumen) - Kim Hemsø Rasmussen (kimhemsoe) - Oleg Andreyev + - Martin Herndl (herndlm) - Pavel Kirpitsov (pavel-kirpichyov) - Pascal Luna (skalpa) - Wouter Van Hecke - Iker Ibarguren (ikerib) + - Bob van de Vijver (bobvandevijver) - Peter Kruithof (pkruithof) - Michael Holm (hollo) - Sylvain Fabre (sylfabre) @@ -437,6 +440,7 @@ The Symfony Connect username in parenthesis allows to get more information - Gonzalo Vilaseca (gonzalovilaseca) - Ben Hakim - Haralan Dobrev (hkdobrev) + - Marco Petersen (ocrampete16) - MatTheCat - Vilius Grigaliūnas - David Badura (davidbadura) @@ -493,7 +497,6 @@ The Symfony Connect username in parenthesis allows to get more information - Oleksandr Barabolia (oleksandrbarabolia) - Christopher Davis (chrisguitarguy) - ivan - - Artem Henvald (artemgenvald) - Greg Anderson - Tri Pham (phamuyentri) - BoShurik @@ -519,15 +522,14 @@ The Symfony Connect username in parenthesis allows to get more information - Inal DJAFAR (inalgnu) - Christian Gärtner (dagardner) - Dmytro Borysovskyi (dmytr0) - - Bob van de Vijver (bobvandevijver) - Tomasz Kowalczyk (thunderer) - Artur Eshenbrener + - Soner Sayakci - Thomas Perez (scullwm) - Felix Labrecque - Yaroslav Kiliba - Terje Bråten - Renan Gonçalves (renan_saddam) - - Marco Petersen (ocrampete16) - Tarmo Leppänen (tarlepp) - Martin Auswöger - Robbert Klarenbeek (robbertkl) @@ -536,6 +538,7 @@ The Symfony Connect username in parenthesis allows to get more information - JhonnyL - hossein zolfi (ocean) - Clément Gautier (clementgautier) + - Koen Reiniers (koenre) - Dāvis Zālītis (k0d3r1s) - Sanpi - Eduardo Gulias (egulias) @@ -617,6 +620,7 @@ The Symfony Connect username in parenthesis allows to get more information - Valentin Jonovs (valentins-jonovs) - Bastien DURAND (deamon) - Jeanmonod David (jeanmonod) + - Christin Gruber (christingruber) - Andrey Sevastianov - Webnet team (webnet) - Urinbayev Shakhobiddin (shokhaa) @@ -666,6 +670,7 @@ The Symfony Connect username in parenthesis allows to get more information - Andrew M-Y (andr) - Krasimir Bosilkov (kbosilkov) - Marcin Michalski (marcinmichalski) + - Yoann RENARD (yrenard) - Vitaliy Tverdokhlib (vitaliytv) - Ariel Ferrandini (aferrandini) - Niklas Keller @@ -676,7 +681,6 @@ The Symfony Connect username in parenthesis allows to get more information - Tim Goudriaan (codedmonkey) - Jonas Flodén (flojon) - AnneKir - - Soner Sayakci - Tobias Weichart - Miro Michalicka - Marcin Sikoń (marphi) @@ -692,7 +696,6 @@ The Symfony Connect username in parenthesis allows to get more information - Christian Wahler - Giso Stallenberg (gisostallenberg) - Gintautas Miselis - - Koen Reiniers (koenre) - Rob Bast - Roberto Espinoza (respinoza) - Pierre Rineau @@ -725,10 +728,13 @@ The Symfony Connect username in parenthesis allows to get more information - Jan van Thoor (janvt) - Gladhon - Joshua Nye + - Martin Kirilov (wucdbm) - Nathan Dench (ndenc2) + - Thibault Richard (t-richard) - Sebastian Bergmann - Miroslav Sustek - Pablo Díez (pablodip) + - SiD (plbsid) - Michel Roca (mroca) - Piotr Kugla (piku235) - Kevin McBride @@ -777,8 +783,10 @@ The Symfony Connect username in parenthesis allows to get more information - Norbert Orzechowicz (norzechowicz) - Denis Charrier (brucewouaigne) - Matthijs van den Bos (matthijs) + - Simon Podlipsky (simpod) - DemigodCode - Jaik Dean (jaikdean) + - Pavel Popov (metaer) - Lenard Palko - arai - Nils Adermann (naderman) @@ -824,6 +832,7 @@ The Symfony Connect username in parenthesis allows to get more information - Jeremy Benoist - Lenar Lõhmus - Daniël Brekelmans (dbrekelmans) + - Simon Heimberg (simon_heimberg) - Benjamin Laugueux (yzalis) - Zach Badgett (zachbadgett) - Aurélien Fredouelle @@ -860,7 +869,6 @@ The Symfony Connect username in parenthesis allows to get more information - Jules Matsounga (hyoa) - Quentin Dequippe (qdequippe) - khoptynskyi - - Christin Gruber (christingruber) - Jean-Christophe Cuvelier [Artack] - julien57 - Julien Montel (julienmgel) @@ -914,6 +922,7 @@ The Symfony Connect username in parenthesis allows to get more information - Nahuel Cuesta (ncuesta) - Chris Boden (cboden) - Christophe Villeger (seragan) + - Hendrik Luup - Julien Fredon - Jacek Wilczyński (jacekwilczynski) - Xavier Leune (xleune) @@ -954,7 +963,6 @@ The Symfony Connect username in parenthesis allows to get more information - Mardari Dorel (dorumd) - Daisuke Ohata - Vincent Simonin - - Yoann RENARD (yrenard) - Alex Bogomazov (alebo) - maxime.steinhausser - Claus Due (namelesscoder) @@ -987,7 +995,6 @@ The Symfony Connect username in parenthesis allows to get more information - rtek - Benjamin Dos Santos - Jérémy Jarrié (gagnar) - - Martin Herndl (herndlm) - Tomas Javaisis - Ivan Grigoriev - Johann Saunier (prophet777) @@ -1058,7 +1065,6 @@ The Symfony Connect username in parenthesis allows to get more information - mohammadreza honarkhah - develop - flip111 - - Thibault Richard (t-richard) - VJ - RJ Garcia - Adam Wójs (awojs) @@ -1081,7 +1087,6 @@ The Symfony Connect username in parenthesis allows to get more information - Andrea Sprega (asprega) - Alexander Volochnev (exelenz) - Viktor Bajraktar (njutn95) - - SiD (plbsid) - Mbechezi Nawo - Michael Piecko - Toni Peric (tperic) @@ -1144,7 +1149,6 @@ The Symfony Connect username in parenthesis allows to get more information - michaelwilliams - Romain - Matěj Humpál - - Martin Kirilov - Pierre Grimaud (pgrimaud) - Alexandre Parent - 1emming @@ -1182,6 +1186,7 @@ The Symfony Connect username in parenthesis allows to get more information - Krzysiek Łabuś - Juraj Surman - Camille Dejoye + - Fabien S (bafs) - 1ma (jautenim) - Douglas Hammond (wizhippo) - Xavier Lacot (xavier) @@ -1204,7 +1209,6 @@ The Symfony Connect username in parenthesis allows to get more information - roromix - Dmitry Pigin (dotty) - Vincent Composieux (eko) - - Simon Podlipsky (simpod) - Jayson Xu (superjavason) - Hubert Lenoir (hubert_lenoir) - fago @@ -1248,6 +1252,7 @@ The Symfony Connect username in parenthesis allows to get more information - Aleksandar Jakovljevic (ajakov) - Laurent Bassin (lbassin) - Hamza Makraz (makraz) + - Tomasz Ignatiuk - andrey1s - Abhoryo - Fabian Vogler (fabian) @@ -1269,11 +1274,13 @@ The Symfony Connect username in parenthesis allows to get more information - Tony Malzhacker - Pchol - Mathieu MARCHOIS + - W0rma - Cyril Quintin (cyqui) - Cyrille Bourgois (cyrilleb) - Gerard van Helden (drm) - Johnny Peck (johnnypeck) - Marcos Rezende (rezehnde) + - Roman Anasal - Ivan Menshykov - David Romaní - Patrick Allaert @@ -1344,7 +1351,6 @@ The Symfony Connect username in parenthesis allows to get more information - Harry Walter (haswalt) - Johnson Page (jwpage) - Ruben Gonzalez (rubenruateltek) - - Simon Heimberg (simon_heimberg) - Michael Roterman (wtfzdotnet) - Arno Geurts - Adán Lobato (adanlobato) @@ -1412,6 +1418,7 @@ The Symfony Connect username in parenthesis allows to get more information - Antoine Bluchet (soyuka) - Patrick Kaufmann - Anton Dyshkant + - Paul Oms - Reece Fowell (reecefowell) - stefan.r - Guillaume Gammelin @@ -1558,6 +1565,7 @@ The Symfony Connect username in parenthesis allows to get more information - Maximilian Berghoff (electricmaxxx) - nacho - Piotr Antosik (antek88) + - Volker Killesreiter (ol0lll) - Vedran Mihočinec (v-m-i) - Sergey Novikov (s12v) - creiner @@ -1677,6 +1685,7 @@ The Symfony Connect username in parenthesis allows to get more information - rchoquet - gitlost - Taras Girnyk + - Dmitry Derepko - Jan Vernarsky - Amine Yakoubi - Eduardo García Sanz (coma) @@ -1686,7 +1695,9 @@ The Symfony Connect username in parenthesis allows to get more information - James Gilliland - fduch (fduch) - Juan Miguel Besada Vidal (soutlink) + - dlorek - Stuart Fyfe + - Carl Casbolt (carlcasbolt) - David de Boer (ddeboer) - Eno Mullaraj (emullaraj) - Nathan PAGE (nathix) @@ -1695,6 +1706,7 @@ The Symfony Connect username in parenthesis allows to get more information - arnaud (arnooo999) - Gilles Doge (gido) - Oscar Esteve (oesteve) + - Peter Potrowl - abulford - Philipp Kretzschmar - antograssiot @@ -1762,10 +1774,12 @@ The Symfony Connect username in parenthesis allows to get more information - Christian Rishøj - Patrick Berenschot - SuRiKmAn + - rtek - Jelte Steijaert (jelte) - David Négrier (moufmouf) - Quique Porta (quiqueporta) - Artem Oliynyk (artemoliynyk) + - Ben Roberts (benr77) - Andrea Quintino (dirk39) - Tomasz Szymczyk (karion) - Alex Vasilchenko @@ -1896,7 +1910,6 @@ The Symfony Connect username in parenthesis allows to get more information - Luis Galeas - Bogdan Scordaliu - Martin Pärtel - - Fabien S (bafs) - Daniel Rotter (danrot) - Frédéric Bouchery (fbouchery) - kylekatarnls (kylekatarnls) @@ -1921,6 +1934,7 @@ The Symfony Connect username in parenthesis allows to get more information - Guillaume BRETOU (guiguiboy) - Nikita Popov (nikic) - Carsten Nielsen (phreaknerd) + - Michael Olšavský - Jay Severson - Benny Born - Emirald Mateli @@ -2049,6 +2063,7 @@ The Symfony Connect username in parenthesis allows to get more information - Antonio Peric-Mazar (antonioperic) - César Suárez (csuarez) - Bjorn Twachtmann (dotbjorn) + - Wahyu Kristianto (kristories) - Tobias Genberg (lorceroth) - Nicolas Badey (nico-b) - Shane Preece (shane) @@ -2112,6 +2127,7 @@ The Symfony Connect username in parenthesis allows to get more information - Joni Halme - Matt Farmer - catch + - aetxebeste - siganushka - Alexandre Segura - Josef Cech @@ -2192,6 +2208,7 @@ The Symfony Connect username in parenthesis allows to get more information - David Stone - jjanvier - Julius Beckmann + - Ruben Jansen - shreypuranik - loru88 - Thibaut Salanon @@ -2261,6 +2278,7 @@ The Symfony Connect username in parenthesis allows to get more information - Alessio Baglio (ioalessio) - Johannes Müller (johmue) - Jordi Llonch (jordillonch) + - Jordi Sala Morales (jsala) - Mouad ZIANI (mouadziani) - Nicholas Ruunu (nicholasruunu) - Jeroen van den Nieuwenhuisen (nieuwenhuisen) @@ -2275,8 +2293,8 @@ The Symfony Connect username in parenthesis allows to get more information - alex - Nicole Cordes - Nicolas PHILIPPE - - Roman Anasal - Roman Orlov + - Simon Ackermann - VolCh - Alexey Popkov - Gijs Kunze @@ -2437,6 +2455,7 @@ The Symfony Connect username in parenthesis allows to get more information - Filipe Guerra - Jean Ragouin - Gerben Wijnja + - Emre YILMAZ - Rowan Manning - Per Modin - David Windell @@ -2521,6 +2540,7 @@ The Symfony Connect username in parenthesis allows to get more information - Bart Reunes (metalarend) - Muriel (metalmumu) - Michael Pohlers (mick_the_big) + - Misha Klomp (mishaklomp) - mlpo (mlpo) - Marek Šimeček (mssimi) - Dmitriy Tkachenko (neka) @@ -2598,6 +2618,7 @@ The Symfony Connect username in parenthesis allows to get more information - temperatur - misterx - Cas + - Vincent Godé - Dusan Kasan - Michael Steininger - Nardberjean @@ -2628,6 +2649,7 @@ The Symfony Connect username in parenthesis allows to get more information - Michel Bardelmeijer - Tomas Kmieliauskas - Ikko Ashimine + - Brad Jones - Billie Thompson - lol768 - jamogon @@ -2681,6 +2703,7 @@ The Symfony Connect username in parenthesis allows to get more information - James Michael DuPont - Kasperki - Tammy D + - Rodolfo Ruiz - Enrico - Ryan Rud - Ondrej Slinták @@ -2701,6 +2724,7 @@ The Symfony Connect username in parenthesis allows to get more information - Markus Staab - Pierre-Louis LAUNAY - djama + - Benjamin Rosenberger - Vladyslav Startsev - Michael Gwynne - Eduardo Conceição @@ -2717,6 +2741,7 @@ The Symfony Connect username in parenthesis allows to get more information - nsbx - Shude - Richard Hodgson + - Sven Fabricius - Ondřej Führer - Sema - Thorsten Hallwas @@ -2760,6 +2785,7 @@ The Symfony Connect username in parenthesis allows to get more information - Sam Anthony - Christian Stocker - Oussama Elgoumri + - Steve Marvell - Dawid Nowak - Lesnykh Ilia - sabruss @@ -2823,6 +2849,7 @@ The Symfony Connect username in parenthesis allows to get more information - Gerry Vandermaesen (gerryvdm) - Ghazy Ben Ahmed (ghazy) - Arash Tabriziyan (ghost098) + - Greg Szczotka (greg606) - ibasaw (ibasaw) - Vladislav Krupenkin (ideea) - Ilija Tovilo (ilijatovilo) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 05faff5872dd8..c0945114c9c23 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -827,6 +827,10 @@ public function resultWithEmptyIterator(): array return [ [$entity, new class() implements \Iterator { + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function current() { return null; @@ -837,19 +841,28 @@ public function valid(): bool return false; } - public function next() + public function next(): void { } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function key() { + return false; } - public function rewind() + public function rewind(): void { } }], [$entity, new class() implements \Iterator { + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function current() { return false; @@ -860,15 +873,20 @@ public function valid(): bool return false; } - public function next() + public function next(): void { } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function key() { + return false; } - public function rewind() + public function rewind(): void { } }], diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php index c43a1ccff1e04..179280b87b510 100644 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php @@ -226,7 +226,7 @@ @copy("$PHPUNIT_VERSION_DIR/phpunit.xsd", 'phpunit.xsd'); chdir("$PHPUNIT_VERSION_DIR"); if ($SYMFONY_PHPUNIT_REMOVE) { - $passthruOrFail("$COMPOSER remove --no-update ".$SYMFONY_PHPUNIT_REMOVE); + $passthruOrFail("$COMPOSER remove --no-update --no-interaction ".$SYMFONY_PHPUNIT_REMOVE); } if (5.1 <= $PHPUNIT_VERSION && $PHPUNIT_VERSION < 5.4) { $passthruOrFail("$COMPOSER require --no-update phpunit/phpunit-mock-objects \"~3.1.0\""); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index 908858019b371..d112c9b086c0f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -59,7 +59,7 @@ protected function configure() ]) ->setDescription('Clear the cache') ->setHelp(<<<'EOF' -The %command.name% command clears the application cache for a given environment +The %command.name% command clears and warms up the application cache for a given environment and debug mode: php %command.full_name% --env=dev diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index 7f0a7813a8ddd..a0623f396127b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Config\Definition\Processor; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -18,6 +19,8 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\Yaml\Yaml; /** @@ -77,22 +80,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $extension = $this->findExtension($name); - $container = $this->compileContainer(); - $extensionAlias = $extension->getAlias(); - $extensionConfig = []; - foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { - if ($pass instanceof ValidateEnvPlaceholdersPass) { - $extensionConfig = $pass->getExtensionConfig(); - break; - } - } - - if (!isset($extensionConfig[$extensionAlias])) { - throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias)); - } + $container = $this->compileContainer(); - $config = $container->resolveEnvPlaceholders($extensionConfig[$extensionAlias]); + $config = $container->resolveEnvPlaceholders( + $container->getParameterBag()->resolveValue( + $this->getConfigForExtension($extension, $container) + ) + ); if (null === $path = $input->getArgument('path')) { $io->title( @@ -153,4 +148,33 @@ private function getConfigForPath(array $config, string $path, string $alias) return $config; } + + private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container): array + { + $extensionAlias = $extension->getAlias(); + + $extensionConfig = []; + foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { + if ($pass instanceof ValidateEnvPlaceholdersPass) { + $extensionConfig = $pass->getExtensionConfig(); + break; + } + } + + if (isset($extensionConfig[$extensionAlias])) { + return $extensionConfig[$extensionAlias]; + } + + // Fall back to default config if the extension has one + + if (!$extension instanceof ConfigurationExtensionInterface) { + throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias)); + } + + $configs = $container->getExtensionConfig($extensionAlias); + $configuration = $extension->getConfiguration($configs, $container); + $this->validateConfiguration($extension, $configuration); + + return (new Processor())->processConfiguration($configuration, $configs); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index cd9037e7c9fdc..eb3b19991bb92 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1216,7 +1216,9 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $container->getDefinition('console.command.translation_update')->replaceArgument(6, $transPaths); } - if ($container->fileExists($defaultDir)) { + if (null === $defaultDir) { + // allow null + } elseif ($container->fileExists($defaultDir)) { $dirs[] = $defaultDir; } else { $nonExistingDirs[] = $defaultDir; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DefaultConfigTestBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DefaultConfigTestBundle.php new file mode 100644 index 0000000000000..8c7a89574729f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DefaultConfigTestBundle.php @@ -0,0 +1,9 @@ +getRootNode() + ->children() + ->scalarNode('foo')->defaultValue('%default_config_test_foo%')->end() + ->scalarNode('baz')->defaultValue('%env(BAZ)%')->end() + ->end(); + + return $treeBuilder; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php new file mode 100644 index 0000000000000..d380bcaad17fa --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/DefaultConfigTestBundle/DependencyInjection/DefaultConfigTestExtension.php @@ -0,0 +1,18 @@ +processConfiguration($configuration, $configs); + + $container->setParameter('default_config_test', $config['foo']); + $container->setParameter('default_config_test', $config['baz']); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php new file mode 100644 index 0000000000000..79f0a7006c89b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/ExtensionWithoutConfigTestBundle/DependencyInjection/ExtensionWithoutConfigTestExtension.php @@ -0,0 +1,28 @@ +assertStringContainsString('secret: test', $tester->getDisplay()); } + public function testDefaultParameterValueIsResolvedIfConfigIsExisting() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(['name' => 'framework']); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); + $kernelCacheDir = $this->application->getKernel()->getContainer()->getParameter('kernel.cache_dir'); + $this->assertStringContainsString(sprintf("dsn: 'file:%s/profiler'", $kernelCacheDir), $tester->getDisplay()); + } + public function testDumpUndefinedBundleOption() { $tester = $this->createCommandTester(); @@ -74,6 +84,33 @@ public function testDumpWithPrefixedEnv() $this->assertStringContainsString("cookie_httponly: '%env(bool:COOKIE_HTTPONLY)%'", $tester->getDisplay()); } + public function testDumpFallsBackToDefaultConfigAndResolvesParameterValue() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(['name' => 'DefaultConfigTestBundle']); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); + $this->assertStringContainsString('foo: bar', $tester->getDisplay()); + } + + public function testDumpFallsBackToDefaultConfigAndResolvesEnvPlaceholder() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(['name' => 'DefaultConfigTestBundle']); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); + $this->assertStringContainsString("baz: '%env(BAZ)%'", $tester->getDisplay()); + } + + public function testDumpThrowsExceptionWhenDefaultConfigFallbackIsImpossible() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The extension with alias "extension_without_config_test" does not have configuration.'); + + $tester = $this->createCommandTester(); + $tester->execute(['name' => 'ExtensionWithoutConfigTestBundle']); + } + private function createCommandTester(): CommandTester { $command = $this->application->find('debug:config'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/bundles.php index 15ff182c6fed5..c4fb0bbe8ce48 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/bundles.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/bundles.php @@ -10,9 +10,13 @@ */ use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\DefaultConfigTestBundle\DefaultConfigTestBundle; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\ExtensionWithoutConfigTestBundle\ExtensionWithoutConfigTestBundle; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle; return [ + new DefaultConfigTestBundle(), + new ExtensionWithoutConfigTestBundle(), new FrameworkBundle(), new TestBundle(), ]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml index 432e35bd2f24d..a7a03a31d6602 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ConfigDump/config.yml @@ -11,3 +11,4 @@ parameters: env(LOCALE): en env(COOKIE_HTTPONLY): '1' secret: test + default_config_test_foo: bar diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php index 50744192a9dfe..ff88b34007069 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Test/WebTestCaseTest.php @@ -183,7 +183,7 @@ public function testAssertSelectorTextNotContains() { $this->getCrawlerTester(new Crawler('

Foo'))->assertSelectorTextNotContains('body > h1', 'Bar'); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage('matches selector "body > h1" and does not have a node matching selector "body > h1" with content containing "Foo".'); + $this->expectExceptionMessage('matches selector "body > h1" and the text "Foo" of the node matching selector "body > h1" does not contain "Foo".'); $this->getCrawlerTester(new Crawler('

Foo'))->assertSelectorTextNotContains('body > h1', 'Foo'); } @@ -199,7 +199,7 @@ public function testAssertPageTitleContains() { $this->getCrawlerTester(new Crawler('Foobar'))->assertPageTitleContains('Foo'); $this->expectException(AssertionFailedError::class); - $this->expectExceptionMessage('matches selector "title" and has a node matching selector "title" with content containing "Bar".'); + $this->expectExceptionMessage('matches selector "title" and the text "Foo" of the node matching selector "title" contains "Bar".'); $this->getCrawlerTester(new Crawler('<html><head><title>Foo'))->assertPageTitleContains('Bar'); } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index ba276ab35103b..6562fa2d1e241 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -39,7 +39,7 @@ "symfony/browser-kit": "^4.3|^5.0", "symfony/console": "^4.4.21|^5.0", "symfony/css-selector": "^3.4|^4.0|^5.0", - "symfony/dom-crawler": "^4.3|^5.0", + "symfony/dom-crawler": "^4.4.30|^5.3.7", "symfony/dotenv": "^4.3.6|^5.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/form": "^4.3.5|^5.0", diff --git a/src/Symfony/Bundle/TwigBundle/TemplateIterator.php b/src/Symfony/Bundle/TwigBundle/TemplateIterator.php index 8af15aa3a9f5f..b16eadc40bf54 100644 --- a/src/Symfony/Bundle/TwigBundle/TemplateIterator.php +++ b/src/Symfony/Bundle/TwigBundle/TemplateIterator.php @@ -45,6 +45,7 @@ public function __construct(KernelInterface $kernel, string $rootDir, array $pat /** * @return \Traversable */ + #[\ReturnTypeWillChange] public function getIterator() { if (null !== $this->templates) { diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 040a095ab11ab..71338c3c12e0a 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -51,7 +51,7 @@ static function ($key, $value, $isHit) { // Detect wrapped values that encode for their expiry and creation duration // For compactness, these values are packed in the key of an array using // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F - if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { + if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { $item->value = $v[$k]; $v = unpack('Ve/Nc', substr($k, 1, -1)); $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index c9baa3ad3961f..d5b0593353ae9 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -59,7 +59,7 @@ static function ($key, $innerItem) use ($poolHash) { // Detect wrapped values that encode for their expiry and creation duration // For compactness, these values are packed in the key of an array using // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F - if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { + if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { $item->value = $v[$k]; $v = unpack('Ve/Nc', substr($k, 1, -1)); $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; diff --git a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php index f52d0271e4117..c707ad9a28793 100644 --- a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php +++ b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php @@ -14,6 +14,7 @@ use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -130,7 +131,7 @@ public function process(ContainerBuilder $container) $chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider']))); } - if (isset($tags[0]['namespace']) && ArrayAdapter::class !== $adapter->getClass()) { + if (isset($tags[0]['namespace']) && !\in_array($adapter->getClass(), [ArrayAdapter::class, NullAdapter::class], true)) { $chainedPool->replaceArgument($i++, $tags[0]['namespace']); } @@ -155,7 +156,7 @@ public function process(ContainerBuilder $container) if ($tags[0][$attr]) { $pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]); } - } elseif ('namespace' !== $attr || ArrayAdapter::class !== $class) { + } elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class], true)) { $pool->replaceArgument($i++, $tags[0][$attr]); } unset($tags[0][$attr]); diff --git a/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php b/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php index 9769b3992e88f..a77df6b1ec9ad 100644 --- a/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php +++ b/src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; use Symfony\Component\Cache\Adapter\RedisAdapter; use Symfony\Component\Cache\DependencyInjection\CachePoolPass; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -116,6 +117,23 @@ public function testNamespaceArgumentIsNotReplacedIfArrayAdapterIsUsed() $this->assertCount(0, $container->getDefinition('app.cache_pool')->getArguments()); } + public function testNamespaceArgumentIsNotReplacedIfNullAdapterIsUsed() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.container_class', 'app'); + $container->setParameter('kernel.project_dir', 'foo'); + + $container->register('cache.adapter.null', NullAdapter::class); + + $cachePool = new ChildDefinition('cache.adapter.null'); + $cachePool->addTag('cache.pool'); + $container->setDefinition('app.cache_pool', $cachePool); + + $this->cachePoolPass->process($container); + + $this->assertCount(0, $container->getDefinition('app.cache_pool')->getArguments()); + } + public function testArgsAreReplaced() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/Cache/Traits/ApcuTrait.php b/src/Symfony/Component/Cache/Traits/ApcuTrait.php index 9a02148c14dca..b56ae4f7c39ac 100644 --- a/src/Symfony/Component/Cache/Traits/ApcuTrait.php +++ b/src/Symfony/Component/Cache/Traits/ApcuTrait.php @@ -112,7 +112,7 @@ protected function doSave(array $values, int $lifetime) } catch (\Throwable $e) { if (1 === \count($values)) { // Workaround https://github.com/krakjoe/apcu/issues/170 - apcu_delete(key($values)); + apcu_delete(array_key_first($values)); } throw $e; diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index e481d66a9d427..d71f2169e03c2 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -25,6 +25,7 @@ "psr/cache": "^1.0|^2.0", "psr/log": "^1|^2|^3", "symfony/cache-contracts": "^1.1.7|^2", + "symfony/polyfill-php73": "^1.9", "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2", "symfony/var-exporter": "^4.2|^5.0" diff --git a/src/Symfony/Component/Config/Resource/GlobResource.php b/src/Symfony/Component/Config/Resource/GlobResource.php index 0b535682f4af3..f9429eb8f9bd9 100644 --- a/src/Symfony/Component/Config/Resource/GlobResource.php +++ b/src/Symfony/Component/Config/Resource/GlobResource.php @@ -102,6 +102,7 @@ public function __wakeup(): void /** * @return \Traversable */ + #[\ReturnTypeWillChange] public function getIterator() { if (!file_exists($this->prefix) || (!$this->recursive && '' === $this->pattern)) { diff --git a/src/Symfony/Component/Console/Event/ConsoleEvent.php b/src/Symfony/Component/Console/Event/ConsoleEvent.php index 5440da216c96f..400eb5731bad2 100644 --- a/src/Symfony/Component/Console/Event/ConsoleEvent.php +++ b/src/Symfony/Component/Console/Event/ConsoleEvent.php @@ -28,7 +28,7 @@ class ConsoleEvent extends Event private $input; private $output; - public function __construct(Command $command = null, InputInterface $input, OutputInterface $output) + public function __construct(?Command $command, InputInterface $input, OutputInterface $output) { $this->command = $command; $this->input = $input; diff --git a/src/Symfony/Component/Console/Helper/HelperSet.php b/src/Symfony/Component/Console/Helper/HelperSet.php index d9d73f25fc69a..9aa1e67ba8c9b 100644 --- a/src/Symfony/Component/Console/Helper/HelperSet.php +++ b/src/Symfony/Component/Console/Helper/HelperSet.php @@ -98,8 +98,9 @@ public function getCommand() } /** - * @return Helper[] + * @return \Traversable<Helper> */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->helpers); diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 3f5c2a50d6fec..de7b3aedc37ae 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -1149,6 +1149,7 @@ public function getNode($position) /** * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->nodes); @@ -1157,6 +1158,7 @@ public function count() /** * @return \ArrayIterator|\DOMNode[] */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->nodes); diff --git a/src/Symfony/Component/DomCrawler/Form.php b/src/Symfony/Component/DomCrawler/Form.php index dbba779ced5d0..0c86116a64eca 100644 --- a/src/Symfony/Component/DomCrawler/Form.php +++ b/src/Symfony/Component/DomCrawler/Form.php @@ -321,6 +321,7 @@ public function all() * * @return bool true if the field exists, false otherwise */ + #[\ReturnTypeWillChange] public function offsetExists($name) { return $this->has($name); @@ -335,6 +336,7 @@ public function offsetExists($name) * * @throws \InvalidArgumentException if the field does not exist */ + #[\ReturnTypeWillChange] public function offsetGet($name) { return $this->fields->get($name); @@ -350,6 +352,7 @@ public function offsetGet($name) * * @throws \InvalidArgumentException if the field does not exist */ + #[\ReturnTypeWillChange] public function offsetSet($name, $value) { $this->fields->set($name, $value); @@ -362,6 +365,7 @@ public function offsetSet($name, $value) * * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($name) { $this->fields->remove($name); diff --git a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php index 7008779e14203..a198784d448df 100644 --- a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php +++ b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php @@ -47,7 +47,7 @@ protected function matches($crawler): bool return false; } - return $this->expectedText === trim($crawler->attr($this->attribute)); + return $this->expectedText === trim($crawler->attr($this->attribute) ?? ''); } /** diff --git a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorTextContains.php b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorTextContains.php index ffc6f5677cc5d..c4697ecf75376 100644 --- a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorTextContains.php +++ b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorTextContains.php @@ -18,6 +18,8 @@ final class CrawlerSelectorTextContains extends Constraint { private $selector; private $expectedText; + private $hasNode = false; + private $nodeText; public function __construct(string $selector, string $expectedText) { @@ -30,7 +32,11 @@ public function __construct(string $selector, string $expectedText) */ public function toString(): string { - return sprintf('has a node matching selector "%s" with content containing "%s"', $this->selector, $this->expectedText); + if ($this->hasNode) { + return sprintf('the text "%s" of the node matching selector "%s" contains "%s"', $this->nodeText, $this->selector, $this->expectedText); + } + + return sprintf('the Crawler has a node matching selector "%s"', $this->selector); } /** @@ -42,10 +48,15 @@ protected function matches($crawler): bool { $crawler = $crawler->filter($this->selector); if (!\count($crawler)) { + $this->hasNode = false; + return false; } - return false !== mb_strpos($crawler->text(null, true), $this->expectedText); + $this->hasNode = true; + $this->nodeText = $crawler->text(null, true); + + return false !== mb_strpos($this->nodeText, $this->expectedText); } /** @@ -55,6 +66,6 @@ protected function matches($crawler): bool */ protected function failureDescription($crawler): string { - return 'the Crawler '.$this->toString(); + return $this->toString(); } } diff --git a/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorAttributeValueSameTest.php b/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorAttributeValueSameTest.php index ab528ab356443..47ecdc8a04438 100644 --- a/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorAttributeValueSameTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorAttributeValueSameTest.php @@ -23,6 +23,7 @@ public function testConstraint() { $constraint = new CrawlerSelectorAttributeValueSame('input[name="username"]', 'value', 'Fabien'); $this->assertTrue($constraint->evaluate(new Crawler('<html><body><form><input type="text" name="username" value="Fabien">'), '', true)); + $this->assertFalse($constraint->evaluate(new Crawler('<html><body><form><input type="text" name="username">'), '', true)); $this->assertFalse($constraint->evaluate(new Crawler('<html><head><title>Bar'), '', true)); try { diff --git a/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorTextContainsTest.php b/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorTextContainsTest.php index b92a13187bd5f..0d7656e589b49 100644 --- a/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorTextContainsTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/Test/Constraint/CrawlerSelectorTextContainsTest.php @@ -24,15 +24,22 @@ public function testConstraint() $constraint = new CrawlerSelectorTextContains('title', 'Foo'); $this->assertTrue($constraint->evaluate(new Crawler('<html><head><title>Foobar'), '', true)); $this->assertFalse($constraint->evaluate(new Crawler('<html><head><title>Bar'), '', true)); + $this->assertFalse($constraint->evaluate(new Crawler('<html><head></head><body>Bar'), '', true)); try { $constraint->evaluate(new Crawler('<html><head><title>Bar')); - } catch (ExpectationFailedException $e) { - $this->assertEquals("Failed asserting that the Crawler has a node matching selector \"title\" with content containing \"Foo\".\n", TestFailure::exceptionToString($e)); - return; + $this->fail(); + } catch (ExpectationFailedException $e) { + $this->assertEquals("Failed asserting that the text \"Bar\" of the node matching selector \"title\" contains \"Foo\".\n", TestFailure::exceptionToString($e)); } - $this->fail(); + try { + $constraint->evaluate(new Crawler('<html><head></head><body>Bar')); + + $this->fail(); + } catch (ExpectationFailedException $e) { + $this->assertEquals("Failed asserting that the Crawler has a node matching selector \"title\".\n", TestFailure::exceptionToString($e)); + } } } diff --git a/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt b/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt index 15933828bd426..92f7e12fde227 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt +++ b/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt @@ -1,5 +1,7 @@ --TEST-- Test catching fatal errors when handlers are nested +--SKIPIF-- +<?php if (\PHP_VERSION_ID < 80100) echo 'skip' ?> --FILE-- <?php @@ -36,6 +38,9 @@ array(1) { string(37) "Error and exception handlers do match" } object(Symfony\Component\ErrorHandler\Error\FatalError)#%d (%d) { + ["message":protected]=> + string(186) "Error: Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)" +%a ["error":"Symfony\Component\ErrorHandler\Error\FatalError":private]=> array(4) { ["type"]=> @@ -47,7 +52,4 @@ object(Symfony\Component\ErrorHandler\Error\FatalError)#%d (%d) { ["line"]=> int(%d) } - ["message":protected]=> - string(186) "Error: Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)" -%a } diff --git a/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers_pre81.phpt b/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers_pre81.phpt new file mode 100644 index 0000000000000..d6b2baa6c904d --- /dev/null +++ b/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers_pre81.phpt @@ -0,0 +1,55 @@ +--TEST-- +Test catching fatal errors when handlers are nested +--SKIPIF-- +<?php if (\PHP_VERSION_ID >= 80100) echo 'skip' ?> +--FILE-- +<?php + +namespace Symfony\Component\ErrorHandler; + +$vendor = __DIR__; +while (!file_exists($vendor.'/vendor')) { + $vendor = \dirname($vendor); +} +require $vendor.'/vendor/autoload.php'; + +Debug::enable(); +ini_set('display_errors', 0); + +$eHandler = set_error_handler('var_dump'); +$xHandler = set_exception_handler('var_dump'); + +var_dump([ + $eHandler[0] === $xHandler[0] ? 'Error and exception handlers do match' : 'Error and exception handlers are different', +]); + +$eHandler[0]->setExceptionHandler('print_r'); + +if (true) { + class Broken implements \JsonSerializable + { + } +} + +?> +--EXPECTF-- +array(1) { + [0]=> + string(37) "Error and exception handlers do match" +} +object(Symfony\Component\ErrorHandler\Error\FatalError)#%d (%d) { + ["error":"Symfony\Component\ErrorHandler\Error\FatalError":private]=> + array(4) { + ["type"]=> + int(1) + ["message"]=> + string(179) "Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + } + ["message":protected]=> + string(186) "Error: Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)" +%a +} diff --git a/src/Symfony/Component/EventDispatcher/GenericEvent.php b/src/Symfony/Component/EventDispatcher/GenericEvent.php index d86a584153ded..23333bc21284f 100644 --- a/src/Symfony/Component/EventDispatcher/GenericEvent.php +++ b/src/Symfony/Component/EventDispatcher/GenericEvent.php @@ -123,6 +123,7 @@ public function hasArgument($key) * * @throws \InvalidArgumentException if key does not exist in $this->args */ + #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->getArgument($key); @@ -136,6 +137,7 @@ public function offsetGet($key) * * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($key, $value) { $this->setArgument($key, $value); @@ -148,6 +150,7 @@ public function offsetSet($key, $value) * * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($key) { if ($this->hasArgument($key)) { @@ -162,6 +165,7 @@ public function offsetUnset($key) * * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($key) { return $this->hasArgument($key); @@ -172,6 +176,7 @@ public function offsetExists($key) * * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->arguments); diff --git a/src/Symfony/Component/ExpressionLanguage/Lexer.php b/src/Symfony/Component/ExpressionLanguage/Lexer.php index af859318d50d6..46d3e1c0c38e6 100644 --- a/src/Symfony/Component/ExpressionLanguage/Lexer.php +++ b/src/Symfony/Component/ExpressionLanguage/Lexer.php @@ -50,13 +50,13 @@ public function tokenize($expression) } $tokens[] = new Token(Token::NUMBER_TYPE, $number, $cursor + 1); $cursor += \strlen($match[0]); - } elseif (str_contains('([{', $expression[$cursor])) { + } elseif (false !== strpos('([{', $expression[$cursor])) { // opening bracket $brackets[] = [$expression[$cursor], $cursor]; $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); ++$cursor; - } elseif (str_contains(')]}', $expression[$cursor])) { + } elseif (false !== strpos(')]}', $expression[$cursor])) { // closing bracket if (empty($brackets)) { throw new SyntaxError(sprintf('Unexpected "%s".', $expression[$cursor]), $cursor, $expression); @@ -77,7 +77,7 @@ public function tokenize($expression) // operators $tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1); $cursor += \strlen($match[0]); - } elseif (str_contains('.,?:', $expression[$cursor])) { + } elseif (false !== strpos('.,?:', $expression[$cursor])) { // punctuation $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); ++$cursor; diff --git a/src/Symfony/Component/ExpressionLanguage/composer.json b/src/Symfony/Component/ExpressionLanguage/composer.json index 01922550ee40f..0e5cdadfb0573 100644 --- a/src/Symfony/Component/ExpressionLanguage/composer.json +++ b/src/Symfony/Component/ExpressionLanguage/composer.json @@ -18,7 +18,6 @@ "require": { "php": ">=7.1.3", "symfony/cache": "^3.4|^4.0|^5.0", - "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2" }, "autoload": { diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 92249ad902178..e1194ed695e23 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -618,6 +618,7 @@ public function in($dirs) * * @throws \LogicException if the in() method has not been called */ + #[\ReturnTypeWillChange] public function getIterator() { if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { @@ -702,6 +703,7 @@ public function hasResults() * * @return int */ + #[\ReturnTypeWillChange] public function count() { return iterator_count($this->getIterator()); diff --git a/src/Symfony/Component/Finder/Iterator/CustomFilterIterator.php b/src/Symfony/Component/Finder/Iterator/CustomFilterIterator.php index a30bbd0b9d265..f85cb7bffb772 100644 --- a/src/Symfony/Component/Finder/Iterator/CustomFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/CustomFilterIterator.php @@ -46,6 +46,7 @@ public function __construct(\Iterator $iterator, array $filters) * * @return bool true if the value should be kept, false otherwise */ + #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); diff --git a/src/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php b/src/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php index 2e97e00d37456..90616f471b1f7 100644 --- a/src/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php @@ -38,6 +38,7 @@ public function __construct(\Iterator $iterator, array $comparators) * * @return bool true if the value should be kept, false otherwise */ + #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); diff --git a/src/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php b/src/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php index 18e751d77b928..e96fefd961b16 100644 --- a/src/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php @@ -38,6 +38,7 @@ public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth * * @return bool true if the value should be kept, false otherwise */ + #[\ReturnTypeWillChange] public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; diff --git a/src/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php b/src/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php index 366ad70cdb9bc..cf9e678771da9 100644 --- a/src/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -52,6 +52,7 @@ public function __construct(\Iterator $iterator, array $directories) * * @return bool True if the value should be kept, false otherwise */ + #[\ReturnTypeWillChange] public function accept() { if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { @@ -71,6 +72,7 @@ public function accept() /** * @return bool */ + #[\ReturnTypeWillChange] public function hasChildren() { return $this->isRecursive && $this->iterator->hasChildren(); @@ -79,6 +81,7 @@ public function hasChildren() /** * @return self */ + #[\ReturnTypeWillChange] public function getChildren() { $children = new self($this->iterator->getChildren(), []); diff --git a/src/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php b/src/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php index 0ea2c508802de..d054cefb9fff9 100644 --- a/src/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php @@ -39,6 +39,7 @@ public function __construct(\Iterator $iterator, int $mode) * * @return bool true if the value should be kept, false otherwise */ + #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); diff --git a/src/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php b/src/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php index 81594b8774048..41eb767f7a651 100644 --- a/src/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php @@ -24,6 +24,7 @@ class FilecontentFilterIterator extends MultiplePcreFilterIterator * * @return bool true if the value should be kept, false otherwise */ + #[\ReturnTypeWillChange] public function accept() { if (!$this->matchRegexps && !$this->noMatchRegexps) { diff --git a/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php b/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php index e168cd8ffa798..8365756c15209 100644 --- a/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php @@ -25,6 +25,7 @@ class FilenameFilterIterator extends MultiplePcreFilterIterator * * @return bool true if the value should be kept, false otherwise */ + #[\ReturnTypeWillChange] public function accept() { return $this->isAccepted($this->current()->getFilename()); diff --git a/src/Symfony/Component/Finder/Iterator/PathFilterIterator.php b/src/Symfony/Component/Finder/Iterator/PathFilterIterator.php index 3fda557be366b..f4aaa1fb0027d 100644 --- a/src/Symfony/Component/Finder/Iterator/PathFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/PathFilterIterator.php @@ -24,6 +24,7 @@ class PathFilterIterator extends MultiplePcreFilterIterator * * @return bool true if the value should be kept, false otherwise */ + #[\ReturnTypeWillChange] public function accept() { $filename = $this->current()->getRelativePathname(); diff --git a/src/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php b/src/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php index 2aeef67b87f73..4078f3692e052 100644 --- a/src/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php @@ -38,6 +38,7 @@ public function __construct(\Iterator $iterator, array $comparators) * * @return bool true if the value should be kept, false otherwise */ + #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); diff --git a/src/Symfony/Component/Finder/Iterator/SortableIterator.php b/src/Symfony/Component/Finder/Iterator/SortableIterator.php index 8559ba51cd794..adc7e999db8b4 100644 --- a/src/Symfony/Component/Finder/Iterator/SortableIterator.php +++ b/src/Symfony/Component/Finder/Iterator/SortableIterator.php @@ -81,6 +81,7 @@ public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = /** * @return \Traversable */ + #[\ReturnTypeWillChange] public function getIterator() { if (1 === $this->sort) { diff --git a/src/Symfony/Component/Finder/Tests/Iterator/Iterator.php b/src/Symfony/Component/Finder/Tests/Iterator/Iterator.php index bc2eb53b393e0..108c66a273c99 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/Iterator.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/Iterator.php @@ -23,12 +23,12 @@ public function __construct(array $values = []) $this->rewind(); } - public function attach(\SplFileInfo $fileinfo) + public function attach(\SplFileInfo $fileinfo): void { $this->values[] = $fileinfo; } - public function rewind() + public function rewind(): void { reset($this->values); } @@ -38,16 +38,24 @@ public function valid(): bool return false !== $this->current(); } - public function next() + public function next(): void { next($this->values); } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function current() { return current($this->values); } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function key() { return key($this->values); diff --git a/src/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php index 15e069b9f81c1..590aea21af693 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php @@ -54,7 +54,7 @@ public function __construct() { } - public function accept() + public function accept(): bool { throw new \BadFunctionCallException('Not implemented'); } diff --git a/src/Symfony/Component/Form/Button.php b/src/Symfony/Component/Form/Button.php index f3f1edebf10d2..9c3cf9b6b5976 100644 --- a/src/Symfony/Component/Form/Button.php +++ b/src/Symfony/Component/Form/Button.php @@ -51,6 +51,7 @@ public function __construct(FormConfigInterface $config) * * @return bool Always returns false */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return false; @@ -67,6 +68,7 @@ public function offsetExists($offset) * * @throws BadMethodCallException */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { throw new BadMethodCallException('Buttons cannot have children.'); @@ -84,6 +86,7 @@ public function offsetGet($offset) * * @throws BadMethodCallException */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { throw new BadMethodCallException('Buttons cannot have children.'); @@ -100,6 +103,7 @@ public function offsetSet($offset, $value) * * @throws BadMethodCallException */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { throw new BadMethodCallException('Buttons cannot have children.'); @@ -436,6 +440,7 @@ public function createView(FormView $parent = null) * * @return int Always returns 0 */ + #[\ReturnTypeWillChange] public function count() { return 0; @@ -446,6 +451,7 @@ public function count() * * @return \EmptyIterator Always returns an empty iterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \EmptyIterator(); diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index 4d16632bbe40d..fb310899700f8 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -717,6 +717,7 @@ public function getOption($name, $default = null) * * @return int Always returns 0 */ + #[\ReturnTypeWillChange] public function count() { return 0; @@ -727,6 +728,7 @@ public function count() * * @return \EmptyIterator Always returns an empty iterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \EmptyIterator(); diff --git a/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php b/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php index dd80486c9acea..a8446655c3565 100644 --- a/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php +++ b/src/Symfony/Component/Form/ChoiceList/View/ChoiceGroupView.php @@ -36,8 +36,9 @@ public function __construct($label, array $choices = []) /** * {@inheritdoc} * - * @return self[]|ChoiceView[] + * @return \Traversable<ChoiceGroupView|ChoiceView> */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->choices); diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php index fca0bfa16b6b3..47593b82cb1ff 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php @@ -223,6 +223,7 @@ public function mapsForm($index) * * @return ViolationPathIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new ViolationPathIterator($this); diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 9abb6bdeab146..32cc437574d4b 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -958,6 +958,7 @@ public function get($name) * * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($name) { return $this->has($name); @@ -972,6 +973,7 @@ public function offsetExists($name) * * @throws OutOfBoundsException if the named child does not exist */ + #[\ReturnTypeWillChange] public function offsetGet($name) { return $this->get($name); @@ -990,6 +992,7 @@ public function offsetGet($name) * * @see self::add() */ + #[\ReturnTypeWillChange] public function offsetSet($name, $child) { $this->add($child); @@ -1004,6 +1007,7 @@ public function offsetSet($name, $child) * * @throws AlreadySubmittedException if the form has already been submitted */ + #[\ReturnTypeWillChange] public function offsetUnset($name) { $this->remove($name); @@ -1014,6 +1018,7 @@ public function offsetUnset($name) * * @return \Traversable<FormInterface> */ + #[\ReturnTypeWillChange] public function getIterator() { return $this->children; @@ -1024,6 +1029,7 @@ public function getIterator() * * @return int The number of embedded form children */ + #[\ReturnTypeWillChange] public function count() { return \count($this->children); diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index 2fa65e095c496..9ec863ac493c8 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -161,6 +161,7 @@ public function all() /** * @return int */ + #[\ReturnTypeWillChange] public function count() { if ($this->locked) { @@ -215,6 +216,7 @@ public function getForm() * * @return FormBuilderInterface[]|\Traversable */ + #[\ReturnTypeWillChange] public function getIterator() { if ($this->locked) { diff --git a/src/Symfony/Component/Form/FormErrorIterator.php b/src/Symfony/Component/Form/FormErrorIterator.php index f4614844985a0..0ef5a2e95844c 100644 --- a/src/Symfony/Component/Form/FormErrorIterator.php +++ b/src/Symfony/Component/Form/FormErrorIterator.php @@ -93,6 +93,7 @@ public function getForm() * * @return FormError|self An error or an iterator containing nested errors */ + #[\ReturnTypeWillChange] public function current() { return current($this->errors); @@ -101,6 +102,7 @@ public function current() /** * Advances the iterator to the next position. */ + #[\ReturnTypeWillChange] public function next() { next($this->errors); @@ -111,6 +113,7 @@ public function next() * * @return int The 0-indexed position */ + #[\ReturnTypeWillChange] public function key() { return key($this->errors); @@ -121,6 +124,7 @@ public function key() * * @return bool Whether the iterator is valid */ + #[\ReturnTypeWillChange] public function valid() { return null !== key($this->errors); @@ -132,6 +136,7 @@ public function valid() * This method detects if errors have been added to the form since the * construction of the iterator. */ + #[\ReturnTypeWillChange] public function rewind() { reset($this->errors); @@ -144,6 +149,7 @@ public function rewind() * * @return bool Whether that position exists */ + #[\ReturnTypeWillChange] public function offsetExists($position) { return isset($this->errors[$position]); @@ -158,6 +164,7 @@ public function offsetExists($position) * * @throws OutOfBoundsException If the given position does not exist */ + #[\ReturnTypeWillChange] public function offsetGet($position) { if (!isset($this->errors[$position])) { @@ -174,6 +181,7 @@ public function offsetGet($position) * * @throws BadMethodCallException */ + #[\ReturnTypeWillChange] public function offsetSet($position, $value) { throw new BadMethodCallException('The iterator doesn\'t support modification of elements.'); @@ -186,6 +194,7 @@ public function offsetSet($position, $value) * * @throws BadMethodCallException */ + #[\ReturnTypeWillChange] public function offsetUnset($position) { throw new BadMethodCallException('The iterator doesn\'t support modification of elements.'); @@ -197,6 +206,7 @@ public function offsetUnset($position) * * @return bool Whether the current element is an instance of this class */ + #[\ReturnTypeWillChange] public function hasChildren() { return current($this->errors) instanceof self; @@ -207,6 +217,7 @@ public function hasChildren() * * @return self */ + #[\ReturnTypeWillChange] public function getChildren() { return current($this->errors); @@ -229,6 +240,7 @@ public function getChildren() * * @return int The number of iterated elements */ + #[\ReturnTypeWillChange] public function count() { return \count($this->errors); @@ -243,6 +255,7 @@ public function count() * * @throws OutOfBoundsException If the position is invalid */ + #[\ReturnTypeWillChange] public function seek($position) { if (!isset($this->errors[$position])) { diff --git a/src/Symfony/Component/Form/FormView.php b/src/Symfony/Component/Form/FormView.php index 644bf515e6a71..460e52857680d 100644 --- a/src/Symfony/Component/Form/FormView.php +++ b/src/Symfony/Component/Form/FormView.php @@ -108,6 +108,7 @@ public function setMethodRendered() * * @return self The child view */ + #[\ReturnTypeWillChange] public function offsetGet($name) { return $this->children[$name]; @@ -120,6 +121,7 @@ public function offsetGet($name) * * @return bool Whether the child view exists */ + #[\ReturnTypeWillChange] public function offsetExists($name) { return isset($this->children[$name]); @@ -132,6 +134,7 @@ public function offsetExists($name) * * @throws BadMethodCallException always as setting a child by name is not allowed */ + #[\ReturnTypeWillChange] public function offsetSet($name, $value) { throw new BadMethodCallException('Not supported.'); @@ -144,6 +147,7 @@ public function offsetSet($name, $value) * * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($name) { unset($this->children[$name]); @@ -154,6 +158,7 @@ public function offsetUnset($name) * * @return \ArrayIterator<string, FormView> The iterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->children); @@ -164,6 +169,7 @@ public function getIterator() * * @return int The number of children views */ + #[\ReturnTypeWillChange] public function count() { return \count($this->children); diff --git a/src/Symfony/Component/Form/Resources/translations/validators.da.xlf b/src/Symfony/Component/Form/Resources/translations/validators.da.xlf index dafe20fa02c32..b4f078ff35f40 100644 --- a/src/Symfony/Component/Form/Resources/translations/validators.da.xlf +++ b/src/Symfony/Component/Form/Resources/translations/validators.da.xlf @@ -120,7 +120,7 @@ </trans-unit> <trans-unit id="125"> <source>Please enter a valid email address.</source> - <target>Indtast venligst en gyldig emailaddresse.</target> + <target>Indtast venligst en gyldig e-mailadresse.</target> </trans-unit> <trans-unit id="126"> <source>Please select a valid option.</source> diff --git a/src/Symfony/Component/Form/Resources/translations/validators.et.xlf b/src/Symfony/Component/Form/Resources/translations/validators.et.xlf index 1a9867fa20953..6524c86b144ee 100644 --- a/src/Symfony/Component/Form/Resources/translations/validators.et.xlf +++ b/src/Symfony/Component/Form/Resources/translations/validators.et.xlf @@ -14,6 +14,126 @@ <source>The CSRF token is invalid. Please try to resubmit the form.</source> <target>CSRF-märgis on vigane. Palun proovi vormi uuesti esitada.</target> </trans-unit> + <trans-unit id="99"> + <source>This value is not a valid HTML5 color.</source> + <target>See väärtus ei ole korrektne HTML5 värv.</target> + </trans-unit> + <trans-unit id="100"> + <source>Please enter a valid birthdate.</source> + <target>Palun sisesta korrektne sünnikuupäev.</target> + </trans-unit> + <trans-unit id="101"> + <source>The selected choice is invalid.</source> + <target>Tehtud valik on vigane.</target> + </trans-unit> + <trans-unit id="102"> + <source>The collection is invalid.</source> + <target>Kogum on vigane.</target> + </trans-unit> + <trans-unit id="103"> + <source>Please select a valid color.</source> + <target>Palun vali korrektne värv.</target> + </trans-unit> + <trans-unit id="104"> + <source>Please select a valid country.</source> + <target>Palun vali korrektne riik.</target> + </trans-unit> + <trans-unit id="105"> + <source>Please select a valid currency.</source> + <target>Palun vali korrektne valuuta.</target> + </trans-unit> + <trans-unit id="106"> + <source>Please choose a valid date interval.</source> + <target>Palun vali korrektne kuupäevade vahemik.</target> + </trans-unit> + <trans-unit id="107"> + <source>Please enter a valid date and time.</source> + <target>Palun sisesta korrektne kuupäev ja kellaaeg.</target> + </trans-unit> + <trans-unit id="108"> + <source>Please enter a valid date.</source> + <target>Palun sisesta korrektne kuupäev.</target> + </trans-unit> + <trans-unit id="109"> + <source>Please select a valid file.</source> + <target>Palun vali korrektne fail.</target> + </trans-unit> + <trans-unit id="110"> + <source>The hidden field is invalid.</source> + <target>Peidetud väli on vigane.</target> + </trans-unit> + <trans-unit id="111"> + <source>Please enter an integer.</source> + <target>Palun sisesta täisarv.</target> + </trans-unit> + <trans-unit id="112"> + <source>Please select a valid language.</source> + <target>Palun vali korrektne keel.</target> + </trans-unit> + <trans-unit id="113"> + <source>Please select a valid locale.</source> + <target>Palun vali korrektne keelekood.</target> + </trans-unit> + <trans-unit id="114"> + <source>Please enter a valid money amount.</source> + <target>Palun sisesta korrektne rahaline väärtus.</target> + </trans-unit> + <trans-unit id="115"> + <source>Please enter a number.</source> + <target>Palun sisesta number.</target> + </trans-unit> + <trans-unit id="116"> + <source>The password is invalid.</source> + <target>Vigane parool.</target> + </trans-unit> + <trans-unit id="117"> + <source>Please enter a percentage value.</source> + <target>Palun sisesta protsendiline väärtus.</target> + </trans-unit> + <trans-unit id="118"> + <source>The values do not match.</source> + <target>Väärtused ei klapi.</target> + </trans-unit> + <trans-unit id="119"> + <source>Please enter a valid time.</source> + <target>Palun sisesta korrektne aeg.</target> + </trans-unit> + <trans-unit id="120"> + <source>Please select a valid timezone.</source> + <target>Palun vali korrektne ajavöönd.</target> + </trans-unit> + <trans-unit id="121"> + <source>Please enter a valid URL.</source> + <target>Palun sisesta korrektne URL.</target> + </trans-unit> + <trans-unit id="122"> + <source>Please enter a valid search term.</source> + <target>Palun sisesta korrektne otsingutermin.</target> + </trans-unit> + <trans-unit id="123"> + <source>Please provide a valid phone number.</source> + <target>Palun sisesta korrektne telefoninumber.</target> + </trans-unit> + <trans-unit id="124"> + <source>The checkbox has an invalid value.</source> + <target>Märkeruudu väärtus on vigane.</target> + </trans-unit> + <trans-unit id="125"> + <source>Please enter a valid email address.</source> + <target>Palun sisesta korrektne e-posti aadress.</target> + </trans-unit> + <trans-unit id="126"> + <source>Please select a valid option.</source> + <target>Palun tee korrektne valik.</target> + </trans-unit> + <trans-unit id="127"> + <source>Please select a valid range.</source> + <target>Palun vali korrektne vahemik.</target> + </trans-unit> + <trans-unit id="128"> + <source>Please enter a valid week.</source> + <target>Palun sisesta korrektne nädal.</target> + </trans-unit> </body> </file> </xliff> diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php index 60d9d5a84e428..dc01ba15503d9 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php @@ -17,9 +17,9 @@ class DateTimeToStringTransformerTest extends TestCase { - public function dataProvider() + public function dataProvider(): array { - $data = [ + return [ ['Y-m-d H:i:s', '2010-02-03 16:05:06', '2010-02-03 16:05:06 UTC'], ['Y-m-d H:i:00', '2010-02-03 16:05:00', '2010-02-03 16:05:00 UTC'], ['Y-m-d H:i', '2010-02-03 16:05', '2010-02-03 16:05:00 UTC'], @@ -36,7 +36,6 @@ public function dataProvider() // different day representations ['Y-m-j', '2010-02-3', '2010-02-03 00:00:00 UTC'], - ['z', '33', '1970-02-03 00:00:00 UTC'], // not bijective // this will not work as PHP will use actual date to replace missing info @@ -63,8 +62,6 @@ public function dataProvider() ['Y-z', '2010-33', '2010-02-03 00:00:00 UTC'], ]; - - return $data; } /** diff --git a/src/Symfony/Component/Form/Tests/Fixtures/CustomArrayObject.php b/src/Symfony/Component/Form/Tests/Fixtures/CustomArrayObject.php index 5c12b6b400bb8..942add40e3736 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/CustomArrayObject.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/CustomArrayObject.php @@ -29,12 +29,18 @@ public function offsetExists($offset): bool return \array_key_exists($offset, $this->array); } + /** + * @param mixed $offset + * + * @return mixed + */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->array[$offset]; } - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { if (null === $offset) { $this->array[] = $value; @@ -43,7 +49,7 @@ public function offsetSet($offset, $value) } } - public function offsetUnset($offset) + public function offsetUnset($offset): void { unset($this->array[$offset]); } @@ -73,7 +79,7 @@ public function __unserialize(array $data): void $this->array = $data; } - public function unserialize($serialized) + public function unserialize($serialized): void { $this->__unserialize((array) unserialize((string) $serialized)); } diff --git a/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php b/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php index 1bb324423623b..7cba17dbebe1c 100644 --- a/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php +++ b/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php @@ -30,6 +30,7 @@ class InheritDataAwareIterator extends \IteratorIterator implements \RecursiveIt * * @return static */ + #[\ReturnTypeWillChange] public function getChildren() { return new static($this->current()); @@ -38,6 +39,7 @@ public function getChildren() /** * @return bool */ + #[\ReturnTypeWillChange] public function hasChildren() { return (bool) $this->current()->getConfig()->getInheritData(); diff --git a/src/Symfony/Component/Form/Util/OrderedHashMap.php b/src/Symfony/Component/Form/Util/OrderedHashMap.php index 7b1ca264d2bbb..b60a7ce4b0959 100644 --- a/src/Symfony/Component/Form/Util/OrderedHashMap.php +++ b/src/Symfony/Component/Form/Util/OrderedHashMap.php @@ -101,6 +101,7 @@ public function __construct(array $elements = []) /** * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($key) { return isset($this->elements[$key]); @@ -111,6 +112,7 @@ public function offsetExists($key) * * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($key) { if (!isset($this->elements[$key])) { @@ -125,6 +127,7 @@ public function offsetGet($key) * * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($key, $value) { if (null === $key || !isset($this->elements[$key])) { @@ -148,6 +151,7 @@ public function offsetSet($key, $value) * * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($key) { if (false !== ($position = array_search((string) $key, $this->orderedKeys))) { @@ -165,6 +169,7 @@ public function offsetUnset($key) /** * @return \Traversable */ + #[\ReturnTypeWillChange] public function getIterator() { return new OrderedHashMapIterator($this->elements, $this->orderedKeys, $this->managedCursors); @@ -173,6 +178,7 @@ public function getIterator() /** * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->elements); diff --git a/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php b/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php index 6e295e1871451..87cab46052b40 100644 --- a/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php +++ b/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php @@ -105,6 +105,7 @@ public function __destruct() * * @return mixed */ + #[\ReturnTypeWillChange] public function current() { return $this->current; @@ -112,10 +113,8 @@ public function current() /** * {@inheritdoc} - * - * @return void */ - public function next() + public function next(): void { ++$this->cursor; @@ -133,6 +132,7 @@ public function next() * * @return mixed */ + #[\ReturnTypeWillChange] public function key() { if (null === $this->key) { @@ -154,10 +154,8 @@ public function valid(): bool /** * {@inheritdoc} - * - * @return void */ - public function rewind() + public function rewind(): void { $this->cursor = 0; diff --git a/src/Symfony/Component/HttpFoundation/File/Stream.php b/src/Symfony/Component/HttpFoundation/File/Stream.php index 4a08e7f2dd355..cef3e03977cfe 100644 --- a/src/Symfony/Component/HttpFoundation/File/Stream.php +++ b/src/Symfony/Component/HttpFoundation/File/Stream.php @@ -23,6 +23,7 @@ class Stream extends File * * @return int|false */ + #[\ReturnTypeWillChange] public function getSize() { return false; diff --git a/src/Symfony/Component/HttpFoundation/HeaderBag.php b/src/Symfony/Component/HttpFoundation/HeaderBag.php index 15393544fcbfe..9fb113de44e26 100644 --- a/src/Symfony/Component/HttpFoundation/HeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/HeaderBag.php @@ -279,6 +279,7 @@ public function removeCacheControlDirective($key) * * @return \ArrayIterator An \ArrayIterator instance */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->headers); @@ -289,6 +290,7 @@ public function getIterator() * * @return int The number of headers */ + #[\ReturnTypeWillChange] public function count() { return \count($this->headers); diff --git a/src/Symfony/Component/HttpFoundation/ParameterBag.php b/src/Symfony/Component/HttpFoundation/ParameterBag.php index 1de753d3da37b..a2fe0af869873 100644 --- a/src/Symfony/Component/HttpFoundation/ParameterBag.php +++ b/src/Symfony/Component/HttpFoundation/ParameterBag.php @@ -210,6 +210,7 @@ public function filter($key, $default = null, $filter = \FILTER_DEFAULT, $option * * @return \ArrayIterator An \ArrayIterator instance */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->parameters); @@ -220,6 +221,7 @@ public function getIterator() * * @return int The number of parameters */ + #[\ReturnTypeWillChange] public function count() { return \count($this->parameters); diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index d1f8461ac8683..82caf56087cb4 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1564,7 +1564,7 @@ public function getContent($asResource = false) */ public function getETags() { - return preg_split('/\s*,\s*/', $this->headers->get('if_none_match', ''), -1, \PREG_SPLIT_NO_EMPTY); + return preg_split('/\s*,\s*/', $this->headers->get('If-None-Match', ''), -1, \PREG_SPLIT_NO_EMPTY); } /** diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index e6b913f416508..63125846b326b 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -1079,12 +1079,27 @@ public function isNotModified(Request $request): bool $lastModified = $this->headers->get('Last-Modified'); $modifiedSince = $request->headers->get('If-Modified-Since'); - if ($etags = $request->getETags()) { - $notModified = \in_array($this->getEtag(), $etags) || \in_array('*', $etags); - } + if ($ifNoneMatchEtags = $request->getETags()) { + $etag = $this->getEtag(); + if (0 == strncmp($etag, 'W/', 2)) { + $etag = substr($etag, 2); + } + + // Use weak comparison as per https://tools.ietf.org/html/rfc7232#section-3.2. + foreach ($ifNoneMatchEtags as $ifNoneMatchEtag) { + if (0 == strncmp($ifNoneMatchEtag, 'W/', 2)) { + $ifNoneMatchEtag = substr($ifNoneMatchEtag, 2); + } - if ($modifiedSince && $lastModified) { - $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified); + if ($ifNoneMatchEtag === $etag || '*' === $ifNoneMatchEtag) { + $notModified = true; + break; + } + } + } + // Only do If-Modified-Since date comparison when If-None-Match is not present as per https://tools.ietf.org/html/rfc7232#section-3.3. + elseif ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified); } if ($notModified) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php index ee33698cf0842..bd35531739d3c 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php +++ b/src/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php @@ -131,6 +131,7 @@ public function clear() * * @return \ArrayIterator An \ArrayIterator instance */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->attributes); @@ -141,6 +142,7 @@ public function getIterator() * * @return int The number of attributes */ + #[\ReturnTypeWillChange] public function count() { return \count($this->attributes); diff --git a/src/Symfony/Component/HttpFoundation/Session/Session.php b/src/Symfony/Component/HttpFoundation/Session/Session.php index 960679ace8f61..f42ed38ef5573 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Session.php +++ b/src/Symfony/Component/HttpFoundation/Session/Session.php @@ -126,6 +126,7 @@ public function isStarted() * * @return \ArrayIterator An \ArrayIterator instance */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->getAttributeBag()->all()); @@ -136,6 +137,7 @@ public function getIterator() * * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->getAttributeBag()->all()); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php index d8663a57b76d2..af9d45cae3b40 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -57,6 +57,7 @@ public function __construct(\Memcached $memcached, array $options = []) /** * @return bool */ + #[\ReturnTypeWillChange] public function close() { return $this->memcached->quit(); @@ -73,6 +74,7 @@ protected function doRead($sessionId) /** * @return bool */ + #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php index 0146ee259b384..8efdb856f16e5 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php @@ -163,6 +163,6 @@ private function stampCreated(int $lifetime = null): void { $timeStamp = time(); $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; - $this->meta[self::LIFETIME] = $lifetime ?? ini_get('session.cookie_lifetime'); + $this->meta[self::LIFETIME] = $lifetime ?? (int) ini_get('session.cookie_lifetime'); } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index 97975dba92b4f..916961f5eed5c 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -86,7 +86,7 @@ class NativeSessionStorage implements SessionStorageInterface * name, "PHPSESSID" * referer_check, "" * serialize_handler, "php" - * use_strict_mode, "0" + * use_strict_mode, "1" * use_cookies, "1" * use_only_cookies, "1" * use_trans_sid, "0" diff --git a/src/Symfony/Component/HttpFoundation/Tests/File/FakeFile.php b/src/Symfony/Component/HttpFoundation/Tests/File/FakeFile.php index c415989f2f7f8..8b2f12f4144cf 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/File/FakeFile.php +++ b/src/Symfony/Component/HttpFoundation/Tests/File/FakeFile.php @@ -17,28 +17,28 @@ class FakeFile extends OrigFile { private $realpath; - public function __construct($realpath, $path) + public function __construct(string $realpath, string $path) { $this->realpath = $realpath; parent::__construct($path, false); } - public function isReadable() + public function isReadable(): bool { return true; } - public function getRealpath() + public function getRealpath(): string { return $this->realpath; } - public function getSize() + public function getSize(): int { return 42; } - public function getMTime() + public function getMTime(): int { return time(); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php index d4b8812d99c21..7f23a9397562b 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php @@ -194,7 +194,7 @@ public function testIsNotModifiedEtag() $etagTwo = 'randomly_generated_etag_2'; $request = new Request(); - $request->headers->set('if_none_match', sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree')); + $request->headers->set('If-None-Match', sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree')); $response = new Response(); @@ -206,6 +206,38 @@ public function testIsNotModifiedEtag() $response->headers->set('ETag', ''); $this->assertFalse($response->isNotModified($request)); + + // Test wildcard + $request = new Request(); + $request->headers->set('If-None-Match', '*'); + + $response->headers->set('ETag', $etagOne); + $this->assertTrue($response->isNotModified($request)); + } + + public function testIsNotModifiedWeakEtag() + { + $etag = 'randomly_generated_etag'; + $weakEtag = 'W/randomly_generated_etag'; + + $request = new Request(); + $request->headers->set('If-None-Match', $etag); + $response = new Response(); + + $response->headers->set('ETag', $etag); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('ETag', $weakEtag); + $this->assertTrue($response->isNotModified($request)); + + $request->headers->set('If-None-Match', $weakEtag); + $response = new Response(); + + $response->headers->set('ETag', $etag); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('ETag', $weakEtag); + $this->assertTrue($response->isNotModified($request)); } public function testIsNotModifiedLastModifiedAndEtag() @@ -216,14 +248,14 @@ public function testIsNotModifiedLastModifiedAndEtag() $etag = 'randomly_generated_etag'; $request = new Request(); - $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree')); + $request->headers->set('If-None-Match', sprintf('%s, %s', $etag, 'etagThree')); $request->headers->set('If-Modified-Since', $modified); $response = new Response(); $response->headers->set('ETag', $etag); $response->headers->set('Last-Modified', $after); - $this->assertFalse($response->isNotModified($request)); + $this->assertTrue($response->isNotModified($request)); $response->headers->set('ETag', 'non-existent-etag'); $response->headers->set('Last-Modified', $before); @@ -240,7 +272,7 @@ public function testIsNotModifiedIfModifiedSinceAndEtagWithoutLastModified() $etag = 'randomly_generated_etag'; $request = new Request(); - $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree')); + $request->headers->set('If-None-Match', sprintf('%s, %s', $etag, 'etagThree')); $request->headers->set('If-Modified-Since', $modified); $response = new Response(); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/MetadataBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/MetadataBagTest.php index e040f4862755b..51a1b6472f764 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/MetadataBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/MetadataBagTest.php @@ -136,4 +136,14 @@ public function testDoesNotSkipLastUsedUpdate() $this->assertEquals($timeStamp, $sessionMetadata[MetadataBag::UPDATED]); } + + public function testLifetimeIsInt() + { + $sessionMetadata = []; + + $bag = new MetadataBag(); + $bag->initialize($sessionMetadata); + + $this->assertIsInt($bag->getLifetime()); + } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php index 1422eccfbc049..972a2745132e1 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php @@ -103,17 +103,21 @@ public function testRead() public function testWrite() { $this->mock->expects($this->once()) - ->method('write'); + ->method('write') + ->willReturn(true) + ; - $this->proxy->write('id', 'data'); + $this->assertTrue($this->proxy->write('id', 'data')); } public function testDestroy() { $this->mock->expects($this->once()) - ->method('destroy'); + ->method('destroy') + ->willReturn(true) + ; - $this->proxy->destroy('id'); + $this->assertTrue($this->proxy->destroy('id')); } public function testGc() @@ -149,7 +153,9 @@ public function testUpdateTimestamp() $proxy->updateTimestamp('id', 'data'); $this->mock->expects($this->once()) - ->method('write'); + ->method('write') + ->willReturn(true) + ; $this->proxy->updateTimestamp('id', 'data'); } diff --git a/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php index ce4ddb35d3f75..832bfb58d0637 100644 --- a/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php +++ b/src/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php @@ -30,6 +30,7 @@ protected function beforeDispatch(string $eventName, $event) { switch ($eventName) { case KernelEvents::REQUEST: + $event->getRequest()->attributes->set('_stopwatch_token', substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6)); $this->stopwatch->openSection(); break; case KernelEvents::VIEW: @@ -40,8 +41,8 @@ protected function beforeDispatch(string $eventName, $event) } break; case KernelEvents::TERMINATE: - $token = $event->getResponse()->headers->get('X-Debug-Token'); - if (null === $token) { + $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); + if (null === $sectionId) { break; } // There is a very special case when using built-in AppCache class as kernel wrapper, in the case @@ -50,7 +51,7 @@ protected function beforeDispatch(string $eventName, $event) // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception // which must be caught. try { - $this->stopwatch->openSection($token); + $this->stopwatch->openSection($sectionId); } catch (\LogicException $e) { } break; @@ -67,21 +68,21 @@ protected function afterDispatch(string $eventName, $event) $this->stopwatch->start('controller', 'section'); break; case KernelEvents::RESPONSE: - $token = $event->getResponse()->headers->get('X-Debug-Token'); - if (null === $token) { + $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); + if (null === $sectionId) { break; } - $this->stopwatch->stopSection($token); + $this->stopwatch->stopSection($sectionId); break; case KernelEvents::TERMINATE: // In the special case described in the `preDispatch` method above, the `$token` section // does not exist, then closing it throws an exception which must be caught. - $token = $event->getResponse()->headers->get('X-Debug-Token'); - if (null === $token) { + $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); + if (null === $sectionId) { break; } try { - $this->stopwatch->stopSection($token); + $this->stopwatch->stopSection($sectionId); } catch (\LogicException $e) { } break; diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 6c4715802efda..7deda42fc7186 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -5,12 +5,14 @@ * * (c) Fabien Potencier <fabien@symfony.com> * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* * This code is partially based on the Rack-Cache library by Ryan Tomayko, * which is released under the MIT license. * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. */ namespace Symfony\Component\HttpKernel\HttpCache; @@ -382,7 +384,7 @@ protected function validate(Request $request, Response $entry, $catch = false) // add our cached last-modified validator if ($entry->headers->has('Last-Modified')) { - $subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified')); + $subRequest->headers->set('If-Modified-Since', $entry->headers->get('Last-Modified')); } // Add our cached etag validator to the environment. @@ -391,7 +393,7 @@ protected function validate(Request $request, Response $entry, $catch = false) $cachedEtags = $entry->getEtag() ? [$entry->getEtag()] : []; $requestEtags = $request->getETags(); if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) { - $subRequest->headers->set('if_none_match', implode(', ', $etags)); + $subRequest->headers->set('If-None-Match', implode(', ', $etags)); } $response = $this->forward($subRequest, $catch, $entry); @@ -444,8 +446,8 @@ protected function fetch(Request $request, $catch = false) } // avoid that the backend sends no content - $subRequest->headers->remove('if_modified_since'); - $subRequest->headers->remove('if_none_match'); + $subRequest->headers->remove('If-Modified-Since'); + $subRequest->headers->remove('If-None-Match'); $response = $this->forward($subRequest, $catch); diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 321cca741b69d..6c816aaf63d63 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,11 +76,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - public const VERSION = '4.4.29'; - public const VERSION_ID = 40429; + public const VERSION = '4.4.30'; + public const VERSION_ID = 40430; public const MAJOR_VERSION = 4; public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 29; + public const RELEASE_VERSION = 30; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '11/2022'; diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php index 2fb050dafd9f9..90ea544b1abe2 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php @@ -28,12 +28,12 @@ class TraceableEventDispatcherTest extends TestCase public function testStopwatchSections() { $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch = new Stopwatch()); - $kernel = $this->getHttpKernel($dispatcher, function () { return new Response('', 200, ['X-Debug-Token' => '292e1e']); }); + $kernel = $this->getHttpKernel($dispatcher); $request = Request::create('/'); $response = $kernel->handle($request); $kernel->terminate($request, $response); - $events = $stopwatch->getSectionEvents($response->headers->get('X-Debug-Token')); + $events = $stopwatch->getSectionEvents($request->attributes->get('_stopwatch_token')); $this->assertEquals([ '__section__', 'kernel.request', @@ -56,7 +56,7 @@ public function testStopwatchCheckControllerOnRequestEvent() $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch); - $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $kernel = $this->getHttpKernel($dispatcher); $request = Request::create('/'); $kernel->handle($request); } @@ -69,12 +69,12 @@ public function testStopwatchStopControllerOnRequestEvent() $stopwatch->expects($this->once()) ->method('isStarted') ->willReturn(true); - $stopwatch->expects($this->once()) + $stopwatch->expects($this->exactly(3)) ->method('stop'); $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch); - $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $kernel = $this->getHttpKernel($dispatcher); $request = Request::create('/'); $kernel->handle($request); } @@ -110,10 +110,12 @@ public function testListenerCanRemoveItselfWhenExecuted() $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed'); } - protected function getHttpKernel($dispatcher, $controller) + protected function getHttpKernel($dispatcher) { $controllerResolver = $this->createMock(ControllerResolverInterface::class); - $controllerResolver->expects($this->once())->method('getController')->willReturn($controller); + $controllerResolver->expects($this->once())->method('getController')->willReturn(function () { + return new Response(); + }); $argumentResolver = $this->createMock(ArgumentResolverInterface::class); $argumentResolver->expects($this->once())->method('getArguments')->willReturn([]); diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php index 9bec2f7c94eb1..fc363862ea680 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php @@ -156,7 +156,7 @@ public function testRespondsWith304WhenIfNoneMatchMatchesETag() $this->assertTraceContains('store'); } - public function testRespondsWith304OnlyIfIfNoneMatchAndIfModifiedSinceBothMatch() + public function testRespondsWith304WhenIfNoneMatchAndIfModifiedSinceBothMatch() { $time = \DateTime::createFromFormat('U', time()); @@ -172,7 +172,7 @@ public function testRespondsWith304OnlyIfIfNoneMatchAndIfModifiedSinceBothMatch( $t = \DateTime::createFromFormat('U', time() - 3600); $this->request('GET', '/', ['HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $t->format(\DATE_RFC2822)]); $this->assertHttpKernelIsCalled(); - $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals(304, $this->response->getStatusCode()); // only Last-Modified matches $this->request('GET', '/', ['HTTP_IF_NONE_MATCH' => '1234', 'HTTP_IF_MODIFIED_SINCE' => $time->format(\DATE_RFC2822)]); diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpKernelBrowserTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpKernelBrowserTest.php index 7cd4d52d39650..78adfa72b71e1 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpKernelBrowserTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpKernelBrowserTest.php @@ -143,6 +143,10 @@ public function testUploadedFileWhenNoFileSelected() public function testUploadedFileWhenSizeExceedsUploadMaxFileSize() { + if (UploadedFile::getMaxFilesize() > \PHP_INT_MAX) { + $this->markTestSkipped('Requires PHP_INT_MAX to be greater than "upload_max_filesize" and "post_max_size" ini settings'); + } + $source = tempnam(sys_get_temp_dir(), 'source'); $kernel = new TestHttpKernel(); @@ -157,7 +161,7 @@ public function testUploadedFileWhenSizeExceedsUploadMaxFileSize() /* should be modified when the getClientSize will be removed */ $file->expects($this->any()) ->method('getSize') - ->willReturn(\INF) + ->willReturn(\PHP_INT_MAX) ; $file->expects($this->any()) ->method('getClientSize') diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index d928c7aceaaf7..c2758e45ae8cc 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -20,7 +20,7 @@ "symfony/error-handler": "^4.4", "symfony/event-dispatcher": "^4.4", "symfony/http-client-contracts": "^1.1|^2", - "symfony/http-foundation": "^4.4|^5.0", + "symfony/http-foundation": "^4.4.30|^5.3.7", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-php73": "^1.9", "symfony/polyfill-php80": "^1.16", diff --git a/src/Symfony/Component/Intl/Data/Util/ArrayAccessibleResourceBundle.php b/src/Symfony/Component/Intl/Data/Util/ArrayAccessibleResourceBundle.php index da6faf67810b6..803e5561f6573 100644 --- a/src/Symfony/Component/Intl/Data/Util/ArrayAccessibleResourceBundle.php +++ b/src/Symfony/Component/Intl/Data/Util/ArrayAccessibleResourceBundle.php @@ -45,25 +45,22 @@ public function offsetExists($offset): bool } /** + * @param mixed $offset + * * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->get($offset); } - /** - * @return void - */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { throw new BadMethodCallException('Resource bundles cannot be modified.'); } - /** - * @return void - */ - public function offsetUnset($offset) + public function offsetUnset($offset): void { throw new BadMethodCallException('Resource bundles cannot be modified.'); } diff --git a/src/Symfony/Component/Intl/Data/Util/RingBuffer.php b/src/Symfony/Component/Intl/Data/Util/RingBuffer.php index 76bca285bbbc2..72d86025f718a 100644 --- a/src/Symfony/Component/Intl/Data/Util/RingBuffer.php +++ b/src/Symfony/Component/Intl/Data/Util/RingBuffer.php @@ -52,6 +52,7 @@ public function offsetExists($key): bool * * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($key) { if (!isset($this->indices[$key])) { @@ -66,7 +67,7 @@ public function offsetGet($key) * * @return void */ - public function offsetSet($key, $value) + public function offsetSet($key, $value): void { if (false !== ($keyToRemove = array_search($this->cursor, $this->indices))) { unset($this->indices[$keyToRemove]); @@ -83,7 +84,7 @@ public function offsetSet($key, $value) * * @return void */ - public function offsetUnset($key) + public function offsetUnset($key): void { if (isset($this->indices[$key])) { $this->values[$this->indices[$key]] = null; diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php index 05e81c8378e57..d8da8ddbf6586 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php @@ -45,6 +45,7 @@ public function toArray() /** * @return int */ + #[\ReturnTypeWillChange] public function count() { $con = $this->connection->getResource(); diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php index 0134a6ff5ef7f..07108f19aab3c 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php @@ -64,7 +64,7 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e throw new HttpTransportException('Unable to send an email: '.implode('; ', array_column($result['errors'], 'message')).sprintf(' (code %d).', $statusCode), $response); } catch (DecodingExceptionInterface $e) { - throw new HttpTransportException('Unable to send an email: '.$response->getContent(false).sprintf(' (code %d).', $statusCode), $response); + throw new HttpTransportException('Unable to send an email: '.$response->getContent(false).sprintf(' (code %d).', $statusCode), $response, 0, $e); } } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php index c7575e9b8746a..d8b1447257ba1 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Messenger\Tests\Transport\Doctrine; use Doctrine\DBAL\Abstraction\Result as AbstractionResult; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection as DBALConnection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\Result as DriverResult; @@ -23,8 +24,11 @@ use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaConfig; +use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Statement; +use Doctrine\DBAL\Types\Types; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Exception\InvalidArgumentException; use Symfony\Component\Messenger\Exception\TransportException; @@ -402,4 +406,54 @@ public function providePlatformSql(): iterable 'SELECT m.* FROM messenger_messages m WITH (UPDLOCK, ROWLOCK) WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY available_at ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY ', ]; } + + /** + * @dataProvider setupIndicesProvider + */ + public function testSetupIndices(string $platformClass, array $expectedIndices) + { + $driverConnection = $this->createMock(DBALConnection::class); + $driverConnection->method('getConfiguration')->willReturn(new Configuration()); + + $schemaManager = $this->createMock(AbstractSchemaManager::class); + $schema = new Schema(); + $expectedTable = $schema->createTable('messenger_messages'); + $expectedTable->addColumn('id', Types::BIGINT); + $expectedTable->setPrimaryKey(['id']); + // Make sure columns for indices exists so addIndex() will not throw + foreach (array_unique(array_merge(...$expectedIndices)) as $columnName) { + $expectedTable->addColumn($columnName, Types::STRING); + } + foreach ($expectedIndices as $indexColumns) { + $expectedTable->addIndex($indexColumns); + } + $schemaManager->method('createSchema')->willReturn($schema); + $driverConnection->method('getSchemaManager')->willReturn($schemaManager); + + $platformMock = $this->createMock($platformClass); + $platformMock + ->expects(self::once()) + ->method('getAlterTableSQL') + ->with(self::callback(static function (TableDiff $tableDiff): bool { + return 0 === \count($tableDiff->addedIndexes) && 0 === \count($tableDiff->changedIndexes) && 0 === \count($tableDiff->removedIndexes); + })) + ->willReturn([]); + $driverConnection->method('getDatabasePlatform')->willReturn($platformMock); + + $connection = new Connection([], $driverConnection); + $connection->setup(); + } + + public function setupIndicesProvider(): iterable + { + yield 'MySQL' => [ + MySQL57Platform::class, + [['delivered_at']], + ]; + + yield 'Other platforms' => [ + AbstractPlatform::class, + [['queue_name'], ['available_at'], ['delivered_at']], + ]; + } } diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php index 20098e2283669..e936ca09e871f 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php @@ -17,6 +17,7 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\LockMode; +use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\Comparator; @@ -386,7 +387,6 @@ private function getSchema(): Schema $table->addColumn('headers', self::$useDeprecatedConstants ? Type::TEXT : Types::TEXT) ->setNotnull(true); $table->addColumn('queue_name', self::$useDeprecatedConstants ? Type::STRING : Types::STRING) - ->setLength(190) // MySQL 5.6 only supports 191 characters on an indexed column in utf8mb4 mode ->setNotnull(true); $table->addColumn('created_at', self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE) ->setNotnull(true); @@ -395,8 +395,11 @@ private function getSchema(): Schema $table->addColumn('delivered_at', self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE) ->setNotnull(false); $table->setPrimaryKey(['id']); - $table->addIndex(['queue_name']); - $table->addIndex(['available_at']); + // No indices on queue_name and available_at on MySQL to prevent deadlock issues when running multiple consumers. + if (!$this->driverConnection->getDatabasePlatform() instanceof MySqlPlatform) { + $table->addIndex(['queue_name']); + $table->addIndex(['available_at']); + } $table->addIndex(['delivered_at']); return $schema; diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php index 6c6764e1818bd..fc1cf85475861 100644 --- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php +++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php @@ -832,6 +832,7 @@ public function resolve(array $options = []) * @throws OptionDefinitionException If there is a cyclic dependency between * lazy options and/or normalizers */ + #[\ReturnTypeWillChange] public function offsetGet($option/*, bool $triggerDeprecation = true*/) { if (!$this->locked) { @@ -1072,6 +1073,7 @@ private function verifyTypes(string $type, $value, array &$invalidTypes, int $le * * @see \ArrayAccess::offsetExists() */ + #[\ReturnTypeWillChange] public function offsetExists($option) { if (!$this->locked) { @@ -1088,6 +1090,7 @@ public function offsetExists($option) * * @throws AccessException */ + #[\ReturnTypeWillChange] public function offsetSet($option, $value) { throw new AccessException('Setting options via array access is not supported. Use setDefault() instead.'); @@ -1100,6 +1103,7 @@ public function offsetSet($option, $value) * * @throws AccessException */ + #[\ReturnTypeWillChange] public function offsetUnset($option) { throw new AccessException('Removing options via array access is not supported. Use remove() instead.'); @@ -1116,6 +1120,7 @@ public function offsetUnset($option) * * @see \Countable::count() */ + #[\ReturnTypeWillChange] public function count() { if (!$this->locked) { diff --git a/src/Symfony/Component/Process/InputStream.php b/src/Symfony/Component/Process/InputStream.php index c86fca86878df..4f8f71331aafc 100644 --- a/src/Symfony/Component/Process/InputStream.php +++ b/src/Symfony/Component/Process/InputStream.php @@ -69,6 +69,7 @@ public function isClosed() /** * @return \Traversable */ + #[\ReturnTypeWillChange] public function getIterator() { $this->open = true; diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index a30cc3d118e0e..9fafddd64413d 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -622,6 +622,7 @@ public function getIncrementalOutput() * * @return \Generator */ + #[\ReturnTypeWillChange] public function getIterator($flags = 0) { $this->readPipesForOutput(__FUNCTION__, false); diff --git a/src/Symfony/Component/PropertyAccess/PropertyPath.php b/src/Symfony/Component/PropertyAccess/PropertyPath.php index f831ec3fda0bb..ac1f7c3c04cf4 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyPath.php +++ b/src/Symfony/Component/PropertyAccess/PropertyPath.php @@ -154,6 +154,7 @@ public function getParent() * * @return PropertyPathIteratorInterface */ + #[\ReturnTypeWillChange] public function getIterator() { return new PropertyPathIterator($this); diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php index cf02ee69f2979..72e053171c841 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/NonTraversableArrayObject.php @@ -29,12 +29,18 @@ public function offsetExists($offset): bool return \array_key_exists($offset, $this->array); } + /** + * @param mixed $offset + * + * @return mixed + */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->array[$offset]; } - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { if (null === $offset) { $this->array[] = $value; @@ -43,7 +49,7 @@ public function offsetSet($offset, $value) } } - public function offsetUnset($offset) + public function offsetUnset($offset): void { unset($this->array[$offset]); } @@ -68,7 +74,7 @@ public function __unserialize(array $data): void $this->array = $data; } - public function unserialize($serialized) + public function unserialize($serialized): void { $this->__unserialize((array) unserialize((string) $serialized)); } diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php index 5693c6b73e685..eb4da3f201342 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TraversableArrayObject.php @@ -29,12 +29,18 @@ public function offsetExists($offset): bool return \array_key_exists($offset, $this->array); } + /** + * @param mixed $offset + * + * @return mixed + */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->array[$offset]; } - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { if (null === $offset) { $this->array[] = $value; @@ -43,7 +49,7 @@ public function offsetSet($offset, $value) } } - public function offsetUnset($offset) + public function offsetUnset($offset): void { unset($this->array[$offset]); } diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index d3b9b721e9c5f..0af4b9bf1bb36 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -335,7 +335,7 @@ private function extractFromReflectionType(\ReflectionType $reflectionType, \Ref foreach ($reflectionType instanceof \ReflectionUnionType ? $reflectionType->getTypes() : [$reflectionType] as $type) { $phpTypeOrClass = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : (string) $type; - if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass) { + if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) { continue; } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index 60b4efdbcf815..95e1ca5cda118 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -233,7 +233,7 @@ public function php71TypesProvider() } /** - * * @dataProvider php80TypesProvider + * @dataProvider php80TypesProvider * @requires PHP 8 */ public function testExtractPhp80Type($property, array $type = null) @@ -255,6 +255,14 @@ public function php80TypesProvider() ]; } + /** + * @requires PHP 8.1 + */ + public function testExtractPhp81Type() + { + $this->assertNull($this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy', 'nothing', [])); + } + /** * @dataProvider defaultValueProvider */ diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php81Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php81Dummy.php new file mode 100644 index 0000000000000..b4e896a434524 --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php81Dummy.php @@ -0,0 +1,11 @@ +<?php + +namespace Symfony\Component\PropertyInfo\Tests\Fixtures; + +class Php81Dummy +{ + public function getNothing(): never + { + throw new \Exception('Oops'); + } +} diff --git a/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php b/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php index 04009cd16d3a8..22df1af05469b 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php @@ -126,7 +126,7 @@ final public function methods(array $methods): self /** * Adds the "_controller" entry to defaults. * - * @param callable|string $controller a callable or parseable pseudo-callable + * @param callable|string|array $controller a callable or parseable pseudo-callable * * @return $this */ diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index 3baf0986c5cb1..56e925cac5158 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -51,6 +51,7 @@ public function __clone() * * @return \ArrayIterator|Route[] An \ArrayIterator object for iterating over routes */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->routes); @@ -61,6 +62,7 @@ public function getIterator() * * @return int The number of routes */ + #[\ReturnTypeWillChange] public function count() { return \count($this->routes); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php b/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php index 86caa99696149..4208579476f68 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php @@ -9,7 +9,9 @@ ->condition('abc') ->options(['utf8' => true]) ->add('buz', 'zub') - ->controller('foo:act'); + ->controller('foo:act') + ->add('controller_class', '/controller') + ->controller(['Acme\MyApp\MyController', 'myAction']); $routes->import('php_dsl_sub.php') ->prefix('/sub') diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/php_object_dsl.php b/src/Symfony/Component/Routing/Tests/Fixtures/php_object_dsl.php index 9b9183a1b9427..6036dfd29ac08 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/php_object_dsl.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/php_object_dsl.php @@ -11,7 +11,9 @@ public function __invoke(RoutingConfigurator $routes) ->condition('abc') ->options(['utf8' => true]) ->add('buz', 'zub') - ->controller('foo:act'); + ->controller('foo:act') + ->add('controller_class', '/controller') + ->controller(['Acme\MyApp\MyController', 'myAction']); $routes->import('php_dsl_sub.php') ->prefix('/sub') diff --git a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php index 70d45fc2383e8..f5f3a638e8067 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php @@ -174,6 +174,9 @@ public function testRoutingConfigurator() $expectedCollection->add('buz', (new Route('/zub')) ->setDefaults(['_controller' => 'foo:act']) ); + $expectedCollection->add('controller_class', (new Route('/controller')) + ->setDefaults(['_controller' => ['Acme\MyApp\MyController', 'myAction']]) + ); $expectedCollection->add('c_root', (new Route('/sub/pub/')) ->setRequirements(['id' => '\d+']) ); diff --git a/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php b/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php index 8a1c8327087dc..04a1212e21bfb 100644 --- a/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php +++ b/src/Symfony/Component/Security/Core/Exception/AuthenticationException.php @@ -128,7 +128,7 @@ public function __sleep(): array /** * @internal */ - public function __wakeup() + public function __wakeup(): void { if (__CLASS__ !== $c = (new \ReflectionMethod($this, 'unserialize'))->getDeclaringClass()->name) { @trigger_error(sprintf('Implementing the "%s::unserialize()" method is deprecated since Symfony 4.3, implement the __serialize() and __unserialize() methods instead.', $c), \E_USER_DEPRECATED); diff --git a/src/Symfony/Component/Security/Core/Resources/translations/security.et.xlf b/src/Symfony/Component/Security/Core/Resources/translations/security.et.xlf new file mode 100644 index 0000000000000..cc2b16ae853dc --- /dev/null +++ b/src/Symfony/Component/Security/Core/Resources/translations/security.et.xlf @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> + <file source-language="en" datatype="plaintext" original="file.ext"> + <body> + <trans-unit id="1"> + <source>An authentication exception occurred.</source> + <target>Autentimisel juhtus ootamatu viga.</target> + </trans-unit> + <trans-unit id="2"> + <source>Authentication credentials could not be found.</source> + <target>Autentimisandmeid ei leitud.</target> + </trans-unit> + <trans-unit id="3"> + <source>Authentication request could not be processed due to a system problem.</source> + <target>Autentimispäring ei õnnestunud süsteemi probleemi tõttu.</target> + </trans-unit> + <trans-unit id="4"> + <source>Invalid credentials.</source> + <target>Vigased autentimisandmed.</target> + </trans-unit> + <trans-unit id="5"> + <source>Cookie has already been used by someone else.</source> + <target>Küpsis on juba kellegi teise poolt kasutuses.</target> + </trans-unit> + <trans-unit id="6"> + <source>Not privileged to request the resource.</source> + <target>Ressursi pärimiseks pole piisavalt õiguseid.</target> + </trans-unit> + <trans-unit id="7"> + <source>Invalid CSRF token.</source> + <target>Vigane CSRF märgis.</target> + </trans-unit> + <trans-unit id="9"> + <source>No authentication provider found to support the authentication token.</source> + <target>Ei leitud sobivat autentimismeetodit, mis toetaks autentimismärgist.</target> + </trans-unit> + <trans-unit id="10"> + <source>No session available, it either timed out or cookies are not enabled.</source> + <target>Seanss puudub, see on kas aegunud või pole küpsised lubatud.</target> + </trans-unit> + <trans-unit id="11"> + <source>No token could be found.</source> + <target>Identsustõendit ei leitud.</target> + </trans-unit> + <trans-unit id="12"> + <source>Username could not be found.</source> + <target>Kasutajanime ei leitud.</target> + </trans-unit> + <trans-unit id="13"> + <source>Account has expired.</source> + <target>Kasutajakonto on aegunud.</target> + </trans-unit> + <trans-unit id="14"> + <source>Credentials have expired.</source> + <target>Autentimistunnused on aegunud.</target> + </trans-unit> + <trans-unit id="15"> + <source>Account is disabled.</source> + <target>Kasutajakonto on keelatud.</target> + </trans-unit> + <trans-unit id="16"> + <source>Account is locked.</source> + <target>Kasutajakonto on lukustatud.</target> + </trans-unit> + <trans-unit id="17"> + <source>Too many failed login attempts, please try again later.</source> + <target>Liiga palju ebaõnnestunud autentimise katseid, palun proovi hiljem uuesti.</target> + </trans-unit> + <trans-unit id="18"> + <source>Invalid or expired login link.</source> + <target>Vigane või aegunud sisselogimise link.</target> + </trans-unit> + <trans-unit id="19"> + <source>Too many failed login attempts, please try again in %minutes% minute.</source> + <target>Liiga palju ebaõnnestunud autentimise katseid, palun proovi uuesti %minutes% minuti pärast.</target> + </trans-unit> + <trans-unit id="20"> + <source>Too many failed login attempts, please try again in %minutes% minutes.</source> + <target>Liiga palju ebaõnnestunud autentimise katseid, palun proovi uuesti %minutes% minuti pärast.</target> + </trans-unit> + </body> + </file> +</xliff> diff --git a/src/Symfony/Component/Security/Core/Resources/translations/security.id.xlf b/src/Symfony/Component/Security/Core/Resources/translations/security.id.xlf index 91315bdf1d016..119e2d0cd70fb 100644 --- a/src/Symfony/Component/Security/Core/Resources/translations/security.id.xlf +++ b/src/Symfony/Component/Security/Core/Resources/translations/security.id.xlf @@ -64,11 +64,19 @@ </trans-unit> <trans-unit id="17"> <source>Too many failed login attempts, please try again later.</source> - <target>Terlalu banyak percobaan login yang salah, Silahkan coba lagi nanti.</target> + <target>Terlalu banyak percobaan login yang salah, silahkan coba lagi nanti.</target> </trans-unit> <trans-unit id="18"> <source>Invalid or expired login link.</source> - <target>Link login salah atau sudah kadaluwarsa.</target> + <target>Link login salah atau sudah kedaluwarsa.</target> + </trans-unit> + <trans-unit id="19"> + <source>Too many failed login attempts, please try again in %minutes% minute.</source> + <target>Terlalu banyak percobaan login yang salah, silahkan coba lagi dalam %minutes% menit.</target> + </trans-unit> + <trans-unit id="20"> + <source>Too many failed login attempts, please try again in %minutes% minutes.</source> + <target>Terlalu banyak percobaan login yang salah, silahkan coba lagi dalam %minutes% menit.</target> </trans-unit> </body> </file> diff --git a/src/Symfony/Component/Security/Core/Resources/translations/security.tl.xlf b/src/Symfony/Component/Security/Core/Resources/translations/security.tl.xlf index 5bf5304b5cae1..66547b2a3d1be 100644 --- a/src/Symfony/Component/Security/Core/Resources/translations/security.tl.xlf +++ b/src/Symfony/Component/Security/Core/Resources/translations/security.tl.xlf @@ -70,6 +70,14 @@ <source>Invalid or expired login link.</source> <target>Inbalido o nagexpire na ang link para makapaglogin.</target> </trans-unit> + <trans-unit id="19"> + <source>Too many failed login attempts, please try again in %minutes% minute.</source> + <target>Madaming bagsak na pagtatangka, pakisubukan ulit pagkatapos ng %minutes% minuto.</target> + </trans-unit> + <trans-unit id="20"> + <source>Too many failed login attempts, please try again in %minutes% minute.</source> + <target>Madaming bagsak na pagtatangka, pakisubukan ulit pagkatapos ng %minutes% minuto.</target> + </trans-unit> </body> </file> </xliff> diff --git a/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php b/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php index b357564864cd0..71459406614ab 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ChannelListener.php @@ -51,7 +51,7 @@ public function supports(Request $request): ?bool if (null !== $this->logger) { if ('https' === $request->headers->get('X-Forwarded-Proto')) { $this->logger->info('Redirecting to HTTPS. ("X-Forwarded-Proto" header is set to "https" - did you set "trusted_proxies" correctly?)'); - } elseif (str_contains($request->headers->get('Forwarded'), 'proto=https')) { + } elseif (str_contains($request->headers->get('Forwarded', ''), 'proto=https')) { $this->logger->info('Redirecting to HTTPS. ("Forwarded" header is set to "proto=https" - did you set "trusted_proxies" correctly?)'); } else { $this->logger->info('Redirecting to HTTPS.'); diff --git a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php index e78f21826f362..83aba4166a6e4 100644 --- a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php @@ -87,7 +87,7 @@ public function authenticate(RequestEvent $event) if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); - if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { + if (!\is_string($csrfToken) || false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new LogoutException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php index f158d906a4c5f..997e2e7ba04eb 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php @@ -84,7 +84,7 @@ protected function attemptAuthentication(Request $request) if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); - if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { + if (!\is_string($csrfToken) || false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php index b3661eae8afd1..3aaa6dd71a1af 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php @@ -72,7 +72,7 @@ protected function attemptAuthentication(Request $request) if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); - if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { + if (!\is_string($csrfToken) || false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ChannelListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ChannelListenerTest.php index 5fab54c13227d..0153d30395d9c 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ChannelListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ChannelListenerTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Security\Http\Tests\Firewall; use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Component\HttpFoundation\HeaderBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -153,4 +155,29 @@ public function testHandleWithSecuredRequestAndHttpChannel() $this->assertSame($response, $event->getResponse()); } + + public function testSupportsWithoutHeaders() + { + $request = $this->createMock(Request::class); + $request + ->expects($this->any()) + ->method('isSecure') + ->willReturn(false) + ; + $request->headers = new HeaderBag(); + + $accessMap = $this->createMock(AccessMapInterface::class); + $accessMap + ->expects($this->any()) + ->method('getPatterns') + ->with($this->equalTo($request)) + ->willReturn([[], 'https']) + ; + + $entryPoint = $this->createMock(AuthenticationEntryPointInterface::class); + + $listener = new ChannelListener($accessMap, $entryPoint, new NullLogger()); + + $this->assertTrue($listener->supports($request)); + } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php index 397639fd940f7..4e86dccdd8c39 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php @@ -152,7 +152,10 @@ public function testSuccessHandlerReturnsNonResponse() $listener($event); } - public function testCsrfValidationFails() + /** + * @dataProvider provideInvalidCsrfTokens + */ + public function testCsrfValidationFails($invalidToken) { $this->expectException(LogoutException::class); $tokenManager = $this->getTokenManager(); @@ -160,20 +163,31 @@ public function testCsrfValidationFails() [$listener, , $httpUtils, $options] = $this->getListener(null, $tokenManager); $request = new Request(); - $request->query->set('_csrf_token', 'token'); + if (null !== $invalidToken) { + $request->query->set('_csrf_token', $invalidToken); + } $httpUtils->expects($this->once()) ->method('checkRequestPath') ->with($request, $options['logout_path']) ->willReturn(true); - $tokenManager->expects($this->once()) + $tokenManager ->method('isTokenValid') ->willReturn(false); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); } + public function provideInvalidCsrfTokens(): array + { + return [ + ['invalid'], + [['in' => 'valid']], + [null], + ]; + } + private function getTokenManager() { return $this->createMock(CsrfTokenManagerInterface::class); diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php index 312014cd1a6f5..e6d9e06d8b698 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php @@ -24,6 +24,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; @@ -37,7 +38,7 @@ class UsernamePasswordFormAuthenticationListenerTest extends TestCase /** * @dataProvider getUsernameForLength */ - public function testHandleWhenUsernameLength($username, $ok) + public function testHandleWhenUsernameLength(string $username, bool $ok) { $request = Request::create('/login_check', 'POST', ['_username' => $username]); $request->setSession($this->createMock(SessionInterface::class)); @@ -84,10 +85,8 @@ public function testHandleWhenUsernameLength($username, $ok) /** * @dataProvider postOnlyDataProvider */ - public function testHandleNonStringUsernameWithArray($postOnly) + public function testHandleNonStringUsernameWithArray(bool $postOnly) { - $this->expectException(BadRequestHttpException::class); - $this->expectExceptionMessage('The key "_username" must be a string, "array" given.'); $request = Request::create('/login_check', 'POST', ['_username' => []]); $request->setSession($this->createMock(SessionInterface::class)); $listener = new UsernamePasswordFormAuthenticationListener( @@ -101,16 +100,18 @@ public function testHandleNonStringUsernameWithArray($postOnly) ['require_previous_session' => false, 'post_only' => $postOnly] ); $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST); + + $this->expectException(BadRequestHttpException::class); + $this->expectExceptionMessage('The key "_username" must be a string, "array" given.'); + $listener($event); } /** * @dataProvider postOnlyDataProvider */ - public function testHandleNonStringUsernameWithInt($postOnly) + public function testHandleNonStringUsernameWithInt(bool $postOnly) { - $this->expectException(BadRequestHttpException::class); - $this->expectExceptionMessage('The key "_username" must be a string, "integer" given.'); $request = Request::create('/login_check', 'POST', ['_username' => 42]); $request->setSession($this->createMock(SessionInterface::class)); $listener = new UsernamePasswordFormAuthenticationListener( @@ -124,16 +125,18 @@ public function testHandleNonStringUsernameWithInt($postOnly) ['require_previous_session' => false, 'post_only' => $postOnly] ); $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST); + + $this->expectException(BadRequestHttpException::class); + $this->expectExceptionMessage('The key "_username" must be a string, "integer" given.'); + $listener($event); } /** * @dataProvider postOnlyDataProvider */ - public function testHandleNonStringUsernameWithObject($postOnly) + public function testHandleNonStringUsernameWithObject(bool $postOnly) { - $this->expectException(BadRequestHttpException::class); - $this->expectExceptionMessage('The key "_username" must be a string, "object" given.'); $request = Request::create('/login_check', 'POST', ['_username' => new \stdClass()]); $request->setSession($this->createMock(SessionInterface::class)); $listener = new UsernamePasswordFormAuthenticationListener( @@ -147,13 +150,17 @@ public function testHandleNonStringUsernameWithObject($postOnly) ['require_previous_session' => false, 'post_only' => $postOnly] ); $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST); + + $this->expectException(BadRequestHttpException::class); + $this->expectExceptionMessage('The key "_username" must be a string, "object" given.'); + $listener($event); } /** * @dataProvider postOnlyDataProvider */ - public function testHandleNonStringUsernameWith__toString($postOnly) + public function testHandleNonStringUsernameWith__toString(bool $postOnly) { $usernameClass = $this->createMock(DummyUserClass::class); $usernameClass @@ -177,7 +184,63 @@ public function testHandleNonStringUsernameWith__toString($postOnly) $listener($event); } - public function postOnlyDataProvider() + /** + * @dataProvider provideInvalidCsrfTokens + */ + public function testInvalidCsrfToken($invalidToken) + { + $formBody = ['_username' => 'fabien', '_password' => 'symfony']; + if (null !== $invalidToken) { + $formBody['_csrf_token'] = $invalidToken; + } + + $request = Request::create('/login_check', 'POST', $formBody); + $request->setSession($this->createMock(SessionInterface::class)); + + $httpUtils = $this->createMock(HttpUtils::class); + $httpUtils + ->method('checkRequestPath') + ->willReturn(true) + ; + $httpUtils + ->method('createRedirectResponse') + ->willReturn(new RedirectResponse('/hello')) + ; + + $failureHandler = $this->createMock(AuthenticationFailureHandlerInterface::class); + $failureHandler + ->expects($this->once()) + ->method('onAuthenticationFailure') + ->willReturn(new Response()) + ; + + $authenticationManager = $this->createMock(AuthenticationProviderManager::class); + $authenticationManager + ->expects($this->never()) + ->method('authenticate') + ; + + $csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class); + $csrfTokenManager->method('isTokenValid')->willReturn(false); + + $listener = new UsernamePasswordFormAuthenticationListener( + $this->createMock(TokenStorageInterface::class), + $authenticationManager, + $this->createMock(SessionAuthenticationStrategyInterface::class), + $httpUtils, + 'TheProviderKey', + new DefaultAuthenticationSuccessHandler($httpUtils), + $failureHandler, + ['require_previous_session' => false], + null, + null, + $csrfTokenManager + ); + + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); + } + + public function postOnlyDataProvider(): array { return [ [true], @@ -185,13 +248,22 @@ public function postOnlyDataProvider() ]; } - public function getUsernameForLength() + public function getUsernameForLength(): array { return [ [str_repeat('x', Security::MAX_USERNAME_LENGTH + 1), false], [str_repeat('x', Security::MAX_USERNAME_LENGTH - 1), true], ]; } + + public function provideInvalidCsrfTokens(): array + { + return [ + ['invalid'], + [['in' => 'valid']], + [null], + ]; + } } class DummyUserClass diff --git a/src/Symfony/Component/Templating/PhpEngine.php b/src/Symfony/Component/Templating/PhpEngine.php index b849da174b5f8..e4f85606981ce 100644 --- a/src/Symfony/Component/Templating/PhpEngine.php +++ b/src/Symfony/Component/Templating/PhpEngine.php @@ -173,6 +173,7 @@ protected function evaluate(Storage $template, array $parameters = []) * * @throws \InvalidArgumentException if the helper is not defined */ + #[\ReturnTypeWillChange] public function offsetGet($name) { return $this->get($name); @@ -185,6 +186,7 @@ public function offsetGet($name) * * @return bool true if the helper is defined, false otherwise */ + #[\ReturnTypeWillChange] public function offsetExists($name) { return isset($this->helpers[$name]); @@ -198,6 +200,7 @@ public function offsetExists($name) * * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($name, $value) { $this->set($name, $value); @@ -212,6 +215,7 @@ public function offsetSet($name, $value) * * @throws \LogicException */ + #[\ReturnTypeWillChange] public function offsetUnset($name) { throw new \LogicException(sprintf('You can\'t unset a helper (%s).', $name)); diff --git a/src/Symfony/Component/Translation/Resources/bin/translation-status.php b/src/Symfony/Component/Translation/Resources/bin/translation-status.php index 4e0723bb40615..fac8acbadca32 100644 --- a/src/Symfony/Component/Translation/Resources/bin/translation-status.php +++ b/src/Symfony/Component/Translation/Resources/bin/translation-status.php @@ -19,13 +19,16 @@ # show the translation status of all locales $ php translation-status.php - # show the translation status of all locales and all their missing translations + # only show the translation status of incomplete or erroneous locales + $ php translation-status.php --incomplete + + # show the translation status of all locales, all their missing translations and mismatches between trans-unit id and source $ php translation-status.php -v # show the status of a single locale $ php translation-status.php fr - # show the status of a single locale and all its missing translations + # show the status of a single locale, missing translations and mismatches between trans-unit id and source $ php translation-status.php fr -v END; @@ -35,6 +38,8 @@ 'verbose_output' => false, // NULL = analyze all locales 'locale_to_analyze' => null, + // append --incomplete to only show incomplete languages + 'include_completed_languages' => true, // the reference files all the other translations are compared to 'original_files' => [ 'src/Symfony/Component/Form/Resources/translations/validators.en.xlf', @@ -46,13 +51,18 @@ $argc = $_SERVER['argc']; $argv = $_SERVER['argv']; -if ($argc > 3) { +if ($argc > 4) { echo str_replace('translation-status.php', $argv[0], $usageInstructions); exit(1); } foreach (array_slice($argv, 1) as $argumentOrOption) { - if (str_starts_with($argumentOrOption, '-')) { + if ('--incomplete' === $argumentOrOption) { + $config['include_completed_languages'] = false; + continue; + } + + if (0 === strpos($argumentOrOption, '-')) { $config['verbose_output'] = true; } else { $config['locale_to_analyze'] = $argumentOrOption; @@ -67,6 +77,7 @@ } $totalMissingTranslations = 0; +$totalTranslationMismatches = 0; foreach ($config['original_files'] as $originalFilePath) { $translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']); @@ -75,11 +86,14 @@ $totalMissingTranslations += array_sum(array_map(function ($translation) { return count($translation['missingKeys']); }, array_values($translationStatus))); + $totalTranslationMismatches += array_sum(array_map(function ($translation) { + return count($translation['mismatches']); + }, array_values($translationStatus))); - printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output']); + printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']); } -exit($totalMissingTranslations > 0 ? 1 : 0); +exit($totalTranslationMismatches > 0 ? 1 : 0); function findTranslationFiles($originalFilePath, $localeToAnalyze) { @@ -112,21 +126,29 @@ function calculateTranslationStatus($originalFilePath, $translationFilePaths) foreach ($translationFilePaths as $locale => $translationPath) { $translatedKeys = extractTranslationKeys($translationPath); $missingKeys = array_diff_key($allTranslationKeys, $translatedKeys); + $mismatches = findTransUnitMismatches($allTranslationKeys, $translatedKeys); $translationStatus[$locale] = [ 'total' => count($allTranslationKeys), 'translated' => count($translatedKeys), 'missingKeys' => $missingKeys, + 'mismatches' => $mismatches, ]; + $translationStatus[$locale]['is_completed'] = isTranslationCompleted($translationStatus[$locale]); } return $translationStatus; } -function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput) +function isTranslationCompleted(array $translationStatus): bool +{ + return $translationStatus['total'] === $translationStatus['translated'] && 0 === count($translationStatus['mismatches']); +} + +function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput, $includeCompletedLanguages) { printTitle($originalFilePath); - printTable($translationStatus, $verboseOutput); + printTable($translationStatus, $verboseOutput, $includeCompletedLanguages); echo \PHP_EOL.\PHP_EOL; } @@ -152,13 +174,35 @@ function extractTranslationKeys($filePath) return $translationKeys; } +/** + * Check whether the trans-unit id and source match with the base translation. + */ +function findTransUnitMismatches(array $baseTranslationKeys, array $translatedKeys): array +{ + $mismatches = []; + + foreach ($baseTranslationKeys as $translationId => $translationKey) { + if (!isset($translatedKeys[$translationId])) { + continue; + } + if ($translatedKeys[$translationId] !== $translationKey) { + $mismatches[$translationId] = [ + 'found' => $translatedKeys[$translationId], + 'expected' => $translationKey, + ]; + } + } + + return $mismatches; +} + function printTitle($title) { echo $title.\PHP_EOL; echo str_repeat('=', strlen($title)).\PHP_EOL.\PHP_EOL; } -function printTable($translations, $verboseOutput) +function printTable($translations, $verboseOutput, bool $includeCompletedLanguages) { if (0 === count($translations)) { echo 'No translations found'; @@ -168,24 +212,47 @@ function printTable($translations, $verboseOutput) $longestLocaleNameLength = max(array_map('strlen', array_keys($translations))); foreach ($translations as $locale => $translation) { + if (!$includeCompletedLanguages && $translation['is_completed']) { + continue; + } + if ($translation['translated'] > $translation['total']) { textColorRed(); - } elseif ($translation['translated'] === $translation['total']) { + } elseif (count($translation['mismatches']) > 0) { + textColorRed(); + } elseif ($translation['is_completed']) { textColorGreen(); } - echo sprintf('| Locale: %-'.$longestLocaleNameLength.'s | Translated: %d/%d', $locale, $translation['translated'], $translation['total']).\PHP_EOL; + echo sprintf( + '| Locale: %-'.$longestLocaleNameLength.'s | Translated: %2d/%2d | Mismatches: %d |', + $locale, + $translation['translated'], + $translation['total'], + count($translation['mismatches']) + ).\PHP_EOL; textColorNormal(); + $shouldBeClosed = false; if (true === $verboseOutput && count($translation['missingKeys']) > 0) { - echo str_repeat('-', 80).\PHP_EOL; - echo '| Missing Translations:'.\PHP_EOL; + echo '| Missing Translations:'.\PHP_EOL; foreach ($translation['missingKeys'] as $id => $content) { - echo sprintf('| (id=%s) %s', $id, $content).\PHP_EOL; + echo sprintf('| (id=%s) %s', $id, $content).\PHP_EOL; } + $shouldBeClosed = true; + } + if (true === $verboseOutput && count($translation['mismatches']) > 0) { + echo '| Mismatches between trans-unit id and source:'.\PHP_EOL; + foreach ($translation['mismatches'] as $id => $content) { + echo sprintf('| (id=%s) Expected: %s', $id, $content['expected']).\PHP_EOL; + echo sprintf('| Found: %s', $content['found']).\PHP_EOL; + } + $shouldBeClosed = true; + } + if ($shouldBeClosed) { echo str_repeat('-', 80).\PHP_EOL; } } diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php index b7d2d1cd18d58..073f2255fe7f4 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -304,11 +304,17 @@ public function testTransWithIcuVariantFallbackLocale() $translator->addResource('array', ['foo' => 'foofoo'], 'en_GB_scouse'); $translator->addResource('array', ['bar' => 'foobar'], 'en_GB'); $translator->addResource('array', ['baz' => 'foobaz'], 'en_001'); - $translator->addResource('array', ['qux' => 'fooqux'], 'en'); + $translator->addResource('array', ['bar' => 'en', 'qux' => 'fooqux'], 'en'); + $translator->addResource('array', ['bar' => 'nl_NL', 'fallback' => 'nl_NL'], 'nl_NL'); + $translator->addResource('array', ['bar' => 'nl', 'fallback' => 'nl'], 'nl'); + + $translator->setFallbackLocales(['nl_NL', 'nl']); + $this->assertSame('foofoo', $translator->trans('foo')); $this->assertSame('foobar', $translator->trans('bar')); $this->assertSame('foobaz', $translator->trans('baz')); $this->assertSame('fooqux', $translator->trans('qux')); + $this->assertSame('nl_NL', $translator->trans('fallback')); } public function testTransWithIcuRootFallbackLocale() @@ -358,7 +364,7 @@ public function testTransWithFallbackLocaleTer() $translator = new Translator('fr_FR'); $translator->addLoader('array', new ArrayLoader()); $translator->addResource('array', ['foo' => 'foo (en_US)'], 'en_US'); - $translator->addResource('array', ['bar' => 'bar (en)'], 'en'); + $translator->addResource('array', ['foo' => 'foo (en)', 'bar' => 'bar (en)'], 'en'); $translator->setFallbackLocales(['en_US', 'en']); diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index c36ae331bac8c..5eb0183cbab43 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -462,14 +462,8 @@ protected function computeFallbackLocales($locale) $this->parentLocales = json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true); } + $originLocale = $locale; $locales = []; - foreach ($this->fallbackLocales as $fallback) { - if ($fallback === $locale) { - continue; - } - - $locales[] = $fallback; - } while ($locale) { $parent = $this->parentLocales[$locale] ?? null; @@ -490,10 +484,18 @@ protected function computeFallbackLocales($locale) } if (null !== $locale) { - array_unshift($locales, $locale); + $locales[] = $locale; } } + foreach ($this->fallbackLocales as $fallback) { + if ($fallback === $originLocale) { + continue; + } + + $locales[] = $fallback; + } + return array_unique($locales); } diff --git a/src/Symfony/Component/Validator/ConstraintViolationList.php b/src/Symfony/Component/Validator/ConstraintViolationList.php index bd6443fc45889..e057e916ae16f 100644 --- a/src/Symfony/Component/Validator/ConstraintViolationList.php +++ b/src/Symfony/Component/Validator/ConstraintViolationList.php @@ -110,6 +110,7 @@ public function remove($offset) * * @return \ArrayIterator|ConstraintViolationInterface[] */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->violations); @@ -118,6 +119,7 @@ public function getIterator() /** * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->violations); @@ -126,6 +128,7 @@ public function count() /** * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return $this->has($offset); @@ -136,6 +139,7 @@ public function offsetExists($offset) * * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->get($offset); @@ -146,6 +150,7 @@ public function offsetGet($offset) * * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $violation) { if (null === $offset) { @@ -160,6 +165,7 @@ public function offsetSet($offset, $violation) * * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { $this->remove($offset); diff --git a/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php b/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php index 478047fb8f272..a63366f51a0a1 100644 --- a/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php @@ -77,9 +77,9 @@ class CardSchemeValidator extends ConstraintValidator '/^5[1-5][0-9]{14}$/', '/^2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12})$/', ], - // Payment system MIR numbers start with 220, then 1 digit from 0 to 4, then 12 digits + // Payment system MIR numbers start with 220, then 1 digit from 0 to 4, then between 12 and 15 digits 'MIR' => [ - '/^220[0-4][0-9]{12}$/', + '/^220[0-4][0-9]{12,15}$/', ], // All UATP card numbers start with a 1 and have a length of 15 digits. 'UATP' => [ diff --git a/src/Symfony/Component/Validator/Constraints/UrlValidator.php b/src/Symfony/Component/Validator/Constraints/UrlValidator.php index 68f26d08755fa..4d962eb098760 100644 --- a/src/Symfony/Component/Validator/Constraints/UrlValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UrlValidator.php @@ -26,7 +26,7 @@ class UrlValidator extends ConstraintValidator (%s):// # protocol (((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+)@)? # basic auth ( - ([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name + ([\pL\pN\pS]+\.?[\pL\pN\pS\-\_]+)+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name | # or \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address | # or diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php index 14f7617e43fb2..f23182e473fbe 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php @@ -76,8 +76,8 @@ public function addViolation($message, array $params = []); * add the violation when you're done with the configuration: * * $context->buildViolation('Please enter a number between %min% and %max%.') - * ->setParameter('%min%', 3) - * ->setParameter('%max%', 10) + * ->setParameter('%min%', '3') + * ->setParameter('%max%', '10') * ->setTranslationDomain('number_validation') * ->addViolation(); * diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.et.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.et.xlf index 261b8f34e62f9..930b47f82e95a 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.et.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.et.xlf @@ -32,11 +32,11 @@ </trans-unit> <trans-unit id="8"> <source>One or more of the given values is invalid.</source> - <target>One or more of the given values is invalid.</target> + <target>Üks või rohkem väärtustest on vigane.</target> </trans-unit> <trans-unit id="9"> <source>This field was not expected.</source> - <target>See väli ei oodatud.</target> + <target>See väli ei olnud oodatud.</target> </trans-unit> <trans-unit id="10"> <source>This field is missing.</source> @@ -179,7 +179,7 @@ <target>Väärtus peaks olema kasutaja kehtiv salasõna.</target> </trans-unit> <trans-unit id="48"> - <source>This value should have exactly {{ limit }} characters.</source> + <source>This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.</source> <target>Väärtus peaks olema täpselt {{ limit }} tähemärk pikk.|Väärtus peaks olema täpselt {{ limit }} tähemärki pikk.</target> </trans-unit> <trans-unit id="49"> @@ -203,15 +203,15 @@ <target>PHP laiendi tõttu ebaõnnestus faili üleslaadimine.</target> </trans-unit> <trans-unit id="54"> - <source>This collection should contain {{ limit }} elements or more.</source> + <source>This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.</source> <target>Kogumikus peaks olema vähemalt {{ limit }} element.|Kogumikus peaks olema vähemalt {{ limit }} elementi.</target> </trans-unit> <trans-unit id="55"> - <source>This collection should contain {{ limit }} elements or less.</source> + <source>This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.</source> <target>Kogumikus peaks olema ülimalt {{ limit }} element.|Kogumikus peaks olema ülimalt {{ limit }} elementi.</target> </trans-unit> <trans-unit id="56"> - <source>This collection should contain exactly {{ limit }} elements.</source> + <source>This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.</source> <target>Kogumikus peaks olema täpselt {{ limit }} element.|Kogumikus peaks olema täpselt {{ limit }}|elementi.</target> </trans-unit> <trans-unit id="57"> @@ -304,7 +304,7 @@ </trans-unit> <trans-unit id="79"> <source>The host could not be resolved.</source> - <target>Peremeest ei õnnestunud lahendada.</target> + <target>Sellist domeeni ei õnnestunud leida.</target> </trans-unit> <trans-unit id="80"> <source>This value does not match the expected {{ charset }} charset.</source> @@ -366,6 +366,30 @@ <source>This value should be between {{ min }} and {{ max }}.</source> <target>See väärtus peaks olema vahemikus {{ min }} kuni {{ max }}.</target> </trans-unit> + <trans-unit id="95"> + <source>This value is not a valid hostname.</source> + <target>See väärtus pole korrektne domeeninimi.</target> + </trans-unit> + <trans-unit id="96"> + <source>The number of elements in this collection should be a multiple of {{ compared_value }}.</source> + <target>Selles kogus olevate elementide arv peab olema arvu {{ compared_value }} kordne.</target> + </trans-unit> + <trans-unit id="97"> + <source>This value should satisfy at least one of the following constraints:</source> + <target>See väärtus peab vastama vähemalt ühele järgmistest tingimustest:</target> + </trans-unit> + <trans-unit id="98"> + <source>Each element of this collection should satisfy its own set of constraints.</source> + <target>Kõik väärtused selles kogus peavad vastama oma tingimustele.</target> + </trans-unit> + <trans-unit id="99"> + <source>This value is not a valid International Securities Identification Number (ISIN).</source> + <target>See väärtus pole korrektne ISIN-kood.</target> + </trans-unit> + <trans-unit id="100"> + <source>This value should be a valid expression.</source> + <target>See väärtus pole korrektne avaldis.</target> + </trans-unit> </body> </file> </xliff> diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf index 40d07cf57cbb9..4793a16f32032 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.id.xlf @@ -386,6 +386,10 @@ <source>This value is not a valid International Securities Identification Number (ISIN).</source> <target>Nilai ini bukan merupakan International Securities Identification Number (ISIN) yang sah.</target> </trans-unit> + <trans-unit id="100"> + <source>This value should be a valid expression.</source> + <target>Nilai ini harus berupa ekspresi yang valid.</target> + </trans-unit> </body> </file> </xliff> diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf index e930e59014191..90fe83bb31cb9 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf @@ -386,6 +386,10 @@ <source>This value is not a valid International Securities Identification Number (ISIN).</source> <target>Ang halagang ito ay hindi wastong International Securities Identification Number (ISIN).</target> </trans-unit> + <trans-unit id="100"> + <source>This value should be a valid expression.</source> + <target>Ang halagang ito ay dapat wastong ekspresyon.</target> + </trans-unit> </body> </file> </xliff> diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf index 6c39fac818238..8dc9c70de500d 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf @@ -386,6 +386,10 @@ <source>This value is not a valid International Securities Identification Number (ISIN).</source> <target>Bu değer geçerli bir Uluslararası Menkul Kıymetler Kimlik Numarası değil (ISIN).</target> </trans-unit> + <trans-unit id="100"> + <source>This value should be a valid expression.</source> + <target>Bu değer geçerli bir ifade olmalıdır.</target> + </trans-unit> </body> </file> </xliff> diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeValidatorTest.php index ddc9edb6c094d..de1850eafb39f 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CardSchemeValidatorTest.php @@ -104,6 +104,9 @@ public function getValidNumbers() ['MASTERCARD', '2709999999999999'], ['MASTERCARD', '2720995105105100'], ['MIR', '2200381427330082'], + ['MIR', '22003814273300821'], + ['MIR', '220038142733008212'], + ['MIR', '2200381427330082123'], ['UATP', '110165309696173'], ['VISA', '4111111111111111'], ['VISA', '4012888888881881'], @@ -136,7 +139,8 @@ public function getInvalidNumbers() ['MASTERCARD', '2721001234567890', CardScheme::INVALID_FORMAT_ERROR], // Not assigned yet ['MASTERCARD', '2220991234567890', CardScheme::INVALID_FORMAT_ERROR], // Not assigned yet ['UATP', '11016530969617', CardScheme::INVALID_FORMAT_ERROR], // invalid length - ['MIR', '22003814273300821', CardScheme::INVALID_FORMAT_ERROR], // invalid length + ['MIR', '220038142733008', CardScheme::INVALID_FORMAT_ERROR], // invalid length + ['MIR', '22003814273300821234', CardScheme::INVALID_FORMAT_ERROR], // invalid length ]; } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php index e154c6b2df9cc..ca2a27db4c780 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php @@ -105,6 +105,7 @@ public function getValidUrls() return [ ['http://a.pl'], ['http://www.example.com'], + ['http://tt.example.com'], ['http://www.example.com.'], ['http://www.example.museum'], ['https://example.com/'], @@ -265,6 +266,10 @@ public function getInvalidUrls() ['http://example.com/exploit.html?hel lo'], ['http://example.com/exploit.html?not_a%hex'], ['http://'], + ['http://www..com'], + ['http://www..example.com'], + ['http://wwww.example..com'], + ['http://.www.example.com'], ]; } diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/CustomArrayObject.php b/src/Symfony/Component/Validator/Tests/Fixtures/CustomArrayObject.php index 34b208b2bea0c..4ca7f4a99fabe 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/CustomArrayObject.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/CustomArrayObject.php @@ -29,12 +29,18 @@ public function offsetExists($offset): bool return \array_key_exists($offset, $this->array); } + /** + * @param mixed $offset + * + * @return mixed + */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->array[$offset]; } - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { if (null === $offset) { $this->array[] = $value; @@ -43,7 +49,7 @@ public function offsetSet($offset, $value) } } - public function offsetUnset($offset) + public function offsetUnset($offset): void { unset($this->array[$offset]); } diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php index 114bf50e5b038..8f621b12a7627 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -111,6 +111,7 @@ public function getValue($recursive = false) /** * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->getValue()); @@ -119,6 +120,7 @@ public function count() /** * @return \Traversable */ + #[\ReturnTypeWillChange] public function getIterator() { if (!\is_array($value = $this->getValue())) { @@ -150,6 +152,7 @@ public function __isset($key) /** * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($key) { return $this->__isset($key); @@ -158,6 +161,7 @@ public function offsetExists($key) /** * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->__get($key); @@ -166,6 +170,7 @@ public function offsetGet($key) /** * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($key, $value) { throw new \BadMethodCallException(self::class.' objects are immutable.'); @@ -174,6 +179,7 @@ public function offsetSet($key, $value) /** * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($key) { throw new \BadMethodCallException(self::class.' objects are immutable.'); diff --git a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php index 852e30c393885..cd10c7af37a8a 100644 --- a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php +++ b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php @@ -313,7 +313,7 @@ public function __construct(array $array) parent::__construct($array, 1); } - public function setFlags($flags) + public function setFlags($flags): void { throw new \BadMethodCallException('Calling MyArrayObject::setFlags() is forbidden'); } @@ -341,12 +341,12 @@ public function __construct(bool $throw = true) final class FinalArrayIterator extends \ArrayIterator { - public function serialize() + public function serialize(): string { return serialize([123, parent::serialize()]); } - public function unserialize($data) + public function unserialize($data): void { if ('' === $data) { throw new \InvalidArgumentException('Serialized data is empty.');