diff --git a/.travis.yml b/.travis.yml index a5246b3eafb58..99e6310eb2b7e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -269,7 +269,12 @@ install: export PHP=$1 if [[ !$deps && $PHP = 7.2 ]]; then - tfold src/Symfony/Component/HttpClient.h2push "$COMPOSER_UP symfony/contracts && docker run -it --rm -v $(pwd):/app -v $(phpenv which composer):/usr/local/bin/composer -v /usr/local/bin/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push" + phpenv global $PHP + tfold 'composer update' $COMPOSER_UP + [ -d .phpunit ] && mv .phpunit .phpunit.bak + tfold src/Symfony/Component/HttpClient.h2push "docker run -it --rm -v $(pwd):/app -v $(phpenv which composer):/usr/local/bin/composer -v /usr/local/bin/vulcain:/usr/local/bin/vulcain -w /app php:7.3-alpine ./phpunit src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php --filter testHttp2Push" + sudo rm .phpunit -rf + [ -d .phpunit.bak ] && mv .phpunit.bak .phpunit fi if [[ $PHP != 7.4* && $PHP != $TRAVIS_PHP_VERSION && $TRAVIS_PULL_REQUEST != false ]]; then @@ -278,7 +283,9 @@ install: fi phpenv global $PHP ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; cp composer.json composer.bak; composer config platform.ext-mongodb 1.6.0; composer require --dev --no-update mongodb/mongodb ~1.5.0) - tfold 'composer update' $COMPOSER_UP + if [[ $deps || $PHP != 7.2 ]]; then + tfold 'composer update' $COMPOSER_UP + fi tfold 'phpunit install' ./phpunit install if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu "tfold {} 'cd {} && $COMPOSER_UP && $PHPUNIT_X$LEGACY'" || X=1 diff --git a/CHANGELOG-4.4.md b/CHANGELOG-4.4.md index 68eceda5b9742..be58f04b95501 100644 --- a/CHANGELOG-4.4.md +++ b/CHANGELOG-4.4.md @@ -7,6 +7,56 @@ 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.8 (2020-04-28) + + * bug #36536 [Cache] Allow invalidateTags calls to be traced by data collector (l-vo) + * bug #36566 [PhpUnitBridge] Use COMPOSER_BINARY env var if available (fancyweb) + * bug #36560 [YAML] escape DEL(\x7f) (sdkawata) + * bug #36539 [PhpUnitBridge] fix compatibility with phpunit 9 (garak) + * bug #36555 [Cache] skip APCu in chains when the backend is disabled (nicolas-grekas) + * bug #36523 [Form] apply automatically step=1 for datetime-local input (ottaviano) + * bug #36519 [FrameworkBundle] debug:autowiring: Fix wrong display when using class_alias (weaverryan) + * bug #36454 [DependencyInjection][ServiceSubscriber] Support late aliases (fancyweb) + * bug #36498 [Security/Core] fix escape for username in LdapBindAuthenticationProvider.php (stoccc) + * bug #36506 [FrameworkBundle] Fix session.attribute_bag service definition (fancyweb) + * bug #36500 [Routing][PrefixTrait] Add the _locale requirement (fancyweb) + * bug #36457 [Cache] CacheItem with tag is never a hit after expired (alexander-schranz, nicolas-grekas) + * bug #36490 [HttpFoundation] workaround PHP bug in the session module (nicolas-grekas) + * bug #36483 [SecurityBundle] fix accepting env vars in remember-me configurations (zek) + * bug #36343 [Form] Fixed handling groups sequence validation (HeahDude) + * bug #36460 [Cache] Avoid memory leak in TraceableAdapter::reset() (lyrixx) + * bug #36467 Mailer from sender fixes (fabpot) + * bug #36408 [PhpUnitBridge] add PolyfillTestCaseTrait::expectExceptionMessageMatches to provide FC with recent phpunit versions (soyuka) + * bug #36447 Remove return type for Twig function workflow_metadata() (gisostallenberg) + * bug #36449 [Messenger] Make sure redis transports are initialized correctly (Seldaek) + * bug #36411 [Form] RepeatedType should always have inner types mapped (biozshock) + * bug #36441 [DI] fix loading defaults when using the PHP-DSL (nicolas-grekas) + * bug #36434 [HttpKernel] silence E_NOTICE triggered since PHP 7.4 (xabbuh) + * bug #36365 [Validator] Fixed default group for nested composite constraints (HeahDude) + * bug #36422 [HttpClient] fix HTTP/2 support on non-SSL connections - CurlHttpClient only (nicolas-grekas) + * bug #36417 Force ping after transport exception (oesteve) + * bug #35591 [Validator] do not merge constraints within interfaces (greedyivan) + * bug #36377 [HttpClient] Fix scoped client without query option configuration (X-Coder264) + * bug #36387 [DI] fix detecting short service syntax in yaml (nicolas-grekas) + * bug #36392 [DI] add missing property declarations in InlineServiceConfigurator (nicolas-grekas) + * bug #36400 Allowing empty secrets to be set (weaverryan) + * bug #36380 [Process] Fixed input/output error on PHP 7.4 (mbardelmeijer) + * bug #36376 [Workflow] Use a strict comparison when retrieving raw marking in MarkingStore (lyrixx) + * bug #36375 [Workflow] Use a strict comparison when retrieving raw marking in MarkingStore (lyrixx) + * bug #36305 [PropertyInfo][ReflectionExtractor] Check the array mutator prefixes last when the property is singular (fancyweb) + * bug #35656 [HttpFoundation] Fixed session migration with custom cookie lifetime (Guite) + * bug #36342 [HttpKernel][FrameworkBundle] fix compat with Debug component (nicolas-grekas) + * bug #36315 [WebProfilerBundle] Support for Content Security Policy style-src-elem and script-src-elem in WebProfiler (ampaze) + * bug #36286 [Validator] Allow URL-encoded special characters in basic auth part of URLs (cweiske) + * bug #36335 [Security] Track session usage whenever a new token is set (wouterj) + * bug #36332 [Serializer] Fix unitialized properties (from PHP 7.4.2) when serializing context for the cache key (alanpoulain) + * bug #36337 [MonologBridge] Fix $level type (fancyweb) + * bug #36223 [Security][Http][SwitchUserListener] Ignore all non existent username protection errors (fancyweb) + * bug #36239 [HttpKernel][LoggerDataCollector] Prevent keys collisions in the sanitized logs processing (fancyweb) + * bug #36245 [Validator] Fixed calling getters before resolving groups (HeahDude) + * bug #36265 Fix the reporting of deprecations in twig:lint (stof) + * bug #36283 [Security] forward multiple attributes voting flag (xabbuh) + * 4.4.7 (2020-03-30) * security #cve-2020-5255 [HttpFoundation] Do not set the default Content-Type based on the Accept header (yceruto) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 53a2faec2f45f..449973d7578eb 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -24,10 +24,10 @@ Symfony is the result of the work of many people who made the code better - Kris Wallsmith (kriswallsmith) - Yonel Ceruto (yonelceruto) - Hugo Hamon (hhamon) + - Wouter de Jong (wouterj) + - Thomas Calvet (fancyweb) - Abdellatif Ait boudad (aitboudad) - Samuel ROZE (sroze) - - Thomas Calvet (fancyweb) - - Wouter de Jong (wouterj) - Romain Neutron (romain) - Pascal Borreli (pborreli) - Joseph Bielawski (stloyd) @@ -37,8 +37,8 @@ Symfony is the result of the work of many people who made the code better - Jules Pietri (heah) - Hamza Amrouche (simperfit) - Martin Hasoň (hason) - - Jeremy Mikola (jmikola) - Jérémy DERUSSÉ (jderusse) + - Jeremy Mikola (jmikola) - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) - Igor Wiedler (igorw) @@ -50,9 +50,9 @@ Symfony is the result of the work of many people who made the code better - Lynn van der Berg (kjarli) - Diego Saint Esteben (dosten) - Matthias Pigulla (mpdude) + - Pierre du Plessis (pierredup) - Alexandre Salomé (alexandresalome) - William Durand (couac) - - Pierre du Plessis (pierredup) - ornicar - Dany Maillard (maidmaid) - Francis Besset (francisbesset) @@ -69,10 +69,10 @@ Symfony is the result of the work of many people who made the code better - Gabriel Ostrolucký (gadelat) - Miha Vrhovnik - David Maicher (dmaicher) - - Diego Saint Esteben (dii3g0) - Gábor Egyed (1ed) - - Titouan Galopin (tgalopin) + - Diego Saint Esteben (dii3g0) - Jan Schädlich (jschaedl) + - Titouan Galopin (tgalopin) - Konstantin Kudryashov (everzet) - Bilal Amarni (bamarni) - Mathieu Piot (mpiot) @@ -110,6 +110,7 @@ Symfony is the result of the work of many people who made the code better - Baptiste Clavié (talus) - Michal Piotrowski (eventhorizon) - Tim Nagel (merk) + - Sebastiaan Stok (sstok) - Chris Wilkinson (thewilkybarkid) - Brice BERNARD (brikou) - marc.weistroff @@ -119,7 +120,6 @@ Symfony is the result of the work of many people who made the code better - lenar - Alexander Schwenn (xelaris) - Włodzimierz Gajda (gajdaw) - - Sebastiaan Stok (sstok) - Adrien Brault (adrienbrault) - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) @@ -139,18 +139,18 @@ Symfony is the result of the work of many people who made the code better - Przemysław Bogusz (przemyslaw-bogusz) - Eric GELOEN (gelo) - Lars Strojny (lstrojny) + - Massimiliano Arione (garak) - Jannik Zschiesche (apfelbox) - Robert Schönthal (digitalkaoz) - Gregor Harlan (gharlan) - Florian Lonqueu-Brochard (florianlb) + - Alexander Schranz (alexander-schranz) - Gabriel Caruso (carusogabriel) - Stefano Sala (stefano.sala) - Evgeniy (ewgraf) - - Massimiliano Arione (garak) - Julien Falque (julienfalque) - Vincent AUBERT (vincent) - Juti Noppornpitak (shiroyuki) - - Alexander Schranz (alexander-schranz) - Anthony MARTIN (xurudragon) - Tigran Azatyan (tigranazatyan) - Sebastian Hörl (blogsh) @@ -176,6 +176,7 @@ Symfony is the result of the work of many people who made the code better - Richard van Laak (rvanlaak) - Richard Shank (iampersistent) - Thomas Rabaix (rande) + - Ahmed TAILOULOUTE (ahmedtai) - Vincent Touzet (vincenttouzet) - jeremyFreeAgent (jeremyfreeagent) - Rouven Weßling (realityking) @@ -198,6 +199,7 @@ Symfony is the result of the work of many people who made the code better - James Halsall (jaitsu) - Matthieu Napoli (mnapoli) - Florent Mata (fmata) + - Antoine Makdessi (amakdessi) - Warnar Boekkooi (boekkooi) - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) @@ -213,15 +215,18 @@ Symfony is the result of the work of many people who made the code better - Dennis Benkert (denderello) - DQNEO - Andre Rømcke (andrerom) + - Saif (╯°□°)╯ (azjezz) - mcfedr (mcfedr) - Gary PEGEOT (gary-p) - Ruben Gonzalez (rubenrua) - Benjamin Dulau (dbenjamin) + - Jan Rosier (rosier) - Andreas Braun - Mathieu Lemoine (lemoinem) - Christian Schmidt - Andreas Hucks (meandmymonkey) - Tom Van Looy (tvlooy) + - Guillaume Pédelagrabe - Noel Guilbert (noel) - Anthony GRASSIOT (antograssiot) - Stadly @@ -233,7 +238,7 @@ Symfony is the result of the work of many people who made the code better - Nikolay Labinskiy (e-moe) - Martin Schuhfuß (usefulthink) - apetitpa - - Antoine Makdessi (amakdessi) + - Maxime Helias (maxhelias) - Matthieu Bontemps (mbontemps) - apetitpa - Pierre Minnieur (pminnieur) @@ -243,14 +248,13 @@ Symfony is the result of the work of many people who made the code better - Laurent VOULLEMIER (lvo) - Michael Lee (zerustech) - Matthieu Auger (matthieuauger) - - Ahmed TAILOULOUTE (ahmedtai) + - Mathias Arlaud (mtarld) - Leszek Prabucki (l3l0) - Fabien Bourigault (fbourigault) - François Zaninotto (fzaninotto) - Dustin Whittle (dustinwhittle) - jeff - John Kary (johnkary) - - Jan Rosier (rosier) - Justin Hileman (bobthecow) - Blanchon Vincent (blanchonvincent) - Michele Orselli (orso) @@ -270,7 +274,6 @@ Symfony is the result of the work of many people who made the code better - Marcel Beerta (mazen) - Christopher Hertel (chertel) - Ruud Kamphuis (ruudk) - - Maxime Helias (maxhelias) - Pavel Batanov (scaytrase) - Mantis Development - David Prévot @@ -283,7 +286,6 @@ Symfony is the result of the work of many people who made the code better - Lorenz Schori - Sébastien Lavoie (lavoiesl) - Dariusz - - Saif (╯°□°)╯ (azjezz) - Dmitrii Poddubnyi (karser) - Michael Babker (mbabker) - Francois Zaninotto @@ -307,7 +309,6 @@ Symfony is the result of the work of many people who made the code better - Arjen Brouwer (arjenjb) - Katsuhiro OGAWA - Patrick McDougle (patrick-mcdougle) - - Guillaume Pédelagrabe - Alif Rachmawadi - Anton Chernikov (anton_ch1989) - Kristen Gilden (kgilden) @@ -336,9 +337,9 @@ Symfony is the result of the work of many people who made the code better - Jeroen Spee (jeroens) - Nikita Konstantinov - Wodor Wodorski + - Olivier Dolbeau (odolbeau) - Thomas Lallement (raziel057) - Colin O'Dell (colinodell) - - Mathias Arlaud (mtarld) - Giorgio Premi - renanbr - Alex Rock (pierstoval) @@ -364,6 +365,7 @@ Symfony is the result of the work of many people who made the code better - Vilius Grigaliūnas - David Badura (davidbadura) - Chad Sikorra (chadsikorra) + - Alan Poulain (alanpoulain) - Chris Smith (cs278) - Thomas Bisignani (toma) - Florian Klein (docteurklein) @@ -413,7 +415,6 @@ Symfony is the result of the work of many people who made the code better - Thomas Royer (cydonia7) - Nicolas LEFEVRE (nicoweb) - alquerci - - Olivier Dolbeau (odolbeau) - Oleg Andreyev - Mateusz Sip (mateusz_sip) - Francesco Levorato @@ -426,6 +427,7 @@ Symfony is the result of the work of many people who made the code better - Tomasz Kowalczyk (thunderer) - Artur Eshenbrener - Timo Bakx (timobakx) + - Antonio Pauletich (x-coder264) - Thomas Perez (scullwm) - Felix Labrecque - Yaroslav Kiliba @@ -443,7 +445,6 @@ Symfony is the result of the work of many people who made the code better - Eduardo Gulias (egulias) - giulio de donato (liuggio) - ShinDarth - - Alan Poulain - Stéphane PY (steph_py) - Philipp Kräutli (pkraeutli) - Grzegorz (Greg) Zdanowski (kiler129) @@ -540,10 +541,10 @@ Symfony is the result of the work of many people who made the code better - Martijn Cuppens - Vlad Gregurco (vgregurco) - Boris Vujicic (boris.vujicic) + - Artem Lopata - Chris Sedlmayr (catchamonkey) - Kamil Kokot (pamil) - Seb Koelen - - Antonio Pauletich (x-coder264) - Christoph Mewes (xrstf) - Vitaliy Tverdokhlib (vitaliytv) - Ariel Ferrandini (aferrandini) @@ -553,14 +554,17 @@ Symfony is the result of the work of many people who made the code better - Jonas Flodén (flojon) - Tobias Weichart - Gonzalo Vilaseca (gonzalovilaseca) + - Daniel STANCU - Tarmo Leppänen (tarlepp) - Marcin Sikoń (marphi) - Dominik Zogg (dominik.zogg) - Marek Pietrzak - Luc Vieillescazes (iamluc) - franek (franek) + - soyuka - Raulnet - Christian Wahler + - Giso Stallenberg (gisostallenberg) - Gintautas Miselis - Rob Bast - Roberto Espinoza (respinoza) @@ -599,6 +603,7 @@ Symfony is the result of the work of many people who made the code better - Philipp Rieber (bicpi) - Manuel de Ruiter (manuel) - Nathanael Noblet (gnat) + - Dimitri Gritsajuk (ottaviano) - nikos.sotiropoulos - Eduardo Oliveira (entering) - Ilya Antipenko (aivus) @@ -744,6 +749,7 @@ Symfony is the result of the work of many people who made the code better - Benjamin Cremer (bcremer) - Javier López (loalf) - Reinier Kip + - Jérôme Tamarelle (jtamarelle-prismamedia) - Geoffrey Brier (geoffrey-brier) - Alexandre Parent - Vladimir Tsykun @@ -778,7 +784,6 @@ Symfony is the result of the work of many people who made the code better - Miquel Rodríguez Telep (mrtorrent) - Sergey Kolodyazhnyy (skolodyazhnyy) - umpirski - - Artem Lopata - M. Vondano - Quentin de Longraye (quentinus95) - Chris Heng (gigablah) @@ -786,10 +791,10 @@ Symfony is the result of the work of many people who made the code better - Richard Bradley - Ulumuddin Yunus (joenoez) - rtek + - Ivan Grigoriev - Johann Saunier (prophet777) - Sergey (upyx) - Andreas Erhard - - Giso Stallenberg (gisostallenberg) - Michael Devery (mickadoo) - Antoine Corcy - Ahmed Ashraf (ahmedash95) @@ -810,6 +815,7 @@ Symfony is the result of the work of many people who made the code better - Cameron Porter - Hossein Bukhamsin - Oliver Hoff + - William Arslett - Christian Sciberras (uuf6429) - Disparity - origaminal @@ -856,7 +862,6 @@ Symfony is the result of the work of many people who made the code better - Markus Fasselt (digilist) - Julien DIDIER (juliendidier) - Dominik Ritter (dritter) - - Dimitri Gritsajuk (ottaviano) - Sebastian Grodzicki (sgrodzicki) - Mohamed Gamal - Jeroen van den Enden (stoefke) @@ -871,6 +876,7 @@ Symfony is the result of the work of many people who made the code better - Yuen-Chi Lian - Tarjei Huse (tarjei) - Besnik Br + - Axel Guckelsberger (guite) - Jose Gonzalez - Jonathan (jls-esokia) - Oleksii Zhurbytskyi @@ -881,12 +887,14 @@ Symfony is the result of the work of many people who made the code better - Jakub Kulhan (jakubkulhan) - Shaharia Azam - avorobiev + - stoccc - Grégoire Penverne (gpenverne) - Venu - Lars Vierbergen - Jonatan Männchen - Dennis Hotson - Andrew Tchircoff (andrewtch) + - Ahmed Raafat - michaelwilliams - Martin Kirilov - 1emming @@ -894,6 +902,7 @@ Symfony is the result of the work of many people who made the code better - Tri Pham (phamuyentri) - Jordan Deitch - Casper Valdemar Poulsen + - Laurent Masforné (heisenberg) - Josiah (josiah) - Guillaume Verstraete (versgui) - Greg ORIOL @@ -961,10 +970,8 @@ Symfony is the result of the work of many people who made the code better - Laurent Bassin (lbassin) - andrey1s - Abhoryo - - Daniel STANCU - Fabian Vogler (fabian) - Korvin Szanto - - soyuka - Stéphan Kochen - Arjan Keeman - Alaattin Kahramanlar (alaattin) @@ -1133,6 +1140,7 @@ Symfony is the result of the work of many people who made the code better - Nicolas Le Goff (nlegoff) - Ben Oman - Chris de Kok + - Eduard Bulava (nonanerz) - Lorenzo Millucci - Andreas Kleemann - Manuele Menozzi @@ -1150,6 +1158,7 @@ Symfony is the result of the work of many people who made the code better - hamza - dantleech - Bastien DURAND (deamon) + - Kajetan Kołtuniak (kajtii) - Sander Goossens (sandergo90) - Rudy Onfroy - Tero Alén (tero) @@ -1237,7 +1246,6 @@ Symfony is the result of the work of many people who made the code better - Benjamin Paap (benjaminpaap) - Claus Due (namelesscoder) - Christian - - William Arslett - Denis Golubovskiy (bukashk0zzz) - Sergii Smertin (nfx) - Mikkel Paulson @@ -1246,6 +1254,7 @@ Symfony is the result of the work of many people who made the code better - Marc Duboc (icemad) - Matthias Krauser (mkrauser) - Martynas Narbutas + - Nilmar Sanchez Muguercia - Toon Verwerft (veewee) - Bailey Parker - Eddie Jaoude @@ -1289,6 +1298,7 @@ Symfony is the result of the work of many people who made the code better - Stephen Clouse - e-ivanov - Michał (bambucha15) + - Benjamin Dos Santos - Einenlum - Jérémy Jarrié (gagnar) - Jochen Bayer (jocl) @@ -1344,6 +1354,7 @@ Symfony is the result of the work of many people who made the code better - Klaus Purer - arnaud (arnooo999) - Gilles Doge (gido) + - Oscar Esteve (oesteve) - abulford - Philipp Kretzschmar - antograssiot @@ -1367,7 +1378,6 @@ Symfony is the result of the work of many people who made the code better - Derek Lambert - MightyBranch - Kacper Gunia (cakper) - - Jérôme Tamarelle (jtamarelle-prismamedia) - Peter Thompson (petert82) - error56 - Felicitus @@ -1392,6 +1402,7 @@ Symfony is the result of the work of many people who made the code better - Nyro (nyro) - Marco - Marc Torres + - Mark Spink - Alberto Aldegheri - Dmitri Petmanson - heccjj @@ -1412,7 +1423,6 @@ Symfony is the result of the work of many people who made the code better - David Négrier (moufmouf) - Quique Porta (quiqueporta) - mohammadreza honarkhah - - stoccc - Andrea Quintino (dirk39) - Tomasz Szymczyk (karion) - Alex Vasilchenko @@ -1468,7 +1478,6 @@ Symfony is the result of the work of many people who made the code better - Walter Dal Mut (wdalmut) - abluchet - Ruud Arentsen - - Ahmed Raafat - Harald Tollefsen - Matthieu - Albin Kerouaton @@ -1504,7 +1513,6 @@ Symfony is the result of the work of many people who made the code better - Antal Áron (antalaron) - Vašek Purchart (vasek-purchart) - Janusz Jabłoński (yanoosh) - - Ivan Grigoriev - Fleuv - Sandro Hopf - Łukasz Makuch @@ -1522,6 +1530,7 @@ Symfony is the result of the work of many people who made the code better - Philip Frank - David Brooks - Lance McNearney + - Serhiy Lunak (slunak) - Giorgio Premi - Aurélien Fontaine - ncou @@ -1531,6 +1540,7 @@ Symfony is the result of the work of many people who made the code better - Matt Daum (daum) - Alberto Pirovano (geezmo) - Pete Mitchell (peterjmit) + - phuc vo (phucwan) - Tom Corrigan (tomcorrigan) - Luis Galeas - Bogdan Scordaliu @@ -1543,7 +1553,6 @@ Symfony is the result of the work of many people who made the code better - WedgeSama - Felds Liscia - Chihiro Adachi (chihiro-adachi) - - Axel Guckelsberger (guite) - Raphaëll Roussel - Tadcka - Beth Binkovitz @@ -1651,6 +1660,7 @@ Symfony is the result of the work of many people who made the code better - Flavian (2much) - Gautier Deuette - mike + - tadas - Kirk Madera - Keith Maika - Mephistofeles @@ -1834,6 +1844,7 @@ Symfony is the result of the work of many people who made the code better - alefranz - David Barratt - Andrea Giannantonio + - Dries Vints - Pavel.Batanov - avi123 - Pavel Prischepa @@ -1900,6 +1911,7 @@ Symfony is the result of the work of many people who made the code better - Yannick Warnier (ywarnier) - Kevin Decherf - Jason Woods + - Christian Weiske - Maria Grazia Patteri - klemens - dened @@ -2149,6 +2161,7 @@ Symfony is the result of the work of many people who made the code better - Evgeniy Tetenchuk - Sjoerd Adema - Shrey Puranik + - Evgeniy Koval - Lars Moelleken - dasmfm - Mathias Geat @@ -2208,6 +2221,7 @@ Symfony is the result of the work of many people who made the code better - Niklas Keller - Andras Debreczeni - Vladimir Sazhin + - Michel Bardelmeijer - Tomas Kmieliauskas - Billie Thompson - lol768 @@ -2241,6 +2255,7 @@ Symfony is the result of the work of many people who made the code better - Michael Schneider - Cédric Bertolini - n-aleha + - Talha Zekeriya Durmuş - Anatol Belski - Anderson Müller - Şəhriyar İmanov @@ -2268,6 +2283,7 @@ Symfony is the result of the work of many people who made the code better - Neophy7e - bokonet - Arrilot + - ampaze - Markus Staab - Pierre-Louis LAUNAY - djama @@ -2417,6 +2433,7 @@ Symfony is the result of the work of many people who made the code better - Michal Čihař (mcihar) - Matt Drollette (mdrollette) - Adam Monsen (meonkeys) + - Mike Milano (mmilano) - diego aguiar (mollokhan) - Hugo Monteiro (monteiro) - Ala Eddine Khefifi (nayzo) diff --git a/composer.json b/composer.json index 2b089d9d48fba..ae4553377613e 100644 --- a/composer.json +++ b/composer.json @@ -118,7 +118,7 @@ "psr/http-client": "^1.0", "psr/simple-cache": "^1.0", "egulias/email-validator": "~1.2,>=1.2.8|~2.0", - "symfony/phpunit-bridge": "^3.4.31|^4.3.4|~5.0", + "symfony/phpunit-bridge": "^5.0.8", "symfony/security-acl": "~2.8|~3.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0", "twig/cssinliner-extra": "^2.12", diff --git a/phpunit b/phpunit index c89d2e400b602..fbce26d8edcca 100755 --- a/phpunit +++ b/phpunit @@ -1,7 +1,7 @@ #!/usr/bin/env php find($id); if (null === $refreshedUser) { - throw new UsernameNotFoundException(sprintf('User with id %s not found.', json_encode($id))); + throw new UsernameNotFoundException('User with id '.json_encode($id).' not found.'); } } diff --git a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php index 60e7341db9e2c..eb1a4dc257677 100644 --- a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php +++ b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php @@ -102,7 +102,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!$socket = stream_socket_server($host, $errno, $errstr)) { - throw new RuntimeException(sprintf('Server start failed on "%s": %s %s.', $host, $errstr, $errno)); + throw new RuntimeException(sprintf('Server start failed on "%s": '.$errstr.' '.$errno, $host)); } foreach ($this->getLogs($socket) as $clientId => $message) { diff --git a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php index b8b0f7b9e99cb..99ba3bbc3d94a 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php @@ -49,7 +49,10 @@ class ElasticsearchLogstashHandler extends AbstractHandler private $client; private $responses; - public function __construct(string $endpoint = 'http://127.0.0.1:9200', string $index = 'monolog', HttpClientInterface $client = null, int $level = Logger::DEBUG, bool $bubble = true) + /** + * @param string|int $level The minimum logging level at which this handler will be triggered + */ + public function __construct(string $endpoint = 'http://127.0.0.1:9200', string $index = 'monolog', HttpClientInterface $client = null, $level = Logger::DEBUG, bool $bubble = true) { if (!interface_exists(HttpClientInterface::class)) { throw new \LogicException(sprintf('The "%s" handler needs an HTTP client. Try running "composer require symfony/http-client".', __CLASS__)); diff --git a/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php b/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php index 28582d31725a3..e5a26ccd221e2 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ServerLogHandler.php @@ -25,7 +25,10 @@ class ServerLogHandler extends AbstractHandler private $context; private $socket; - public function __construct(string $host, int $level = Logger::DEBUG, bool $bubble = true, array $context = []) + /** + * @param string|int $level The minimum logging level at which this handler will be triggered + */ + public function __construct(string $host, $level = Logger::DEBUG, bool $bubble = true, array $context = []) { parent::__construct($level, $bubble); diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php index ebb9db2c94acb..cb3fbf44903bd 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillTestCaseTrait.php @@ -95,6 +95,16 @@ public function expectExceptionMessage($message) $property->setValue($this, $message); } + /** + * @param string $messageRegExp + * + * @return void + */ + public function expectExceptionMessageMatches($messageRegExp) + { + $this->expectExceptionMessageRegExp($messageRegExp); + } + /** * @param string $messageRegExp * diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index 1e030825e6fde..f9a1cadb49652 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -48,7 +48,12 @@ class SymfonyTestsListenerTrait */ public function __construct(array $mockedNamespaces = []) { - Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait'] = 2; + if (method_exists(Blacklist::class, 'addDirectory')) { + (new BlackList())->getBlacklistedDirectories(); + Blacklist::addDirectory(\dirname((new \ReflectionClass(__CLASS__))->getFileName(), 2)); + } else { + Blacklist::$blacklistedClassNames[__CLASS__] = 2; + } $enableDebugClassLoader = class_exists(DebugClassLoader::class) || class_exists(LegacyDebugClassLoader::class); diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php index 401aafde162a4..dc2730bc9a620 100644 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php @@ -215,9 +215,10 @@ if (!class_exists('SymfonyBlacklistPhpunit', false)) { class SymfonyBlacklistPhpunit {} } -if (class_exists('PHPUnit_Util_Blacklist')) { - PHPUnit_Util_Blacklist::$blacklistedClassNames['SymfonyBlacklistPhpunit'] = 1; - PHPUnit_Util_Blacklist::$blacklistedClassNames['SymfonyBlacklistSimplePhpunit'] = 1; +if (method_exists('PHPUnit\Util\Blacklist', 'addDirectory')) { + (new PHPUnit\Util\BlackList())->getBlacklistedDirectories(); + PHPUnit\Util\Blacklist::addDirectory(\dirname((new \ReflectionClass('SymfonyBlacklistPhpunit'))->getFileName())); + PHPUnit\Util\Blacklist::addDirectory(\dirname((new \ReflectionClass('SymfonyBlacklistSimplePhpunit'))->getFileName())); } else { PHPUnit\Util\Blacklist::$blacklistedClassNames['SymfonyBlacklistPhpunit'] = 1; PHPUnit\Util\Blacklist::$blacklistedClassNames['SymfonyBlacklistSimplePhpunit'] = 1; diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index da2e101784961..9bcabc69dc44f 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -24,7 +24,7 @@ "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0" + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0|9.1.2" }, "autoload": { "files": [ "bootstrap.php" ], diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index fa12718818815..16f960e9f74c0 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -110,7 +110,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $prevErrorHandler = set_error_handler(static function ($level, $message, $file, $line) use (&$prevErrorHandler) { if (E_USER_DEPRECATED === $level) { $templateLine = 0; - if (preg_match('/ at line (\d+) /', $message, $matches)) { + if (preg_match('/ at line (\d+)[ .]/', $message, $matches)) { $templateLine = $matches[1]; } @@ -236,6 +236,14 @@ private function renderException(OutputInterface $output, string $template, Erro $output->text(sprintf(' ERROR (line %s)', $line)); } + // If the line is not known (this might happen for deprecations if we fail at detecting the line for instance), + // we render the message without context, to ensure the message is displayed. + if ($line <= 0) { + $output->text(sprintf(' >> %s ', $exception->getRawMessage())); + + return; + } + foreach ($this->getContext($template, $line) as $lineNumber => $code) { $output->text(sprintf( '%s %-6s %s', diff --git a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php index d5a2e759aa34a..b5f3badea88fd 100644 --- a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @@ -117,7 +117,7 @@ public function getMarkedPlaces($subject, $placesNameOnly = true, $name = null) * Use a string (the place name) to get place metadata * Use a Transition instance to get transition metadata */ - public function getMetadata($subject, string $key, $metadataSubject = null, string $name = null): ?string + public function getMetadata($subject, string $key, $metadataSubject = null, string $name = null) { return $this ->workflowRegistry diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index 6d9bffcfcc47e..e6b28c4db43d2 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -51,7 +51,7 @@ public function __construct(Headers $headers = null, AbstractPart $body = null) } if ($missingPackages) { - throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available; try running "composer require "%s"".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', implode(' ', array_keys($missingPackages)))); + throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available; try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); } parent::__construct($headers, $body); diff --git a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php index 46c0883722a20..8970bb497764e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Console\Tester\CommandTester; use Twig\Environment; use Twig\Loader\FilesystemLoader; +use Twig\TwigFilter; class LintCommandTest extends TestCase { @@ -66,6 +67,34 @@ public function testLintFileCompileTimeException() $this->assertRegExp('/ERROR in \S+ \(line /', trim($tester->getDisplay())); } + /** + * When deprecations are not reported by the command, the testsuite reporter will catch them so we need to mark the test as legacy. + * + * @group legacy + */ + public function testLintFileWithNotReportedDeprecation() + { + $tester = $this->createCommandTester(); + $filename = $this->createFile('{{ foo|deprecated_filter }}'); + + $ret = $tester->execute(['filename' => [$filename]], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false]); + + $this->assertEquals(0, $ret, 'Returns 0 in case of success'); + $this->assertStringContainsString('OK in', trim($tester->getDisplay())); + } + + public function testLintFileWithReportedDeprecation() + { + $tester = $this->createCommandTester(); + $filename = $this->createFile('{{ foo|deprecated_filter }}'); + + $ret = $tester->execute(['filename' => [$filename], '--show-deprecations' => true], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false]); + + $this->assertEquals(1, $ret, 'Returns 1 in case of error'); + $this->assertRegExp('/ERROR in \S+ \(line 1\)/', trim($tester->getDisplay())); + $this->assertStringContainsString('Filter "deprecated_filter" is deprecated', trim($tester->getDisplay())); + } + /** * @group tty */ @@ -80,7 +109,12 @@ public function testLintDefaultPaths() private function createCommandTester(): CommandTester { - $command = new LintCommand(new Environment(new FilesystemLoader(\dirname(__DIR__).'/Fixtures/templates/'))); + $environment = new Environment(new FilesystemLoader(\dirname(__DIR__).'/Fixtures/templates/')); + $environment->addFilter(new TwigFilter('deprecated_filter', function ($v) { + return $v; + }, ['deprecated' => true])); + + $command = new LintCommand($environment); $application = new Application(); $application->add($command); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php index 04d391da17775..32bd630f32516 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php @@ -103,9 +103,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $serviceIdsNb = 0; foreach ($serviceIds as $serviceId) { $text = []; + $resolvedServiceId = $serviceId; if (0 !== strpos($serviceId, $previousId)) { $text[] = ''; - if ('' !== $description = Descriptor::getClassDescription($serviceId, $serviceId)) { + if ('' !== $description = Descriptor::getClassDescription($serviceId, $resolvedServiceId)) { if (isset($hasAlias[$serviceId])) { continue; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php index 5cca8d7011fac..91e0031299c3b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsSetCommand.php @@ -96,6 +96,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $value = strtr(substr(base64_encode(random_bytes($random)), 0, $random), '+/', '-_'); } elseif (!$file = $input->getArgument('file')) { $value = $io->askHidden('Please type the secret value'); + + if (null === $value) { + $io->warning('No value provided: using empty string'); + $value = ''; + } } elseif ('-' === $file) { $value = file_get_contents('php://stdin'); } elseif (is_file($file) && is_readable($file)) { @@ -106,12 +111,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new \InvalidArgumentException(sprintf('File is not readable: "%s".', $file)); } - if (null === $value) { - $io->warning('No value provided, aborting.'); - - return 1; - } - if ($vault->generateKeys()) { $io->success($vault->getLastMessage()); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 45c995a467371..ee5e97c203e6a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -355,7 +355,7 @@ protected function describeContainerDefinition(Definition $definition, array $op protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) { - if ($alias->isPublic()) { + if ($alias->isPublic() && !$alias->isPrivate()) { $options['output']->comment(sprintf('This service is a public alias for the service %s', (string) $alias)); } else { $options['output']->comment(sprintf('This service is a private alias for the service %s', (string) $alias)); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 105695c963d91..13c63ff9988d5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1482,7 +1482,7 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode) ->thenInvalid('Either "scope" or "base_uri" should be defined.') ->end() ->validate() - ->ifTrue(function ($v) { return isset($v['query']) && !isset($v['base_uri']); }) + ->ifTrue(function ($v) { return !empty($v['query']) && !isset($v['base_uri']); }) ->thenInvalid('"query" applies to "base_uri" but no base URI is defined.') ->end() ->children() diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index b505daf131c36..0dc2860229bdf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -35,6 +35,7 @@ use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass; use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; +use Symfony\Component\Debug\ErrorHandler as LegacyErrorHandler; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -89,6 +90,9 @@ class FrameworkBundle extends Bundle public function boot() { ErrorHandler::register(null, false)->throwAt($this->container->getParameter('debug.error_handler.throw_at'), true); + if (class_exists(LegacyErrorHandler::class, false)) { + LegacyErrorHandler::register(null, false)->throwAt($this->container->getParameter('debug.error_handler.throw_at'), true); + } if ($this->container->getParameter('kernel.http_method_override')) { Request::enableHttpMethodParameterOverride(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml index 26f08f78895ed..c9b17f311f576 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml @@ -41,7 +41,8 @@ - + + attributes diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/CustomPathBundle/src/CustomPathBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/CustomPathBundle/src/CustomPathBundle.php index ad7194de97b0e..f1f2bdc746e9e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/CustomPathBundle/src/CustomPathBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/CustomPathBundle/src/CustomPathBundle.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\FrameworkBundle\Tests; +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\CustomPathBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/TestBundle/TestBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/TestBundle/TestBundle.php index 2f090b2de8d53..c58b25066bf4a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/TestBundle/TestBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/TestBundle/TestBundle.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\FrameworkBundle\Tests; +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\TestBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_scoped_without_query_option.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_scoped_without_query_option.php new file mode 100644 index 0000000000000..0d3dc88472f84 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_scoped_without_query_option.php @@ -0,0 +1,11 @@ +loadFromExtension('framework', [ + 'http_client' => [ + 'scoped_clients' => [ + 'foo' => [ + 'scope' => '.*', + ], + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_scoped_without_query_option.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_scoped_without_query_option.xml new file mode 100644 index 0000000000000..43043aeda2a01 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_scoped_without_query_option.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_scoped_without_query_option.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_scoped_without_query_option.yml new file mode 100644 index 0000000000000..ecfc9d41fd4c3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_scoped_without_query_option.yml @@ -0,0 +1,5 @@ +framework: + http_client: + scoped_clients: + foo: + scope: '.*' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index edcdb7aae487f..9a8fc0776f0af 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1547,6 +1547,14 @@ public function testHttpClientDefaultOptions() $this->assertSame(ScopingHttpClient::class, $container->getDefinition('foo')->getClass()); } + public function testScopedHttpClientWithoutQueryOption() + { + $container = $this->createContainerFromFile('http_client_scoped_without_query_option'); + + $this->assertTrue($container->hasDefinition('foo'), 'should have the "foo" service.'); + $this->assertSame(ScopingHttpClient::class, $container->getDefinition('foo')->getClass()); + } + public function testHttpClientOverrideDefaultOptions() { $container = $this->createContainerFromFile('http_client_override_default_options'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/ClassAliasExampleClass.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/ClassAliasExampleClass.php new file mode 100644 index 0000000000000..c1298658597b1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/ClassAliasExampleClass.php @@ -0,0 +1,14 @@ +run(['command' => 'debug:autowiring', 'search' => 'redirect', '--all' => true]); $this->assertStringContainsString('Pro-tip: use interfaces in your type-hints instead of classes to benefit from the dependency inversion principle.', $tester->getDisplay()); } + + public function testNotConfusedByClassAliases() + { + static::bootKernel(['test_case' => 'ContainerDebug', 'root_config' => 'config.yml']); + + $application = new Application(static::$kernel); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(['command' => 'debug:autowiring', 'search' => 'ClassAlias']); + $this->assertStringContainsString('Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ClassAliasExampleClass (public)', $tester->getDisplay()); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml index 0cc73dbc81422..25c1c784298b5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ContainerDebug/config.yml @@ -13,6 +13,7 @@ services: public: false Symfony\Bundle\FrameworkBundle\Tests\Fixtures\BackslashClass: class: Symfony\Bundle\FrameworkBundle\Tests\Fixtures\BackslashClass + Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ClassAliasExampleClass: '@public' env: class: stdClass arguments: diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index b9258a93f8d1b..42114072d27fc 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -558,7 +558,7 @@ private function createEncoder(array $config) $config['algorithm'] = 'native'; $config['native_algorithm'] = PASSWORD_ARGON2I; } else { - throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available. Either use %s"auto" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? '"argon2id", ' : '')); + throw new InvalidConfigurationException(sprintf('Algorithm "argon2i" is not available. Either use "%s" or upgrade to PHP 7.2+ instead.', \defined('SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13') ? 'argon2id", "auto' : 'auto')); } return $this->createEncoder($config); @@ -571,7 +571,7 @@ private function createEncoder(array $config) $config['algorithm'] = 'native'; $config['native_algorithm'] = PASSWORD_ARGON2ID; } else { - throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. Either use %s"auto", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? '"argon2i", ' : '')); + throw new InvalidConfigurationException(sprintf('Algorithm "argon2id" is not available. Either use "%s", upgrade to PHP 7.3+ or use libsodium 1.0.15+ instead.', \defined('PASSWORD_ARGON2I') || $hasSodium ? 'argon2i", "auto' : 'auto')); } return $this->createEncoder($config); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php index fef2732759fa1..22d378835e4c0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AppCustomAuthenticator.php @@ -23,7 +23,7 @@ class AppCustomAuthenticator extends AbstractGuardAuthenticator { public function supports(Request $request) { - return true; + return '/manual_login' !== $request->getPathInfo() && '/profile' !== $request->getPathInfo(); } public function getCredentials(Request $request) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AuthenticationController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AuthenticationController.php new file mode 100644 index 0000000000000..9833d05513833 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AuthenticationController.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; +use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; + +class AuthenticationController +{ + public function manualLoginAction(GuardAuthenticatorHandler $guardAuthenticatorHandler, Request $request) + { + $guardAuthenticatorHandler->authenticateWithToken(new PostAuthenticationGuardToken(new User('Jane', 'test', ['ROLE_USER']), 'secure', ['ROLE_USER']), $request, 'secure'); + + return new Response('Logged in.'); + } + + public function profileAction(UserInterface $user = null) + { + if (null === $user) { + return new Response('Not logged in.'); + } + + return new Response('Username: '.$user->getUsername()); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php index bb0969c36a2fd..83cd4118d76e4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/GuardedTest.php @@ -21,4 +21,14 @@ public function testGuarded() $this->assertSame(418, $client->getResponse()->getStatusCode()); } + + public function testManualLogin() + { + $client = $this->createClient(['debug' => true, 'test_case' => 'Guarded', 'root_config' => 'config.yml']); + + $client->request('GET', '/manual_login'); + $client->request('GET', '/profile'); + + $this->assertSame('Username: Jane', $client->getResponse()->getContent()); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml index 2d1f779a530ec..7f87c307d28b5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/config.yml @@ -10,8 +10,19 @@ framework: services: logger: { class: Psr\Log\NullLogger } Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AppCustomAuthenticator: ~ + Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AuthenticationController: + tags: [controller.service_arguments] security: + encoders: + Symfony\Component\Security\Core\User\User: plaintext + + providers: + in_memory: + memory: + users: + Jane: { password: test, roles: [ROLE_USER] } + firewalls: secure: pattern: ^/ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml index 4d11154375219..146aa811a143d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/Guarded/routing.yml @@ -3,3 +3,12 @@ main: defaults: _controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction path: /app +profile: + path: /profile + defaults: + _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AuthenticationController::profileAction + +manual_login: + path: /manual_login + defaults: + _controller: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\GuardedBundle\AuthenticationController::manualLoginAction diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php index 2d678fa8f3502..b760cca92627e 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php @@ -101,7 +101,7 @@ public function testTwigErrorIfLocatorReturnsFalse() public function testTwigErrorIfTemplateDoesNotExist() { $this->expectException('Twig\Error\LoaderError'); - $this->expectExceptionMessageRegExp('/Unable to find template "name\.format\.engine" \(looked into: .*Tests.Loader.\.\..DependencyInjection.Fixtures.templates\)/'); + $this->expectExceptionMessageMatches('/Unable to find template "name\.format\.engine" \(looked into: .*Tests.Loader.\.\..DependencyInjection.Fixtures.templates\)/'); $parser = $this->getMockBuilder('Symfony\Component\Templating\TemplateNameParserInterface')->getMock(); $locator = $this->getMockBuilder('Symfony\Component\Config\FileLocatorInterface')->getMock(); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php b/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php index 0679d2bf6d04a..7c111a8ba3560 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Csp/ContentSecurityPolicyHandler.php @@ -124,7 +124,7 @@ private function updateCspHeaders(Response $response, array $nonces = []): array $headers = $this->getCspHeaders($response); foreach ($headers as $header => $directives) { - foreach (['script-src' => 'csp_script_nonce', 'style-src' => 'csp_style_nonce'] as $type => $tokenName) { + foreach (['script-src' => 'csp_script_nonce', 'script-src-elem' => 'csp_script_nonce', 'style-src' => 'csp_style_nonce', 'style-src-elem' => 'csp_style_nonce'] as $type => $tokenName) { if ($this->authorizesInline($directives, $type)) { continue; } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig index 268f8fdc7e3f6..49878a72d5d65 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/memory.html.twig @@ -5,18 +5,18 @@ {% set status_color = (collector.memory / 1024 / 1024) > 50 ? 'yellow' : '' %} {{ include('@WebProfiler/Icon/memory.svg') }} {{ '%.1f'|format(collector.memory / 1024 / 1024) }} - MB + MiB {% endset %} {% set text %}
Peak memory usage - {{ '%.1f'|format(collector.memory / 1024 / 1024) }} MB + {{ '%.1f'|format(collector.memory / 1024 / 1024) }} MiB
PHP memory limit - {{ collector.memoryLimit == -1 ? 'Unlimited' : '%.0f MB'|format(collector.memoryLimit / 1024 / 1024) }} + {{ collector.memoryLimit == -1 ? 'Unlimited' : '%.0f MiB'|format(collector.memoryLimit / 1024 / 1024) }}
{% endset %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig index 62187dbab4f31..9284a0a708d36 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig @@ -52,7 +52,7 @@ {% if profile.collectors.memory %}
- {{ '%.2f'|format(profile.collectors.memory.memory / 1024 / 1024) }} MB + {{ '%.2f'|format(profile.collectors.memory.memory / 1024 / 1024) }} MiB Peak memory usage
{% endif %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php index acccc7cbfb6d2..349db2aaf75b4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Csp/ContentSecurityPolicyHandlerTest.php @@ -41,7 +41,7 @@ public function testOnKernelResponse($nonce, $expectedNonce, Request $request, R $this->assertFalse($response->headers->has('X-SymfonyProfiler-Style-Nonce')); foreach ($expectedCsp as $header => $value) { - $this->assertSame($value, $response->headers->get($header)); + $this->assertSame($value, $response->headers->get($header), $header); } } @@ -131,7 +131,7 @@ public function provideRequestAndResponsesForOnKernelResponse() ['csp_script_nonce' => $nonce, 'csp_style_nonce' => $nonce], $this->createRequest(), $this->createResponse(['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'']), - ['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; style-src \'self\' domain.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; style-src \'self\' domain-report-only.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null], + ['Content-Security-Policy' => 'default-src \'self\' domain.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\' domain.com \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' domain.com \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src-elem \'self\' domain.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'Content-Security-Policy-Report-Only' => 'default-src \'self\' domain-report-only.com; script-src \'self\' \'unsafe-inline\'; script-src-elem \'self\' domain-report-only.com \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src \'self\' domain-report-only.com \'unsafe-inline\' \'nonce-'.$nonce.'\'; style-src-elem \'self\' domain-report-only.com \'unsafe-inline\' \'nonce-'.$nonce.'\'', 'X-Content-Security-Policy' => null], ], [ $nonce, diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php index 1eb1916a3f129..57feb65cc1f80 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php @@ -106,7 +106,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!$socket = stream_socket_server($host, $errno, $errstr)) { - throw new RuntimeException(sprintf('Server start failed on "%s": %s %s.', $host, $errstr, $errno)); + throw new RuntimeException(sprintf('Server start failed on "%s": '.$errstr.' '.$errno, $host)); } foreach ($this->getLogs($socket) as $clientId => $message) { diff --git a/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php index 0130ed3d02866..99413dd1167bf 100644 --- a/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php +++ b/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php @@ -59,7 +59,7 @@ private function getManifestPath(string $path): ?string $this->manifestData = json_decode(file_get_contents($this->manifestPath), true); if (0 < json_last_error()) { - throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s" - %s.', $this->manifestPath, json_last_error_msg())); + throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": '.json_last_error_msg(), $this->manifestPath)); } } diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 79ecf3ce995d9..25ce2d923570f 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -13,7 +13,6 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; -use Psr\Log\NullLogger; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\ResettableInterface; @@ -117,10 +116,12 @@ public static function createSystemCache($namespace, $defaultLifetime, $version, return $opcache; } + if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) { + return $opcache; + } + $apcu = new ApcuAdapter($namespace, (int) $defaultLifetime / 5, $version); - if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) { - $apcu->setLogger(new NullLogger()); - } elseif (null !== $logger) { + if (null !== $logger) { $apcu->setLogger($logger); } diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php index 63e97a8d0261e..05b8fc9c4be6e 100644 --- a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php @@ -51,6 +51,9 @@ public function __construct(array $adapters, int $defaultLifetime = 0) if (!$adapter instanceof CacheItemPoolInterface) { throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($adapter), CacheItemPoolInterface::class)); } + if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $adapter instanceof ApcuAdapter && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) { + continue; // skip putting APCu in the chain when the backend is disabled + } if ($adapter instanceof AdapterInterface) { $this->adapters[] = $adapter; diff --git a/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php index d9a1ad39ac6ef..2ce9eeb977fc5 100644 --- a/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php @@ -107,7 +107,7 @@ protected function doSave(array $values, ?int $lifetime, array $addTagData = [], $file = $this->getFile($id); - if (!@symlink($file, $this->getFile($id, true, $tagFolder))) { + if (!@symlink($file, $tagLink = $this->getFile($id, true, $tagFolder)) && !is_link($tagLink)) { @unlink($file); $failed[] = $id; } diff --git a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php index 7e9dac803c32d..c296c8847c5cf 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php @@ -247,15 +247,11 @@ public function prune() */ public function reset() { - if (!$this->pool instanceof ResetInterface) { - return; - } - $event = $this->start(__FUNCTION__); - try { + if ($this->pool instanceof ResetInterface) { $this->pool->reset(); - } finally { - $event->end = microtime(true); } + + $this->clearCalls(); } /** diff --git a/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php b/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php index 6193d34798757..b534e5dc8a7fb 100644 --- a/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php +++ b/src/Symfony/Component/Cache/DependencyInjection/CacheCollectorPass.php @@ -46,27 +46,38 @@ public function process(ContainerBuilder $container) return; } - $collectorDefinition = $container->getDefinition($this->dataCollectorCacheId); foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) { - $definition = $container->getDefinition($id); - if ($definition->isAbstract()) { - continue; + $this->addToCollector($id, $container); + + if (($attributes[0]['name'] ?? $id) !== $id) { + $this->addToCollector($attributes[0]['name'], $container); } + } + } - $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class); - $recorder->setTags($definition->getTags()); + private function addToCollector(string $id, ContainerBuilder $container) + { + $definition = $container->getDefinition($id); + if ($definition->isAbstract()) { + return; + } + + $collectorDefinition = $container->getDefinition($this->dataCollectorCacheId); + $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class); + $recorder->setTags($definition->getTags()); + if (!$definition->isPublic() || !$definition->isPrivate()) { $recorder->setPublic($definition->isPublic()); - $recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]); + } + $recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]); - $definition->setTags([]); - $definition->setPublic(false); + $definition->setTags([]); + $definition->setPublic(false); - $container->setDefinition($innerId, $definition); - $container->setDefinition($id, $recorder); + $container->setDefinition($innerId, $definition); + $container->setDefinition($id, $recorder); - // Tell the collector to add the new instance - $collectorDefinition->addMethodCall('addInstance', [$id, new Reference($id)]); - $collectorDefinition->setPublic(false); - } + // Tell the collector to add the new instance + $collectorDefinition->addMethodCall('addInstance', [$id, new Reference($id)]); + $collectorDefinition->setPublic(false); } } diff --git a/src/Symfony/Component/Cache/Tests/DependencyInjection/CacheCollectorPassTest.php b/src/Symfony/Component/Cache/Tests/DependencyInjection/CacheCollectorPassTest.php index 7e77491de867b..8aff19fc3e14b 100644 --- a/src/Symfony/Component/Cache/Tests/DependencyInjection/CacheCollectorPassTest.php +++ b/src/Symfony/Component/Cache/Tests/DependencyInjection/CacheCollectorPassTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; use Symfony\Component\Cache\Adapter\TagAwareAdapter; use Symfony\Component\Cache\Adapter\TraceableAdapter; use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter; @@ -34,16 +35,29 @@ public function testProcess() ->addArgument(new Reference('fs')) ->addTag('cache.pool'); + $container + ->register('.php.inner', PhpArrayAdapter::class) + ->addTag('cache.pool', ['name' => 'php']); + $container + ->register('php', TagAwareAdapter::class) + ->addArgument(new Reference('.php.inner')); + $collector = $container->register('data_collector.cache', CacheDataCollector::class); (new CacheCollectorPass())->process($container); $this->assertEquals([ ['addInstance', ['fs', new Reference('fs')]], ['addInstance', ['tagged_fs', new Reference('tagged_fs')]], + ['addInstance', ['.php.inner', new Reference('.php.inner')]], + ['addInstance', ['php', new Reference('php')]], ], $collector->getMethodCalls()); $this->assertSame(TraceableAdapter::class, $container->findDefinition('fs')->getClass()); $this->assertSame(TraceableTagAwareAdapter::class, $container->getDefinition('tagged_fs')->getClass()); + + $this->assertSame(TraceableAdapter::class, $container->findDefinition('.php.inner')->getClass()); + $this->assertSame(TraceableTagAwareAdapter::class, $container->getDefinition('php')->getClass()); + $this->assertFalse($collector->isPublic(), 'The "data_collector.cache" should be private after processing'); } } diff --git a/src/Symfony/Component/Cache/Tests/Traits/TagAwareTestTrait.php b/src/Symfony/Component/Cache/Tests/Traits/TagAwareTestTrait.php index 7f1544af5b41d..8b687b5a7564c 100644 --- a/src/Symfony/Component/Cache/Tests/Traits/TagAwareTestTrait.php +++ b/src/Symfony/Component/Cache/Tests/Traits/TagAwareTestTrait.php @@ -155,4 +155,42 @@ public function testGetMetadata() $i = $pool->getItem('k'); $this->assertSame(['foo' => 'foo'], $i->getMetadata()[CacheItem::METADATA_TAGS]); } + + public function testRefreshAfterExpires() + { + $pool = $this->createCachePool(); + $pool->clear(); + + $cacheItem = $pool->getItem('test'); + + $this->assertFalse($cacheItem->isHit()); + + // write cache with expires + $cacheItem->set('test'); + $cacheItem->tag('1234'); + $cacheItem->expiresAfter(1); + + $pool->save($cacheItem); + + $cacheItem = $pool->getItem('test'); + $this->assertTrue($cacheItem->isHit()); + + // wait until expired + sleep(2); + + // item should not longer be a hit + $cacheItem = $pool->getItem('test'); + $this->assertFalse($cacheItem->isHit()); + + // update expired item + $cacheItem->set('test'); + $cacheItem->tag('1234'); + $cacheItem->expiresAfter(1); + + $pool->save($cacheItem); + + // item should be again a hit + $cacheItem = $pool->getItem('test'); + $this->assertTrue($cacheItem->isHit()); + } } diff --git a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php index 507bc1fbfb123..ca26f7e4ff7fc 100644 --- a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php +++ b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php @@ -303,7 +303,7 @@ private function checkResultCode($result) return $result; } - throw new CacheException(sprintf('MemcachedAdapter client error: %s.', strtolower($this->client->getResultMessage()))); + throw new CacheException('MemcachedAdapter client error: '.strtolower($this->client->getResultMessage())); } private function getClient(): \Memcached diff --git a/src/Symfony/Component/Config/Definition/BaseNode.php b/src/Symfony/Component/Config/Definition/BaseNode.php index ddf2f1155d6ba..edfb46e628347 100644 --- a/src/Symfony/Component/Config/Definition/BaseNode.php +++ b/src/Symfony/Component/Config/Definition/BaseNode.php @@ -438,7 +438,7 @@ final public function finalize($value) throw $e; } catch (\Exception $e) { - throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": %s.', $this->getPath(), $e->getMessage()), $e->getCode(), $e); + throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": '.$e->getMessage(), $this->getPath()), $e->getCode(), $e); } } diff --git a/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php index aaadc8c4f430b..fbaed34a212a6 100644 --- a/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php @@ -66,7 +66,7 @@ public function testGetPattern() public function testResourceDoesNotExist() { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The directory ".*" does not exist./'); + $this->expectExceptionMessageMatches('/The directory ".*" does not exist./'); new DirectoryResource('/____foo/foobar'.mt_rand(1, 999999)); } diff --git a/src/Symfony/Component/Config/Tests/Resource/FileResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/FileResourceTest.php index f85a820fb95bb..e3a45566c2617 100644 --- a/src/Symfony/Component/Config/Tests/Resource/FileResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/FileResourceTest.php @@ -56,7 +56,7 @@ public function testToString() public function testResourceDoesNotExist() { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The file ".*" does not exist./'); + $this->expectExceptionMessageMatches('/The file ".*" does not exist./'); new FileResource('/____foo/foobar'.mt_rand(1, 999999)); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php index c56feafbcfb03..75813ba4976b0 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -153,7 +153,7 @@ protected function getConstructor(Definition $definition, $required) throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class)); } } catch (\ReflectionException $e) { - throw new RuntimeException(sprintf('Invalid service "%s": %s.', $this->currentId, lcfirst(rtrim($e->getMessage(), '.')))); + throw new RuntimeException(sprintf('Invalid service "%s": '.lcfirst($e->getMessage()), $this->currentId)); } if (!$r = $r->getConstructor()) { if ($required) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 982472f5adf07..4a84cd1a24f11 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -1700,7 +1700,11 @@ private function dumpValue($value, bool $interpolate = true): string if (!$v) { continue; } - $definition = $this->container->findDefinition($id = (string) $v); + $id = (string) $v; + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + $definition = $this->container->getDefinition($id); $load = !($definition->hasErrors() && $e = $definition->getErrors()) ? $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) : reset($e); $serviceMap .= sprintf("\n %s => [%s, %s, %s, %s],", $this->export($k), diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php index 9d3305e8ee08c..2257edaef6daf 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractServiceConfigurator.php @@ -42,7 +42,7 @@ public function __destruct() /** * Registers a service. */ - final public function set(string $id, string $class = null): ServiceConfigurator + final public function set(?string $id, string $class = null): ServiceConfigurator { $this->__destruct(); diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/AliasConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/AliasConfigurator.php index cb00f58c049d4..8ac635758bb74 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/AliasConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/AliasConfigurator.php @@ -20,6 +20,7 @@ class AliasConfigurator extends AbstractServiceConfigurator { const FACTORY = 'alias'; + use Traits\DeprecateTrait; use Traits\PublicTrait; public function __construct(ServicesConfigurator $parent, Alias $alias) diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InlineServiceConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InlineServiceConfigurator.php index 362b374e55970..ea5db9778bad8 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/InlineServiceConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/InlineServiceConfigurator.php @@ -29,6 +29,10 @@ class InlineServiceConfigurator extends AbstractConfigurator use Traits\ParentTrait; use Traits\TagTrait; + private $id = '[inline]'; + private $allowParent = true; + private $path = null; + public function __construct(Definition $definition) { $this->definition = $definition; diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php index 3cd56e0f19a48..1b740cee74461 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/PrototypeConfigurator.php @@ -45,10 +45,13 @@ class PrototypeConfigurator extends AbstractServiceConfigurator public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent) { $definition = new Definition(); - $definition->setPublic($defaults->isPublic()); + if (!$defaults->isPublic() || !$defaults->isPrivate()) { + $definition->setPublic($defaults->isPublic()); + } $definition->setAutowired($defaults->isAutowired()); $definition->setAutoconfigured($defaults->isAutoconfigured()); - $definition->setBindings($defaults->getBindings()); + // deep clone, to avoid multiple process of the same instance in the passes + $definition->setBindings(unserialize(serialize($defaults->getBindings()))); $definition->setChanges([]); $this->loader = $loader; diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php index f0fdde81c33a4..237099d08cd7e 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServicesConfigurator.php @@ -83,13 +83,14 @@ final public function set(?string $id, string $class = null): ServiceConfigurato $id = sprintf('.%d_%s', ++$this->anonymousCount, preg_replace('/^.*\\\\/', '', $class).'~'.$this->anonymousHash); $definition->setPublic(false); - } else { - $definition->setPublic($defaults->isPublic()); + } elseif (!$defaults->isPublic() || !$defaults->isPrivate()) { + $definition->setPublic($defaults->isPublic() && !$defaults->isPrivate()); } $definition->setAutowired($defaults->isAutowired()); $definition->setAutoconfigured($defaults->isAutoconfigured()); - $definition->setBindings($defaults->getBindings()); + // deep clone, to avoid multiple process of the same instance in the passes + $definition->setBindings(unserialize(serialize($defaults->getBindings()))); $definition->setChanges([]); $configurator = new ServiceConfigurator($this->container, $this->instanceof, $allowParent, $this, $definition, $id, $defaults->getTags(), $this->path); @@ -103,7 +104,10 @@ final public function set(?string $id, string $class = null): ServiceConfigurato final public function alias(string $id, string $referencedId): AliasConfigurator { $ref = static::processValue($referencedId, true); - $alias = new Alias((string) $ref, $this->defaults->isPublic()); + $alias = new Alias((string) $ref); + if (!$this->defaults->isPublic() || !$this->defaults->isPrivate()) { + $alias->setPublic($this->defaults->isPublic()); + } $this->container->setAlias($id, $alias); return new AliasConfigurator($this, $alias); diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index a31959e456552..b35952e4522fb 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -299,7 +299,7 @@ private function parseDefaults(array &$content, string $file): array private function isUsingShortSyntax(array $service): bool { foreach ($service as $key => $value) { - if (\is_string($key) && ('' === $key || '$' !== $key[0])) { + if (\is_string($key) && ('' === $key || ('$' !== $key[0] && false === strpos($key, '\\')))) { return false; } } @@ -345,7 +345,7 @@ private function parseDefinition(string $id, $service, string $file, array $defa if (isset($service['alias'])) { $this->container->setAlias($id, $alias = new Alias($service['alias'])); - if (\array_key_exists('public', $service)) { + if (isset($service['public'])) { $alias->setPublic($service['public']); } elseif (isset($defaults['public'])) { $alias->setPublic($defaults['public']); @@ -690,7 +690,7 @@ protected function loadFile($file) try { $configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS); } catch (ParseException $e) { - throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: "%s".', $file, $e->getMessage()), 0, $e); + throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: '.$e->getMessage(), $file), 0, $e); } return $this->validate($configuration, $file); @@ -763,7 +763,7 @@ private function resolveServices($value, string $file, bool $isParameter = false if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) { if ($diff = array_diff(array_keys($argument), ['tag', 'index_by', 'default_index_method', 'default_priority_method'])) { - throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by", "default_index_method", and "default_priority_method".', $value->getTag(), implode('"", "', $diff))); + throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by", "default_index_method", and "default_priority_method".', $value->getTag(), implode('", "', $diff))); } $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php index 77b1969f60b1c..d8e7d62d478f4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php @@ -88,6 +88,7 @@ public function testNoAttributes() CustomDefinition::class => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), 'bar' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'bar')), 'baz' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'baz')), + 'late_alias' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class, TestDefinition1::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'late_alias')), ]; $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); @@ -118,6 +119,7 @@ public function testWithAttributes() CustomDefinition::class => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), 'bar' => new ServiceClosureArgument(new TypedReference('bar', CustomDefinition::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'bar')), 'baz' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'baz')), + 'late_alias' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class, TestDefinition1::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'late_alias')), ]; $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); @@ -260,6 +262,7 @@ public function testBinding() CustomDefinition::class => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), 'bar' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'bar')), 'baz' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'baz')), + 'late_alias' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class, TestDefinition1::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'late_alias')), ]; $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php index dfe37445d4e88..dd58f90990310 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveChildDefinitionsPassTest.php @@ -400,7 +400,7 @@ protected function process(ContainerBuilder $container) public function testProcessDetectsChildDefinitionIndirectCircularReference() { $this->expectException('Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException'); - $this->expectExceptionMessageRegExp('/^Circular reference detected for service "c", path: "c -> b -> a -> c"./'); + $this->expectExceptionMessageMatches('/^Circular reference detected for service "c", path: "c -> b -> a -> c"./'); $container = new ContainerBuilder(); $container->register('a'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php index ebd8f3df1024a..3acfbed776f1f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php @@ -233,7 +233,7 @@ public function testProcessForAutoconfiguredCalls() public function testProcessThrowsExceptionForArguments() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/Autoconfigured instanceof for type "PHPUnit[\\\\_]Framework[\\\\_]TestCase" defines arguments but these are not supported and should be removed\./'); + $this->expectExceptionMessageMatches('/Autoconfigured instanceof for type "PHPUnit[\\\\_]Framework[\\\\_]TestCase" defines arguments but these are not supported and should be removed\./'); $container = new ContainerBuilder(); $container->registerForAutoconfiguration(parent::class) ->addArgument('bar'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php index 457c7a7e5cf03..8b50689dc0f5b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php @@ -105,7 +105,7 @@ public function testInvalidEnvInConfig() public function testNulledEnvInConfig() { $this->expectException('Symfony\Component\Config\Definition\Exception\InvalidTypeException'); - $this->expectExceptionMessageRegexp('/^Invalid type for path "env_extension\.int_node"\. Expected "?int"?, but got (NULL|"null")\.$/'); + $this->expectExceptionMessageMatches('/^Invalid type for path "env_extension\.int_node"\. Expected "?int"?, but got (NULL|"null")\.$/'); $container = new ContainerBuilder(); $container->setParameter('env(NULLED)', null); $container->registerExtension(new EnvExtension()); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 60f1ab1d4f070..51a88705d6fe6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -19,6 +19,8 @@ use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocator as ArgumentServiceLocator; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; use Symfony\Component\DependencyInjection\Definition; @@ -35,6 +37,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition; use Symfony\Component\DependencyInjection\Tests\Fixtures\ScalarFactory; use Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator; +use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1; use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\DependencyInjection\Variable; @@ -906,6 +909,20 @@ public function testServiceSubscriber() $container->register(CustomDefinition::class, CustomDefinition::class) ->setPublic(false); + + $container->register(TestDefinition1::class, TestDefinition1::class)->setPublic(true); + + $container->addCompilerPass(new class() implements CompilerPassInterface { + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $container->setDefinition('late_alias', new Definition(TestDefinition1::class)); + $container->setAlias(TestDefinition1::class, 'late_alias'); + } + }, PassConfig::TYPE_AFTER_REMOVING); + $container->compile(); $dumper = new PhpDumper($container); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php index 6876fc5cca53d..f4a4bdda6451e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php @@ -22,6 +22,7 @@ public static function getSubscribedServices(): array '?'.CustomDefinition::class, 'bar' => CustomDefinition::class, 'baz' => '?'.CustomDefinition::class, + 'late_alias' => TestDefinition1::class, ]; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/anonymous.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/anonymous.php index 112b162bcaca9..65605bcccff89 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/anonymous.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/anonymous.php @@ -7,7 +7,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\StdClassDecorator; return function (ContainerConfigurator $c) { - $s = $c->services(); + $s = $c->services()->defaults()->public(); $s->set('decorated', stdClass::class); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/basic.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/basic.php index a9e250b9213a1..89132620ec3c8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/basic.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/basic.php @@ -5,7 +5,7 @@ use App\BarService; return function (ContainerConfigurator $c) { - $s = $c->services(); + $s = $c->services()->defaults()->public(); $s->set(BarService::class) ->args([inline('FooClass')]); }; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/child.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/child.php index 8a5f2431d0abf..d2735bcfb1bfb 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/child.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/child.php @@ -6,12 +6,14 @@ return function (ContainerConfigurator $c) { $c->services() - ->set('bar', 'Class1') + ->set('bar', 'Class1')->public() ->set(BarService::class) + ->public() ->abstract(true) ->lazy() ->set('foo') ->parent(BarService::class) + ->public() ->decorate('bar', 'b', 1) ->args([ref('b')]) ->class('Class2') diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.expected.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.expected.yml index a534f7267a078..2b389b694590a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.expected.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.expected.yml @@ -18,7 +18,7 @@ services: arguments: ['@bar'] bar: class: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo - public: false + public: true tags: - { name: t, a: b } autowire: true diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.php index 2889d3fbb6400..b04413d2102f3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/defaults.php @@ -14,7 +14,7 @@ ->autowire() ->tag('t', ['a' => 'b']) ->bind(Foo::class, ref('bar')) - ->private(); + ->public(); $s->set(Foo::class)->args([ref('bar')])->public(); $s->set('bar', Foo::class)->call('setFoo')->autoconfigure(false); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof.php index 0d6aac7a0051e..d9f62a67ec9a8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/instanceof.php @@ -6,7 +6,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype; return function (ContainerConfigurator $c) { - $s = $c->services(); + $s = $c->services()->defaults()->public(); $s->instanceof(Prototype\Foo::class) ->property('p', 0) ->call('setFoo', [ref('foo')]) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/lazy_fqcn.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/lazy_fqcn.php index 7cde4ef2d0699..d23f995993424 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/lazy_fqcn.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/lazy_fqcn.php @@ -3,6 +3,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; return function (ContainerConfigurator $c) { - $di = $c->services(); + $di = $c->services()->defaults()->public(); $di->set('foo', 'stdClass')->lazy('SomeInterface'); }; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/object.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/object.php index daf4682bf7efa..59c4d43fd2b11 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/object.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/object.php @@ -7,7 +7,7 @@ return new class() { public function __invoke(ContainerConfigurator $c) { - $s = $c->services(); + $s = $c->services()->defaults()->public(); $s->set(BarService::class) ->args([inline('FooClass')]); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/php7.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/php7.php index 7711624e6f0f6..98134fb230409 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/php7.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/php7.php @@ -9,7 +9,7 @@ ('foo', 'Foo') ('bar', 'Bar') ; - $c->services() + $c->services()->defaults()->public() (Foo::class) ->arg('$bar', ref('bar')) ->public() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.php index 6a7d859df1fd6..f5baa0f642738 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype.php @@ -8,6 +8,7 @@ $di = $c->services()->defaults() ->tag('baz'); $di->load(Prototype::class.'\\', '../Prototype') + ->public() ->autoconfigure() ->exclude('../Prototype/{OtherDir,BadClasses,SinglyImplementedInterface}') ->factory('f') @@ -17,6 +18,6 @@ ->autoconfigure(false) ->tag('foo') ->parent('foo'); - $di->set('foo')->lazy()->abstract(); + $di->set('foo')->lazy()->abstract()->public(); $di->get(Prototype\Foo::class)->lazy(false); }; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php index 501baa3c10ab7..026ee6c1ab693 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.php @@ -8,6 +8,7 @@ $di = $c->services()->defaults() ->tag('baz'); $di->load(Prototype::class.'\\', '../Prototype') + ->public() ->autoconfigure() ->exclude(['../Prototype/OtherDir', '../Prototype/BadClasses', '../Prototype/SinglyImplementedInterface']) ->factory('f') @@ -17,6 +18,6 @@ ->autoconfigure(false) ->tag('foo') ->parent('foo'); - $di->set('foo')->lazy()->abstract(); + $di->set('foo')->lazy()->abstract()->public(); $di->get(Prototype\Foo::class)->lazy(false); }; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services9.php index 7c070ef64f450..8691d48efe257 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/services9.php @@ -16,8 +16,7 @@ $p->set('foo_class', FooClass::class) ->set('foo', 'bar'); - $s = $c->services()->defaults() - ->public(); + $s = $c->services()->defaults()->public(); $s->set('foo') ->args(['foo', ref('foo.baz'), ['%foo%' => 'foo is %foo%', 'foobar' => '%foo%'], true, ref('service_container')]) ->class(FooClass::class) @@ -127,12 +126,10 @@ ->tag('foo'); $s->set('tagged_iterator', 'Bar') - ->public() ->args([tagged_iterator('foo')]); $s->set('runtime_error', 'stdClass') - ->args([new Reference('errored_definition', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE)]) - ->public(); + ->args([new Reference('errored_definition', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE)]); $s->set('errored_definition', 'stdClass')->private(); $s->alias('alias_for_foo', 'foo')->private()->public(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php index e184fad7bdf0c..caec312fdda41 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php @@ -27,9 +27,11 @@ public function __construct() $this->methodMap = [ 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => 'getTestServiceSubscriberService', 'foo_service' => 'getFooServiceService', + 'late_alias' => 'getLateAliasService', + ]; + $this->aliases = [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestDefinition1' => 'late_alias', ]; - - $this->aliases = []; } public function compile(): void @@ -45,11 +47,13 @@ public function isCompiled(): bool public function getRemovedIds(): array { return [ - '.service_locator.bPEFRiK' => true, - '.service_locator.bPEFRiK.foo_service' => true, + '.service_locator.CpwjbIa' => true, + '.service_locator.CpwjbIa.foo_service' => true, 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => true, + 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestDefinition1' => true, + 'late_alias' => true, ]; } @@ -75,14 +79,26 @@ protected function getFooServiceService() 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => ['services', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber', 'getTestServiceSubscriberService', false], 'bar' => ['services', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber', 'getTestServiceSubscriberService', false], 'baz' => ['privates', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition', 'getCustomDefinitionService', false], + 'late_alias' => ['services', 'late_alias', 'getLateAliasService', false], ], [ 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber', 'bar' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition', 'baz' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition', + 'late_alias' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestDefinition1', ]))->withContext('foo_service', $this)); } + /** + * Gets the public 'late_alias' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1 + */ + protected function getLateAliasService() + { + return $this->services['late_alias'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1(); + } + /** * Gets the private 'Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition' shared service. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_short_syntax.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_short_syntax.yml new file mode 100644 index 0000000000000..16ddb6bdf241a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_short_syntax.yml @@ -0,0 +1,6 @@ +services: + foo_bar: [1, 2] + + bar_foo: + $a: 'a' + App\Foo: 'foo' diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index 57ea37e6f4964..61971f408c477 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -216,7 +216,7 @@ public function testMissingParentClass() public function testRegisterClassesWithBadPrefix() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/Expected to find class "Symfony\\\Component\\\DependencyInjection\\\Tests\\\Fixtures\\\Prototype\\\Bar" in file ".+" while importing services from resource "Prototype\/Sub\/\*", but it was not found\! Check the namespace prefix used with the resource/'); + $this->expectExceptionMessageMatches('/Expected to find class "Symfony\\\Component\\\DependencyInjection\\\Tests\\\Fixtures\\\Prototype\\\Bar" in file ".+" while importing services from resource "Prototype\/Sub\/\*", but it was not found\! Check the namespace prefix used with the resource/'); $container = new ContainerBuilder(); $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 14ba4c7f6e567..e5868edad2cea 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -378,7 +378,7 @@ public function testParseTagsWithoutNameThrowsException() public function testParseTagWithEmptyNameThrowsException() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The tag name for service ".+" in .* must be a non-empty string/'); + $this->expectExceptionMessageMatches('/The tag name for service ".+" in .* must be a non-empty string/'); $container = new ContainerBuilder(); $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); $loader->load('tag_with_empty_name.xml'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index f40fc22cc3452..fc87dd7168ccc 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -50,7 +50,7 @@ public static function setUpBeforeClass(): void public function testLoadUnExistFile() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The file ".+" does not exist./'); + $this->expectExceptionMessageMatches('/The file ".+" does not exist./'); $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator(self::$fixturesPath.'/ini')); $r = new \ReflectionObject($loader); $m = $r->getMethod('loadFile'); @@ -62,7 +62,7 @@ public function testLoadUnExistFile() public function testLoadInvalidYamlFile() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The file ".+" does not contain valid YAML./'); + $this->expectExceptionMessageMatches('/The file ".+" does not contain valid YAML./'); $path = self::$fixturesPath.'/ini'; $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator($path)); $r = new \ReflectionObject($loader); @@ -205,6 +205,17 @@ public function testLoadServices() $this->assertEquals(['decorated', 'decorated.pif-pouf', 5, ContainerInterface::IGNORE_ON_INVALID_REFERENCE], $services['decorator_service_with_name_and_priority_and_on_invalid']->getDecoratedService()); } + public function testLoadShortSyntax() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('services_short_syntax.yml'); + $services = $container->getDefinitions(); + + $this->assertSame([1, 2], $services['foo_bar']->getArguments()); + $this->assertSame(['$a' => 'a', 'App\Foo' => 'foo'], $services['bar_foo']->getArguments()); + } + public function testDeprecatedAliases() { $container = new ContainerBuilder(); @@ -384,7 +395,7 @@ public function testLoadYamlOnlyWithKeys() public function testTagWithEmptyNameThrowsException() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The tag name for service ".+" in .+ must be a non-empty string/'); + $this->expectExceptionMessageMatches('/The tag name for service ".+" in .+ must be a non-empty string/'); $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('tag_name_empty_string.yml'); } @@ -392,7 +403,7 @@ public function testTagWithEmptyNameThrowsException() public function testTagWithNonStringNameThrowsException() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The tag name for service ".+" in .+ must be a non-empty string/'); + $this->expectExceptionMessageMatches('/The tag name for service ".+" in .+ must be a non-empty string/'); $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('tag_name_no_string.yml'); } @@ -493,7 +504,7 @@ public function testPrototypeWithNamespace() public function testPrototypeWithNamespaceAndNoResource() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/A "resource" attribute must be set when the "namespace" attribute is set for service ".+" in .+/'); + $this->expectExceptionMessageMatches('/A "resource" attribute must be set when the "namespace" attribute is set for service ".+" in .+/'); $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('services_prototype_namespace_without_resource.yml'); @@ -621,7 +632,7 @@ public function testDecoratedServicesWithWrongOnInvalidSyntaxThrowsException() public function testInvalidTagsWithDefaults() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/Parameter "tags" must be an array for service "Foo\\\Bar" in ".+services31_invalid_tags\.yml"\. Check your YAML syntax./'); + $this->expectExceptionMessageMatches('/Parameter "tags" must be an array for service "Foo\\\Bar" in ".+services31_invalid_tags\.yml"\. Check your YAML syntax./'); $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('services31_invalid_tags.yml'); } @@ -711,7 +722,7 @@ public function testAnonymousServicesInInstanceof() public function testAnonymousServicesWithAliases() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/Creating an alias using the tag "!service" is not allowed in ".+anonymous_services_alias\.yml"\./'); + $this->expectExceptionMessageMatches('/Creating an alias using the tag "!service" is not allowed in ".+anonymous_services_alias\.yml"\./'); $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('anonymous_services_alias.yml'); @@ -720,7 +731,7 @@ public function testAnonymousServicesWithAliases() public function testAnonymousServicesInParameters() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/Using an anonymous service in a parameter is not allowed in ".+anonymous_services_in_parameters\.yml"\./'); + $this->expectExceptionMessageMatches('/Using an anonymous service in a parameter is not allowed in ".+anonymous_services_in_parameters\.yml"\./'); $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('anonymous_services_in_parameters.yml'); @@ -739,7 +750,7 @@ public function testAutoConfigureInstanceof() public function testEmptyDefaultsThrowsClearException() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/Service "_defaults" key must be an array, "NULL" given in ".+bad_empty_defaults\.yml"\./'); + $this->expectExceptionMessageMatches('/Service "_defaults" key must be an array, "NULL" given in ".+bad_empty_defaults\.yml"\./'); $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('bad_empty_defaults.yml'); @@ -748,7 +759,7 @@ public function testEmptyDefaultsThrowsClearException() public function testEmptyInstanceofThrowsClearException() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/Service "_instanceof" key must be an array, "NULL" given in ".+bad_empty_instanceof\.yml"\./'); + $this->expectExceptionMessageMatches('/Service "_instanceof" key must be an array, "NULL" given in ".+bad_empty_instanceof\.yml"\./'); $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('bad_empty_instanceof.yml'); @@ -757,7 +768,7 @@ public function testEmptyInstanceofThrowsClearException() public function testUnsupportedKeywordThrowsException() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/^The configuration key "private" is unsupported for definition "bar"/'); + $this->expectExceptionMessageMatches('/^The configuration key "private" is unsupported for definition "bar"/'); $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('bad_keyword.yml'); @@ -766,7 +777,7 @@ public function testUnsupportedKeywordThrowsException() public function testUnsupportedKeywordInServiceAliasThrowsException() { $this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/^The configuration key "calls" is unsupported for the service "bar" which is defined as an alias/'); + $this->expectExceptionMessageMatches('/^The configuration key "calls" is unsupported for the service "bar" which is defined as an alias/'); $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('bad_alias.yml'); diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php index 9c7b30dc225d9..ed8b6852a5dbc 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php @@ -231,7 +231,7 @@ public function testRegisterAfterEval($registerCallback) public function testCallBadCallable() { $this->expectException('RuntimeException'); - $this->expectExceptionMessageRegExp('/Unable to call method "\w+" of object "\w+"./'); + $this->expectExceptionMessageMatches('/Unable to call method "\w+" of object "\w+"./'); $el = new ExpressionLanguage(); $el->evaluate('foo.myfunction()', ['foo' => new \stdClass()]); } diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 9054d7be3d336..85c997109a733 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -102,7 +102,7 @@ public function mkdir($dirs, $mode = 0777) if (!is_dir($dir)) { // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one if (self::$lastError) { - throw new IOException(sprintf('Failed to create "%s": %s.', $dir, self::$lastError), 0, null, $dir); + throw new IOException(sprintf('Failed to create "%s": '.self::$lastError, $dir), 0, null, $dir); } throw new IOException(sprintf('Failed to create "%s".', $dir), 0, null, $dir); } @@ -172,16 +172,16 @@ public function remove($files) if (is_link($file)) { // See https://bugs.php.net/52176 if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) { - throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, self::$lastError)); + throw new IOException(sprintf('Failed to remove symlink "%s": '.self::$lastError, $file)); } } elseif (is_dir($file)) { $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); if (!self::box('rmdir', $file) && file_exists($file)) { - throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, self::$lastError)); + throw new IOException(sprintf('Failed to remove directory "%s": '.self::$lastError, $file)); } } elseif (!self::box('unlink', $file) && file_exists($file)) { - throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, self::$lastError)); + throw new IOException(sprintf('Failed to remove file "%s": '.self::$lastError, $file)); } } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index 5d936ffb6b6e3..c271d098086c7 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -218,6 +218,14 @@ public function buildView(FormView $view, FormInterface $form, array $options) // * the html5 is set to true if ($options['html5'] && 'single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) { $view->vars['type'] = 'datetime-local'; + + // we need to force the browser to display the seconds by + // adding the HTML attribute step if not already defined. + // Otherwise the browser will not display and so not send the seconds + // therefore the value will always be considered as invalid. + if ($options['with_seconds'] && !isset($view->vars['attr']['step'])) { + $view->vars['attr']['step'] = 1; + } } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php b/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php index ffb7520a582b6..6ed403523cb77 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php @@ -31,13 +31,16 @@ public function buildForm(FormBuilderInterface $builder, array $options) $options['options']['error_bubbling'] = $options['error_bubbling']; } + // children fields must always be mapped + $defaultOptions = ['mapped' => true]; + $builder ->addViewTransformer(new ValueToDuplicatesTransformer([ $options['first_name'], $options['second_name'], ])) - ->add($options['first_name'], $options['type'], array_merge($options['options'], $options['first_options'])) - ->add($options['second_name'], $options['type'], array_merge($options['options'], $options['second_options'])) + ->add($options['first_name'], $options['type'], array_merge($options['options'], $options['first_options'], $defaultOptions)) + ->add($options['second_name'], $options['type'], array_merge($options['options'], $options['second_options'], $defaultOptions)) ; } diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index e0885eae6bf33..81d4c632e1f87 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -24,6 +24,8 @@ */ class FormValidator extends ConstraintValidator { + private $resolvedGroups; + /** * {@inheritdoc} */ @@ -44,44 +46,70 @@ public function validate($form, Constraint $formConstraint) if ($form->isSubmitted() && $form->isSynchronized()) { // Validate the form data only if transformation succeeded - $groups = self::getValidationGroups($form); + $groups = $this->getValidationGroups($form); if (!$groups) { return; } $data = $form->getData(); - // Validate the data against its own constraints - if ($form->isRoot() && (\is_object($data) || \is_array($data))) { - if (($groups && \is_array($groups)) || ($groups instanceof GroupSequence && $groups->groups)) { - $validator->atPath('data')->validate($form->getData(), null, $groups); - } - } + $validateDataGraph = $form->isRoot() + && (\is_object($data) || \is_array($data)) + && (($groups && \is_array($groups)) || ($groups instanceof GroupSequence && $groups->groups)) + ; - // Validate the data against the constraints defined - // in the form + // Validate the data against the constraints defined in the form + /** @var Constraint[] $constraints */ $constraints = $config->getOption('constraints', []); if ($groups instanceof GroupSequence) { - $validator->atPath('data')->validate($form->getData(), $constraints, $groups); - // Otherwise validate a constraint only once for the first - // matching group - foreach ($groups as $group) { - if (\in_array($group, $formConstraint->groups)) { - $validator->atPath('data')->validate($form->getData(), $formConstraint, $group); - if (\count($this->context->getViolations()) > 0) { - break; + // Validate the data, the form AND nested fields in sequence + $violationsCount = $this->context->getViolations()->count(); + $fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s'; + $hasChildren = $form->count() > 0; + $this->resolvedGroups = $hasChildren ? new \SplObjectStorage() : null; + + foreach ($groups->groups as $group) { + if ($validateDataGraph) { + $validator->atPath('data')->validate($data, null, $group); + } + + if ($groupedConstraints = self::getConstraintsInGroups($constraints, $group)) { + $validator->atPath('data')->validate($data, $groupedConstraints, $group); + } + + foreach ($form->all() as $field) { + if ($field->isSubmitted()) { + // remember to validate this field is one group only + // otherwise resolving the groups would reuse the same + // sequence recursively, thus some fields could fail + // in different steps without breaking early enough + $this->resolvedGroups[$field] = (array) $group; + $validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $formConstraint); } } + + if ($violationsCount < $this->context->getViolations()->count()) { + break; + } + } + + if ($hasChildren) { + // destroy storage at the end of the sequence to avoid memory leaks + $this->resolvedGroups = null; } } else { + if ($validateDataGraph) { + $validator->atPath('data')->validate($data, null, $groups); + } + $groupedConstraints = []; foreach ($constraints as $constraint) { // For the "Valid" constraint, validate the data in all groups if ($constraint instanceof Valid) { - $validator->atPath('data')->validate($form->getData(), $constraint, $groups); + $validator->atPath('data')->validate($data, $constraint, $groups); continue; } @@ -101,7 +129,7 @@ public function validate($form, Constraint $formConstraint) } foreach ($groupedConstraints as $group => $constraint) { - $validator->atPath('data')->validate($form->getData(), $constraint, $group); + $validator->atPath('data')->validate($data, $constraint, $group); } } } elseif (!$form->isSynchronized()) { @@ -159,7 +187,7 @@ public function validate($form, Constraint $formConstraint) * * @return string|GroupSequence|(string|GroupSequence)[] The validation groups */ - private static function getValidationGroups(FormInterface $form) + private function getValidationGroups(FormInterface $form) { // Determine the clicked button of the complete form tree $clickedButton = null; @@ -183,6 +211,10 @@ private static function getValidationGroups(FormInterface $form) return self::resolveValidationGroups($groups, $form); } + if (isset($this->resolvedGroups[$form])) { + return $this->resolvedGroups[$form]; + } + $form = $form->getParent(); } while (null !== $form); @@ -208,4 +240,11 @@ private static function resolveValidationGroups($groups, FormInterface $form) return (array) $groups; } + + private static function getConstraintsInGroups($constraints, $group) + { + return array_filter($constraints, static function (Constraint $constraint) use ($group) { + return \in_array($group, $constraint->groups, true); + }); + } } diff --git a/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php b/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php index b9cc334256174..867a5768aee6c 100644 --- a/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php @@ -50,8 +50,7 @@ public function validateForm(FormEvent $event) foreach ($this->validator->validate($form) as $violation) { // Allow the "invalid" constraint to be put onto // non-synchronized forms - // ConstraintViolation::getConstraint() must not expect to provide a constraint as long as Symfony\Component\Validator\ExecutionContext exists (before 3.0) - $allowNonSynchronized = (null === $violation->getConstraint() || $violation->getConstraint() instanceof Form) && Form::NOT_SYNCHRONIZED_ERROR === $violation->getCode(); + $allowNonSynchronized = $violation->getConstraint() instanceof Form && Form::NOT_SYNCHRONIZED_ERROR === $violation->getCode(); $this->violationMapper->mapViolation($violation, $form, $allowNonSynchronized); } diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index d6073f3ed3b67..3981627e6b4b2 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -1056,7 +1056,7 @@ private function modelToNorm($value) $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException(sprintf('Unable to transform data for property path "%s": %s', $this->getPropertyPath(), $exception->getMessage()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + throw new TransformationFailedException(sprintf('Unable to transform data for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; @@ -1078,7 +1078,7 @@ private function normToModel($value) $value = $transformers[$i]->reverseTransform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": %s', $this->getPropertyPath(), $exception->getMessage()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; @@ -1107,7 +1107,7 @@ private function normToView($value) $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException(sprintf('Unable to transform value for property path "%s": %s', $this->getPropertyPath(), $exception->getMessage()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + throw new TransformationFailedException(sprintf('Unable to transform value for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; @@ -1131,7 +1131,7 @@ private function viewToNorm($value) $value = $transformers[$i]->reverseTransform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": %s', $this->getPropertyPath(), $exception->getMessage()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); + throw new TransformationFailedException(sprintf('Unable to reverse value for property path "%s": '.$exception->getMessage(), $this->getPropertyPath()), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php index 02a06c85e1161..fdffc946a5eeb 100644 --- a/src/Symfony/Component/Form/ResolvedFormType.php +++ b/src/Symfony/Component/Form/ResolvedFormType.php @@ -96,7 +96,7 @@ public function createBuilder(FormFactoryInterface $factory, $name, array $optio try { $options = $this->getOptionsResolver()->resolve($options); } catch (ExceptionInterface $e) { - throw new $e(sprintf('An error has occurred resolving the options of the form "%s": %s.', \get_class($this->getInnerType()), $e->getMessage()), $e->getCode(), $e); + throw new $e(sprintf('An error has occurred resolving the options of the form "%s": '.$e->getMessage(), \get_class($this->getInnerType())), $e->getCode(), $e); } // Should be decoupled from the specific option at some point diff --git a/src/Symfony/Component/Form/Resources/config/validation.xml b/src/Symfony/Component/Form/Resources/config/validation.xml index cbd586b915451..b2b935442d467 100644 --- a/src/Symfony/Component/Form/Resources/config/validation.xml +++ b/src/Symfony/Component/Form/Resources/config/validation.xml @@ -7,7 +7,7 @@ - + diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php index 8391805b22666..e12347405dee9 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php @@ -197,7 +197,7 @@ public function provideCustomFalseValues() public function testDontAllowNonArrayFalseValues() { $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); - $this->expectExceptionMessageRegExp('/"false_values" with value "invalid" is expected to be of type "array"/'); + $this->expectExceptionMessageMatches('/"false_values" with value "invalid" is expected to be of type "array"/'); $this->factory->create(static::TESTED_TYPE, null, [ 'false_values' => 'invalid', ]); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php index b0c4f3549252d..b9012bdc699e4 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -495,6 +495,37 @@ public function testDontPassHtml5TypeIfNotSingleText() $this->assertArrayNotHasKey('type', $view->vars); } + public function testSingleTextWidgetWithSecondsShouldHaveRightStepAttribute() + { + $view = $this->factory + ->create(static::TESTED_TYPE, null, [ + 'widget' => 'single_text', + 'with_seconds' => true, + ]) + ->createView() + ; + + $this->assertArrayHasKey('step', $view->vars['attr']); + $this->assertEquals(1, $view->vars['attr']['step']); + } + + public function testSingleTextWidgetWithSecondsShouldNotOverrideStepAttribute() + { + $view = $this->factory + ->create(static::TESTED_TYPE, null, [ + 'widget' => 'single_text', + 'with_seconds' => true, + 'attr' => [ + 'step' => 30, + ], + ]) + ->createView() + ; + + $this->assertArrayHasKey('step', $view->vars['attr']); + $this->assertEquals(30, $view->vars['attr']['step']); + } + public function testSingleTextWidgetWithCustomNonHtml5Format() { $form = $this->factory->create(static::TESTED_TYPE, new \DateTime('2019-02-13 19:12:13'), [ diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php index 267511d88ccd4..a70f65021680f 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/RepeatedTypeTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; use Symfony\Component\Form\Form; +use Symfony\Component\Form\Tests\Fixtures\NotMappedType; class RepeatedTypeTest extends BaseTypeTest { @@ -78,6 +79,41 @@ public function testSetRequired() $this->assertFalse($form['second']->isRequired()); } + public function testMappedOverridesDefault() + { + $form = $this->factory->create(NotMappedType::class); + $this->assertFalse($form->getConfig()->getMapped()); + + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'type' => NotMappedType::class, + ]); + + $this->assertTrue($form['first']->getConfig()->getMapped()); + $this->assertTrue($form['second']->getConfig()->getMapped()); + } + + /** + * @dataProvider notMappedConfigurationKeys + */ + public function testNotMappedInnerIsOverridden($configurationKey) + { + $form = $this->factory->create(static::TESTED_TYPE, null, [ + 'type' => TextTypeTest::TESTED_TYPE, + $configurationKey => ['mapped' => false], + ]); + + $this->assertTrue($form['first']->getConfig()->getMapped()); + $this->assertTrue($form['second']->getConfig()->getMapped()); + } + + public function notMappedConfigurationKeys() + { + return [ + ['first_options'], + ['second_options'], + ]; + } + public function testSetInvalidOptions() { $this->expectException('Symfony\Component\OptionsResolver\Exception\InvalidOptionsException'); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index 3f6294c53b3d6..782e1ed32749e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -444,8 +444,8 @@ public function testHandleGroupSequenceValidationGroups() $form = $this->getCompoundForm($object, $options); $form->submit([]); - $this->expectValidateAt(0, 'data', $object, new GroupSequence(['group1', 'group2'])); - $this->expectValidateAt(1, 'data', $object, new GroupSequence(['group1', 'group2'])); + $this->expectValidateAt(0, 'data', $object, 'group1'); + $this->expectValidateAt(1, 'data', $object, 'group2'); $this->validator->validate($form, new Form()); @@ -799,6 +799,39 @@ public function testCompositeConstraintValidatedInEachGroup() $this->assertSame('data[field2]', $context->getViolations()[1]->getPropertyPath()); } + public function testCompositeConstraintValidatedInSequence() + { + $form = $this->getCompoundForm([], [ + 'constraints' => [ + new Collection([ + 'field1' => new NotBlank([ + 'groups' => ['field1'], + ]), + 'field2' => new NotBlank([ + 'groups' => ['field2'], + ]), + ]), + ], + 'validation_groups' => new GroupSequence(['field1', 'field2']), + ]) + ->add($this->getForm('field1')) + ->add($this->getForm('field2')) + ; + + $form->submit([ + 'field1' => '', + 'field2' => '', + ]); + + $context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator()); + $this->validator->initialize($context); + $this->validator->validate($form, new Form()); + + $this->assertCount(1, $context->getViolations()); + $this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage()); + $this->assertSame('data[field1]', $context->getViolations()[0]->getPropertyPath()); + } + protected function createValidator() { return new FormValidator(); @@ -821,7 +854,7 @@ private function getForm($name = 'name', $dataClass = null, array $options = []) private function getCompoundForm($data, array $options = []) { - return $this->getBuilder('name', \get_class($data), $options) + return $this->getBuilder('name', \is_object($data) ? \get_class($data) : null, $options) ->setData($data) ->setCompound(true) ->setDataMapper(new PropertyPathMapper()) diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php index a920e3be5b3ac..0b1dab5485ecc 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php @@ -12,15 +12,19 @@ namespace Symfony\Component\Form\Tests\Extension\Validator\Type; use Symfony\Component\Form\Extension\Validator\ValidatorExtension; +use Symfony\Component\Form\Form; use Symfony\Component\Form\Forms; use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait; use Symfony\Component\Form\Tests\Extension\Core\Type\FormTypeTest; use Symfony\Component\Form\Tests\Extension\Core\Type\TextTypeTest; -use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Form\Tests\Fixtures\Author; use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; use Symfony\Component\Validator\Validation; class FormTypeValidatorExtensionTest extends BaseValidatorExtensionTest @@ -66,14 +70,71 @@ public function testGroupSequenceWithConstraintsOption() ->add('field', TextTypeTest::TESTED_TYPE, [ 'constraints' => [ new Length(['min' => 10, 'groups' => ['First']] + $allowEmptyString), - new Email(['groups' => ['Second']]), + new NotBlank(['groups' => ['Second']]), ], ]) ; $form->submit(['field' => 'wrong']); - $this->assertCount(1, $form->getErrors(true)); + $errors = $form->getErrors(true); + + $this->assertCount(1, $errors); + $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); + } + + public function testManyFieldsGroupSequenceWithConstraintsOption() + { + $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; + + $formMetadata = new ClassMetadata(Form::class); + $authorMetadata = (new ClassMetadata(Author::class)) + ->addPropertyConstraint('firstName', new NotBlank(['groups' => 'Second'])) + ; + $metadataFactory = $this->createMock(MetadataFactoryInterface::class); + $metadataFactory->expects($this->any()) + ->method('getMetadataFor') + ->willReturnCallback(static function ($classOrObject) use ($formMetadata, $authorMetadata) { + if (Author::class === $classOrObject || $classOrObject instanceof Author) { + return $authorMetadata; + } + + if (Form::class === $classOrObject || $classOrObject instanceof Form) { + return $formMetadata; + } + + return new ClassMetadata(\is_string($classOrObject) ? $classOrObject : \get_class($classOrObject)); + }) + ; + + $validator = Validation::createValidatorBuilder() + ->setMetadataFactory($metadataFactory) + ->getValidator() + ; + $form = Forms::createFormFactoryBuilder() + ->addExtension(new ValidatorExtension($validator)) + ->getFormFactory() + ->create(FormTypeTest::TESTED_TYPE, new Author(), (['validation_groups' => new GroupSequence(['First', 'Second'])])) + ->add('firstName', TextTypeTest::TESTED_TYPE) + ->add('lastName', TextTypeTest::TESTED_TYPE, [ + 'constraints' => [ + new Length(['min' => 10, 'groups' => ['First']] + $allowEmptyString), + ], + ]) + ->add('australian', TextTypeTest::TESTED_TYPE, [ + 'constraints' => [ + new NotBlank(['groups' => ['Second']]), + ], + ]) + ; + + $form->submit(['firstName' => '', 'lastName' => 'wrong_1', 'australian' => '']); + + $errors = $form->getErrors(true); + + $this->assertCount(1, $errors); + $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); + $this->assertSame('children[lastName].data', $errors[0]->getCause()->getPropertyPath()); } protected function createForm(array $options = []) diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php index 136086a5e5ba8..383b7556d51b8 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php @@ -13,6 +13,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Validator\Constraints\Form as FormConstraint; use Symfony\Component\Form\Extension\Validator\ValidatorExtension; use Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser; @@ -20,6 +22,8 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormFactoryBuilder; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Mapping\CascadingStrategy; use Symfony\Component\Validator\Mapping\ClassMetadata; @@ -49,6 +53,8 @@ public function test2Dot5ValidationApi() $this->assertCount(1, $metadata->getConstraints()); $this->assertInstanceOf(FormConstraint::class, $metadata->getConstraints()[0]); + $this->assertSame(CascadingStrategy::NONE, $metadata->cascadingStrategy); + $this->assertSame(TraversalStrategy::IMPLICIT, $metadata->traversalStrategy); $this->assertSame(CascadingStrategy::CASCADE, $metadata->getPropertyMetadata('children')[0]->cascadingStrategy); $this->assertSame(TraversalStrategy::IMPLICIT, $metadata->getPropertyMetadata('children')[0]->traversalStrategy); } @@ -86,7 +92,57 @@ public function testFieldConstraintsInvalidateFormIfFieldIsSubmitted() $this->assertFalse($form->get('baz')->isValid()); } - private function createForm($type) + public function testFieldsValidateInSequence() + { + $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; + + $form = $this->createForm(FormType::class, null, [ + 'validation_groups' => new GroupSequence(['group1', 'group2']), + ]) + ->add('foo', TextType::class, [ + 'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)], + ]) + ->add('bar', TextType::class, [ + 'constraints' => [new NotBlank(['groups' => ['group2']])], + ]) + ; + + $form->submit(['foo' => 'invalid', 'bar' => null]); + + $errors = $form->getErrors(true); + + $this->assertCount(1, $errors); + $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); + } + + public function testFieldsValidateInSequenceWithNestedGroupsArray() + { + $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; + + $form = $this->createForm(FormType::class, null, [ + 'validation_groups' => new GroupSequence([['group1', 'group2'], 'group3']), + ]) + ->add('foo', TextType::class, [ + 'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)], + ]) + ->add('bar', TextType::class, [ + 'constraints' => [new Length(['min' => 10, 'groups' => ['group2']] + $allowEmptyString)], + ]) + ->add('baz', TextType::class, [ + 'constraints' => [new NotBlank(['groups' => ['group3']])], + ]) + ; + + $form->submit(['foo' => 'invalid', 'bar' => 'invalid', 'baz' => null]); + + $errors = $form->getErrors(true); + + $this->assertCount(2, $errors); + $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); + $this->assertInstanceOf(Length::class, $errors[1]->getCause()->getConstraint()); + } + + private function createForm($type, $data = null, array $options = []) { $validator = Validation::createValidatorBuilder() ->setMetadataFactory(new LazyLoadingMetadataFactory(new StaticMethodLoader())) @@ -95,7 +151,7 @@ private function createForm($type) $formFactoryBuilder->addExtension(new ValidatorExtension($validator)); $formFactory = $formFactoryBuilder->getFormFactory(); - return $formFactory->create($type); + return $formFactory->create($type, $data, $options); } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/NotMappedType.php b/src/Symfony/Component/Form/Tests/Fixtures/NotMappedType.php new file mode 100644 index 0000000000000..14c340b8917af --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Fixtures/NotMappedType.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Fixtures; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class NotMappedType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('mapped', false); + } +} diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index ddc7f9d9f351c..2fcba7902ebef 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -141,12 +141,12 @@ public function request(string $method, string $url, array $options = []): Respo CURLOPT_CERTINFO => $options['capture_peer_cert_chain'], ]; - if (1.0 === (float) $options['http_version']) { + if (\defined('CURL_VERSION_HTTP2') && (CURL_VERSION_HTTP2 & self::$curlVersion['features']) && ('https:' === $scheme || 2.0 === (float) $options['http_version'])) { + $curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; + } elseif (1.0 === (float) $options['http_version']) { $curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; - } elseif (1.1 === (float) $options['http_version'] || 'https:' !== $scheme) { + } elseif (1.1 === (float) $options['http_version']) { $curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; - } elseif (\defined('CURL_VERSION_HTTP2') && CURL_VERSION_HTTP2 & self::$curlVersion['features']) { - $curlopts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; } if (isset($options['auth_ntlm'])) { diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index e150e56a0c9c0..51856da9bfef4 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -343,11 +343,11 @@ private static function jsonEncode($value, int $flags = null, int $maxDepth = 51 try { $value = json_encode($value, $flags | (\PHP_VERSION_ID >= 70300 ? JSON_THROW_ON_ERROR : 0), $maxDepth); } catch (\JsonException $e) { - throw new InvalidArgumentException(sprintf('Invalid value for "json" option: %s.', $e->getMessage())); + throw new InvalidArgumentException('Invalid value for "json" option: '.$e->getMessage()); } if (\PHP_VERSION_ID < 70300 && JSON_ERROR_NONE !== json_last_error() && (false === $value || !($flags & JSON_PARTIAL_OUTPUT_ON_ERROR))) { - throw new InvalidArgumentException(sprintf('Invalid value for "json" option: %s.', json_last_error_msg())); + throw new InvalidArgumentException('Invalid value for "json" option: '.json_last_error_msg()); } return $value; diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php index f49ca68338408..de4d7598b923e 100644 --- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php @@ -153,11 +153,11 @@ public function toArray(bool $throw = true): array try { $content = json_decode($content, true, 512, JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? JSON_THROW_ON_ERROR : 0)); } catch (\JsonException $e) { - throw new JsonException(sprintf('%s for "%s".', $e->getMessage(), $this->getInfo('url')), $e->getCode()); + throw new JsonException(sprintf($e->getMessage().' for "%s".', $this->getInfo('url')), $e->getCode()); } if (\PHP_VERSION_ID < 70300 && JSON_ERROR_NONE !== json_last_error()) { - throw new JsonException(sprintf('%s for "%s".', json_last_error_msg(), $this->getInfo('url')), json_last_error()); + throw new JsonException(sprintf(json_last_error_msg().' for "%s".', $this->getInfo('url')), json_last_error()); } if (!\is_array($content)) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php index a196833cbf5d3..c4fa1fae206fa 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -71,6 +71,15 @@ public function validateId($sessionId) $this->prefetchData = $this->read($sessionId); $this->prefetchId = $sessionId; + if (\PHP_VERSION_ID < 70317 || (70400 <= \PHP_VERSION_ID && \PHP_VERSION_ID < 70405)) { + // work around https://bugs.php.net/79413 + foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) { + if (!isset($frame['class']) && isset($frame['function']) && \in_array($frame['function'], ['session_regenerate_id', 'session_create_id'], true)) { + return '' === $this->prefetchData; + } + } + } + return '' !== $this->prefetchData; } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index 54933d953af3a..b6cce8165edb8 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -215,8 +215,10 @@ public function regenerate($destroy = false, $lifetime = null) return false; } - if (null !== $lifetime) { + if (null !== $lifetime && $lifetime != ini_get('session.cookie_lifetime')) { + $this->save(); ini_set('session.cookie_lifetime', $lifetime); + $this->start(); } if ($destroy) { @@ -225,10 +227,6 @@ public function regenerate($destroy = false, $lifetime = null) $isRegenerated = session_regenerate_id($destroy); - // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it. - // @see https://bugs.php.net/70013 - $this->loadSession(); - if (null !== $this->emulateSameSite) { $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id()); if (null !== $originalCookie) { diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected index baa5f2f6f5cb0..d825f44f7cb86 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected @@ -11,6 +11,7 @@ validateId read doRead: abc|i:123; read +doRead: abc|i:123; write doWrite: abc|i:123; diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php index ace6249394365..4cb0b2dab3885 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php @@ -120,6 +120,19 @@ public function testRegenerateDestroy() $this->assertEquals(11, $storage->getBag('attributes')->get('legs')); } + public function testRegenerateWithCustomLifetime() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $lifetime = 999999; + $storage->getBag('attributes')->set('legs', 11); + $storage->regenerate(false, $lifetime); + $this->assertNotEquals($id, $storage->getId()); + $this->assertEquals(11, $storage->getBag('attributes')->get('legs')); + $this->assertEquals($lifetime, ini_get('session.cookie_lifetime')); + } + public function testSessionGlobalIsUpToDateAfterIdRegeneration() { $storage = $this->getStorage(); diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php index feb69e54ac9fb..b3e21e5f26c21 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php @@ -64,7 +64,7 @@ public function getController(Request $request) } if (!\is_callable($controller)) { - throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s.', $request->getPathInfo(), $this->getControllerError($controller))); + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '.$this->getControllerError($controller), $request->getPathInfo())); } return $controller; @@ -72,7 +72,7 @@ public function getController(Request $request) if (\is_object($controller)) { if (!\is_callable($controller)) { - throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s.', $request->getPathInfo(), $this->getControllerError($controller))); + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '.$this->getControllerError($controller), $request->getPathInfo())); } return $controller; @@ -85,11 +85,11 @@ public function getController(Request $request) try { $callable = $this->createController($controller); } catch (\InvalidArgumentException $e) { - throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s.', $request->getPathInfo(), $e->getMessage()), 0, $e); + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '.$e->getMessage(), $request->getPathInfo()), 0, $e); } if (!\is_callable($callable)) { - throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s.', $request->getPathInfo(), $this->getControllerError($callable))); + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: '.$this->getControllerError($callable), $request->getPathInfo())); } return $callable; diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php index 9314e432e150f..3bce904ccd2d2 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php @@ -179,7 +179,7 @@ private function sanitizeLogs(array $logs) continue; } - $message = $log['message']; + $message = '_'.$log['message']; $exception = $log['context']['exception']; if ($exception instanceof SilencedErrorContext) { diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index dcb54ea891c4a..0e672a299dd41 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleEvent; use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Debug\ErrorHandler as LegacyErrorHandler; use Symfony\Component\Debug\Exception\FatalThrowableError; use Symfony\Component\ErrorHandler\ErrorHandler; use Symfony\Component\EventDispatcher\Event; @@ -79,7 +80,7 @@ public function configure(Event $event = null) restore_exception_handler(); if ($this->logger || null !== $this->throwAt) { - if ($handler instanceof ErrorHandler) { + if ($handler instanceof ErrorHandler || $handler instanceof LegacyErrorHandler) { if ($this->logger) { $handler->setDefaultLogger($this->logger, $this->levels); if (\is_array($this->levels)) { @@ -138,7 +139,7 @@ public function configure(Event $event = null) } } if ($this->exceptionHandler) { - if ($handler instanceof ErrorHandler) { + if ($handler instanceof ErrorHandler || $handler instanceof LegacyErrorHandler) { $handler->setExceptionHandler($this->exceptionHandler); } $this->exceptionHandler = null; diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 89198bd7cffcc..9cb3a034d6961 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,11 +76,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '4.4.7'; - const VERSION_ID = 40407; + const VERSION = '4.4.8'; + const VERSION_ID = 40408; const MAJOR_VERSION = 4; const MINOR_VERSION = 4; - const RELEASE_VERSION = 7; + const RELEASE_VERSION = 8; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '11/2022'; diff --git a/src/Symfony/Component/HttpKernel/Log/Logger.php b/src/Symfony/Component/HttpKernel/Log/Logger.php index 6ea3b464f0d5e..e3badc3676e62 100644 --- a/src/Symfony/Component/HttpKernel/Log/Logger.php +++ b/src/Symfony/Component/HttpKernel/Log/Logger.php @@ -79,7 +79,7 @@ public function log($level, $message, array $context = []) } $formatter = $this->formatter; - fwrite($this->handle, $formatter($level, $message, $context)); + @fwrite($this->handle, $formatter($level, $message, $context)); } private function format(string $level, string $message, array $context): string diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php index 014b21e83aff3..d5ac4ad5c2609 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php @@ -173,17 +173,17 @@ public function getUndefinedControllers() ['foo', \Error::class, 'Class \'foo\' not found'], ['oof::bar', \Error::class, 'Class \'oof\' not found'], [['oof', 'bar'], \Error::class, 'Class \'oof\' not found'], - ['Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::staticsAction', \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Expected method "staticsAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest", did you mean "staticAction"?'], - ['Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::privateAction', \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Method "privateAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'], - ['Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::protectedAction', \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Method "protectedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'], - ['Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::undefinedAction', \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Expected method "undefinedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest". Available methods: "publicAction", "staticAction"'], - ['Symfony\Component\HttpKernel\Tests\Controller\ControllerTest', \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Controller class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" cannot be called without a method name. You need to implement "__invoke" or use one of the available methods: "publicAction", "staticAction".'], - [[$controller, 'staticsAction'], \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Expected method "staticsAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest", did you mean "staticAction"?'], - [[$controller, 'privateAction'], \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Method "privateAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'], - [[$controller, 'protectedAction'], \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Method "protectedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'], - [[$controller, 'undefinedAction'], \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Expected method "undefinedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest". Available methods: "publicAction", "staticAction"'], - [$controller, \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Controller class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" cannot be called without a method name. You need to implement "__invoke" or use one of the available methods: "publicAction", "staticAction".'], - [['a' => 'foo', 'b' => 'bar'], \InvalidArgumentException::class, 'The controller for URI "/" is not callable. Invalid array callable, expected [controller, method].'], + ['Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::staticsAction', \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Expected method "staticsAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest", did you mean "staticAction"?'], + ['Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::privateAction', \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Method "privateAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'], + ['Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::protectedAction', \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Method "protectedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'], + ['Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::undefinedAction', \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Expected method "undefinedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest". Available methods: "publicAction", "staticAction"'], + ['Symfony\Component\HttpKernel\Tests\Controller\ControllerTest', \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Controller class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" cannot be called without a method name. You need to implement "__invoke" or use one of the available methods: "publicAction", "staticAction".'], + [[$controller, 'staticsAction'], \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Expected method "staticsAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest", did you mean "staticAction"?'], + [[$controller, 'privateAction'], \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Method "privateAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'], + [[$controller, 'protectedAction'], \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Method "protectedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'], + [[$controller, 'undefinedAction'], \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Expected method "undefinedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest". Available methods: "publicAction", "staticAction"'], + [$controller, \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Controller class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" cannot be called without a method name. You need to implement "__invoke" or use one of the available methods: "publicAction", "staticAction".'], + [['a' => 'foo', 'b' => 'bar'], \InvalidArgumentException::class, 'The controller for URI "/" is not callable: Invalid array callable, expected [controller, method].'], ]; } diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php index adfba5d4220ee..9c175397fced7 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php @@ -176,13 +176,15 @@ public function getCollectTestData() [ ['message' => 'foo3', 'context' => ['exception' => new \ErrorException('warning', 0, E_USER_WARNING)], 'priority' => 100, 'priorityName' => 'DEBUG'], ['message' => 'foo3', 'context' => ['exception' => new SilencedErrorContext(E_USER_WARNING, __FILE__, __LINE__)], 'priority' => 100, 'priorityName' => 'DEBUG'], + ['message' => '0', 'context' => ['exception' => new SilencedErrorContext(E_USER_WARNING, __FILE__, __LINE__)], 'priority' => 100, 'priorityName' => 'DEBUG'], ], [ ['message' => 'foo3', 'context' => ['exception' => ['warning', E_USER_WARNING]], 'priority' => 100, 'priorityName' => 'DEBUG'], ['message' => 'foo3', 'context' => ['exception' => [E_USER_WARNING]], 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => true], + ['message' => '0', 'context' => ['exception' => [E_USER_WARNING]], 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => true], ], 0, - 1, + 2, ]; } } diff --git a/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php b/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php index a56fd6af1c233..555dd377c9341 100644 --- a/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php +++ b/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php @@ -46,7 +46,7 @@ public function read($path, $locale) $data = json_decode(file_get_contents($fileName), true); if (null === $data) { - throw new RuntimeException(sprintf('The resource bundle "%s" contains invalid JSON: %s.', $fileName, json_last_error_msg())); + throw new RuntimeException(sprintf('The resource bundle "%s" contains invalid JSON: '.json_last_error_msg(), $fileName)); } return $data; diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php index 573ab0ce99475..5531a7c435321 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php @@ -53,7 +53,7 @@ public function count() foreach ($searches as $search) { $searchCount = ldap_count_entries($con, $search); if (false === $searchCount) { - throw new LdapException(sprintf('Error while retrieving entry count: %s.', ldap_error($con))); + throw new LdapException('Error while retrieving entry count: '.ldap_error($con)); } $count += $searchCount; } @@ -76,7 +76,7 @@ public function getIterator() $current = ldap_first_entry($con, $search); if (false === $current) { - throw new LdapException(sprintf('Could not rewind entries array: %s.', ldap_error($con))); + throw new LdapException('Could not rewind entries array: '.ldap_error($con)); } yield $this->getSingleEntry($con, $current); @@ -123,7 +123,7 @@ private function getSingleEntry($con, $current): Entry $attributes = ldap_get_attributes($con, $current); if (false === $attributes) { - throw new LdapException(sprintf('Could not fetch attributes: %s.', ldap_error($con))); + throw new LdapException('Could not fetch attributes: '.ldap_error($con)); } $attributes = $this->cleanupAttributes($attributes); @@ -131,7 +131,7 @@ private function getSingleEntry($con, $current): Entry $dn = ldap_get_dn($con, $current); if (false === $dn) { - throw new LdapException(sprintf('Could not fetch DN: %s.', ldap_error($con))); + throw new LdapException('Could not fetch DN: '.ldap_error($con)); } return new Entry($dn, $attributes); diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php index 53fc5a86025d4..b25a6eb447c7e 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php @@ -140,11 +140,11 @@ private function connect() } if (false === $this->connection) { - throw new LdapException(sprintf('Could not connect to Ldap server: %s.', ldap_error($this->connection))); + throw new LdapException('Could not connect to Ldap server: '.ldap_error($this->connection)); } if ('tls' === $this->config['encryption'] && false === @ldap_start_tls($this->connection)) { - throw new LdapException(sprintf('Could not initiate TLS connection: %s.', ldap_error($this->connection))); + throw new LdapException('Could not initiate TLS connection: '.ldap_error($this->connection)); } } diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php index 20055c2f3b24f..c08e2c1550303 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php @@ -38,7 +38,7 @@ public function add(Entry $entry) $con = $this->getConnectionResource(); if (!@ldap_add($con, $entry->getDn(), $entry->getAttributes())) { - throw new LdapException(sprintf('Could not add entry "%s": %s.', $entry->getDn(), ldap_error($con))); + throw new LdapException(sprintf('Could not add entry "%s": '.ldap_error($con), $entry->getDn())); } return $this; @@ -52,7 +52,7 @@ public function update(Entry $entry) $con = $this->getConnectionResource(); if (!@ldap_modify($con, $entry->getDn(), $entry->getAttributes())) { - throw new LdapException(sprintf('Could not update entry "%s": %s.', $entry->getDn(), ldap_error($con))); + throw new LdapException(sprintf('Could not update entry "%s": '.ldap_error($con), $entry->getDn())); } } @@ -64,7 +64,7 @@ public function remove(Entry $entry) $con = $this->getConnectionResource(); if (!@ldap_delete($con, $entry->getDn())) { - throw new LdapException(sprintf('Could not remove entry "%s": %s.', $entry->getDn(), ldap_error($con))); + throw new LdapException(sprintf('Could not remove entry "%s": '.ldap_error($con), $entry->getDn())); } } @@ -79,7 +79,7 @@ public function addAttributeValues(Entry $entry, string $attribute, array $value $con = $this->getConnectionResource(); if (!@ldap_mod_add($con, $entry->getDn(), [$attribute => $values])) { - throw new LdapException(sprintf('Could not add values to entry "%s", attribute %s: %s.', $entry->getDn(), $attribute, ldap_error($con))); + throw new LdapException(sprintf('Could not add values to entry "%s", attribute %s: '.ldap_error($con), $entry->getDn(), $attribute)); } } @@ -94,7 +94,7 @@ public function removeAttributeValues(Entry $entry, string $attribute, array $va $con = $this->getConnectionResource(); if (!@ldap_mod_del($con, $entry->getDn(), [$attribute => $values])) { - throw new LdapException(sprintf('Could not remove values from entry "%s", attribute %s: %s.', $entry->getDn(), $attribute, ldap_error($con))); + throw new LdapException(sprintf('Could not remove values from entry "%s", attribute %s: '.ldap_error($con), $entry->getDn(), $attribute)); } } @@ -106,7 +106,7 @@ public function rename(Entry $entry, $newRdn, $removeOldRdn = true) $con = $this->getConnectionResource(); if (!@ldap_rename($con, $entry->getDn(), $newRdn, null, $removeOldRdn)) { - throw new LdapException(sprintf('Could not rename entry "%s" to "%s": %s.', $entry->getDn(), $newRdn, ldap_error($con))); + throw new LdapException(sprintf('Could not rename entry "%s" to "%s": '.ldap_error($con), $entry->getDn(), $newRdn)); } } @@ -122,7 +122,7 @@ public function move(Entry $entry, string $newParent) $rdn = $this->parseRdnFromEntry($entry); // deleteOldRdn does not matter here, since the Rdn will not be changing in the move. if (!@ldap_rename($con, $entry->getDn(), $rdn, $newParent, true)) { - throw new LdapException(sprintf('Could not move entry "%s" to "%s": %s.', $entry->getDn(), $newParent, ldap_error($con))); + throw new LdapException(sprintf('Could not move entry "%s" to "%s": '.ldap_error($con), $entry->getDn(), $newParent)); } } diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php index 533d0e617618b..af6edfcfcc9fc 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php @@ -49,7 +49,7 @@ public function __destruct() continue; } if (!ldap_free_result($result)) { - throw new LdapException(sprintf('Could not free results: %s.', ldap_error($con))); + throw new LdapException('Could not free results: '.ldap_error($con)); } } $this->results = null; diff --git a/src/Symfony/Component/Lock/composer.json b/src/Symfony/Component/Lock/composer.json index 8e82285b40f1e..d604d2bd6837d 100644 --- a/src/Symfony/Component/Lock/composer.json +++ b/src/Symfony/Component/Lock/composer.json @@ -21,7 +21,6 @@ }, "require-dev": { "doctrine/dbal": "~2.5", - "mongodb/mongodb": "~1.1", "predis/predis": "~1.0" }, "conflict": { diff --git a/src/Symfony/Component/Lock/phpunit.xml.dist b/src/Symfony/Component/Lock/phpunit.xml.dist index 96c3ea1903abe..4a066573f7d08 100644 --- a/src/Symfony/Component/Lock/phpunit.xml.dist +++ b/src/Symfony/Component/Lock/phpunit.xml.dist @@ -12,7 +12,6 @@ - diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php index c9cd0ad8cc6f6..95cbdbfd987b7 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php @@ -65,7 +65,7 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e $result = new \SimpleXMLElement($response->getContent(false)); if (200 !== $response->getStatusCode()) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result->Error->Message, $result->Error->Code), $response); + throw new HttpTransportException(sprintf('Unable to send an email: '.$result->Error->Message.' (code %d).', $result->Error->Code), $response); } $property = $payload['Action'].'Result'; diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php index 9cedbdf4c7f7c..f11e970f23cf5 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php @@ -65,7 +65,7 @@ protected function doSendHttp(SentMessage $message): ResponseInterface $result = new \SimpleXMLElement($response->getContent(false)); if (200 !== $response->getStatusCode()) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result->Error->Message, $result->Error->Code), $response); + throw new HttpTransportException(sprintf('Unable to send an email: '.$result->Error->Message.' (code %d).', $result->Error->Code), $response); } $message->setMessageId($result->SendRawEmailResult->MessageId); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php index 9b7c181b2943d..6ea7eae4d1eed 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php @@ -51,7 +51,7 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('error' === ($result['status'] ?? false)) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result['message'], $result['code']), $response); + throw new HttpTransportException(sprintf('Unable to send an email: '.$result['message'].' (code %d).', $result['code']), $response); } throw new HttpTransportException(sprintf('Unable to send an email (code %d).', $result['code']), $response); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php index 92f90b8563fa5..d2058799ec957 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php @@ -58,7 +58,7 @@ protected function doSendHttp(SentMessage $message): ResponseInterface $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('error' === ($result['status'] ?? false)) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result['message'], $result['code']), $response); + throw new HttpTransportException(sprintf('Unable to send an email: '.$result['message'].' (code %d).', $result['code']), $response); } throw new HttpTransportException(sprintf('Unable to send an email (code %d).', $result['code']), $response); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php index c8ed383fcc66b..2b6075c723eed 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunApiTransport.php @@ -65,10 +65,10 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('application/json' === $response->getHeaders(false)['content-type'][0]) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result['message'], $response->getStatusCode()), $response); + throw new HttpTransportException(sprintf('Unable to send an email: '.$result['message'].' (code %d).', $response->getStatusCode()), $response); } - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $response->getContent(false), $response->getStatusCode()), $response); + throw new HttpTransportException(sprintf('Unable to send an email: '.$response->getContent(false).' (code %d).', $response->getStatusCode()), $response); } $sentMessage->setMessageId($result['id']); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php index 3ed6f73369fc8..72fbaac1e77e3 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Transport/MailgunHttpTransport.php @@ -67,10 +67,10 @@ protected function doSendHttp(SentMessage $message): ResponseInterface $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { if ('application/json' === $response->getHeaders(false)['content-type'][0]) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result['message'], $response->getStatusCode()), $response); + throw new HttpTransportException(sprintf('Unable to send an email: '.$result['message'].' (code %d).', $response->getStatusCode()), $response); } - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $response->getContent(false), $response->getStatusCode()), $response); + throw new HttpTransportException(sprintf('Unable to send an email: '.$response->getContent(false).' (code %d).', $response->getStatusCode()), $response); } $message->setMessageId($result['id']); diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php index 352b2eded1568..a7c185264097f 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Transport/PostmarkApiTransport.php @@ -54,7 +54,7 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e $result = $response->toArray(false); if (200 !== $response->getStatusCode()) { - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', $result['Message'], $result['ErrorCode']), $response); + throw new HttpTransportException(sprintf('Unable to send an email: '.$result['Message'].' (code %d).', $result['ErrorCode']), $response); } $sentMessage->setMessageId($result['MessageID']); diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php index 550fd4bf331a5..aee333ddb16cf 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php @@ -53,7 +53,7 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e if (202 !== $response->getStatusCode()) { $errors = $response->toArray(false); - throw new HttpTransportException(sprintf('Unable to send an email: %s (code %d).', implode('; ', array_column($errors['errors'], 'message')), $response->getStatusCode()), $response); + throw new HttpTransportException(sprintf('Unable to send an email: '.implode('; ', array_column($errors['errors'], 'message')).' (code %d).', $response->getStatusCode()), $response); } $sentMessage->setMessageId($response->getHeaders(false)['x-message-id'][0]); diff --git a/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php b/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php index cbb3922a19a46..570a9e39551f5 100644 --- a/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php +++ b/src/Symfony/Component/Mailer/EventListener/EnvelopeListener.php @@ -14,6 +14,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Mailer\Event\MessageEvent; use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Message; /** * Manipulates the Envelope of a Message. @@ -43,6 +44,13 @@ public function onMessage(MessageEvent $event): void { if ($this->sender) { $event->getEnvelope()->setSender($this->sender); + + $message = $event->getMessage(); + if ($message instanceof Message) { + if (!$message->getHeaders()->has('Sender') && !$message->getHeaders()->has('From')) { + $message->getHeaders()->addMailboxHeader('Sender', $this->sender->getAddress()); + } + } } if ($this->recipients) { diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php index 7ec8313612049..2c8d91e9e4402 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Exception\TransportException; use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport; use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream; use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; @@ -43,6 +44,29 @@ public function testSendDoesNotPingBelowThreshold(): void $this->assertNotContains("NOOP\r\n", $stream->getCommands()); } + public function testSendPingAfterTransportException(): void + { + $stream = new DummyStream(); + $envelope = new Envelope(new Address('sender@example.org'), [new Address('recipient@example.org')]); + + $transport = new SmtpTransport($stream); + $transport->send(new RawMessage('Message 1'), $envelope); + $stream->close(); + $catch = false; + + try { + $transport->send(new RawMessage('Message 2'), $envelope); + } catch (TransportException $exception) { + $catch = true; + } + $this->assertTrue($catch); + $this->assertTrue($stream->isClosed()); + + $transport->send(new RawMessage('Message 3'), $envelope); + + $this->assertFalse($stream->isClosed()); + } + public function testSendDoesPingAboveThreshold(): void { $stream = new DummyStream(); @@ -76,13 +100,23 @@ class DummyStream extends AbstractStream */ private $commands; + /** + * @var bool + */ + private $closed = true; + public function initialize(): void { + $this->closed = false; $this->nextResponse = '220 localhost'; } public function write(string $bytes, $debug = true): void { + if ($this->closed) { + throw new TransportException('Unable to write bytes on the wire.'); + } + $this->commands[] = $bytes; if (0 === strpos($bytes, 'DATA')) { @@ -120,4 +154,14 @@ protected function getReadConnectionDescription(): string { return 'null'; } + + public function close(): void + { + $this->closed = true; + } + + public function isClosed(): bool + { + return $this->closed; + } } diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/Stream/SocketStreamTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/Stream/SocketStreamTest.php index d7912f9ccba2d..a67a1629d0169 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/Stream/SocketStreamTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/Stream/SocketStreamTest.php @@ -19,7 +19,7 @@ class SocketStreamTest extends TestCase public function testSocketErrorNoConnection() { $this->expectException('Symfony\Component\Mailer\Exception\TransportException'); - $this->expectExceptionMessageRegExp('/Connection refused|unable to connect/'); + $this->expectExceptionMessageMatches('/Connection refused|unable to connect/'); $s = new SocketStream(); $s->setTimeout(0.1); $s->setPort(9999); @@ -29,7 +29,7 @@ public function testSocketErrorNoConnection() public function testSocketErrorBeforeConnectError() { $this->expectException('Symfony\Component\Mailer\Exception\TransportException'); - $this->expectExceptionMessageRegExp('/no valid certs found cafile stream|Unable to find the socket transport "ssl"/'); + $this->expectExceptionMessageMatches('/no valid certs found cafile stream|Unable to find the socket transport "ssl"/'); $s = new SocketStream(); $s->setStreamOptions([ 'ssl' => [ diff --git a/src/Symfony/Component/Mailer/Transport/AbstractApiTransport.php b/src/Symfony/Component/Mailer/Transport/AbstractApiTransport.php index e1a2d5eee2ac8..37b92a699bbc6 100644 --- a/src/Symfony/Component/Mailer/Transport/AbstractApiTransport.php +++ b/src/Symfony/Component/Mailer/Transport/AbstractApiTransport.php @@ -31,7 +31,7 @@ protected function doSendHttp(SentMessage $message): ResponseInterface try { $email = MessageConverter::toEmail($message->getOriginalMessage()); } catch (\Exception $e) { - throw new RuntimeException(sprintf('Unable to send message with the "%s" transport: %s.', __CLASS__, $e->getMessage()), 0, $e); + throw new RuntimeException(sprintf('Unable to send message with the "%s" transport: '.$e->getMessage(), __CLASS__), 0, $e); } return $this->doSendApi($message, $email, $message->getEnvelope()); diff --git a/src/Symfony/Component/Mailer/Transport/AbstractTransport.php b/src/Symfony/Component/Mailer/Transport/AbstractTransport.php index 1bc3fa12a5616..33f75fb3faa4f 100644 --- a/src/Symfony/Component/Mailer/Transport/AbstractTransport.php +++ b/src/Symfony/Component/Mailer/Transport/AbstractTransport.php @@ -63,10 +63,6 @@ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessa $envelope = $event->getEnvelope(); } - if (!$envelope->getRecipients()) { - return null; - } - $message = new SentMessage($message, $envelope); $this->doSend($message); diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php index 3f68227ba4190..afa522ae97e13 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransport.php @@ -106,6 +106,9 @@ protected function doHeloCommand(): void /** @var SocketStream $stream */ $stream = $this->getStream(); + // WARNING: !$stream->isTLS() is right, 100% sure :) + // if you think that the ! should be removed, read the code again + // if doing so "fixes" your issue then it probably means your SMTP server behaves incorrectly or is wrongly configured if (!$stream->isTLS() && \defined('OPENSSL_VERSION_NUMBER') && \array_key_exists('STARTTLS', $capabilities)) { $this->executeCommand("STARTTLS\r\n", [220]); diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php index 091b5e2bc5a60..26f2057f941ae 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php @@ -206,12 +206,11 @@ protected function doSend(SentMessage $message): void $this->stream->flush(); $this->executeCommand("\r\n.\r\n", [250]); $message->appendDebug($this->stream->getDebug()); + $this->lastMessageTime = microtime(true); } catch (TransportExceptionInterface $e) { $e->appendDebug($this->stream->getDebug()); - + $this->lastMessageTime = 0; throw $e; - } finally { - $this->lastMessageTime = microtime(true); } } diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/ProcessStream.php b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/ProcessStream.php index 455f739a15faa..a8a8603807d27 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/ProcessStream.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/ProcessStream.php @@ -41,7 +41,7 @@ public function initialize(): void $this->stream = proc_open($this->command, $descriptorSpec, $pipes); stream_set_blocking($pipes[2], false); if ($err = stream_get_contents($pipes[2])) { - throw new TransportException(sprintf('Process could not be started: %s.', $err)); + throw new TransportException('Process could not be started: '.$err); } $this->in = &$pipes[0]; $this->out = &$pipes[1]; diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php index debeeb4b01cb9..5aa86296cbc03 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php @@ -135,7 +135,7 @@ public function initialize(): void $streamContext = stream_context_create($options); set_error_handler(function ($type, $msg) { - throw new TransportException(sprintf('Connection could not be established with host "%s": %s.', $this->url, $msg)); + throw new TransportException(sprintf('Connection could not be established with host "%s": '.$msg, $this->url)); }); try { $this->stream = stream_socket_client($this->url, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $streamContext); diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Receiver/SingleMessageReceiverTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Receiver/SingleMessageReceiverTest.php index e8f5ef4ce1720..bfebfc5aa3f02 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Receiver/SingleMessageReceiverTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Receiver/SingleMessageReceiverTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Messenger\Tests\Transport\Sender; +namespace Symfony\Component\Messenger\Tests\Transport\Receiver; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php index 0be034dd3de3d..e278cfb0ddf0e 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php @@ -62,6 +62,14 @@ public function testFromDsnWithOptions() ); } + public function testFromDsnWithOptionsAndTrailingSlash() + { + $this->assertEquals( + Connection::fromDsn('redis://localhost/', ['stream' => 'queue', 'group' => 'group1', 'consumer' => 'consumer1', 'auto_setup' => false, 'serializer' => 2]), + Connection::fromDsn('redis://localhost/queue/group1/consumer1?serializer=2&auto_setup=0') + ); + } + public function testFromDsnWithQueryOptions() { $this->assertEquals( diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php index 6439873fe94cc..5076577b023fc 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/PhpSerializerTest.php @@ -44,7 +44,7 @@ public function testDecodingFailsWithMissingBodyKey() public function testDecodingFailsWithBadFormat() { $this->expectException(MessageDecodingFailedException::class); - $this->expectExceptionMessageRegExp('/Could not decode/'); + $this->expectExceptionMessageMatches('/Could not decode/'); $serializer = new PhpSerializer(); @@ -56,7 +56,7 @@ public function testDecodingFailsWithBadFormat() public function testDecodingFailsWithBadClass() { $this->expectException(MessageDecodingFailedException::class); - $this->expectExceptionMessageRegExp('/class "ReceivedSt0mp" not found/'); + $this->expectExceptionMessageMatches('/class "ReceivedSt0mp" not found/'); $serializer = new PhpSerializer(); diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportFactoryTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportFactoryTest.php index 021c7ae9706a4..63aa43fb7ca0f 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportFactoryTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportFactoryTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Messenger\Tests\Transport\AmqpExt; +namespace Symfony\Component\Messenger\Tests\Transport\Sync; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\MessageBusInterface; diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportTest.php index 13549e27582a1..8ea4eb2e00522 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Sync/SyncTransportTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Messenger\Tests\Transport\AmqpExt; +namespace Symfony\Component\Messenger\Tests\Transport\Sync; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php index 504fe0a10fa98..bb818512c2fda 100644 --- a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php @@ -56,11 +56,17 @@ public function __construct(array $configuration, array $connectionCredentials = $this->connection->setOption(\Redis::OPT_SERIALIZER, $redisOptions['serializer'] ?? \Redis::SERIALIZER_PHP); if (isset($connectionCredentials['auth']) && !$this->connection->auth($connectionCredentials['auth'])) { - throw new InvalidArgumentException(sprintf('Redis connection failed: %s.', $redis->getLastError())); + throw new InvalidArgumentException('Redis connection failed: '.$redis->getLastError()); } if (($dbIndex = $configuration['dbindex'] ?? self::DEFAULT_OPTIONS['dbindex']) && !$this->connection->select($dbIndex)) { - throw new InvalidArgumentException(sprintf('Redis connection failed: %s.', $redis->getLastError())); + throw new InvalidArgumentException('Redis connection failed: '.$redis->getLastError()); + } + + foreach (['stream', 'group', 'consumer'] as $key) { + if (isset($configuration[$key]) && '' === $configuration[$key]) { + throw new InvalidArgumentException(sprintf('"%s" should be configured, got an empty string.', $key)); + } } $this->stream = $configuration['stream'] ?? self::DEFAULT_OPTIONS['stream']; @@ -77,7 +83,7 @@ public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $re throw new InvalidArgumentException(sprintf('The given Redis DSN "%s" is invalid.', $dsn)); } - $pathParts = explode('/', $parsedUrl['path'] ?? ''); + $pathParts = explode('/', rtrim($parsedUrl['path'] ?? '', '/')); $stream = $pathParts[1] ?? $redisOptions['stream'] ?? null; $group = $pathParts[2] ?? $redisOptions['group'] ?? null; diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php index 8d466500fd9a6..22d48f7e23012 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php @@ -80,7 +80,7 @@ public function decode(array $encodedEnvelope): Envelope try { $message = $this->serializer->deserialize($encodedEnvelope['body'], $encodedEnvelope['headers']['type'], $this->format, $context); } catch (ExceptionInterface $e) { - throw new MessageDecodingFailedException(sprintf('Could not decode message: %s.', $e->getMessage()), $e->getCode(), $e); + throw new MessageDecodingFailedException('Could not decode message: '.$e->getMessage(), $e->getCode(), $e); } return new Envelope($message, $stamps); @@ -118,7 +118,7 @@ private function decodeStamps(array $encodedEnvelope): array try { $stamps[] = $this->serializer->deserialize($value, substr($name, \strlen(self::STAMP_HEADER_PREFIX)).'[]', $this->format, $this->context); } catch (ExceptionInterface $e) { - throw new MessageDecodingFailedException(sprintf('Could not decode stamp: %s.', $e->getMessage()), $e->getCode(), $e); + throw new MessageDecodingFailedException('Could not decode stamp: '.$e->getMessage(), $e->getCode(), $e); } } if ($stamps) { diff --git a/src/Symfony/Component/Mime/Message.php b/src/Symfony/Component/Mime/Message.php index 5b4e67f1dbc5b..94a49764e4912 100644 --- a/src/Symfony/Component/Mime/Message.php +++ b/src/Symfony/Component/Mime/Message.php @@ -74,7 +74,10 @@ public function getPreparedHeaders(): Headers $headers = clone $this->headers; if (!$headers->has('From')) { - throw new LogicException('An email must have a "From" header.'); + if (!$headers->has('Sender')) { + throw new LogicException('An email must have a "From" or a "Sender" header.'); + } + $headers->addMailboxListHeader('From', [$headers->get('Sender')->getAddress()]); } $headers->addTextHeader('MIME-Version', '1.0'); @@ -119,8 +122,12 @@ public function toIterable(): iterable public function ensureValidity() { - if (!$this->headers->has('From')) { - throw new LogicException('An email must have a "From" header.'); + if (!$this->headers->has('To')) { + throw new LogicException('An email must have a "To" header.'); + } + + if (!$this->headers->has('From') && !$this->headers->has('Sender')) { + throw new LogicException('An email must have a "From" or a "Sender" header.'); } parent::ensureValidity(); @@ -133,7 +140,7 @@ public function generateMessageId(): string } elseif ($this->headers->has('From')) { $sender = $this->headers->get('From')->getAddresses()[0]; } else { - throw new LogicException('An email must have a "From" or a "Sender" header to compute a Messsage ID.'); + throw new LogicException('An email must have a "From" or a "Sender" header.'); } return bin2hex(random_bytes(16)).strstr($sender->getAddress(), '@'); diff --git a/src/Symfony/Component/Mime/Tests/Crypto/SMimeSignerTest.php b/src/Symfony/Component/Mime/Tests/Crypto/SMimeSignerTest.php index 0a86c3c90e1e7..5522bc6f55cd7 100644 --- a/src/Symfony/Component/Mime/Tests/Crypto/SMimeSignerTest.php +++ b/src/Symfony/Component/Mime/Tests/Crypto/SMimeSignerTest.php @@ -99,6 +99,7 @@ public function testSignedMessageWithBcc() { $message = (new Email()) ->date(new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) + ->to('fabien@symfony.com') ->addBcc('fabien@symfony.com', 's.stok@rollerscapes.net') ->subject('I am your sign of fear') ->from('noreply@example.com') @@ -115,8 +116,9 @@ public function testSignedMessageWithAttachments() $message = new Email((new Headers()) ->addDateHeader('Date', new \DateTime('2019-04-07 10:36:30', new \DateTimeZone('Europe/Paris'))) ->addMailboxListHeader('From', ['fabien@symfony.com']) + ->addMailboxListHeader('To', ['fabien@symfony.com']) ); - $message->html($content = 'html content '); + $message->html('html content '); $message->text('text content'); $message->attach(fopen(__DIR__.'/../Fixtures/mimetypes/test', 'r')); $message->attach(fopen(__DIR__.'/../Fixtures/mimetypes/test.gif', 'r'), 'test.gif'); diff --git a/src/Symfony/Component/Mime/Tests/EmailTest.php b/src/Symfony/Component/Mime/Tests/EmailTest.php index bfd30053af09e..230df0791e15b 100644 --- a/src/Symfony/Component/Mime/Tests/EmailTest.php +++ b/src/Symfony/Component/Mime/Tests/EmailTest.php @@ -251,62 +251,62 @@ public function testGenerateBody() $att = new DataPart($file = fopen(__DIR__.'/Fixtures/mimetypes/test', 'r')); $img = new DataPart($image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r'), 'test.gif'); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->text('text content'); $this->assertEquals($text, $e->getBody()); $this->assertEquals('text content', $e->getTextBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->html('html content'); $this->assertEquals($html, $e->getBody()); $this->assertEquals('html content', $e->getHtmlBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->html('html content'); $e->text('text content'); $this->assertEquals(new AlternativePart($text, $html), $e->getBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->html('html content', 'iso-8859-1'); $e->text('text content', 'iso-8859-1'); $this->assertEquals('iso-8859-1', $e->getTextCharset()); $this->assertEquals('iso-8859-1', $e->getHtmlCharset()); $this->assertEquals(new AlternativePart(new TextPart('text content', 'iso-8859-1'), new TextPart('html content', 'iso-8859-1', 'html')), $e->getBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->attach($file); $e->text('text content'); $this->assertEquals(new MixedPart($text, $att), $e->getBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->attach($file); $e->html('html content'); $this->assertEquals(new MixedPart($html, $att), $e->getBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->attach($file); $this->assertEquals(new MixedPart($att), $e->getBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->html('html content'); $e->text('text content'); $e->attach($file); $this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att), $e->getBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->html('html content'); $e->text('text content'); $e->attach($file); $e->attach($image, 'test.gif'); $this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att, $img), $e->getBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->text('text content'); $e->attach($file); $e->attach($image, 'test.gif'); $this->assertEquals(new MixedPart($text, $att, $img), $e->getBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->html($content = 'html content '); $e->text('text content'); $e->attach($file); @@ -314,7 +314,7 @@ public function testGenerateBody() $fullhtml = new TextPart($content, 'utf-8', 'html'); $this->assertEquals(new MixedPart(new AlternativePart($text, $fullhtml), $att, $img), $e->getBody()); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->html($content = 'html content '); $e->text('text content'); $e->attach($file); @@ -334,7 +334,7 @@ public function testGenerateBody() fwrite($r, $content); rewind($r); - $e = (new Email())->from('me@example.com'); + $e = (new Email())->from('me@example.com')->to('you@example.com'); $e->html($r); // embedding the same image twice results in one image only in the email $e->embed($image, 'test.gif'); @@ -373,6 +373,7 @@ public function testSerialize() $e = new Email(); $e->from('fabien@symfony.com'); + $e->to('you@example.com'); $e->text($r); $e->html($r); $name = __DIR__.'/Fixtures/mimetypes/test'; diff --git a/src/Symfony/Component/Mime/Tests/MessageConverterTest.php b/src/Symfony/Component/Mime/Tests/MessageConverterTest.php index 6a78086246377..a0e71a08a9416 100644 --- a/src/Symfony/Component/Mime/Tests/MessageConverterTest.php +++ b/src/Symfony/Component/Mime/Tests/MessageConverterTest.php @@ -21,7 +21,7 @@ class MessageConverterTest extends TestCase public function testToEmail() { $file = file_get_contents(__DIR__.'/Fixtures/mimetypes/test.gif'); - $email = (new Email())->from('fabien@symfony.com'); + $email = (new Email())->from('fabien@symfony.com')->to('you@example.com'); $this->assertSame($email, MessageConverter::toEmail($email)); $this->assertConversion((clone $email)->text('text content')); diff --git a/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php b/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php index 21a4eb03b1292..2713d5bc079c7 100644 --- a/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php +++ b/src/Symfony/Component/Mime/Tests/Part/MessagePartTest.php @@ -22,7 +22,7 @@ class MessagePartTest extends TestCase { public function testConstructor() { - $p = new MessagePart((new Email())->from('fabien@symfony.com')->text('content')); + $p = new MessagePart((new Email())->from('fabien@symfony.com')->to('you@example.com')->text('content')); $this->assertStringContainsString('content', $p->getBody()); $this->assertStringContainsString('content', $p->bodyToString()); $this->assertStringContainsString('content', implode('', iterator_to_array($p->bodyToIterable()))); diff --git a/src/Symfony/Component/OptionsResolver/Tests/Debug/OptionsResolverIntrospectorTest.php b/src/Symfony/Component/OptionsResolver/Tests/Debug/OptionsResolverIntrospectorTest.php index d58b120457b49..433d9a8a269d6 100644 --- a/src/Symfony/Component/OptionsResolver/Tests/Debug/OptionsResolverIntrospectorTest.php +++ b/src/Symfony/Component/OptionsResolver/Tests/Debug/OptionsResolverIntrospectorTest.php @@ -44,7 +44,7 @@ public function testGetDefaultThrowsOnNoConfiguredValue() $resolver->setDefined($option = 'foo'); $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getDefault($option)); + $debug->getDefault($option); } public function testGetDefaultThrowsOnNotDefinedOption() @@ -54,7 +54,7 @@ public function testGetDefaultThrowsOnNotDefinedOption() $resolver = new OptionsResolver(); $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getDefault('foo')); + $debug->getDefault('foo'); } public function testGetLazyClosures() @@ -75,7 +75,7 @@ public function testGetLazyClosuresThrowsOnNoConfiguredValue() $resolver->setDefined($option = 'foo'); $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getLazyClosures($option)); + $debug->getLazyClosures($option); } public function testGetLazyClosuresThrowsOnNotDefinedOption() @@ -85,7 +85,7 @@ public function testGetLazyClosuresThrowsOnNotDefinedOption() $resolver = new OptionsResolver(); $debug = new OptionsResolverIntrospector($resolver); - $this->assertSame('bar', $debug->getLazyClosures('foo')); + $debug->getLazyClosures('foo'); } public function testGetAllowedTypes() diff --git a/src/Symfony/Component/Process/Pipes/UnixPipes.php b/src/Symfony/Component/Process/Pipes/UnixPipes.php index 603d726b47fc9..70fdd29574ec7 100644 --- a/src/Symfony/Component/Process/Pipes/UnixPipes.php +++ b/src/Symfony/Component/Process/Pipes/UnixPipes.php @@ -118,7 +118,7 @@ public function readAndWrite(bool $blocking, bool $close = false): array $read[$type = array_search($pipe, $this->pipes, true)] = ''; do { - $data = fread($pipe, self::CHUNK_SIZE); + $data = @fread($pipe, self::CHUNK_SIZE); $read[$type] .= $data; } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); diff --git a/src/Symfony/Component/Process/Pipes/WindowsPipes.php b/src/Symfony/Component/Process/Pipes/WindowsPipes.php index 6d9976960ad1b..c548092c51fff 100644 --- a/src/Symfony/Component/Process/Pipes/WindowsPipes.php +++ b/src/Symfony/Component/Process/Pipes/WindowsPipes.php @@ -57,7 +57,7 @@ public function __construct($input, bool $haveReadSupport) if (!$h = fopen($file.'.lock', 'w')) { restore_error_handler(); - throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s.', $lastError)); + throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError); } if (!flock($h, LOCK_EX | LOCK_NB)) { continue 2; diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index db4c2919dff59..81568a1c03409 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -1648,7 +1648,7 @@ private function replacePlaceholders(string $commandline, array $env) { return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) { if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) { - throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": %s.', $matches[1], $commandline)); + throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": '.$commandline, $matches[1])); } return $this->escapeArgument($env[$matches[1]]); diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php index ecbf271aeb8cf..d7461d082f8c1 100644 --- a/src/Symfony/Component/Process/Tests/ProcessTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessTest.php @@ -50,7 +50,7 @@ protected function tearDown(): void public function testInvalidCwd() { $this->expectException('Symfony\Component\Process\Exception\RuntimeException'); - $this->expectExceptionMessageRegExp('/The provided cwd ".*" does not exist\./'); + $this->expectExceptionMessageMatches('/The provided cwd ".*" does not exist\./'); try { // Check that it works fine if the CWD exists $cmd = new Process(['echo', 'test'], __DIR__); @@ -1488,7 +1488,7 @@ public function testPreparedCommandWithQuoteInIt() public function testPreparedCommandWithMissingValue() { $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException'); - $this->expectExceptionMessage('Command line is missing a value for parameter "abc": echo "${:abc}".'); + $this->expectExceptionMessage('Command line is missing a value for parameter "abc": echo "${:abc}"'); $p = Process::fromShellCommandline('echo "${:abc}"'); $p->run(null, ['bcd' => 'BCD']); } @@ -1496,7 +1496,7 @@ public function testPreparedCommandWithMissingValue() public function testPreparedCommandWithNoValues() { $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException'); - $this->expectExceptionMessage('Command line is missing a value for parameter "abc": echo "${:abc}".'); + $this->expectExceptionMessage('Command line is missing a value for parameter "abc": echo "${:abc}"'); $p = Process::fromShellCommandline('echo "${:abc}"'); $p->run(null, []); } diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 01f948291ad5c..cac049a0a637e 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -385,7 +385,7 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid } catch (\TypeError $e) { // handle uninitialized properties in PHP >= 7 if (preg_match((sprintf('/^Return value of %s::%s\(\) must be of the type (\w+), null returned$/', preg_quote(\get_class($object)), $access[self::ACCESS_NAME])), $e->getMessage(), $matches)) { - throw new AccessException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Have you forgotten to initialize a property or to make the return type nullable using "?%3$s" instead?', \get_class($object), $access[self::ACCESS_NAME], $matches[1]), 0, $e); + throw new AccessException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', \get_class($object), $access[self::ACCESS_NAME], $matches[1]), 0, $e); } throw $e; @@ -418,7 +418,7 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) { $r = new \ReflectionProperty($matches[1], $matches[2]); - throw new AccessException(sprintf('The property "%s::$%s" is not readable because it is typed "%3$s". You should either initialize it or make it nullable using "?%3$s" instead.', $r->getDeclaringClass()->getName(), $r->getName(), $r->getType()->getName()), 0, $e); + throw new AccessException(sprintf('The property "%s::$%s" is not readable because it is typed "%s". You should initialize it or declare a default value instead.', $r->getDeclaringClass()->getName(), $r->getName(), $r->getType()->getName()), 0, $e); } throw $e; diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php index 09aebab87b135..94655b9762742 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php @@ -149,7 +149,7 @@ public function testSetValueCallsAdderAndRemoverForNestedCollections() public function testSetValueFailsIfNoAdderNorRemoverFound() { $this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException'); - $this->expectExceptionMessageRegExp('/Could not determine access type for property "axes" in class "Mock_PropertyAccessorCollectionTest_CarNoAdderAndRemover_[^"]*"./'); + $this->expectExceptionMessageMatches('/Could not determine access type for property "axes" in class "Mock_PropertyAccessorCollectionTest_CarNoAdderAndRemover_[^"]*"./'); $car = $this->getMockBuilder(__CLASS__.'_CarNoAdderAndRemover')->getMock(); $axesBefore = $this->getContainer([1 => 'second', 3 => 'fourth']); $axesAfter = $this->getContainer([0 => 'first', 1 => 'second', 2 => 'third']); @@ -188,7 +188,7 @@ public function testIsWritableReturnsFalseIfNoAdderNorRemoverExists() public function testSetValueFailsIfAdderAndRemoverExistButValueIsNotTraversable() { $this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException'); - $this->expectExceptionMessageRegExp('/Could not determine access type for property "axes" in class "Symfony\\\\Component\\\\PropertyAccess\\\\Tests\\\\PropertyAccessorCollectionTest_Car[^"]*": The property "axes" in class "Symfony\\\\Component\\\\PropertyAccess\\\\Tests\\\\PropertyAccessorCollectionTest_Car[^"]*" can be defined with the methods "addAxis\(\)", "removeAxis\(\)" but the new value must be an array or an instance of \\\\Traversable, "string" given./'); + $this->expectExceptionMessageMatches('/Could not determine access type for property "axes" in class "Symfony\\\\Component\\\\PropertyAccess\\\\Tests\\\\PropertyAccessorCollectionTest_Car[^"]*": The property "axes" in class "Symfony\\\\Component\\\\PropertyAccess\\\\Tests\\\\PropertyAccessorCollectionTest_Car[^"]*" can be defined with the methods "addAxis\(\)", "removeAxis\(\)" but the new value must be an array or an instance of \\\\Traversable, "string" given./'); $car = new PropertyAccessorCollectionTest_Car(); $this->propertyAccessor->setValue($car, 'axes', 'Not an array or Traversable'); diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index 5304a807f1b75..eeabf7e9f8de1 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -139,7 +139,7 @@ public function testGetValueThrowsExceptionIfIndexNotFoundAndIndexExceptionsEnab public function testGetValueThrowsExceptionIfUninitializedProperty() { $this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException'); - $this->expectExceptionMessage('The property "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty::$uninitialized" is not readable because it is typed "string". You should either initialize it or make it nullable using "?string" instead.'); + $this->expectExceptionMessage('The property "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedProperty::$uninitialized" is not readable because it is typed "string". You should initialize it or declare a default value instead.'); $this->propertyAccessor->getValue(new UninitializedProperty(), 'uninitialized'); } @@ -150,7 +150,7 @@ public function testGetValueThrowsExceptionIfUninitializedProperty() public function testGetValueThrowsExceptionIfUninitializedPropertyWithGetter() { $this->expectException('Symfony\Component\PropertyAccess\Exception\AccessException'); - $this->expectExceptionMessage('The method "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty::getUninitialized()" returned "null", but expected type "array". Have you forgotten to initialize a property or to make the return type nullable using "?array" instead?'); + $this->expectExceptionMessage('The method "Symfony\Component\PropertyAccess\Tests\Fixtures\UninitializedPrivateProperty::getUninitialized()" returned "null", but expected type "array". Did you forget to initialize a property or to make the return type nullable using "?array"?'); $this->propertyAccessor->getValue(new UninitializedPrivateProperty(), 'uninitialized'); } @@ -768,7 +768,7 @@ public function testAdderAndRemoverArePreferredOverSetterForSameSingularAndPlura public function testAdderWithoutRemover() { $this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException'); - $this->expectExceptionMessageRegExp('/.*The add method "addFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidMethods" was found, but the corresponding remove method "removeFoo" was not found\./'); + $this->expectExceptionMessageMatches('/.*The add method "addFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidMethods" was found, but the corresponding remove method "removeFoo" was not found\./'); $object = new TestAdderRemoverInvalidMethods(); $this->propertyAccessor->setValue($object, 'foos', [1, 2]); } @@ -776,7 +776,7 @@ public function testAdderWithoutRemover() public function testRemoverWithoutAdder() { $this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException'); - $this->expectExceptionMessageRegExp('/.*The remove method "removeBar" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidMethods" was found, but the corresponding add method "addBar" was not found\./'); + $this->expectExceptionMessageMatches('/.*The remove method "removeBar" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidMethods" was found, but the corresponding add method "addBar" was not found\./'); $object = new TestAdderRemoverInvalidMethods(); $this->propertyAccessor->setValue($object, 'bars', [1, 2]); } @@ -784,7 +784,7 @@ public function testRemoverWithoutAdder() public function testAdderAndRemoveNeedsTheExactParametersDefined() { $this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException'); - $this->expectExceptionMessageRegExp('/.*The method "addFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 0 arguments, but should accept only 1\. The method "removeFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 2 arguments, but should accept only 1\./'); + $this->expectExceptionMessageMatches('/.*The method "addFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 0 arguments, but should accept only 1\. The method "removeFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 2 arguments, but should accept only 1\./'); $object = new TestAdderRemoverInvalidArgumentLength(); $this->propertyAccessor->setValue($object, 'foo', [1, 2]); } @@ -792,7 +792,7 @@ public function testAdderAndRemoveNeedsTheExactParametersDefined() public function testSetterNeedsTheExactParametersDefined() { $this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException'); - $this->expectExceptionMessageRegExp('/.*The method "setBar" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 2 arguments, but should accept only 1\./'); + $this->expectExceptionMessageMatches('/.*The method "setBar" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 2 arguments, but should accept only 1\./'); $object = new TestAdderRemoverInvalidArgumentLength(); $this->propertyAccessor->setValue($object, 'bar', [1, 2]); } @@ -800,7 +800,7 @@ public function testSetterNeedsTheExactParametersDefined() public function testSetterNeedsPublicAccess() { $this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException'); - $this->expectExceptionMessageRegExp('/.*The method "setFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestClassSetValue" was found but does not have public access./'); + $this->expectExceptionMessageMatches('/.*The method "setFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestClassSetValue" was found but does not have public access./'); $object = new TestClassSetValue(0); $this->propertyAccessor->setValue($object, 'foo', 1); } diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index 6de6517b726ef..ccb216b624e68 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -58,6 +58,9 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp private $enableConstructorExtraction; private $accessFlags; + private $arrayMutatorPrefixesFirst; + private $arrayMutatorPrefixesLast; + /** * @param string[]|null $mutatorPrefixes * @param string[]|null $accessorPrefixes @@ -70,6 +73,9 @@ public function __construct(array $mutatorPrefixes = null, array $accessorPrefix $this->arrayMutatorPrefixes = null !== $arrayMutatorPrefixes ? $arrayMutatorPrefixes : self::$defaultArrayMutatorPrefixes; $this->enableConstructorExtraction = $enableConstructorExtraction; $this->accessFlags = $accessFlags; + + $this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes)); + $this->arrayMutatorPrefixesLast = array_reverse($this->arrayMutatorPrefixesFirst); } /** @@ -405,7 +411,9 @@ private function getMutatorMethod(string $class, string $property): ?array $ucProperty = ucfirst($property); $ucSingulars = (array) Inflector::singularize($ucProperty); - foreach ($this->mutatorPrefixes as $prefix) { + $mutatorPrefixes = \in_array($ucProperty, $ucSingulars, true) ? $this->arrayMutatorPrefixesLast : $this->arrayMutatorPrefixesFirst; + + foreach ($mutatorPrefixes as $prefix) { $names = [$ucProperty]; if (\in_array($prefix, $this->arrayMutatorPrefixes)) { $names = array_merge($names, $ucSingulars); diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index 3f48a1d828e1e..0d3c32206786e 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\PropertyInfo\Tests\PhpDocExtractor; +namespace Symfony\Component\PropertyInfo\Tests\Extractor; use phpDocumentor\Reflection\Types\Collection; use PHPUnit\Framework\TestCase; diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index 45fd42c39a641..0fadd46413eec 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -70,6 +70,7 @@ public function testGetProperties() 'realParent', 'xTotals', 'YT', + 'date', 'c', 'd', 'e', @@ -109,6 +110,7 @@ public function testGetPropertiesWithCustomPrefixes() 'foo4', 'foo5', 'files', + 'date', 'c', 'd', 'e', @@ -173,6 +175,8 @@ public function typesProvider() ['staticSetter', null], ['self', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')]], ['realParent', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')]], + ['date', [new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTime::class)]], + ['dates', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTime::class))]], ]; } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php index ec4b75a099a73..bcec074438948 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php @@ -210,4 +210,12 @@ public function getXTotals() public function getYT() { } + + public function setDate(\DateTime $date) + { + } + + public function addDate(\DateTime $date) + { + } } diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php index 8183b6fc55e97..52b208c41b067 100644 --- a/src/Symfony/Component/Routing/Annotation/Route.php +++ b/src/Symfony/Component/Routing/Annotation/Route.php @@ -31,9 +31,6 @@ class Route private $methods = []; private $schemes = []; private $condition; - private $locale; - private $format; - private $utf8; /** * @param array $data An array of key/value parameters diff --git a/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php index f11b7957525b1..0059a632a1977 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php @@ -13,6 +13,7 @@ use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RouteCompiler; /** * @author Nicolas Grekas @@ -63,6 +64,7 @@ final public function prefix($prefix, bool $trailingSlashOnRoot = true): self foreach ($prefix as $locale => $localePrefix) { $localizedRoute = clone $route; $localizedRoute->setDefault('_locale', $locale); + $localizedRoute->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER)); $localizedRoute->setDefault('_canonical_route', $name); $localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); $this->route->add($name.'.'.$locale, $localizedRoute); diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index da8f34b293c19..31c614950e41a 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -211,6 +211,7 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, $ $localizedRoute = clone $route; $localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); $localizedRoute->setDefault('_locale', $locale); + $localizedRoute->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER)); $localizedRoute->setDefault('_canonical_route', $name); $subCollection->add($name.'.'.$locale, $localizedRoute); } @@ -314,9 +315,9 @@ private function parseConfigs(\DOMElement $node, string $path): array if ($controller = $node->getAttribute('controller')) { if (isset($defaults['_controller'])) { - $name = $node->hasAttribute('id') ? sprintf('"%s"', $node->getAttribute('id')) : sprintf('the "%s" tag', $node->tagName); + $name = $node->hasAttribute('id') ? sprintf('"%s".', $node->getAttribute('id')) : sprintf('the "%s" tag.', $node->tagName); - throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for %s.', $path, $name)); + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for ', $path).$name); } $defaults['_controller'] = $controller; diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php index 868da9bd30b4b..8d4b9abdb10fa 100644 --- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php @@ -216,6 +216,7 @@ protected function parseImport(RouteCollection $collection, array $config, $path foreach ($prefix as $locale => $localePrefix) { $localizedRoute = clone $route; $localizedRoute->setDefault('_locale', $locale); + $localizedRoute->setRequirement('_locale', preg_quote($locale, RouteCompiler::REGEX_DELIMITER)); $localizedRoute->setDefault('_canonical_route', $name); $localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); $subCollection->add($name.'.'.$locale, $localizedRoute); diff --git a/src/Symfony/Component/Routing/README.md b/src/Symfony/Component/Routing/README.md index a16d9d7fcbbbb..03b258ec8203b 100644 --- a/src/Symfony/Component/Routing/README.md +++ b/src/Symfony/Component/Routing/README.md @@ -3,10 +3,48 @@ Routing Component The Routing component maps an HTTP request to a set of configuration variables. +Getting Started +--------------- + +``` +$ composer require symfony/routing +``` + +```php +use App\Controller\BlogController; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +$route = new Route('/blog/{slug}', ['_controller' => BlogController::class]); +$routes = new RouteCollection(); +$routes->add('blog_show', $route); + +$context = new RequestContext(); + +// Routing can match routes with incoming requests +$matcher = new UrlMatcher($routes, $context); +$parameters = $matcher->match('/blog/lorem-ipsum'); +// $parameters = [ +// '_controller' => 'App\Controller\BlogController', +// 'slug' => 'lorem-ipsum', +// '_route' => 'blog_show' +// ] + +// Routing can also generate URLs for a given route +$generator = new UrlGenerator($routes, $context); +$url = $generator->generate('blog_show', [ + 'slug' => 'my-blog-post', +]); +// $url = '/blog/my-blog-post' +``` + Resources --------- - * [Documentation](https://symfony.com/doc/current/components/routing.html) + * [Documentation](https://symfony.com/doc/current/routing.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub_i18n.php b/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub_i18n.php index e79edc869d2de..e6d846dd463cf 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub_i18n.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub_i18n.php @@ -8,4 +8,6 @@ $add('foo', ['fr' => '/foo']); $add('bar', ['fr' => '/bar']); + + $routes->add('non_localized', '/non-localized'); }; diff --git a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php index 789848c66021a..b84d5ff3543be 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php @@ -234,6 +234,7 @@ public function testRoutingI18nConfigurator() $expectedCollection->add('baz.en', (new Route('/baz'))->setDefaults(['_locale' => 'en', '_canonical_route' => 'baz'])->setRequirement('_locale', 'en')); $expectedCollection->add('c_foo.fr', (new Route('/ench/pub/foo'))->setDefaults(['_locale' => 'fr', '_canonical_route' => 'c_foo'])->setRequirement('_locale', 'fr')); $expectedCollection->add('c_bar.fr', (new Route('/ench/pub/bar'))->setDefaults(['_locale' => 'fr', '_canonical_route' => 'c_bar'])->setRequirement('_locale', 'fr')); + $expectedCollection->add('non_localized.fr', (new Route('/ench/non-localized'))->setDefaults(['_locale' => 'fr', '_canonical_route' => 'non_localized'])->setRequirement('_locale', 'fr')); $expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_sub_i18n.php'))); $expectedCollection->addResource(new FileResource(realpath(__DIR__.'/../Fixtures/php_dsl_i18n.php'))); diff --git a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php index 66d54fc985c4c..383cda0176952 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php @@ -201,6 +201,9 @@ public function testLocalizedImportsOfNotLocalizedRoutes() $this->assertEquals('/le-prefix/suffix', $routeCollection->get('imported.fr')->getPath()); $this->assertEquals('/the-prefix/suffix', $routeCollection->get('imported.en')->getPath()); + + $this->assertSame('fr', $routeCollection->get('imported.fr')->getRequirement('_locale')); + $this->assertSame('en', $routeCollection->get('imported.en')->getRequirement('_locale')); } /** @@ -439,7 +442,7 @@ public function testLoadRouteWithControllerSetInDefaults() public function testOverrideControllerInDefaults() { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for "app_blog"/'); + $this->expectExceptionMessageMatches('/The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for "app_blog"/'); $loader = new XmlFileLoader(new FileLocator([__DIR__.'/../Fixtures/controller'])); $loader->load('override_defaults.xml'); } @@ -471,7 +474,7 @@ public function provideFilesImportingRoutesWithControllers() public function testImportWithOverriddenController() { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for the "import" tag/'); + $this->expectExceptionMessageMatches('/The routing file "[^"]*" must not specify both the "controller" attribute and the defaults key "_controller" for the "import" tag/'); $loader = new XmlFileLoader(new FileLocator([__DIR__.'/../Fixtures/controller'])); $loader->load('import_override_defaults.xml'); } diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php index 52c21c287fcf6..e5571b0b7a6eb 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php @@ -144,7 +144,7 @@ public function testLoadRouteWithControllerSetInDefaults() public function testOverrideControllerInDefaults() { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "app_blog"/'); + $this->expectExceptionMessageMatches('/The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "app_blog"/'); $loader = new YamlFileLoader(new FileLocator([__DIR__.'/../Fixtures/controller'])); $loader->load('override_defaults.yml'); } @@ -176,7 +176,7 @@ public function provideFilesImportingRoutesWithControllers() public function testImportWithOverriddenController() { $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessageRegExp('/The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "_static"/'); + $this->expectExceptionMessageMatches('/The routing file "[^"]*" must not specify both the "controller" key and the defaults key "_controller" for "_static"/'); $loader = new YamlFileLoader(new FileLocator([__DIR__.'/../Fixtures/controller'])); $loader->load('import_override_defaults.yml'); } @@ -334,6 +334,9 @@ public function testImportingNonLocalizedRoutesWithLocales() $this->assertCount(2, $routes); $this->assertEquals('/nl/imported', $routes->get('imported.nl')->getPath()); $this->assertEquals('/en/imported', $routes->get('imported.en')->getPath()); + + $this->assertSame('nl', $routes->get('imported.nl')->getRequirement('_locale')); + $this->assertSame('en', $routes->get('imported.en')->getRequirement('_locale')); } public function testImportingRoutesWithOfficialLocales() diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php index 2c9dc44a17735..a7bd1e087d688 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php @@ -83,14 +83,13 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke } try { - $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_DN); - if ($this->queryString) { if ('' !== $this->searchDn && '' !== $this->searchPassword) { $this->ldap->bind($this->searchDn, $this->searchPassword); } else { @trigger_error('Using the "query_string" config without using a "search_dn" and a "search_password" is deprecated since Symfony 4.4 and will throw an exception in Symfony 5.0.', E_USER_DEPRECATED); } + $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER); $query = str_replace('{username}', $username, $this->queryString); $result = $this->ldap->query($this->dnString, $query)->execute(); if (1 !== $result->count()) { @@ -99,6 +98,7 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke $dn = $result[0]->getDn(); } else { + $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_DN); $dn = str_replace('{username}', $username, $this->dnString); } diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php index bf491797aa25d..dd8a5ae00420a 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php @@ -49,6 +49,11 @@ public function setToken(TokenInterface $token = null) @trigger_error(sprintf('Not implementing the "%s::getRoleNames()" method in "%s" is deprecated since Symfony 4.3.', TokenInterface::class, \get_class($token)), E_USER_DEPRECATED); } + if ($token) { + // ensure any initializer is called + $this->getToken(); + } + $this->initializer = null; $this->token = $token; } diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php index 3ce8913aa4fbb..b90d5ab28b635 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php @@ -52,6 +52,11 @@ public function getToken(): ?TokenInterface public function setToken(TokenInterface $token = null): void { $this->storage->setToken($token); + + if ($token && $this->enableUsageTracking) { + // increments the internal session usage index + $this->sessionLocator->get('session')->getMetadataBag(); + } } public function enableUsageTracking(): void diff --git a/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php b/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php index 7690b3e2264bc..7a9c4d0cfb23a 100644 --- a/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php +++ b/src/Symfony/Component/Security/Core/Authorization/TraceableAccessDecisionManager.php @@ -47,8 +47,10 @@ public function __construct(AccessDecisionManagerInterface $manager) /** * {@inheritdoc} + * + * @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array */ - public function decide(TokenInterface $token, array $attributes, $object = null): bool + public function decide(TokenInterface $token, array $attributes, $object = null/*, bool $allowMultipleAttributes = false*/): bool { $currentDecisionLog = [ 'attributes' => $attributes, @@ -58,7 +60,7 @@ public function decide(TokenInterface $token, array $attributes, $object = null) $this->currentLog[] = &$currentDecisionLog; - $result = $this->manager->decide($token, $attributes, $object); + $result = $this->manager->decide($token, $attributes, $object, 3 < \func_num_args() && func_get_arg(3)); $currentDecisionLog['result'] = $result; diff --git a/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php b/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php index cad50180d181e..a6dbf71a1d1f3 100644 --- a/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php +++ b/src/Symfony/Component/Security/Core/Encoder/EncoderFactory.php @@ -72,10 +72,10 @@ private function createEncoder(array $config, bool $isExtra = false): PasswordEn $config = $this->getEncoderConfigFromAlgorithm($config); } if (!isset($config['class'])) { - throw new \InvalidArgumentException(sprintf('"class" must be set in %s.', json_encode($config))); + throw new \InvalidArgumentException('"class" must be set in '.json_encode($config)); } if (!isset($config['arguments'])) { - throw new \InvalidArgumentException(sprintf('"arguments" must be set in %s.', json_encode($config))); + throw new \InvalidArgumentException('"arguments" must be set in '.json_encode($config)); } $encoder = new $config['class'](...$config['arguments']); diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php index 893c8909719fa..fcc950dbe622b 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php @@ -111,16 +111,16 @@ public function testQueryForDn() ; $ldap = $this->getMockBuilder(LdapInterface::class)->getMock(); + $ldap + ->expects($this->at(0)) + ->method('bind') + ->with('elsa', 'test1234A$'); $ldap ->expects($this->once()) ->method('escape') ->with('foo', '') ->willReturn('foo') ; - $ldap - ->expects($this->at(1)) - ->method('bind') - ->with('elsa', 'test1234A$'); $ldap ->expects($this->once()) ->method('query') @@ -151,16 +151,16 @@ public function testQueryWithUserForDn() ; $ldap = $this->getMockBuilder(LdapInterface::class)->getMock(); + $ldap + ->expects($this->at(0)) + ->method('bind') + ->with('elsa', 'test1234A$'); $ldap ->expects($this->once()) ->method('escape') ->with('foo', '') ->willReturn('foo') ; - $ldap - ->expects($this->at(1)) - ->method('bind') - ->with('elsa', 'test1234A$'); $ldap ->expects($this->once()) ->method('query') @@ -195,7 +195,7 @@ public function testEmptyQueryResultShouldThrowAnException() $ldap = $this->getMockBuilder(LdapInterface::class)->getMock(); $ldap - ->expects($this->at(1)) + ->expects($this->at(0)) ->method('bind') ->with('elsa', 'test1234A$'); $ldap diff --git a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php index 816bd097309e8..e00fb12dfb392 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php @@ -190,7 +190,7 @@ public function testSupportsChecksGuardAuthenticatorsTokenOrigin() public function testAuthenticateFailsOnNonOriginatingToken() { $this->expectException('Symfony\Component\Security\Core\Exception\AuthenticationException'); - $this->expectExceptionMessageRegExp('/second_firewall_0/'); + $this->expectExceptionMessageMatches('/second_firewall_0/'); $authenticatorA = $this->getMockBuilder(AuthenticatorInterface::class)->getMock(); $authenticators = [$authenticatorA]; diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index 21bd1bc1cdb0b..a6718a7232824 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -168,7 +168,7 @@ private function attemptSwitchUser(Request $request, string $username): ?TokenIn try { $this->provider->loadUserByUsername($nonExistentUsername); - } catch (AuthenticationException $e) { + } catch (\Exception $e) { } } catch (AuthenticationException $e) { $this->provider->loadUserByUsername($currentUsername); diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php index 82a5f917d98ee..7e89c0b1b36fb 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php @@ -411,9 +411,9 @@ protected function runSessionOnKernelResponse($newToken, $original = null) private function handleEventWithPreviousSession($userProviders, UserInterface $user = null, RememberMeServicesInterface $rememberMeServices = null) { - $user = $user ?: new User('foo', 'bar'); + $tokenUser = $user ?: new User('foo', 'bar'); $session = new Session(new MockArraySessionStorage()); - $session->set('_security_context_key', serialize(new UsernamePasswordToken($user, '', 'context_key', ['ROLE_USER']))); + $session->set('_security_context_key', serialize(new UsernamePasswordToken($tokenUser, '', 'context_key', ['ROLE_USER']))); $request = new Request(); $request->setSession($session); @@ -442,6 +442,10 @@ private function handleEventWithPreviousSession($userProviders, UserInterface $u $listener(new RequestEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), $request, HttpKernelInterface::MASTER_REQUEST)); if (null !== $usageIndex) { + if (null !== $user) { + ++$usageIndex; + } + $this->assertSame($usageIndex, $session->getUsageIndex()); $tokenStorage->getToken(); $this->assertSame(1 + $usageIndex, $session->getUsageIndex()); diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 699ffcf7032b3..6b23f5cc8e311 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "symfony/security-core": "^4.4.7", + "symfony/security-core": "^4.4.8", "symfony/http-foundation": "^3.4.40|^4.4.7|^5.0.7", "symfony/http-kernel": "^4.4", "symfony/property-access": "^3.4|^4.0|^5.0" diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php index 138f9280dd598..fbfa7574693bf 100644 --- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php @@ -473,7 +473,7 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = return $this->appendNode($parentNode, $data, 'data'); } - throw new NotEncodableValueException(sprintf('An unexpected value could not be serialized: %s.', !\is_resource($data) ? var_export($data, true) : sprintf('%s resource', get_resource_type($data)))); + throw new NotEncodableValueException('An unexpected value could not be serialized: '.(!\is_resource($data) ? var_export($data, true) : sprintf('%s resource', get_resource_type($data)))); } /** diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index be4f9c47ae729..4eb8468360435 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -359,7 +359,7 @@ public function denormalize($data, $type, $format = null, array $context = []) try { $this->setAttributeValue($object, $attribute, $value, $format, $context); } catch (InvalidArgumentException $e) { - throw new NotNormalizableValueException(sprintf('Failed to denormalize attribute "%s" value for class "%s": %s.', $attribute, $type, $e->getMessage()), $e->getCode(), $e); + throw new NotNormalizableValueException(sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), $e->getCode(), $e); } } @@ -482,7 +482,7 @@ private function validateAndDenormalize(string $currentClass, string $attribute, */ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, $parameterName, $parameterData, array $context, $format = null) { - if (null === $this->propertyTypeExtractor || null === $types = $this->propertyTypeExtractor->getTypes($class->getName(), $parameterName)) { + if (null === $this->propertyTypeExtractor || null === $this->propertyTypeExtractor->getTypes($class->getName(), $parameterName)) { return parent::denormalizeParameter($class, $parameter, $parameterName, $parameterData, $context, $format); } @@ -617,6 +617,7 @@ private function getCacheKey(?string $format, array $context) unset($context[$key]); } unset($context[self::EXCLUDE_FROM_CACHE_KEY]); + unset($context[self::OBJECT_TO_POPULATE]); unset($context['cache_key']); // avoid artificially different keys try { diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index 94770484738f0..3f2461cf96a09 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -173,7 +173,7 @@ public function normalize($data, $format = null, array $context = []) throw new NotNormalizableValueException(sprintf('Could not normalize object of type "%s", no supporting normalizer found.', \get_class($data))); } - throw new NotNormalizableValueException(sprintf('An unexpected value could not be normalized: %s.', !\is_resource($data) ? var_export($data, true) : sprintf('%s resource', get_resource_type($data)))); + throw new NotNormalizableValueException('An unexpected value could not be normalized: '.(!\is_resource($data) ? var_export($data, true) : sprintf('%s resource', get_resource_type($data)))); } /** diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index 3de0ac086a5eb..01b8950cb4c98 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -174,6 +174,14 @@ public function testDenormalizeStringCollectionDecodedFromXmlWithTwoChildren() $this->assertEquals('bar', $stringCollection->children[1]); } + public function testDenormalizeNotSerializableObjectToPopulate() + { + $normalizer = new AbstractObjectNormalizerDummy(); + $normalizedData = $normalizer->denormalize(['foo' => 'foo'], Dummy::class, null, [AbstractObjectNormalizer::OBJECT_TO_POPULATE => new NotSerializable()]); + + $this->assertSame('foo', $normalizedData->foo); + } + private function getDenormalizerForStringCollection() { $extractor = $this->getMockBuilder(PhpDocExtractor::class)->getMock(); @@ -448,3 +456,15 @@ public function setSerializer(SerializerInterface $serializer) $this->serializer = $serializer; } } + +class NotSerializable +{ + public function __sleep() + { + if (class_exists(\Error::class)) { + throw new \Error('not serializable'); + } + + throw new \Exception('not serializable'); + } +} diff --git a/src/Symfony/Component/Translation/Formatter/IntlFormatter.php b/src/Symfony/Component/Translation/Formatter/IntlFormatter.php index ad4a45b95b5c0..9101a63aa2ee9 100644 --- a/src/Symfony/Component/Translation/Formatter/IntlFormatter.php +++ b/src/Symfony/Component/Translation/Formatter/IntlFormatter.php @@ -40,7 +40,7 @@ public function formatIntl(string $message, string $locale, array $parameters = try { $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message); } catch (\IntlException $e) { - throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): %s.', intl_get_error_code(), intl_get_error_message()), 0, $e); + throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): '.intl_get_error_message(), intl_get_error_code()), 0, $e); } } @@ -52,7 +52,7 @@ public function formatIntl(string $message, string $locale, array $parameters = } if (false === $message = $formatter->format($parameters)) { - throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): %s.', $formatter->getErrorCode(), $formatter->getErrorMessage())); + throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): '.$formatter->getErrorMessage(), $formatter->getErrorCode())); } return $message; diff --git a/src/Symfony/Component/Translation/Loader/JsonFileLoader.php b/src/Symfony/Component/Translation/Loader/JsonFileLoader.php index fe6747e8aabd4..9f15dbc628893 100644 --- a/src/Symfony/Component/Translation/Loader/JsonFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/JsonFileLoader.php @@ -30,7 +30,7 @@ protected function loadResource($resource) $messages = json_decode($data, true); if (0 < $errorCode = json_last_error()) { - throw new InvalidResourceException(sprintf('Error parsing JSON - %s.', $this->getJSONErrorMessage($errorCode))); + throw new InvalidResourceException('Error parsing JSON: '.$this->getJSONErrorMessage($errorCode)); } } diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php index aa36ebcc483ea..d7741a071007c 100644 --- a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -53,12 +53,12 @@ private function extract($resource, MessageCatalogue $catalogue, string $domain) try { $dom = XmlUtils::loadFile($resource); } catch (\InvalidArgumentException $e) { - throw new InvalidResourceException(sprintf('Unable to load "%s": %s.', $resource, $e->getMessage()), $e->getCode(), $e); + throw new InvalidResourceException(sprintf('Unable to load "%s": '.$e->getMessage(), $resource), $e->getCode(), $e); } $xliffVersion = XliffUtils::getVersionNumber($dom); if ($errors = XliffUtils::validateSchema($dom)) { - throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: %s.', $resource, XliffUtils::getErrorsAsString($errors))); + throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: '.XliffUtils::getErrorsAsString($errors), $resource)); } if ('1.2' === $xliffVersion) { diff --git a/src/Symfony/Component/Translation/Tests/Loader/JsonFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/JsonFileLoaderTest.php index d264bb16b29d9..c5a9ca64d4d7f 100644 --- a/src/Symfony/Component/Translation/Tests/Loader/JsonFileLoaderTest.php +++ b/src/Symfony/Component/Translation/Tests/Loader/JsonFileLoaderTest.php @@ -50,7 +50,7 @@ public function testLoadNonExistingResource() public function testParseException() { $this->expectException('Symfony\Component\Translation\Exception\InvalidResourceException'); - $this->expectExceptionMessage('Error parsing JSON - Syntax error, malformed JSON'); + $this->expectExceptionMessage('Error parsing JSON: Syntax error, malformed JSON'); $loader = new JsonFileLoader(); $resource = __DIR__.'/../fixtures/malformed.json'; $loader->load($resource, 'en', 'domain1'); diff --git a/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php b/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php index 3b3aba0450f85..e06f714bbd2ea 100644 --- a/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php +++ b/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php @@ -55,7 +55,7 @@ public function validate($value, Constraint $constraint) try { $comparedValue = $this->getPropertyAccessor()->getValue($object, $path); } catch (NoSuchPropertyException $e) { - throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: %s.', $path, \get_class($constraint), $e->getMessage()), 0, $e); + throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: '.$e->getMessage(), $path, \get_class($constraint)), 0, $e); } } else { $comparedValue = $constraint->value; diff --git a/src/Symfony/Component/Validator/Constraints/BicValidator.php b/src/Symfony/Component/Validator/Constraints/BicValidator.php index c49986561e980..651d76dcf1c00 100644 --- a/src/Symfony/Component/Validator/Constraints/BicValidator.php +++ b/src/Symfony/Component/Validator/Constraints/BicValidator.php @@ -138,7 +138,7 @@ public function validate($value, Constraint $constraint) try { $iban = $this->getPropertyAccessor()->getValue($object, $path); } catch (NoSuchPropertyException $e) { - throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: %s.', $path, \get_class($constraint), $e->getMessage()), 0, $e); + throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: '.$e->getMessage(), $path, \get_class($constraint)), 0, $e); } } if (!$iban) { diff --git a/src/Symfony/Component/Validator/Constraints/CallbackValidator.php b/src/Symfony/Component/Validator/Constraints/CallbackValidator.php index ef1c10b181ed3..751c2b9c115ed 100644 --- a/src/Symfony/Component/Validator/Constraints/CallbackValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CallbackValidator.php @@ -40,7 +40,7 @@ public function validate($object, Constraint $constraint) if (isset($method[0]) && \is_object($method[0])) { $method[0] = \get_class($method[0]); } - throw new ConstraintDefinitionException(sprintf('%s targeted by Callback constraint is not a valid callable.', json_encode($method))); + throw new ConstraintDefinitionException(json_encode($method).' targeted by Callback constraint is not a valid callable.'); } $method($object, $this->context, $constraint->payload); diff --git a/src/Symfony/Component/Validator/Constraints/Composite.php b/src/Symfony/Component/Validator/Constraints/Composite.php index bd7030ee27a44..b14276c3bbf1a 100644 --- a/src/Symfony/Component/Validator/Constraints/Composite.php +++ b/src/Symfony/Component/Validator/Constraints/Composite.php @@ -88,7 +88,8 @@ public function __construct($options = null) } } - $this->groups = array_keys($mergedGroups); + // prevent empty composite constraint to have empty groups + $this->groups = array_keys($mergedGroups) ?: [self::DEFAULT_GROUP]; $this->$compositeOption = $nestedConstraints; return; diff --git a/src/Symfony/Component/Validator/Constraints/RangeValidator.php b/src/Symfony/Component/Validator/Constraints/RangeValidator.php index 9b7c40ce5ae9a..3cfd43f998676 100644 --- a/src/Symfony/Component/Validator/Constraints/RangeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/RangeValidator.php @@ -157,7 +157,7 @@ private function getLimit($propertyPath, $default, Constraint $constraint) try { return $this->getPropertyAccessor()->getValue($object, $propertyPath); } catch (NoSuchPropertyException $e) { - throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: %s.', $propertyPath, \get_class($constraint), $e->getMessage()), 0, $e); + throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: '.$e->getMessage(), $propertyPath, \get_class($constraint)), 0, $e); } } diff --git a/src/Symfony/Component/Validator/Constraints/UrlValidator.php b/src/Symfony/Component/Validator/Constraints/UrlValidator.php index 43c21f5768118..77d7354946653 100644 --- a/src/Symfony/Component/Validator/Constraints/UrlValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UrlValidator.php @@ -24,7 +24,7 @@ class UrlValidator extends ConstraintValidator { const PATTERN = '~^ (%s):// # protocol - (([\_\.\pL\pN-]+:)?([\_\.\pL\pN-]+)@)? # basic auth + (((?:[\_\.\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 | # or diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php index c18cc0212e491..a197168bf72ab 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php @@ -21,6 +21,7 @@ use Symfony\Component\Validator\Mapping\MetadataInterface; use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; use Symfony\Component\Validator\Util\PropertyPath; +use Symfony\Component\Validator\Validator\LazyProperty; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Violation\ConstraintViolationBuilder; use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface; @@ -192,7 +193,7 @@ public function addViolation($message, array $parameters = []) $parameters, $this->root, $this->propertyPath, - $this->value, + $this->getValue(), null, null, $this->constraint @@ -211,7 +212,7 @@ public function buildViolation($message, array $parameters = []): ConstraintViol $parameters, $this->root, $this->propertyPath, - $this->value, + $this->getValue(), $this->translator, $this->translationDomain ); @@ -246,6 +247,10 @@ public function getRoot() */ public function getValue() { + if ($this->value instanceof LazyProperty) { + return $this->value->getPropertyValue(); + } + return $this->value; } diff --git a/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php index 38df595f5516c..642d8e6e9f0fb 100644 --- a/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php +++ b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php @@ -138,34 +138,25 @@ public function getMetadataFor($value) private function mergeConstraints(ClassMetadata $metadata) { + if ($metadata->getReflectionClass()->isInterface()) { + return; + } + // Include constraints from the parent class if ($parent = $metadata->getReflectionClass()->getParentClass()) { $metadata->mergeConstraints($this->getMetadataFor($parent->name)); } - $interfaces = $metadata->getReflectionClass()->getInterfaces(); - - $interfaces = array_filter($interfaces, function (\ReflectionClass $interface) use ($parent, $interfaces) { - $interfaceName = $interface->getName(); - - if ($parent && $parent->implementsInterface($interfaceName)) { - return false; - } - - foreach ($interfaces as $i) { - if ($i !== $interface && $i->implementsInterface($interfaceName)) { - return false; - } - } - - return true; - }); - // Include constraints from all directly implemented interfaces - foreach ($interfaces as $interface) { + foreach ($metadata->getReflectionClass()->getInterfaces() as $interface) { if ('Symfony\Component\Validator\GroupSequenceProviderInterface' === $interface->name) { continue; } + + if ($parent && \in_array($interface->getName(), $parent->getInterfaceNames(), true)) { + continue; + } + $metadata->mergeConstraints($this->getMetadataFor($interface->name)); } } diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf index 80911a9902910..3c03fd8525cca 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf @@ -370,6 +370,18 @@ This value is not a valid hostname. Значение не является корректным именем хоста. + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Количество элементов в этой коллекции должно быть кратным {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Значение должно удовлетворять как минимум одному из следующих ограничений: + + + Each element of this collection should satisfy its own set of constraints. + Каждый элемент этой коллекции должен удовлетворять своему собственному набору ограничений. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf index cba61915544a3..688e11fbe929c 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf @@ -366,6 +366,22 @@ This value should be between {{ min }} and {{ max }}. Значення має бути між {{ min }} та {{ max }}. + + This value is not a valid hostname. + Значення не є дійсним іменем хоста. + + + The number of elements in this collection should be a multiple of {{ compared_value }}. + Кількість елементів у цій колекції повинна бути кратною {{ compared_value }}. + + + This value should satisfy at least one of the following constraints: + Значення повинно задовольняти хоча б одному з наступних обмежень: + + + Each element of this collection should satisfy its own set of constraints. + Кожен елемент цієї колекції повинен задовольняти власному набору обмежень. + diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CollectionTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CollectionTest.php index fef129cfa7494..254154dae7244 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CollectionTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CollectionTest.php @@ -100,4 +100,16 @@ public function testAcceptRequiredConstraintAsOneElementArray() $this->assertEquals($collection1, $collection2); } + + public function testConstraintHasDefaultGroupWithOptionalValues() + { + $constraint = new Collection([ + 'foo' => new Required(), + 'bar' => new Optional(), + ]); + + $this->assertEquals(['Default'], $constraint->groups); + $this->assertEquals(['Default'], $constraint->fields['foo']->groups); + $this->assertEquals(['Default'], $constraint->fields['bar']->groups); + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php index b076c13885dd0..3b3ee04509a12 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php @@ -143,6 +143,29 @@ public function testExtraFieldsDisallowed() ->assertRaised(); } + public function testExtraFieldsDisallowedWithOptionalValues() + { + $constraint = new Optional(); + + $data = $this->prepareTestData([ + 'baz' => 6, + ]); + + $this->validator->validate($data, new Collection([ + 'fields' => [ + 'foo' => $constraint, + ], + 'extraFieldsMessage' => 'myMessage', + ])); + + $this->buildViolation('myMessage') + ->setParameter('{{ field }}', '"baz"') + ->atPath('property.path[baz]') + ->setInvalidValue(6) + ->setCode(Collection::NO_SUCH_FIELD_ERROR) + ->assertRaised(); + } + // bug fix public function testNullNotConsideredExtraField() { diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CompositeTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CompositeTest.php index c14c1be6b3264..82c224ff4d376 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CompositeTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CompositeTest.php @@ -19,7 +19,7 @@ class ConcreteComposite extends Composite { - public $constraints; + public $constraints = []; protected function getCompositeOption(): string { @@ -37,6 +37,30 @@ public function getDefaultOption(): ?string */ class CompositeTest extends TestCase { + public function testConstraintHasDefaultGroup() + { + $constraint = new ConcreteComposite([ + new NotNull(), + new NotBlank(), + ]); + + $this->assertEquals(['Default'], $constraint->groups); + $this->assertEquals(['Default'], $constraint->constraints[0]->groups); + $this->assertEquals(['Default'], $constraint->constraints[1]->groups); + } + + public function testNestedCompositeConstraintHasDefaultGroup() + { + $constraint = new ConcreteComposite([ + new ConcreteComposite(), + new ConcreteComposite(), + ]); + + $this->assertEquals(['Default'], $constraint->groups); + $this->assertEquals(['Default'], $constraint->constraints[0]->groups); + $this->assertEquals(['Default'], $constraint->constraints[1]->groups); + } + public function testMergeNestedGroupsIfNoExplicitParentGroup() { $constraint = new ConcreteComposite([ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php index 6d98e64d77b3f..dde594d200e35 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php @@ -157,6 +157,8 @@ public function getValidUrls() ['http://user.name:pass.word@symfony.com'], ['http://user-name@symfony.com'], ['http://user_name@symfony.com'], + ['http://u%24er:password@symfony.com'], + ['http://user:pa%24%24word@symfony.com'], ['http://symfony.com?'], ['http://symfony.com?query=1'], ['http://symfony.com/?query=1'], @@ -255,6 +257,8 @@ public function getInvalidUrls() ['http://:password@@symfony.com'], ['http://username:passwordsymfony.com'], ['http://usern@me:password@symfony.com'], + ['http://nota%hex:password@symfony.com'], + ['http://username:nota%hex@symfony.com'], ['http://example.com/exploit.html?'], ['http://example.com/exploit.html?hel lo'], ['http://example.com/exploit.html?not_a%hex'], diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/AbstractPropertyGetter.php b/src/Symfony/Component/Validator/Tests/Fixtures/AbstractPropertyGetter.php new file mode 100644 index 0000000000000..3df0b9469ddf4 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/AbstractPropertyGetter.php @@ -0,0 +1,13 @@ +property; + } +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/ChildGetterInterface.php b/src/Symfony/Component/Validator/Tests/Fixtures/ChildGetterInterface.php new file mode 100644 index 0000000000000..65c144bbeadb9 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/ChildGetterInterface.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +class EntityWithGroupedConstraintOnMethods +{ + public $bar; + + public function isValidInFoo() + { + return false; + } + + public function getBar() + { + throw new \Exception('Should not be called'); + } +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/PropertyGetter.php b/src/Symfony/Component/Validator/Tests/Fixtures/PropertyGetter.php new file mode 100644 index 0000000000000..e1e14bb0075ee --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/PropertyGetter.php @@ -0,0 +1,12 @@ + [ 'Default', 'EntityParentInterface', - 'EntityInterfaceB', 'Entity', ]]), ]; @@ -209,6 +211,15 @@ public function testGroupsFromParent() $this->assertContains('EntityStaticCar', $groups); $this->assertContains('EntityStaticVehicle', $groups); } + + public function testMultipathInterfaceConstraint() + { + $factory = new LazyLoadingMetadataFactory(new PropertyGetterInterfaceConstraintLoader()); + $metadata = $factory->getMetadataFor(PropertyGetter::class); + $constraints = $metadata->getPropertyMetadata('property'); + + $this->assertCount(1, $constraints); + } } class TestLoader implements LoaderInterface @@ -220,3 +231,15 @@ public function loadClassMetadata(ClassMetadata $metadata): bool return true; } } + +class PropertyGetterInterfaceConstraintLoader implements LoaderInterface +{ + public function loadClassMetadata(ClassMetadata $metadata) + { + if (PropertyGetterInterface::class === $metadata->getClassName()) { + $metadata->addGetterConstraint('property', new NotBlank()); + } + + return true; + } +} diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php index c2838792421d1..6e659376a25bb 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php @@ -14,14 +14,19 @@ use Symfony\Component\Translation\IdentityTranslator; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\Context\ExecutionContextFactory; +use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; use Symfony\Component\Validator\Tests\Constraints\Fixtures\ChildA; use Symfony\Component\Validator\Tests\Constraints\Fixtures\ChildB; use Symfony\Component\Validator\Tests\Fixtures\Entity; +use Symfony\Component\Validator\Tests\Fixtures\EntityWithGroupedConstraintOnMethods; use Symfony\Component\Validator\Validator\RecursiveValidator; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -118,6 +123,25 @@ public function testCollectionConstraintValidateAllGroupsForNestedConstraints() $this->assertInstanceOf(NotBlank::class, $violations->get(1)->getConstraint()); } + public function testGroupedMethodConstraintValidateInSequence() + { + $metadata = new ClassMetadata(EntityWithGroupedConstraintOnMethods::class); + $metadata->addPropertyConstraint('bar', new NotNull(['groups' => 'Foo'])); + $metadata->addGetterMethodConstraint('validInFoo', 'isValidInFoo', new IsTrue(['groups' => 'Foo'])); + $metadata->addGetterMethodConstraint('bar', 'getBar', new NotNull(['groups' => 'Bar'])); + + $this->metadataFactory->addMetadata($metadata); + + $entity = new EntityWithGroupedConstraintOnMethods(); + $groups = new GroupSequence(['EntityWithGroupedConstraintOnMethods', 'Foo', 'Bar']); + + $violations = $this->validator->validate($entity, null, $groups); + + $this->assertCount(2, $violations); + $this->assertInstanceOf(NotNull::class, $violations->get(0)->getConstraint()); + $this->assertInstanceOf(IsTrue::class, $violations->get(1)->getConstraint()); + } + public function testAllConstraintValidateAllGroupsForNestedConstraints() { $this->metadata->addPropertyConstraint('data', new All(['constraints' => [ diff --git a/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php b/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php index 622bcbd8c9c9f..b455441e33ad1 100644 --- a/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php +++ b/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php @@ -13,12 +13,98 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Translation\IdentityTranslator; +use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Test\ForwardCompatTestTrait; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; use Symfony\Component\Validator\Violation\ConstraintViolationBuilder; class ConstraintViolationBuilderTest extends TestCase { + use ForwardCompatTestTrait; + + private $root; + private $violations; + private $messageTemplate = '%value% is invalid'; + private $builder; + + private function doSetUp() + { + $this->root = [ + 'data' => [ + 'foo' => 'bar', + 'baz' => 'foobar', + ], + ]; + $this->violations = new ConstraintViolationList(); + $this->builder = new ConstraintViolationBuilder($this->violations, new Valid(), $this->messageTemplate, [], $this->root, 'data', 'foo', new IdentityTranslator()); + } + + public function testAddViolation() + { + $this->builder->addViolation(); + + $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data', 'foo', null, null, new Valid())); + } + + public function testAppendPropertyPath() + { + $this->builder + ->atPath('foo') + ->addViolation(); + + $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data.foo', 'foo', null, null, new Valid())); + } + + public function testAppendMultiplePropertyPaths() + { + $this->builder + ->atPath('foo') + ->atPath('bar') + ->addViolation(); + + $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data.foo.bar', 'foo', null, null, new Valid())); + } + + public function testCodeCanBeSet() + { + $this->builder + ->setCode('5') + ->addViolation(); + + $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data', 'foo', null, '5', new Valid())); + } + + public function testCauseCanBeSet() + { + $cause = new \LogicException(); + + $this->builder + ->setCause($cause) + ->addViolation(); + + $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data', 'foo', null, null, new Valid(), $cause)); + } + + private function assertViolationEquals(ConstraintViolation $expectedViolation) + { + $this->assertCount(1, $this->violations); + + $violation = $this->violations->get(0); + + $this->assertSame($expectedViolation->getMessage(), $violation->getMessage()); + $this->assertSame($expectedViolation->getMessageTemplate(), $violation->getMessageTemplate()); + $this->assertSame($expectedViolation->getParameters(), $violation->getParameters()); + $this->assertSame($expectedViolation->getPlural(), $violation->getPlural()); + $this->assertSame($expectedViolation->getRoot(), $violation->getRoot()); + $this->assertSame($expectedViolation->getPropertyPath(), $violation->getPropertyPath()); + $this->assertSame($expectedViolation->getInvalidValue(), $violation->getInvalidValue()); + $this->assertSame($expectedViolation->getCode(), $violation->getCode()); + $this->assertEquals($expectedViolation->getConstraint(), $violation->getConstraint()); + $this->assertSame($expectedViolation->getCause(), $violation->getCause()); + } + /** * @group legacy * @expectedDeprecation Not using a string as the error code in Symfony\Component\Validator\Violation\ConstraintViolationBuilder::setCode() is deprecated since Symfony 4.4. A type-hint will be added in 5.0. diff --git a/src/Symfony/Component/Validator/Validator/LazyProperty.php b/src/Symfony/Component/Validator/Validator/LazyProperty.php new file mode 100644 index 0000000000000..a0799963c150f --- /dev/null +++ b/src/Symfony/Component/Validator/Validator/LazyProperty.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Validator; + +/** + * A wrapper for a callable initializing a property from a getter. + * + * @internal + */ +class LazyProperty +{ + private $propertyValueCallback; + + public function __construct(\Closure $propertyValueCallback) + { + $this->propertyValueCallback = $propertyValueCallback; + } + + public function getPropertyValue() + { + return \call_user_func($this->propertyValueCallback); + } +} diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index 121ecf236467e..4497ea2989703 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -28,6 +28,7 @@ use Symfony\Component\Validator\Mapping\ClassMetadataInterface; use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; use Symfony\Component\Validator\Mapping\GenericMetadata; +use Symfony\Component\Validator\Mapping\GetterMetadata; use Symfony\Component\Validator\Mapping\MetadataInterface; use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; use Symfony\Component\Validator\Mapping\TraversalStrategy; @@ -506,7 +507,13 @@ private function validateClassNode($object, ?string $cacheKey, ClassMetadataInte throw new UnsupportedMetadataException(sprintf('The property metadata instances should implement "Symfony\Component\Validator\Mapping\PropertyMetadataInterface", got: "%s".', \is_object($propertyMetadata) ? \get_class($propertyMetadata) : \gettype($propertyMetadata))); } - $propertyValue = $propertyMetadata->getPropertyValue($object); + if ($propertyMetadata instanceof GetterMetadata) { + $propertyValue = new LazyProperty(static function () use ($propertyMetadata, $object) { + return $propertyMetadata->getPropertyValue($object); + }); + } else { + $propertyValue = $propertyMetadata->getPropertyValue($object); + } $this->validateGenericNode( $propertyValue, @@ -739,6 +746,10 @@ private function validateInGroup($value, ?string $cacheKey, MetadataInterface $m $validator = $this->validatorFactory->getInstance($constraint); $validator->initialize($context); + if ($value instanceof LazyProperty) { + $value = $value->getPropertyValue(); + } + try { $validator->validate($value, $constraint); } catch (UnexpectedValueException $e) { diff --git a/src/Symfony/Component/VarDumper/Server/DumpServer.php b/src/Symfony/Component/VarDumper/Server/DumpServer.php index ad920bd4fca67..46546d1675998 100644 --- a/src/Symfony/Component/VarDumper/Server/DumpServer.php +++ b/src/Symfony/Component/VarDumper/Server/DumpServer.php @@ -41,7 +41,7 @@ public function __construct(string $host, LoggerInterface $logger = null) public function start(): void { if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) { - throw new \RuntimeException(sprintf('Server start failed on "%s": %s %s.', $this->host, $errstr, $errno)); + throw new \RuntimeException(sprintf('Server start failed on "%s": '.$errstr.' '.$errno, $this->host)); } } diff --git a/src/Symfony/Component/VarExporter/Tests/InstantiatorTest.php b/src/Symfony/Component/VarExporter/Tests/InstantiatorTest.php index 3da602383fc7c..6d0deb68dc9ac 100644 --- a/src/Symfony/Component/VarExporter/Tests/InstantiatorTest.php +++ b/src/Symfony/Component/VarExporter/Tests/InstantiatorTest.php @@ -29,7 +29,7 @@ public function testNotFoundClass() public function testFailingInstantiation(string $class) { $this->expectException('Symfony\Component\VarExporter\Exception\NotInstantiableTypeException'); - $this->expectExceptionMessageRegExp('/Type ".*" is not instantiable\./'); + $this->expectExceptionMessageMatches('/Type ".*" is not instantiable\./'); Instantiator::instantiate($class); } diff --git a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php index ec4cb4896980d..5fc46644c976a 100644 --- a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php +++ b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php @@ -38,7 +38,7 @@ public function testPhpIncompleteClassesAreForbidden() public function testFailingSerialization($value) { $this->expectException('Symfony\Component\VarExporter\Exception\NotInstantiableTypeException'); - $this->expectExceptionMessageRegExp('/Type ".*" is not instantiable\./'); + $this->expectExceptionMessageMatches('/Type ".*" is not instantiable\./'); $expectedDump = $this->getDump($value); try { VarExporter::export($value); diff --git a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php index 8c219f502bc3d..feb012913cf1a 100644 --- a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php +++ b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php @@ -56,7 +56,7 @@ public function getMarking($subject): Marking $marking = $subject->{$method}(); - if (!$marking) { + if (null === $marking) { return new Marking(); } diff --git a/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php index eca8a5d2a1449..fd279e967fcba 100644 --- a/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php +++ b/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php @@ -45,7 +45,7 @@ public function getMarking($subject) { $placeName = $this->propertyAccessor->getValue($subject, $this->property); - if (!$placeName) { + if (null === $placeName) { return new Marking(); } diff --git a/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php b/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php index 7393faa825752..155f285a4a976 100644 --- a/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -53,6 +53,18 @@ public function testGetSetMarkingWithSingleState() $this->assertEquals($marking, $marking2); } + public function testGetSetMarkingWithSingleStateAndAlmostEmptyPlaceName() + { + $subject = new Subject(0); + + $markingStore = new MethodMarkingStore(true); + + $marking = $markingStore->getMarking($subject); + + $this->assertInstanceOf(Marking::class, $marking); + $this->assertCount(1, $marking->getPlaces()); + } + public function testGetMarkingWithValueObject() { $subject = new Subject($this->createValueObject('first_place')); diff --git a/src/Symfony/Component/Workflow/Tests/MarkingStore/SingleStateMarkingStoreTest.php b/src/Symfony/Component/Workflow/Tests/MarkingStore/SingleStateMarkingStoreTest.php index 7b640559ffff2..caaf977b72ec3 100644 --- a/src/Symfony/Component/Workflow/Tests/MarkingStore/SingleStateMarkingStoreTest.php +++ b/src/Symfony/Component/Workflow/Tests/MarkingStore/SingleStateMarkingStoreTest.php @@ -31,4 +31,17 @@ public function testGetSetMarking() $this->assertEquals($marking, $marking2); } + + public function testAlmostEmptyPlaceName() + { + $subject = new \stdClass(); + $subject->myMarks = 0; + + $markingStore = new SingleStateMarkingStore('myMarks'); + + $marking = $markingStore->getMarking($subject); + + $this->assertInstanceOf(Marking::class, $marking); + $this->assertCount(1, $marking->getPlaces()); + } } diff --git a/src/Symfony/Component/Yaml/Escaper.php b/src/Symfony/Component/Yaml/Escaper.php index afff1683e0a77..b975fb34ccfc8 100644 --- a/src/Symfony/Component/Yaml/Escaper.php +++ b/src/Symfony/Component/Yaml/Escaper.php @@ -22,7 +22,7 @@ class Escaper { // Characters that would cause a dumped string to require double quoting. - const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\x7f|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; // Mapping arrays for escaping a double quoted string. The backslash is // first to ensure proper escaping because str_replace operates iteratively @@ -33,6 +33,7 @@ class Escaper "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\x7f", "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9", ]; private static $escaped = ['\\\\', '\\"', '\\\\', '\\"', @@ -40,6 +41,7 @@ class Escaper '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', + '\\x7f', '\\N', '\\_', '\\L', '\\P', ]; diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index bb24add32b001..0a99b0ba77b81 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -615,7 +615,7 @@ private static function evaluateScalar(string $scalar, int $flags, array $refere throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } if (self::$exceptionOnInvalidType) { - throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } return null; diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index 7d25367cc8687..cc2b1e26fe6a4 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -223,6 +223,7 @@ public function getEscapeSequences() 'double-quote' => ['"', "'\"'"], 'slash' => ['/', '/'], 'backslash' => ['\\', '\\'], + 'del' => ["\x7f", '"\x7f"'], 'next-line' => ["\xC2\x85", '"\\N"'], 'non-breaking-space' => ["\xc2\xa0", '"\\_"'], 'line-separator' => ["\xE2\x80\xA8", '"\\L"'], diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index fc3a1ede33d29..24c1b8a5de9b7 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -74,7 +74,7 @@ public function testParsePhpConstantThrowsExceptionWhenUndefined() public function testParsePhpConstantThrowsExceptionOnInvalidType() { $this->expectException('Symfony\Component\Yaml\Exception\ParseException'); - $this->expectExceptionMessageRegExp('#The string "!php/const PHP_INT_MAX" could not be parsed as a constant.*#'); + $this->expectExceptionMessageMatches('#The string "!php/const PHP_INT_MAX" could not be parsed as a constant.*#'); Inline::parse('!php/const PHP_INT_MAX', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); } @@ -599,7 +599,7 @@ public function getBinaryData() public function testParseInvalidBinaryData($data, $expectedMessage) { $this->expectException('Symfony\Component\Yaml\Exception\ParseException'); - $this->expectExceptionMessageRegExp($expectedMessage); + $this->expectExceptionMessageMatches($expectedMessage); Inline::parse($data); } @@ -787,7 +787,7 @@ public function phpConstTagWithEmptyValueProvider() public function testUnquotedExclamationMarkThrows(string $value) { $this->expectException(ParseException::class); - $this->expectExceptionMessageRegExp('/^Using the unquoted scalar value "!" is not supported\. You must quote it at line 1 \(near "/'); + $this->expectExceptionMessageMatches('/^Using the unquoted scalar value "!" is not supported\. You must quote it at line 1 \(near "/'); Inline::parse($value); } diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 4ed79466e6138..6caf8738855a0 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -602,7 +602,7 @@ public function testShortcutKeyUnindentedCollectionException() public function testMultipleDocumentsNotSupportedException() { $this->expectException('Symfony\Component\Yaml\Exception\ParseException'); - $this->expectExceptionMessageRegExp('/^Multiple documents are not supported.+/'); + $this->expectExceptionMessageMatches('/^Multiple documents are not supported.+/'); Yaml::parse(<<<'EOL' # Ranking of 1998 home runs --- @@ -1348,7 +1348,7 @@ public function getBinaryData() public function testParseInvalidBinaryData($data, $expectedMessage) { $this->expectException('Symfony\Component\Yaml\Exception\ParseException'); - $this->expectExceptionMessageRegExp($expectedMessage); + $this->expectExceptionMessageMatches($expectedMessage); $this->parser->parse($data); } @@ -2102,14 +2102,14 @@ public function testParseFile() public function testParsingNonExistentFilesThrowsException() { $this->expectException('Symfony\Component\Yaml\Exception\ParseException'); - $this->expectExceptionMessageRegExp('#^File ".+/Fixtures/nonexistent.yml" does not exist\.$#'); + $this->expectExceptionMessageMatches('#^File ".+/Fixtures/nonexistent.yml" does not exist\.$#'); $this->parser->parseFile(__DIR__.'/Fixtures/nonexistent.yml'); } public function testParsingNotReadableFilesThrowsException() { $this->expectException('Symfony\Component\Yaml\Exception\ParseException'); - $this->expectExceptionMessageRegExp('#^File ".+/Fixtures/not_readable.yml" cannot be read\.$#'); + $this->expectExceptionMessageMatches('#^File ".+/Fixtures/not_readable.yml" cannot be read\.$#'); if ('\\' === \DIRECTORY_SEPARATOR) { $this->markTestSkipped('chmod is not supported on Windows'); }