diff --git a/.appveyor.yml b/.appveyor.yml index 648c9a9849e6e..bba9c562e37c0 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,7 +9,7 @@ cache: init: - SET PATH=c:\php;%PATH% - SET COMPOSER_NO_INTERACTION=1 - - SET SYMFONY_DEPRECATIONS_HELPER=strict + - SET SYMFONY_DEPRECATIONS_HELPER=max[indirect]=7 - SET "SYMFONY_REQUIRE=>=4.2" - SET ANSICON=121x90 (121x90) - SET SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE=1 diff --git a/.travis.yml b/.travis.yml index b1f4265d25f82..54171b24066de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ matrix: env: php_extra="7.2" - php: 7.3 env: deps=high - - php: 7.4snapshot + - php: 7.4 env: deps=low fast_finish: true @@ -290,7 +290,8 @@ install: else echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}" - tfold src/Symfony/Component/Console.tty $PHPUNIT --group tty + tfold src/Symfony/Component/Console.tty $PHPUNIT src/Symfony/Component/Console --group tty + tfold src/Symfony/Bridge/Twig.tty $PHPUNIT src/Symfony/Bridge/Twig --group tty if [[ $PHP = ${MIN_PHP%.*} ]]; then export PHP=$MIN_PHP diff --git a/CHANGELOG-4.3.md b/CHANGELOG-4.3.md index 3695556e82fad..9c95e7e327a31 100644 --- a/CHANGELOG-4.3.md +++ b/CHANGELOG-4.3.md @@ -7,6 +7,84 @@ in 4.3 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.3.0...v4.3.1 +* 4.3.10 (2020-01-21) + + * bug #35364 [Yaml] Throw on unquoted exclamation mark (fancyweb) + * bug #35065 [Security] Use supportsClass in addition to UnsupportedUserException (linaori) + * bug #35343 [Security] Fix RememberMe with null password (jderusse) + * bug #34223 [DI] Suggest typed argument when binding fails with untyped argument (gudfar) + * bug #35324 [HttpClient] Fix strict parsing of response status codes (Armando-Walmeric) + * bug #35318 [Yaml] fix PHP const mapping keys using the inline notation (xabbuh) + * bug #35304 [HttpKernel] Fix that no-cache MUST revalidate with the origin (mpdude) + * bug #35299 Avoid `stale-if-error` in FrameworkBundle's HttpCache if kernel.debug = true (mpdude) + * bug #35151 [DI] deferred exceptions in ResolveParameterPlaceHoldersPass (Islam93) + * bug #35278 [EventDispatcher] expand listener in place (xabbuh) + * bug #35254 [PHPUnit-Bridge] Fail-fast in simple-phpunit if one of the passthru() commands fails (mpdude) + * bug #35261 [Routing] Fix using a custom matcher & generator dumper class (fancyweb) + * bug #34643 [Dotenv] Fixed infinite loop with missing quote followed by quoted value (naitsirch) + * bug #35239 [Security\Http] Prevent canceled remember-me cookie from being accepted (chalasr) + * bug #35267 [Debug] fix ClassNotFoundFatalErrorHandler (nicolas-grekas) + * bug #35193 [TwigBridge] button_widget now has its title attr translated even if its label = null or false (stephen-lewis) + * bug #35219 [PhpUnitBridge] When using phpenv + phpenv-composer plugin, composer executable is wrapped into a bash script (oleg-andreyev) + * bug #35150 [Messenger] Added check if json_encode succeeded (toooni) + * bug #35170 [FrameworkBundle][TranslationUpdateCommand] Do not output positive feedback on stderr (fancyweb) + * bug #35223 [HttpClient] Don't read from the network faster than the CPU can deal with (nicolas-grekas) + * bug #35214 [DI] DecoratorServicePass should keep container.service_locator on the decorated definition (malarzm) + * bug #35210 [HttpClient] NativeHttpClient should not send >1.1 protocol version (nicolas-grekas) + * bug #33672 [Mailer] Remove line breaks in email attachment content (Stuart Fyfe) + * bug #35101 [Routing] Fix i18n routing when the url contains the locale (fancyweb) + * bug #35124 [TwigBridge][Form] Added missing help messages in form themes (cmen) + * bug #35168 [HttpClient] fix capturing SSL certificates with NativeHttpClient (nicolas-grekas) + * bug #35134 [PropertyInfo] Fix BC issue in phpDoc Reflection library (jaapio) + * bug #35173 [Mailer][MailchimpBridge] Fix missing attachments when sending via Mandrill API (vilius-g) + * bug #35172 [Mailer][MailchimpBridge] Fix incorrect sender address when sender has name (vilius-g) + * bug #35125 [Translator] fix performance issue in MessageCatalogue and catalogue operations (ArtemBrovko) + * bug #35120 [HttpClient] fix scheduling pending NativeResponse (nicolas-grekas) + * bug #35117 [Cache] do not overwrite variable value (xabbuh) + * bug #35113 [VarDumper] Fix "Undefined index: argv" when using CliContextProvider (xepozz) + * bug #35103 [Translation] Use `locale_parse` for computing fallback locales (alanpoulain) + * bug #35094 [Console] Fix filtering out identical alternatives when there is a command loader (fancyweb) + * bug #35039 [DI] skip looking for config class when the extension class is anonymous (nicolas-grekas) + * bug #35049 [ProxyManager] fix generating proxies for root-namespaced classes (nicolas-grekas) + * bug #35022 [Dotenv] FIX missing getenv (mccullagh) + * bug #35025 [HttpClient][Psr18Client] Remove Psr18ExceptionTrait (fancyweb) + * bug #35014 [HttpClient] make pushed responses retry-able (nicolas-grekas) + * bug #35010 [VarDumper] ignore failing __debugInfo() (nicolas-grekas) + * bug #34998 [DI] fix auto-binding service providers to their service subscribers (nicolas-grekas) + * bug #33670 [DI] Service locators can't be decorated (malarzm) + * bug #35000 [Console][SymfonyQuestionHelper] Handle multibytes question choices keys and custom prompt (fancyweb) + * bug #34996 Fix displaying anonymous classes on PHP 7.4 (nicolas-grekas) + * bug #29839 [Validator] fix comparisons with null values at property paths (xabbuh) + * bug #34900 [DoctrineBridge] Fixed submitting invalid ids when using queries with limit (HeahDude) + * bug #34791 [Serializer] Skip uninitialized (PHP 7.4) properties in PropertyNormalizer and ObjectNormalizer (vudaltsov) + * bug #34956 [Messenger][AMQP] Use delivery_mode=2 by default (lyrixx) + * bug #34915 [FrameworkBundle] Fix invalid Windows path normalization in TemplateNameParser (mvorisek) + * bug #34981 stop using deprecated Doctrine persistence classes (xabbuh) + * bug #34904 [Validator][ConstraintValidator] Safe fail on invalid timezones (fancyweb) + * bug #34955 Require doctrine/persistence ^1.3 (nicolas-grekas) + * bug #34923 [DI] Fix support for immutable setters in CallTrait (Lctrs) + * bug #34918 [Translation] fix memoryleak in PhpFileLoader (nicolas-grekas) + * bug #34920 [Routing] fix memoryleak when loading compiled routes (nicolas-grekas) + * bug #34787 [Cache] Propagate expiry when syncing items in ChainAdapter (trvrnrth) + * bug #34896 [Cache] fix memory leak when using PhpFilesAdapter (nicolas-grekas) + * bug #34438 [HttpFoundation] Use `Cache-Control: must-revalidate` only if explicit lifetime has been given (mpdude) + * bug #34449 [Yaml] Implement multiline string as scalar block for tagged values (natepage) + * bug #34601 [MonologBridge] Fix debug processor datetime type (mRoca) + * bug #34842 [ExpressionLanguage] Process division by zero (tigr1991) + * bug #34902 [PropertyAccess] forward caught exception (xabbuh) + * bug #34888 [TwigBundle] add tags before processing them (xabbuh) + * bug #34762 [Config] never try loading failed classes twice with ClassExistenceResource (nicolas-grekas) + * bug #34839 [Cache] fix memory leak when using PhpArrayAdapter (nicolas-grekas) + * bug #34812 [Yaml] fix parsing negative octal numbers (xabbuh) + * bug #34854 [Messenger] gracefully handle missing event dispatchers (xabbuh) + * bug #34788 [SecurityBundle] Properly escape regex in AddSessionDomainConstraintPass (fancyweb) + * bug #34755 [FrameworkBundle] resolve service locators in `debug:*` commands (nicolas-grekas) + * bug #34832 [Validator] Allow underscore character "_" in URL username and password (romainneutron) + * bug #34776 [DI] fix resolving bindings for named TypedReference (nicolas-grekas) + * bug #34738 [SecurityBundle] Passwords are not encoded when algorithm set to "true" (nieuwenhuisen) + * bug #34779 [Security] do not validate passwords when the hash is null (xabbuh) + * bug #34757 [DI] Fix making the container path-independent when the app is in /app (nicolas-grekas) + * 4.3.9 (2019-12-01) * bug #34649 more robust initialization from request (dbu) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ccca55ab4bb11..9a6707977b3ec 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -19,8 +19,8 @@ Symfony is the result of the work of many people who made the code better - Jakub Zalas (jakubzalas) - Javier Eguiluz (javier.eguiluz) - Roland Franssen (ro0) - - Johannes S (johannes) - Grégoire Pineau (lyrixx) + - Johannes S (johannes) - Kris Wallsmith (kriswallsmith) - Yonel Ceruto (yonelceruto) - Hugo Hamon (hhamon) @@ -36,13 +36,13 @@ Symfony is the result of the work of many people who made the code better - Martin Hasoň (hason) - Hamza Amrouche (simperfit) - Jeremy Mikola (jmikola) - - Jean-François Simon (jfsimon) - Jules Pietri (heah) + - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) - Igor Wiedler (igorw) - Jérémy DERUSSÉ (jderusse) - - Eriksen Costa (eriksencosta) - Thomas Calvet (fancyweb) + - Eriksen Costa (eriksencosta) - Guilhem Niot (energetick) - Sarah Khalil (saro0h) - Tobias Nyholm (tobias) @@ -57,15 +57,15 @@ Symfony is the result of the work of many people who made the code better - Francis Besset (francisbesset) - stealth35 ‏ (stealth35) - Alexander Mols (asm89) - - Konstantin Myakshin (koc) - Matthias Pigulla (mpdude) + - Konstantin Myakshin (koc) - Bulat Shakirzyanov (avalanche123) + - Valentin Udaltsov (vudaltsov) - Grégoire Paris (greg0ire) - Saša Stamenković (umpirsky) - Peter Rehm (rpet) - Kevin Bond (kbond) - Henrik Bjørnskov (henrikbjorn) - - Valentin Udaltsov (vudaltsov) - Miha Vrhovnik - Diego Saint Esteben (dii3g0) - Gábor Egyed (1ed) @@ -91,11 +91,11 @@ Symfony is the result of the work of many people who made the code better - Henrik Westphal (snc) - Dariusz Górecki (canni) - David Buchmann (dbu) + - Graham Campbell (graham) - Dariusz Ruminski - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) - - Graham Campbell (graham) - Daniel Holmes (dholmes) - Toni Uebernickel (havvg) - Bart van den Burg (burgov) @@ -109,20 +109,21 @@ Symfony is the result of the work of many people who made the code better - Maxime STEINHAUSSER - Michal Piotrowski (eventhorizon) - Tim Nagel (merk) + - Baptiste Clavié (talus) - Chris Wilkinson (thewilkybarkid) - Brice BERNARD (brikou) - - Baptiste Clavié (talus) - marc.weistroff - Tomáš Votruba (tomas_votruba) + - Peter Kokot (maastermedia) - Jérôme Vasseur (jvasseur) - lenar - Alexander Schwenn (xelaris) - Włodzimierz Gajda (gajdaw) - Sebastiaan Stok (sstok) - Adrien Brault (adrienbrault) - - Peter Kokot (maastermedia) - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) + - Teoh Han Hui (teohhanhui) - Colin Frei - Oskar Stark (oskarstark) - Javier Spagnoletti (phansys) @@ -131,13 +132,12 @@ Symfony is the result of the work of many people who made the code better - Daniel Wehner (dawehner) - excelwebzone - Gordon Franke (gimler) - - Teoh Han Hui (teohhanhui) + - Tugdual Saunier (tucksaun) - Fabien Pennequin (fabienpennequin) - Théo FIDRY (theofidry) - Eric GELOEN (gelo) - Joel Wurtz (brouznouf) - Lars Strojny (lstrojny) - - Tugdual Saunier (tucksaun) - Jannik Zschiesche (apfelbox) - Robert Schönthal (digitalkaoz) - Gregor Harlan (gharlan) @@ -209,6 +209,7 @@ Symfony is the result of the work of many people who made the code better - Mario A. Alvarez Garcia (nomack84) - Dennis Benkert (denderello) - DQNEO + - Andre Rømcke (andrerom) - mcfedr (mcfedr) - Ben Davies (bendavies) - Gary PEGEOT (gary-p) @@ -233,7 +234,6 @@ Symfony is the result of the work of many people who made the code better - Pierre Minnieur (pminnieur) - fivestar - Dominique Bongiraud - - Andre Rømcke (andrerom) - Jeremy Livingston (jeremylivingston) - Michael Lee (zerustech) - Matthieu Auger (matthieuauger) @@ -249,6 +249,7 @@ Symfony is the result of the work of many people who made the code better - Michele Orselli (orso) - Sven Paulus (subsven) - Maxime Veber (nek-) + - Anthony GRASSIOT (antograssiot) - Rui Marinho (ruimarinho) - Eugene Wissner - Pascal Montoya @@ -257,8 +258,10 @@ Symfony is the result of the work of many people who made the code better - Tristan Darricau (nicofuma) - Victor Bocharsky (bocharsky_bw) - Marcel Beerta (mazen) + - Maxime Helias (maxhelias) - Pavel Batanov (scaytrase) - Mantis Development + - David Prévot - Loïc Faugeron - Hidde Wieringa (hiddewie) - dFayet @@ -285,7 +288,6 @@ Symfony is the result of the work of many people who made the code better - Xavier Montaña Carreras (xmontana) - Rémon van de Kamp (rpkamp) - Mickaël Andrieu (mickaelandrieu) - - Anthony GRASSIOT (antograssiot) - Xavier Perez - Arjen Brouwer (arjenjb) - Katsuhiro OGAWA @@ -311,13 +313,12 @@ Symfony is the result of the work of many people who made the code better - Smaine Milianni (ismail1432) - Chekote - François Pluchino (francoispluchino) + - Christopher Hertel (chertel) - Antoine Makdessi (amakdessi) - Thomas Adam - Jhonny Lidfors (jhonne) - Diego Agulló (aeoris) - jdhoek - - Maxime Helias (maxhelias) - - David Prévot - Bob den Otter (bopp) - Thomas Schulz (king2500) - Frank de Jonge (frenkynet) @@ -342,6 +343,7 @@ Symfony is the result of the work of many people who made the code better - Arjen van der Meijden - Mathieu Lechat - Marc Weistroff (futurecat) + - Damien Alexandre (damienalexandre) - Simon Mönch (sm) - Christian Schmidt - Patrick Landolt (scube) @@ -349,6 +351,7 @@ Symfony is the result of the work of many people who made the code better - David Badura (davidbadura) - Chad Sikorra (chadsikorra) - Chris Smith (cs278) + - Thomas Bisignani (toma) - Florian Klein (docteurklein) - Manuel Kiessling (manuelkiessling) - Atsuhiro KUBO (iteman) @@ -407,18 +410,17 @@ Symfony is the result of the work of many people who made the code better - Tomasz Kowalczyk (thunderer) - Artur Eshenbrener - Timo Bakx (timobakx) - - Damien Alexandre (damienalexandre) - Thomas Perez (scullwm) - Saif Eddin Gmati (azjezz) - Felix Labrecque - Yaroslav Kiliba - Terje Bråten + - Tien Vo (tienvx) - Robbert Klarenbeek (robbertkl) - Eric Masoero (eric-masoero) - JhonnyL - hossein zolfi (ocean) - Clément Gautier (clementgautier) - - Thomas Bisignani (toma) - Dāvis Zālītis (k0d3r1s) - Sanpi - Eduardo Gulias (egulias) @@ -429,6 +431,7 @@ Symfony is the result of the work of many people who made the code better - Grzegorz (Greg) Zdanowski (kiler129) - Iker Ibarguren (ikerib) - Kirill chEbba Chebunin (chebba) + - Rokas Mikalkėnas (rokasm) - Greg Thornton (xdissent) - Martin Hujer (martinhujer) - Alex Bowers @@ -443,7 +446,6 @@ Symfony is the result of the work of many people who made the code better - Michele Locati - Pavel Volokitin (pvolok) - Valentine Boineau (valentineboineau) - - Christopher Hertel (chertel) - Arthur de Moulins (4rthem) - Matthias Althaus (althaus) - Nicolas Dewez (nicolas_dewez) @@ -476,6 +478,7 @@ Symfony is the result of the work of many people who made the code better - Oscar Cubo Medina (ocubom) - Karel Souffriau - Christophe L. (christophelau) + - Sander Toonen (xatoo) - Anthon Pang (robocoder) - Michael Käfer (michael_kaefer) - Sébastien Santoro (dereckson) @@ -502,6 +505,7 @@ Symfony is the result of the work of many people who made the code better - Olivier Dolbeau (odolbeau) - Jan Rosier (rosier) - Alessandro Lai (jean85) + - Desjardins Jérôme (jewome62) - Arturs Vonda - Josip Kruslin - Matthew Smeets @@ -529,7 +533,6 @@ Symfony is the result of the work of many people who made the code better - Gonzalo Vilaseca (gonzalovilaseca) - Tarmo Leppänen (tarlepp) - Marcin Sikoń (marphi) - - Tien Vo (tienvx) - Denis Brumann (dbrumann) - Dominik Zogg (dominik.zogg) - Marek Pietrzak @@ -540,6 +543,7 @@ Symfony is the result of the work of many people who made the code better - Gintautas Miselis - Rob Bast - Roberto Espinoza (respinoza) + - Emanuele Panzeri (thepanz) - Soufian EZ-ZANTAR (soezz) - Zander Baldwin - Gocha Ossinkine (ossinkine) @@ -603,7 +607,6 @@ Symfony is the result of the work of many people who made the code better - Adam Szaraniec (mimol) - Dariusz Ruminski - Erik Trapman (eriktrapman) - - Rokas Mikalkėnas (rokasm) - De Cock Xavier (xdecock) - Almog Baku (almogbaku) - Karoly Gossler (connorhu) @@ -648,11 +651,11 @@ Symfony is the result of the work of many people who made the code better - Jeremy Benoist - fritzmg - Lenar Lõhmus - - Sander Toonen (xatoo) - Benjamin Laugueux (yzalis) - Zach Badgett (zachbadgett) - Aurélien Fredouelle - Pavel Campr (pcampr) + - Andrii Dembitskyi - Johnny Robeson (johnny) - Marko Kaznovac (kaznovac) - Disquedur @@ -694,7 +697,6 @@ Symfony is the result of the work of many people who made the code better - Sinan Eldem - Alexandre Dupuy (satchette) - Malte Blättermann - - Desjardins Jérôme (jewome62) - Simeon Kolev (simeon_kolev9) - Joost van Driel (j92) - Jonas Elfering @@ -762,11 +764,12 @@ Symfony is the result of the work of many people who made the code better - Giso Stallenberg (gisostallenberg) - Michael Devery (mickadoo) - Antoine Corcy + - Ahmed Ashraf (ahmedash95) - Sascha Grossenbacher - - Emanuele Panzeri (thepanz) - Szijarto Tamas - Robin Lehrmann (robinlehrmann) - Catalin Dan + - Soner Sayakci - Jaroslav Kuba - Kristijan Kanalas - Stephan Vock @@ -825,11 +828,13 @@ 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) - Jeroen van den Enden (stoefke) - Pascal Helfenstein - Baldur Rensch (brensch) - Pierre Rineau + - Vilius Grigaliūnas - Vladyslav Petrovych - Alex Xandra Albert Sim - Carson Full @@ -946,6 +951,7 @@ Symfony is the result of the work of many people who made the code better - Patrick Allaert - Gustavo Falco (gfalco) - Matt Robinson (inanimatt) + - Kristof Van Cauwenbergh (kristofvc) - Aleksey Podskrebyshev - Calin Mihai Pristavu - David Marín Carreño (davefx) @@ -984,6 +990,7 @@ Symfony is the result of the work of many people who made the code better - Thomas Landauer - 243083df - Thibault Duplessis + - Rimas Kudelis - Marc Abramowitz - Martijn Evers - Tony Tran @@ -996,12 +1003,12 @@ Symfony is the result of the work of many people who made the code better - Johnson Page (jwpage) - Ruben Gonzalez (rubenruateltek) - Michael Roterman (wtfzdotnet) - - Andrii Dembitskyi - Arno Geurts - Adán Lobato (adanlobato) - Ian Jenkins (jenkoian) - Marcos Gómez Vilches (markitosgv) - Matthew Davis (mdavis1982) + - Markus S. (staabm) - Maks - Antoine LA - den @@ -1187,7 +1194,6 @@ Symfony is the result of the work of many people who made the code better - Sergii Smertin (nfx) - Mikkel Paulson - Michał Strzelecki - - Soner Sayakci - hugofonseca (fonsecas72) - Marc Duboc (icemad) - Matthias Krauser (mkrauser) @@ -1241,6 +1247,7 @@ Symfony is the result of the work of many people who made the code better - Jeremy Bush - wizhippo - Thomason, James + - Dario Savella - Gordienko Vladislav - marie - Viacheslav Sychov @@ -1291,6 +1298,7 @@ Symfony is the result of the work of many people who made the code better - Oxan van Leeuwen - pkowalczyk - Soner Sayakci + - Koen Reiniers (koenre) - Max Voloshin (maxvoloshin) - Nicolas Fabre (nfabre) - Raul Rodriguez (raul782) @@ -1306,6 +1314,8 @@ Symfony is the result of the work of many people who made the code better - Felicitus - Krzysztof Przybyszewski - alexpozzi + - Vladimir + - Jorge Vahldick (jvahldick) - Frederic Godfrin - Paul Matthews - Jakub Kisielewski @@ -1752,7 +1762,6 @@ Symfony is the result of the work of many people who made the code better - downace - Aarón Nieves Fernández - Mike Meier - - Vilius Grigaliūnas - Kirill Saksin - Koalabaerchen - michalmarcinkowski @@ -1765,6 +1774,7 @@ Symfony is the result of the work of many people who made the code better - efeen - Nicolas Pion - Muhammed Akbulut + - Roy-Orbison - Aaron Somi - Michał Dąbrowski (defrag) - Konstantin Grachev (grachevko) @@ -1802,12 +1812,14 @@ Symfony is the result of the work of many people who made the code better - Alexander Li (aweelex) - Bram Van der Sype (brammm) - Guile (guile) + - Mark Beech (jaybizzle) - Julien Moulin (lizjulien) - Raito Akehanareru (raito) - Mauro Foti (skler) - Yannick Warnier (ywarnier) - Kevin Decherf - Jason Woods + - Maria Grazia Patteri - klemens - dened - Dmitry Korotovsky @@ -1823,6 +1835,7 @@ Symfony is the result of the work of many people who made the code better - Sören Bernstein - devel - taiiiraaa + - Ali Tavafi - Trevor Suarez - gedrox - Bohan Yang @@ -1959,6 +1972,7 @@ Symfony is the result of the work of many people who made the code better - Juan M Martínez - Gilles Gauthier - Pavinthan + - Sylvain METAYER - ddebree - Kuba Werłos - Gyula Szucs @@ -2015,6 +2029,7 @@ Symfony is the result of the work of many people who made the code better - Marcin Szepczynski (szepczynski) - Cyrille Jouineau (tuxosaurus) - Vladimir Chernyshev (volch) + - Wim Godden (wimg) - Yorkie Chadwick (yorkie76) - GuillaumeVerdon - Philipp Keck @@ -2026,6 +2041,7 @@ Symfony is the result of the work of many people who made the code better - Taylan Kasap - Michael Orlitzky - Nicolas A. Bérard-Nault + - Quentin Favrie - Saem Ghani - Stefan Oderbolz - Curtis @@ -2051,12 +2067,10 @@ Symfony is the result of the work of many people who made the code better - Jeffrey Moelands (jeffreymoelands) - Hugo Alliaume (kocal) - Simon CONSTANS (kosssi) - - Kristof Van Cauwenbergh (kristofvc) - Dennis Langen (nijusan) - Paulius Jarmalavičius (pjarmalavicius) - Ramon Henrique Ornelas (ramonornela) - Ricardo de Vries (ricknox) - - Markus S. (staabm) - Thomas Dutrion (theocrite) - Till Klampaeckel (till) - Tobias Weinert (tweini) @@ -2074,6 +2088,7 @@ Symfony is the result of the work of many people who made the code better - Cas - Dusan Kasan - Michael Steininger + - Nardberjean - Karolis - Myke79 - Brian Debuire @@ -2145,7 +2160,6 @@ Symfony is the result of the work of many people who made the code better - Daniel STANCU - Ryan Rud - Ondrej Slinták - - Rimas Kudelis - vlechemin - Brian Corrigan - Ladislav Tánczos @@ -2263,6 +2277,7 @@ Symfony is the result of the work of many people who made the code better - Sebastian Landwehr (dword123) - Adel ELHAIBA (eadel) - Damián Nohales (eagleoneraptor) + - Jordane VASPARD (elementaire) - Elliot Anderson (elliot) - Fabien D. (fabd) - Carsten Eilers (fnc) @@ -2277,6 +2292,7 @@ Symfony is the result of the work of many people who made the code better - Peter Orosz (ill_logical) - Imangazaliev Muhammad (imangazaliev) - j0k (j0k) + - Jeremie Broutier (jbroutier) - joris de wit (jdewit) - Jérémy CROMBEZ (jeremy) - Jose Manuel Gonzalez (jgonzalez) @@ -2293,6 +2309,7 @@ Symfony is the result of the work of many people who made the code better - samuel laulhau (lalop) - Laurent Bachelier (laurentb) - Luís Cobucci (lcobucci) + - Jérémy (libertjeremy) - Mehdi Achour (machour) - Matthieu Mota (matthieumota) - Matthieu Moquet (mattketmo) @@ -2300,12 +2317,12 @@ Symfony is the result of the work of many people who made the code better - Michal Čihař (mcihar) - Matt Drollette (mdrollette) - Adam Monsen (meonkeys) + - Hugo Monteiro (monteiro) - Ala Eddine Khefifi (nayzo) - emilienbouard (neime) - Nicholas Byfleet (nickbyfleet) - Marco Petersen (ocrampete16) - ollie harridge (ollietb) - - Dimitri Gritsajuk (ottaviano) - Paul Andrieux (paulandrieux) - Paulo Ribeiro (paulo) - Paweł Szczepanek (pauluz) @@ -2333,6 +2350,7 @@ Symfony is the result of the work of many people who made the code better - Julien Sanchez (sumbobyboys) - Guillermo Gisinger (t3chn0r) - Markus Tacker (tacker) + - Thiago Cordeiro (thiagocordeiro) - Tom Newby (tomnewbyau) - Andrew Clark (tqt_andrew_clark) - David Lumaye (tux1124) @@ -2341,6 +2359,7 @@ Symfony is the result of the work of many people who made the code better - Moritz Kraft (userfriendly) - Víctor Mateo (victormateo) - Vincent (vincent1870) + - Vincent MOULENE (vints24) - David Herrmann (vworldat) - Eugene Babushkin (warl) - Wouter Sioen (wouter_sioen) @@ -2365,6 +2384,7 @@ Symfony is the result of the work of many people who made the code better - damaya - Kevin Weber - Ben Scott + - Alexandru Năstase - Dionysis Arvanitis - Sergey Fedotov - Konstantin Scheumann @@ -2378,6 +2398,7 @@ Symfony is the result of the work of many people who made the code better - Zander Baldwin - Philipp Scheit - max + - Alexander Bauer (abauer) - Ahmad Mayahi (ahmadmayahi) - Mohamed Karnichi (amiral) - Andrew Carter (andrewcarteruk) diff --git a/LICENSE b/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/UPGRADE-4.2.md b/UPGRADE-4.2.md index c1bb03f5aebcb..8f7cc54411aa9 100644 --- a/UPGRADE-4.2.md +++ b/UPGRADE-4.2.md @@ -192,15 +192,15 @@ HttpKernel * The `Kernel::getRootDir()` and the `kernel.root_dir` parameter have been deprecated * The `KernelInterface::getName()` and the `kernel.name` parameter have been deprecated - * Deprecated the first and second constructor argument of `ConfigDataCollector` - * Deprecated `ConfigDataCollector::getApplicationName()` + * Deprecated the first and second constructor argument of `ConfigDataCollector` + * Deprecated `ConfigDataCollector::getApplicationName()` * Deprecated `ConfigDataCollector::getApplicationVersion()` Messenger --------- * The `MiddlewareInterface::handle()` and `SenderInterface::send()` methods must now return an `Envelope` instance. - * The return value of handlers isn't forwarded anymore by middleware and buses. + * The return value of handlers isn't forwarded anymore by middleware and buses. If you used to return a value, e.g in query bus handlers, you can either: - get the result from the `HandledStamp` in the envelope returned by the bus. - use the `HandleTrait` to leverage a message bus, expecting a single, synchronous message handling and returning its result. @@ -214,15 +214,15 @@ Messenger $query->setResult($yourResult); ``` * The `EnvelopeAwareInterface` was removed and the `MiddlewareInterface::handle()` method now requires an `Envelope` object - as first argument. When using built-in middleware with the provided `MessageBus`, you will not have to do anything. - If you use your own `MessageBusInterface` implementation, you must wrap the message in an `Envelope` before passing it to middleware. + as first argument. When using built-in middleware with the provided `MessageBus`, you will not have to do anything. + If you use your own `MessageBusInterface` implementation, you must wrap the message in an `Envelope` before passing it to middleware. If you created your own middleware, you must change the signature to always expect an `Envelope`. * The `MiddlewareInterface::handle()` second argument (`callable $next`) has changed in favor of a `StackInterface` instance. - When using built-in middleware with the provided `MessageBus`, you will not have to do anything. - If you use your own `MessageBusInterface` implementation, you can use the `StackMiddleware` implementation. + When using built-in middleware with the provided `MessageBus`, you will not have to do anything. + If you use your own `MessageBusInterface` implementation, you can use the `StackMiddleware` implementation. If you created your own middleware, you must change the signature to always expect an `StackInterface` instance and call `$stack->next()->handle($envelope, $stack)` instead of `$next` to call the next middleware: - + Before: ```php public function handle($message, callable $next): Envelope @@ -230,7 +230,7 @@ Messenger // do something before $message = $next($message); // do something after - + return $message; } ``` @@ -242,7 +242,7 @@ Messenger // do something before $envelope = $stack->next()->handle($envelope, $stack); // do something after - + return $envelope; } ``` @@ -251,7 +251,7 @@ Messenger respectively `ReceivedStamp`, `ValidationStamp`, `SerializerStamp` and moved to the `Stamp` namespace. * `AllowNoHandlerMiddleware` has been removed in favor of a new constructor argument on `HandleMessageMiddleware` * The `ConsumeMessagesCommand` class now takes an instance of `Psr\Container\ContainerInterface` - as first constructor argument, i.e a message bus locator. The CLI command now expects a mandatory + as first constructor argument, i.e a message bus locator. The CLI command now expects a mandatory `--bus` option value if there is more than one bus in the locator. * `MessageSubscriberInterface::getHandledMessages()` return value has changed. The value of an array item needs to be an associative array or the method name. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 302cea6dec969..6e136a6cc387f 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -416,7 +416,7 @@ Workflow * `ClassInstanceSupportStrategy` has been removed, use `InstanceOfSupportStrategy` instead. * `WorkflowInterface::apply()` has a third argument: `array $context = []`. * `MarkingStoreInterface::setMarking()` has a third argument: `array $context = []`. - * Removed support of `initial_place`. Use `initial_places` instead. + * Removed support of `initial_place`. Use `initial_marking` instead. * `MultipleStateMarkingStore` has been removed. Use `MethodMarkingStore` instead. * `DefinitionBuilder::setInitialPlace()` has been removed, use `DefinitionBuilder::setInitialPlaces()` instead. diff --git a/composer.json b/composer.json index a379c17200f74..40d02efe38782 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": "^7.1.3", "ext-xml": "*", "doctrine/event-manager": "~1.0", - "doctrine/persistence": "~1.0", + "doctrine/persistence": "^1.3", "fig/link-util": "^1.0", "twig/twig": "^1.41|^2.10", "psr/cache": "~1.0", diff --git a/link b/link index 08687bbba5cc7..cea19663e8873 100755 --- a/link +++ b/link @@ -23,11 +23,14 @@ use Symfony\Component\Filesystem\Filesystem; * @author Kévin Dunglas */ +$copy = false !== $k = array_search('--copy', $argv, true); +$copy && array_splice($argv, $k, 1); $pathToProject = $argv[1] ?? getcwd(); if (!is_dir("$pathToProject/vendor/symfony")) { - echo 'Link dependencies to components to a local clone of the main symfony/symfony GitHub repository.'.PHP_EOL.PHP_EOL; - echo "Usage: $argv[0] /path/to/the/project".PHP_EOL.PHP_EOL; + echo 'Link (or copy) dependencies to components to a local clone of the main symfony/symfony GitHub repository.'.PHP_EOL.PHP_EOL; + echo "Usage: $argv[0] /path/to/the/project".PHP_EOL; + echo ' Use `--copy` to copy dependencies instead of symlink'.PHP_EOL.PHP_EOL; echo "The directory \"$pathToProject\" does not exist or the dependencies are not installed, did you forget to run \"composer install\" in your project?".PHP_EOL; exit(1); } @@ -50,7 +53,7 @@ foreach ($directories as $dir) { foreach (glob("$pathToProject/vendor/symfony/*", GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { $package = 'symfony/'.basename($dir); - if (is_link($dir)) { + if (!$copy && is_link($dir)) { echo "\"$package\" is already a symlink, skipping.".PHP_EOL; continue; } @@ -59,11 +62,17 @@ foreach (glob("$pathToProject/vendor/symfony/*", GLOB_ONLYDIR | GLOB_NOSORT) as continue; } - $sfDir = '\\' === DIRECTORY_SEPARATOR ? $sfPackages[$package] : $filesystem->makePathRelative($sfPackages[$package], dirname(realpath($dir))); + $sfDir = ('\\' === DIRECTORY_SEPARATOR || $copy) ? $sfPackages[$package] : $filesystem->makePathRelative($sfPackages[$package], dirname(realpath($dir))); $filesystem->remove($dir); - $filesystem->symlink($sfDir, $dir); - echo "\"$package\" has been linked to \"$sfPackages[$package]\".".PHP_EOL; + + if ($copy) { + $filesystem->mirror($sfDir, $dir); + echo "\"$package\" has been copied from \"$sfPackages[$package]\".".PHP_EOL; + } else { + $filesystem->symlink($sfDir, $dir); + echo "\"$package\" has been linked to \"$sfPackages[$package]\".".PHP_EOL; + } } foreach (glob("$pathToProject/var/cache/*", GLOB_NOSORT) as $cacheDir) { diff --git a/phpunit b/phpunit index b07ca07d06a20..200a9c28b4c34 100755 --- a/phpunit +++ b/phpunit @@ -21,4 +21,14 @@ if (!getenv('SYMFONY_PATCH_TYPE_DECLARATIONS')) { putenv('SYMFONY_PATCH_TYPE_DECLARATIONS=deprecations=1'); } putenv('SYMFONY_PHPUNIT_DIR='.__DIR__.'/.phpunit'); + +if (!getenv('SYMFONY_DEPRECATIONS_HELPER')) { + foreach ($_SERVER['argv'] as $v) { + if (false !== strpos($v, 'Bridge/Doctrine')) { + putenv('SYMFONY_DEPRECATIONS_HELPER=max[indirect]=7'); + break; + } + } +} + require __DIR__.'/vendor/symfony/phpunit-bridge/bin/simple-phpunit'; diff --git a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php index 9bf22357df895..24cf25be59f3d 100644 --- a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php +++ b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine\CacheWarmer; -use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; /** diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index 0be352033d445..c30e92993e677 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -11,10 +11,10 @@ namespace Symfony\Bridge\Doctrine\DataCollector; -use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\DBAL\Logging\DebugStack; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index 8dd6cb19ff0e5..09979e9b6b3d6 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -239,11 +239,7 @@ protected function assertValidMappingConfiguration(array $mappingConfig, $object } if (!\in_array($mappingConfig['type'], ['xml', 'yml', 'annotation', 'php', 'staticphp'])) { - throw new \InvalidArgumentException(sprintf('Can only configure "xml", "yml", "annotation", "php" or '. - '"staticphp" through the DoctrineBundle. Use your own bundle to configure other metadata drivers. '. - 'You can register them by adding a new driver to the '. - '"%s" service definition.', $this->getObjectManagerElementName($objectManagerName.'_metadata_driver') - )); + throw new \InvalidArgumentException(sprintf('Can only configure "xml", "yml", "annotation", "php" or "staticphp" through the DoctrineBundle. Use your own bundle to configure other metadata drivers. You can register them by adding a new driver to the "%s" service definition.', $this->getObjectManagerElementName($objectManagerName.'_metadata_driver'))); } } diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php index 90da0b0e86ed5..a905cceccd1e2 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php @@ -143,7 +143,7 @@ public function process(ContainerBuilder $container) $mappingDriverDef = $this->getDriver($container); $chainDriverDefService = $this->getChainDriverServiceName($container); - // Definition for a Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain + // Definition for a Doctrine\Persistence\Mapping\Driver\MappingDriverChain $chainDriverDef = $container->getDefinition($chainDriverDefService); foreach ($this->namespaces as $namespace) { $chainDriverDef->addMethodCall('addDriver', [$mappingDriverDef, $namespace]); diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php index cd040d12a9b03..473d68ebf7be1 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; -use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Persistence\ObjectManager; use Symfony\Component\Form\ChoiceList\ArrayChoiceList; use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; @@ -40,9 +40,8 @@ class DoctrineChoiceLoader implements ChoiceLoaderInterface * passed which optimizes the object loading for one of the Doctrine * mapper implementations. * - * @param ObjectManager $manager The object manager * @param string $class The class name of the loaded objects - * @param IdReader|null $idReader The reader for the object IDs + * @param IdReader $idReader The reader for the object IDs * @param EntityLoaderInterface|null $objectLoader The objects loader */ public function __construct(ObjectManager $manager, string $class, IdReader $idReader = null, EntityLoaderInterface $objectLoader = null) diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php index bcba6a4b7e02f..f56193e81062f 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php @@ -11,8 +11,8 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; -use Doctrine\Common\Persistence\Mapping\ClassMetadata; -use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\Persistence\ObjectManager; use Symfony\Component\Form\Exception\RuntimeException; /** diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index 96f5e2f5f1868..875b08dae95e0 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -55,6 +55,21 @@ public function getEntities() */ public function getEntitiesByIds($identifier, array $values) { + if (null !== $this->queryBuilder->getMaxResults() || null !== $this->queryBuilder->getFirstResult()) { + // an offset or a limit would apply on results including the where clause with submitted id values + // that could make invalid choices valid + $choices = []; + $metadata = $this->queryBuilder->getEntityManager()->getClassMetadata(current($this->queryBuilder->getRootEntities())); + + foreach ($this->getEntities() as $entity) { + if (\in_array(current($metadata->getIdentifierValues($entity)), $values, true)) { + $choices[] = $entity; + } + } + + return $choices; + } + $qb = clone $this->queryBuilder; $alias = current($qb->getRootAliases()); $parameter = 'ORMQueryBuilderLoader_getEntitiesByIds_'.$identifier; diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmExtension.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmExtension.php index 891754a1da08f..c2897c6d9aba8 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmExtension.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmExtension.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine\Form; -use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractExtension; diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index b7f16a3cd6c14..6b097087b1e23 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -11,12 +11,12 @@ namespace Symfony\Bridge\Doctrine\Form; -use Doctrine\Common\Persistence\ManagerRegistry; -use Doctrine\Common\Persistence\Mapping\MappingException; -use Doctrine\Common\Persistence\Proxy; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as LegacyMappingException; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\Mapping\MappingException; +use Doctrine\Persistence\Proxy; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\TypeGuess; diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index a88b90d946b79..65a3368ba2156 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\Doctrine\Form\Type; use Doctrine\Common\Collections\Collection; -use Doctrine\Common\Persistence\ManagerRegistry; -use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager; use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface; use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader; @@ -191,7 +191,6 @@ public function configureOptions(OptionsResolver $resolver) }; $emNormalizer = function (Options $options, $em) { - /* @var ManagerRegistry $registry */ if (null !== $em) { if ($em instanceof ObjectManager) { return $em; @@ -263,7 +262,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setNormalizer('query_builder', $queryBuilderNormalizer); $resolver->setNormalizer('id_reader', $idReaderNormalizer); - $resolver->setAllowedTypes('em', ['null', 'string', 'Doctrine\Common\Persistence\ObjectManager']); + $resolver->setAllowedTypes('em', ['null', 'string', ObjectManager::class]); } /** diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index 2d367a2caa395..f48b7f2f0cf82 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Form\Type; -use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\Query\Parameter; use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ObjectManager; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\OptionsResolver\Options; diff --git a/src/Symfony/Bridge/Doctrine/LICENSE b/src/Symfony/Bridge/Doctrine/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Bridge/Doctrine/LICENSE +++ b/src/Symfony/Bridge/Doctrine/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/Doctrine/ManagerRegistry.php b/src/Symfony/Bridge/Doctrine/ManagerRegistry.php index ae481b572628e..a19be6a0c4071 100644 --- a/src/Symfony/Bridge/Doctrine/ManagerRegistry.php +++ b/src/Symfony/Bridge/Doctrine/ManagerRegistry.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine; -use Doctrine\Common\Persistence\AbstractManagerRegistry; +use Doctrine\Persistence\AbstractManagerRegistry; use ProxyManager\Proxy\LazyLoadingInterface; use Symfony\Component\DependencyInjection\Container; diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineCloseConnectionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineCloseConnectionMiddleware.php index 0996859221d22..1be756160e206 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineCloseConnectionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineCloseConnectionMiddleware.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine\Messenger; -use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php index ca9f65d9debf0..ef9e4302491c2 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine\Messenger; -use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php index 62f0bac51dd7b..c26335cbad389 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine\Messenger; -use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\HandlerFailedException; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index c6d2e52cc1e11..a64f868915143 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -11,13 +11,13 @@ namespace Symfony\Bridge\Doctrine\PropertyInfo; -use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory; -use Doctrine\Common\Persistence\Mapping\MappingException; use Doctrine\DBAL\Types\Type as DBALType; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; +use Doctrine\Persistence\Mapping\ClassMetadataFactory; +use Doctrine\Persistence\Mapping\MappingException; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; diff --git a/src/Symfony/Bridge/Doctrine/RegistryInterface.php b/src/Symfony/Bridge/Doctrine/RegistryInterface.php index 6928f8afd4f9c..5fc792ca0cc23 100644 --- a/src/Symfony/Bridge/Doctrine/RegistryInterface.php +++ b/src/Symfony/Bridge/Doctrine/RegistryInterface.php @@ -11,15 +11,15 @@ namespace Symfony\Bridge\Doctrine; -use Doctrine\Common\Persistence\ManagerRegistry as ManagerRegistryInterface; use Doctrine\ORM\EntityManager; +use Doctrine\Persistence\ManagerRegistry; /** * References Doctrine connections and entity managers. * * @author Fabien Potencier */ -interface RegistryInterface extends ManagerRegistryInterface +interface RegistryInterface extends ManagerRegistry { /** * Gets the default entity manager name. diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index 20f68399571f9..7df455abf4192 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -11,7 +11,10 @@ namespace Symfony\Bridge\Doctrine\Security\User; -use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\Persistence\ObjectManager; +use Doctrine\Persistence\ObjectRepository; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; @@ -83,11 +86,7 @@ public function refreshUser(UserInterface $user) // That's the case when the user has been changed by a form with // validation errors. if (!$id = $this->getClassMetadata()->getIdentifierValues($user)) { - throw new \InvalidArgumentException('You cannot refresh a user '. - 'from the EntityUserProvider that does not contain an identifier. '. - 'The user object has to be serialized with its own identifier '. - 'mapped by Doctrine.' - ); + throw new \InvalidArgumentException('You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.'); } $refreshedUser = $repository->find($id); @@ -107,17 +106,17 @@ public function supportsClass($class) return $class === $this->getClass() || is_subclass_of($class, $this->getClass()); } - private function getObjectManager() + private function getObjectManager(): ObjectManager { return $this->registry->getManager($this->managerName); } - private function getRepository() + private function getRepository(): ObjectRepository { return $this->getObjectManager()->getRepository($this->classOrAlias); } - private function getClass() + private function getClass(): string { if (null === $this->class) { $class = $this->classOrAlias; @@ -132,7 +131,7 @@ private function getClass() return $this->class; } - private function getClassMetadata() + private function getClassMetadata(): ClassMetadata { return $this->getObjectManager()->getClassMetadata($this->classOrAlias); } diff --git a/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php b/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php index 090007c06980a..2ad16dcd4de24 100644 --- a/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php +++ b/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php @@ -13,12 +13,12 @@ use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Cache\ArrayCache; -use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain; -use Doctrine\Common\Persistence\Mapping\Driver\SymfonyFileLocator; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\Mapping\Driver\XmlDriver; +use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; +use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator; use PHPUnit\Framework\TestCase; /** diff --git a/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php b/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php index db9db4f286f0a..f98cba4cefa4d 100644 --- a/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php +++ b/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php @@ -11,10 +11,10 @@ namespace Symfony\Bridge\Doctrine\Test; -use Doctrine\Common\Persistence\ObjectRepository; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Repository\RepositoryFactory; +use Doctrine\Persistence\ObjectRepository; /** * @author Andreas Braun @@ -28,6 +28,8 @@ final class TestRepositoryFactory implements RepositoryFactory /** * {@inheritdoc} + * + * @return ObjectRepository */ public function getRepository(EntityManagerInterface $entityManager, $entityName) { @@ -47,10 +49,7 @@ public function setRepository(EntityManagerInterface $entityManager, $entityName $this->repositoryList[$repositoryHash] = $repository; } - /** - * @return ObjectRepository - */ - private function createRepository(EntityManagerInterface $entityManager, string $entityName) + private function createRepository(EntityManagerInterface $entityManager, string $entityName): ObjectRepository { /* @var $metadata ClassMetadata */ $metadata = $entityManager->getClassMetadata($entityName); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php index 6a33f0680a7a3..0e478d6a794b8 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php @@ -13,6 +13,7 @@ use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Version; +use Doctrine\Persistence\ManagerRegistry; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector; use Symfony\Component\HttpFoundation\Request; @@ -180,7 +181,7 @@ private function createCollector($queries) ->method('getDatabasePlatform') ->willReturn(new MySqlPlatform()); - $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); + $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); $registry ->expects($this->any()) ->method('getConnectionNames') diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php index 6c3f880eaacf9..6655033ab4999 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php @@ -12,7 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; use Doctrine\Common\DataFixtures\FixtureInterface; -use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Persistence\ObjectManager; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php index 0fa4d5676fda1..ee9597ed4975a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; -use Doctrine\Common\Persistence\ObjectManager; -use Doctrine\Common\Persistence\ObjectRepository; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\Persistence\ObjectManager; +use Doctrine\Persistence\ObjectRepository; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader; @@ -76,8 +76,8 @@ class DoctrineChoiceLoaderTest extends TestCase protected function setUp(): void { $this->factory = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface')->getMock(); - $this->om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock(); - $this->repository = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectRepository')->getMock(); + $this->om = $this->getMockBuilder(ObjectManager::class)->getMock(); + $this->repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); $this->class = 'stdClass'; $this->idReader = $this->getMockBuilder('Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader') ->disableOriginalConstructor() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php index d2e101b4cdc58..50f222d8e8c59 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php @@ -11,7 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Form; -use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser; use Symfony\Component\Form\Guess\Guess; @@ -83,10 +85,10 @@ public function requiredProvider() private function getGuesser(ClassMetadata $classMetadata) { - $em = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock(); + $em = $this->getMockBuilder(ObjectManager::class)->getMock(); $em->expects($this->once())->method('getClassMetaData')->with('TestEntity')->willReturn($classMetadata); - $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); + $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); $registry->expects($this->once())->method('getManagers')->willReturn([$em]); return new DoctrineOrmTypeGuesser($registry); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php index d7d7cae5ecda9..23b39b3674cb0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\Type; use Doctrine\ORM\Tools\SchemaTool; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; @@ -34,7 +35,7 @@ class EntityTypePerformanceTest extends FormPerformanceTestCase protected function getExtensions() { - $manager = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); + $manager = $this->getMockBuilder(ManagerRegistry::class)->getMock(); $manager->expects($this->any()) ->method('getManager') diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 0367fc3edfad4..8d02d0157ef17 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -12,10 +12,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\Type; use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Tools\SchemaTool; +use Doctrine\Persistence\ManagerRegistry; use PHPUnit\Framework\MockObject\MockObject; use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension; use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser; @@ -955,6 +955,31 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleIdentifie $this->assertNull($field->getData()); } + public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleIdentifierWithLimit() + { + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $entity2 = new SingleIntIdEntity(2, 'Bar'); + $entity3 = new SingleIntIdEntity(3, 'Baz'); + + $this->persist([$entity1, $entity2, $entity3]); + + $repository = $this->em->getRepository(self::SINGLE_IDENT_CLASS); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, [ + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'query_builder' => $repository->createQueryBuilder('e') + ->where('e.id IN (1, 2, 3)') + ->setMaxResults(1), + 'choice_label' => 'name', + ]); + + $field->submit('3'); + + $this->assertFalse($field->isSynchronized()); + $this->assertNull($field->getData()); + } + public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleAssocIdentifier() { $innerEntity1 = new SingleIntIdNoToStringEntity(1, 'InFoo'); @@ -1228,7 +1253,7 @@ public function testLoaderCachingWithParameters() protected function createRegistryMock($name, $em) { - $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); + $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); $registry->expects($this->any()) ->method('getManager') ->with($this->equalTo($name)) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineCloseConnectionMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineCloseConnectionMiddlewareTest.php index 94cab6314e255..6ab046653efa9 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineCloseConnectionMiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineCloseConnectionMiddlewareTest.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Messenger; -use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Bridge\Doctrine\Messenger\DoctrineCloseConnectionMiddleware; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php index f3aa27f314348..55e5d7fbd2147 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Messenger; -use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Bridge\Doctrine\Messenger\DoctrinePingConnectionMiddleware; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php index d6f15e20137a7..be850277e6841 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrineTransactionMiddlewareTest.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Messenger; -use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index 50edcab8d0ccd..c613495922417 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -12,8 +12,12 @@ namespace Symfony\Bridge\Doctrine\Tests\Security\User; use Doctrine\ORM\Tools\SchemaTool; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager; +use Doctrine\Persistence\ObjectRepository; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Security\User\EntityUserProvider; +use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\User; @@ -58,9 +62,7 @@ public function testLoadUserByUsernameWithUserLoaderRepositoryAndWithoutProperty { $user = new User(1, 1, 'user1'); - $repository = $this->getMockBuilder('Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface') - ->disableOriginalConstructor() - ->getMock(); + $repository = $this->createMock([ObjectRepository::class, UserLoaderInterface::class]); $repository ->expects($this->once()) ->method('loadUserByUsername') @@ -146,7 +148,7 @@ public function testSupportProxy() public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided() { - $repository = $this->getMockBuilder('\Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface')->getMock(); + $repository = $this->createMock([ObjectRepository::class, UserLoaderInterface::class]); $repository->expects($this->once()) ->method('loadUserByUsername') ->with('name') @@ -165,7 +167,7 @@ public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided( public function testLoadUserByUserNameShouldDeclineInvalidInterface() { $this->expectException('InvalidArgumentException'); - $repository = $this->getMockBuilder('\Symfony\Component\Security\Core\User\AdvancedUserInterface')->getMock(); + $repository = $this->createMock(ObjectRepository::class); $provider = new EntityUserProvider( $this->getManager($this->getObjectManager($repository)), @@ -177,7 +179,7 @@ public function testLoadUserByUserNameShouldDeclineInvalidInterface() private function getManager($em, $name = null) { - $manager = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); + $manager = $this->getMockBuilder(ManagerRegistry::class)->getMock(); $manager->expects($this->any()) ->method('getManager') ->with($this->equalTo($name)) @@ -188,7 +190,7 @@ private function getManager($em, $name = null) private function getObjectManager($repository) { - $em = $this->getMockBuilder('\Doctrine\Common\Persistence\ObjectManager') + $em = $this->getMockBuilder(ObjectManager::class) ->setMethods(['getClassMetadata', 'getRepository']) ->getMockForAbstractClass(); $em->expects($this->any()) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index fdf0b550ce2f8..e9e905c89c3a4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -12,11 +12,12 @@ namespace Symfony\Bridge\Doctrine\Tests\Validator\Constraints; use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Persistence\ManagerRegistry; -use Doctrine\Common\Persistence\ObjectManager; -use Doctrine\Common\Persistence\ObjectRepository; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Tools\SchemaTool; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\Persistence\ObjectManager; +use Doctrine\Persistence\ObjectRepository; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Test\TestRepositoryFactory; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity; @@ -76,9 +77,9 @@ protected function setUp(): void parent::setUp(); } - protected function createRegistryMock(ObjectManager $em = null) + protected function createRegistryMock($em = null) { - $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); + $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); $registry->expects($this->any()) ->method('getManager') ->with($this->equalTo(self::EM_NAME)) @@ -89,7 +90,7 @@ protected function createRegistryMock(ObjectManager $em = null) protected function createRepositoryMock() { - $repository = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectRepository') + $repository = $this->getMockBuilder(ObjectRepository::class) ->setMethods(['findByCustom', 'find', 'findAll', 'findOneBy', 'findBy', 'getClassName']) ->getMock() ; @@ -99,7 +100,7 @@ protected function createRepositoryMock() protected function createEntityManagerMock($repositoryMock) { - $em = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager') + $em = $this->getMockBuilder(ObjectManager::class) ->getMock() ; $em->expects($this->any()) @@ -107,7 +108,7 @@ protected function createEntityManagerMock($repositoryMock) ->willReturn($repositoryMock) ; - $classMetadata = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')->getMock(); + $classMetadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); $classMetadata ->expects($this->any()) ->method('hasField') @@ -141,7 +142,7 @@ protected function createValidator() return new UniqueEntityValidator($this->registry); } - private function createSchema(ObjectManager $em) + private function createSchema($em) { $schemaTool = new SchemaTool($em); $schemaTool->createSchema([ diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 312b84a71abc8..35f723aa88d9f 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -11,9 +11,7 @@ namespace Symfony\Bridge\Doctrine\Validator\Constraints; -use Doctrine\Common\Persistence\ManagerRegistry; -use Doctrine\Common\Persistence\Mapping\ClassMetadata; -use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; @@ -42,7 +40,7 @@ public function __construct(ManagerRegistry $registry) public function validate($entity, Constraint $constraint) { if (!$constraint instanceof UniqueEntity) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UniqueEntity'); + throw new UnexpectedTypeException($constraint, UniqueEntity::class); } if (!\is_array($constraint->fields) && !\is_string($constraint->fields)) { @@ -78,7 +76,6 @@ public function validate($entity, Constraint $constraint) } $class = $em->getClassMetadata(\get_class($entity)); - /* @var $class \Doctrine\Common\Persistence\Mapping\ClassMetadata */ $criteria = []; $hasNullValue = false; @@ -179,7 +176,7 @@ public function validate($entity, Constraint $constraint) ->addViolation(); } - private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, $value) + private function formatWithIdentifiers($em, $class, $value) { if (!\is_object($value) || $value instanceof \DateTimeInterface) { return $this->formatValue($value, self::PRETTY_DATE); diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php index 010c051581e70..659bd8569759d 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine\Validator; -use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Component\Validator\ObjectInitializerInterface; /** diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php index 138b5d1cbbeee..3cc7340d89ae9 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php @@ -11,10 +11,10 @@ namespace Symfony\Bridge\Doctrine\Validator; -use Doctrine\Common\Persistence\Mapping\MappingException; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; +use Doctrine\Persistence\Mapping\MappingException; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\Valid; diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 57f31044f1cd2..2274936c3a9c2 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^7.1.3", "doctrine/event-manager": "~1.0", - "doctrine/persistence": "~1.0", + "doctrine/persistence": "^1.3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^1.1" diff --git a/src/Symfony/Bridge/Monolog/LICENSE b/src/Symfony/Bridge/Monolog/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Bridge/Monolog/LICENSE +++ b/src/Symfony/Bridge/Monolog/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php index f4e37ff44e739..432c0b5ae8d5e 100644 --- a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php @@ -33,7 +33,7 @@ public function __invoke(array $record) $hash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : ''; $this->records[$hash][] = [ - 'timestamp' => $record['datetime']->getTimestamp(), + 'timestamp' => $record['datetime'] instanceof \DateTimeInterface ? $record['datetime']->getTimestamp() : strtotime($record['datetime']), 'message' => $record['message'], 'priority' => $record['level'], 'priorityName' => $record['level_name'], diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/ServerLogHandlerTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/ServerLogHandlerTest.php new file mode 100644 index 0000000000000..0664727995a66 --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/ServerLogHandlerTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Monolog\Tests\Handler; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Logger; +use Monolog\Processor\ProcessIdProcessor; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Monolog\Formatter\VarDumperFormatter; +use Symfony\Bridge\Monolog\Handler\ServerLogHandler; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * Tests the ServerLogHandler. + */ +class ServerLogHandlerTest extends TestCase +{ + public function testFormatter() + { + $handler = new ServerLogHandler('tcp://127.0.0.1:9999'); + $this->assertInstanceOf(VarDumperFormatter::class, $handler->getFormatter()); + + $formatter = new JsonFormatter(); + $handler->setFormatter($formatter); + $this->assertSame($formatter, $handler->getFormatter()); + } + + public function testIsHandling() + { + $handler = new ServerLogHandler('tcp://127.0.0.1:9999', Logger::INFO); + $this->assertFalse($handler->isHandling(['level' => Logger::DEBUG]), '->isHandling returns false when no output is set'); + } + + public function testGetFormatter() + { + $handler = new ServerLogHandler('tcp://127.0.0.1:9999'); + $this->assertInstanceOf(VarDumperFormatter::class, $handler->getFormatter(), + '-getFormatter returns VarDumperFormatter by default' + ); + } + + public function testWritingAndFormatting() + { + $host = 'tcp://127.0.0.1:9999'; + $handler = new ServerLogHandler($host, Logger::INFO, false); + $handler->pushProcessor(new ProcessIdProcessor()); + + $infoRecord = [ + 'message' => 'My info message', + 'context' => [], + 'level' => Logger::INFO, + 'level_name' => Logger::getLevelName(Logger::INFO), + 'channel' => 'app', + 'datetime' => new \DateTime('2013-05-29 16:21:54'), + 'extra' => [], + ]; + + $socket = stream_socket_server($host, $errno, $errstr); + $this->assertIsResource($socket, sprintf('Server start failed on "%s": %s %s.', $host, $errstr, $errno)); + + $this->assertTrue($handler->handle($infoRecord), 'The handler finished handling the log as bubble is false.'); + + $sockets = [(int) $socket => $socket]; + $write = []; + + for ($i = 0; $i < 10; ++$i) { + $read = $sockets; + stream_select($read, $write, $write, null); + + foreach ($read as $stream) { + if ($socket === $stream) { + $stream = stream_socket_accept($socket); + $sockets[(int) $stream] = $stream; + } elseif (feof($stream)) { + unset($sockets[(int) $stream]); + fclose($stream); + } else { + $message = fgets($stream); + fclose($stream); + + $record = unserialize(base64_decode($message)); + $this->assertIsArray($record); + + $this->assertArrayHasKey('message', $record); + $this->assertEquals('My info message', $record['message']); + + $this->assertArrayHasKey('extra', $record); + $this->assertInstanceOf(Data::class, $record['extra']); + $extra = $record['extra']->getValue(true); + $this->assertIsArray($extra); + $this->assertArrayHasKey('process_id', $extra); + $this->assertSame(getmypid(), $extra['process_id']); + + return; + } + } + usleep(100000); + } + $this->fail('Fail to read message from server'); + } +} diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php index 37456b801dcdd..4ac41c978ec4e 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/DebugProcessorTest.php @@ -19,6 +19,30 @@ class DebugProcessorTest extends TestCase { + /** + * @dataProvider providerDatetimeFormatTests + */ + public function testDatetimeFormat(array $record, $expectedTimestamp) + { + $processor = new DebugProcessor(); + $processor($record); + + $records = $processor->getLogs(); + self::assertCount(1, $records); + self::assertSame($expectedTimestamp, $records[0]['timestamp']); + } + + public function providerDatetimeFormatTests(): array + { + $record = $this->getRecord(); + + return [ + [array_merge($record, ['datetime' => new \DateTime('2019-01-01T00:01:00+00:00')]), 1546300860], + [array_merge($record, ['datetime' => '2019-01-01T00:01:00+00:00']), 1546300860], + [array_merge($record, ['datetime' => 'foo']), false], + ]; + } + public function testDebugProcessor() { $processor = new DebugProcessor(); @@ -75,7 +99,7 @@ public function testInheritedClassWithoutArgument() $debugProcessorChild->countErrors(); } - private function getRecord($level = Logger::WARNING, $message = 'test') + private function getRecord($level = Logger::WARNING, $message = 'test'): array { return [ 'message' => $message, diff --git a/src/Symfony/Bridge/PhpUnit/LICENSE b/src/Symfony/Bridge/PhpUnit/LICENSE index cf8b3ebe87145..684fbf94df83c 100644 --- a/src/Symfony/Bridge/PhpUnit/LICENSE +++ b/src/Symfony/Bridge/PhpUnit/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2019 Fabien Potencier +Copyright (c) 2014-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php index 2da40f2c204f1..a760d126ca854 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php @@ -47,11 +47,6 @@ public function startTest(\PHPUnit_Framework_Test $test) $this->trait->startTest($test); } - public function addWarning(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time) - { - $this->trait->addWarning($test, $e, $time); - } - public function endTest(\PHPUnit_Framework_Test $test, $time) { $this->trait->endTest($test, $time); diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php index 5b864bfe58a32..bcab4be4eeb25 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php @@ -52,11 +52,6 @@ public function startTest(Test $test) $this->trait->startTest($test); } - public function addWarning(Test $test, Warning $e, $time) - { - $this->trait->addWarning($test, $e, $time); - } - public function endTest(Test $test, $time) { $this->trait->endTest($test, $time); diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php index 18bbdbeba0f03..0c2069c66bf5e 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php @@ -55,11 +55,6 @@ public function startTest(Test $test): void $this->trait->startTest($test); } - public function addWarning(Test $test, Warning $e, float $time): void - { - $this->trait->addWarning($test, $e, $time); - } - public function endTest(Test $test, float $time): void { $this->trait->endTest($test, $time); diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index ed84eedf4a92a..7fa6e1007f406 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -37,8 +37,6 @@ class SymfonyTestsListenerTrait private $expectedDeprecations = array(); private $gatheredDeprecations = array(); private $previousErrorHandler; - private $testsWithWarnings; - private $reportUselessTests; private $error; private $runsInSeparateProcess = false; @@ -114,7 +112,6 @@ public function startTestSuite($suite) $Test = 'PHPUnit\Util\Test'; } $suiteName = $suite->getName(); - $this->testsWithWarnings = array(); foreach ($suite->tests() as $test) { if (!($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) { @@ -198,10 +195,6 @@ public function addSkippedTest($test, \Exception $e, $time) public function startTest($test) { if (-2 < $this->state && ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) { - if (null !== $test->getTestResultObject()) { - $this->reportUselessTests = $test->getTestResultObject()->isStrictAboutTestsThatDoNotTestAnything(); - } - // This event is triggered before the test is re-run in isolation if ($this->willBeIsolated($test)) { $this->runsInSeparateProcess = tempnam(sys_get_temp_dir(), 'deprec'); @@ -245,13 +238,6 @@ public function startTest($test) } } - public function addWarning($test, $e, $time) - { - if ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase) { - $this->testsWithWarnings[$test->getName()] = true; - } - } - public function endTest($test, $time) { if (class_exists('PHPUnit_Util_Blacklist', false)) { @@ -264,11 +250,6 @@ public function endTest($test, $time) $className = \get_class($test); $groups = $Test::getGroups($className, $test->getName(false)); - if (null !== $this->reportUselessTests) { - $test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything($this->reportUselessTests); - $this->reportUselessTests = null; - } - if ($errored = null !== $this->error) { $test->getTestResultObject()->addError($test, $this->error, 0); $this->error = null; diff --git a/src/Symfony/Bridge/PhpUnit/Tests/ExpectedDeprecationAnnotationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/ExpectedDeprecationAnnotationTest.php new file mode 100644 index 0000000000000..259c99162a831 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/ExpectedDeprecationAnnotationTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests; + +use PHPUnit\Framework\TestCase; + +final class ExpectedDeprecationAnnotationTest extends TestCase +{ + /** + * Do not remove this test in the next major versions. + * + * @group legacy + * + * @expectedDeprecation foo + */ + public function testOne() + { + @trigger_error('foo', E_USER_DEPRECATED); + } + + /** + * Do not remove this test in the next major versions. + * + * @group legacy + * + * @expectedDeprecation foo + * @expectedDeprecation bar + */ + public function testMany() + { + @trigger_error('foo', E_USER_DEPRECATED); + @trigger_error('bar', E_USER_DEPRECATED); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php index 8cc14d0bbe341..50873b612aa71 100644 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php @@ -53,6 +53,14 @@ return $default; }; +$passthruOrFail = function ($command) { + passthru($command, $status); + + if ($status) { + exit($status); + } +}; + if (PHP_VERSION_ID >= 70100) { // PHPUnit 7 requires PHP 7.1+ $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '7.4'); @@ -60,9 +68,10 @@ // PHPUnit 6 requires PHP 7.0+ $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '6.5'); } elseif (PHP_VERSION_ID >= 50600) { - // PHPUnit 5 requires PHP 5.6+ + // PHPUnit 4 does not support PHP 7 $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '5.7'); } else { + // PHPUnit 5.1 requires PHP 5.6+ $PHPUNIT_VERSION = '4.8'; } @@ -100,7 +109,7 @@ $COMPOSER = file_exists($COMPOSER = $oldPwd.'/composer.phar') || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar 2> /dev/null`)) || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer`) : `which composer 2> /dev/null`)) - ? $PHP.' '.escapeshellarg($COMPOSER) + ? (file_get_contents($COMPOSER, false, null, 0, 18) === '#!/usr/bin/env php' ? $PHP : '').' '.escapeshellarg($COMPOSER) // detect shell wrappers by looking at the shebang : 'composer'; $SYMFONY_PHPUNIT_REMOVE = $getEnvVar('SYMFONY_PHPUNIT_REMOVE', 'phpspec/prophecy'.($PHPUNIT_VERSION < 6.0 ? ' symfony/yaml': '')); @@ -115,25 +124,25 @@ rename("phpunit-$PHPUNIT_VERSION", "phpunit-$PHPUNIT_VERSION.old"); passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s': 'rm -rf %s', "phpunit-$PHPUNIT_VERSION.old")); } - passthru("$COMPOSER create-project --no-install --prefer-dist --no-scripts --no-plugins --no-progress --ansi phpunit/phpunit phpunit-$PHPUNIT_VERSION \"$PHPUNIT_VERSION.*\""); + $passthruOrFail("$COMPOSER create-project --no-install --prefer-dist --no-scripts --no-plugins --no-progress --ansi phpunit/phpunit phpunit-$PHPUNIT_VERSION \"$PHPUNIT_VERSION.*\""); @copy("phpunit-$PHPUNIT_VERSION/phpunit.xsd", 'phpunit.xsd'); chdir("phpunit-$PHPUNIT_VERSION"); if ($SYMFONY_PHPUNIT_REMOVE) { - passthru("$COMPOSER remove --no-update ".$SYMFONY_PHPUNIT_REMOVE); + $passthruOrFail("$COMPOSER remove --no-update ".$SYMFONY_PHPUNIT_REMOVE); } if (5.1 <= $PHPUNIT_VERSION && $PHPUNIT_VERSION < 5.4) { - passthru("$COMPOSER require --no-update phpunit/phpunit-mock-objects \"~3.1.0\""); + $passthruOrFail("$COMPOSER require --no-update phpunit/phpunit-mock-objects \"~3.1.0\""); } - passthru("$COMPOSER config --unset platform"); + $passthruOrFail("$COMPOSER config --unset platform.php"); if (file_exists($path = $root.'/vendor/symfony/phpunit-bridge')) { - passthru("$COMPOSER require --no-update symfony/phpunit-bridge \"*@dev\""); - passthru("$COMPOSER config repositories.phpunit-bridge path ".escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $path))); + $passthruOrFail("$COMPOSER require --no-update symfony/phpunit-bridge \"*@dev\""); + $passthruOrFail("$COMPOSER config repositories.phpunit-bridge path ".escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $path))); if ('\\' === DIRECTORY_SEPARATOR) { file_put_contents('composer.json', preg_replace('/^( {8})"phpunit-bridge": \{$/m', "$0\n$1 ".'"options": {"symlink": false},', file_get_contents('composer.json'))); } } else { - passthru("$COMPOSER require --no-update symfony/phpunit-bridge \"*\""); + $passthruOrFail("$COMPOSER require --no-update symfony/phpunit-bridge \"*\""); } $prevRoot = getenv('COMPOSER_ROOT_VERSION'); putenv("COMPOSER_ROOT_VERSION=$PHPUNIT_VERSION.99"); @@ -186,7 +195,7 @@ class SymfonyBlacklistPhpunit {} $argv[1] = 'src/Symfony'; } if (isset($argv[1]) && is_dir($argv[1]) && !file_exists($argv[1].'/phpunit.xml.dist')) { - // Find Symfony components in plain PHP for Windows portability + // Find Symfony components in plain php for Windows portability $finder = new RecursiveDirectoryIterator($argv[1], FilesystemIterator::KEY_AS_FILENAME | FilesystemIterator::UNIX_PATHS); $finder = new RecursiveIteratorIterator($finder); diff --git a/src/Symfony/Bridge/ProxyManager/LICENSE b/src/Symfony/Bridge/ProxyManager/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Bridge/ProxyManager/LICENSE +++ b/src/Symfony/Bridge/ProxyManager/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index 85628aee9363e..ed12685d2a4d6 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -85,6 +85,7 @@ public function getProxyFactoryCode(Definition $definition, $id, $factoryCode = public function getProxyCode(Definition $definition) { $code = $this->classGenerator->generate($this->generateProxyClass($definition)); + $code = preg_replace('/^(class [^ ]++ extends )([^\\\\])/', '$1\\\\$2', $code); if (version_compare(self::getProxyManagerVersion(), '2.2', '<')) { $code = preg_replace( diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt index 574041b89bbb4..906fff68f7bbe 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt @@ -21,5 +21,5 @@ class LazyServiceProjectServiceContainer extends Container } } -class stdClass_%s extends %SstdClass implements \ProxyManager\%s +class stdClass_%s extends \stdClass implements \ProxyManager\%s {%a}%A diff --git a/src/Symfony/Bridge/Twig/LICENSE b/src/Symfony/Bridge/Twig/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Bridge/Twig/LICENSE +++ b/src/Symfony/Bridge/Twig/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig index 4dc3e9c896fb7..49cd804398b5e 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig @@ -64,6 +64,7 @@ col-sm-10
{#--#}
{{- form_widget(form) -}} + {{- form_help(form) -}} {{- form_errors(form) -}}
{#--#} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig index f0cf0ad81854d..44492cebe74d7 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig @@ -148,6 +148,7 @@ {% block checkbox_row -%} {{- form_widget(form) -}} + {{- form_help(form) -}} {{- form_errors(form) -}} {%- endblock checkbox_row %} @@ -155,6 +156,7 @@ {% block radio_row -%} {{- form_widget(form) -}} + {{- form_help(form) -}} {{- form_errors(form) -}} {%- endblock radio_row %} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index e1a418e84b722..6b18a1dd27c87 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -226,13 +226,11 @@ '%name%': name, '%id%': id, }) %} - {%- elseif label is same as(false) -%} - {% set translation_domain = false %} - {%- else -%} + {%- elseif label is not same as(false) -%} {% set label = name|humanize %} {%- endif -%} {%- endif -%} - + {%- endblock button_widget -%} {%- block submit_widget -%} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig index 83c5e30d803bc..8ab44ccc7738f 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig @@ -311,6 +311,7 @@
{{ form_widget(form) }} + {{- form_help(form) -}} {{ form_errors(form) }}
@@ -320,6 +321,7 @@
{{ form_widget(form) }} + {{- form_help(form) -}} {{ form_errors(form) }}
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3HorizontalLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3HorizontalLayoutTest.php index 9131216182a3d..69064a003d7fe 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3HorizontalLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3HorizontalLayoutTest.php @@ -163,4 +163,23 @@ public function testCheckboxRow() $this->assertMatchesXpath($html, '/div[@class="form-group"]/div[@class="col-sm-2" or @class="col-sm-10"]', 2); } + + public function testCheckboxRowWithHelp() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType'); + $html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']); + + $this->assertMatchesXpath($html, +'/div + [@class="form-group"] + [ + ./div[@class="col-sm-2" or @class="col-sm-10"] + /following-sibling::div[@class="col-sm-2" or @class="col-sm-10"] + [ + ./span[text() = "[trans]really helpful text[/trans]"] + ] + ] +' + ); + } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php index 16f8f818f38cb..be7a11e7977f1 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php @@ -333,6 +333,21 @@ public function testCheckboxWithValue() ); } + public function testCheckboxRowWithHelp() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType'); + $html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']); + + $this->assertMatchesXpath($html, +'/div + [@class="form-group"] + [ + ./span[text() = "[trans]really helpful text[/trans]"] + ] +' + ); + } + public function testSingleChoice() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ @@ -2277,6 +2292,21 @@ public function testRadioWithValue() ); } + public function testRadioRowWithHelp() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false); + $html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']); + + $this->assertMatchesXpath($html, +'/div + [@class="form-group"] + [ + ./span[text() = "[trans]really helpful text[/trans]"] + ] +' + ); + } + public function testRange() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RangeType', 42, ['attr' => ['min' => 5]]); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php index e9416b02213f2..e20f4818b2810 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php @@ -231,6 +231,25 @@ public function testCheckboxRowWithHelp() ./small[text() = "[trans]really helpful text[/trans]"] ] ] +' + ); + } + + public function testRadioRowWithHelp() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false); + $html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']); + + $this->assertMatchesXpath($html, +'/div + [@class="form-group row"] + [ + ./div[@class="col-sm-2" or @class="col-sm-10"] + /following-sibling::div[@class="col-sm-2" or @class="col-sm-10"] + [ + ./small[text() = "[trans]really helpful text[/trans]"] + ] + ] ' ); } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php index 89fbacf2fc2d7..2643274d47c3e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php @@ -422,6 +422,21 @@ public function testCheckboxWithValue() ); } + public function testCheckboxRowWithHelp() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType'); + $html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']); + + $this->assertMatchesXpath($html, + '/div + [@class="form-group"] + [ + ./small[text() = "[trans]really helpful text[/trans]"] + ] +' + ); + } + public function testSingleChoiceExpanded() { $form = $this->factory->createNamed('name', ChoiceType::class, '&a', [ @@ -1027,6 +1042,21 @@ public function testRadioWithValue() ); } + public function testRadioRowWithHelp() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false); + $html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']); + + $this->assertMatchesXpath($html, +'/div + [@class="form-group"] + [ + ./small[text() = "[trans]really helpful text[/trans]"] + ] +' + ); + } + public function testButtonAttributeNameRepeatedIfTrue() { $form = $this->factory->createNamed('button', ButtonType::class, null, [ diff --git a/src/Symfony/Bundle/DebugBundle/LICENSE b/src/Symfony/Bundle/DebugBundle/LICENSE index cf8b3ebe87145..684fbf94df83c 100644 --- a/src/Symfony/Bundle/DebugBundle/LICENSE +++ b/src/Symfony/Bundle/DebugBundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2019 Fabien Potencier +Copyright (c) 2014-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index a21ccca8f2b9d..6bffabe8519d3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -21,6 +21,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -229,6 +230,8 @@ protected function getContainerBuilder() $container->compile(); } else { (new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); + $locatorPass = new ServiceLocatorTagPass(); + $locatorPass->process($container); } return $this->containerBuilder = $container; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 1ea6ae18b1252..1c41992e9b828 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -199,12 +199,12 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $errorIo->title('Translation Messages Extractor and Dumper'); - $errorIo->comment(sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $currentName)); + $io->title('Translation Messages Extractor and Dumper'); + $io->comment(sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $currentName)); // load any messages from templates $extractedCatalogue = new MessageCatalogue($input->getArgument('locale')); - $errorIo->comment('Parsing templates...'); + $io->comment('Parsing templates...'); $this->extractor->setPrefix($input->getOption('prefix')); foreach ($viewsPaths as $path) { if (is_dir($path) || is_file($path)) { @@ -214,7 +214,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // load any existing messages from the translation files $currentCatalogue = new MessageCatalogue($input->getArgument('locale')); - $errorIo->comment('Loading translation files...'); + $io->comment('Loading translation files...'); foreach ($transPaths as $path) { if (is_dir($path)) { $this->reader->read($path, $currentCatalogue); @@ -267,7 +267,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if ('xlf' === $input->getOption('output-format')) { - $errorIo->comment(sprintf('Xliff output version is %s', $input->getOption('xliff-version'))); + $io->comment(sprintf('Xliff output version is %s', $input->getOption('xliff-version'))); } $resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was'); @@ -279,7 +279,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // save the files if (true === $input->getOption('force')) { - $errorIo->comment('Writing files...'); + $io->comment('Writing files...'); $bundleTransPath = false; foreach ($transPaths as $path) { @@ -299,7 +299,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $errorIo->success($resultMessage.'.'); + $io->success($resultMessage.'.'); return null; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index ac7ab231e01a9..298127c3e7ad5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Controller; -use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Persistence\ManagerRegistry; use Psr\Container\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php index b9d4bf84a5cc5..c7ef9b8f2c3bc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Controller; -use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Persistence\ManagerRegistry; use Fig\Link\GenericLinkProvider; use Fig\Link\Link; use Psr\Container\ContainerInterface; @@ -30,6 +30,7 @@ use Symfony\Component\Messenger\Envelope; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; @@ -335,11 +336,13 @@ protected function createFormBuilder($data = null, array $options = []): FormBui /** * Shortcut to return the Doctrine Registry service. * + * @return ManagerRegistry + * * @throws \LogicException If DoctrineBundle is not available * * @final */ - protected function getDoctrine(): ManagerRegistry + protected function getDoctrine() { if (!$this->container->has('doctrine')) { throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".'); @@ -351,7 +354,7 @@ protected function getDoctrine(): ManagerRegistry /** * Get a user from the Security Token Storage. * - * @return object|null + * @return UserInterface|object|null * * @throws \LogicException If SecurityBundle is not available * diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 0f575b90d6362..6833610d0cc6f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -855,6 +855,11 @@ private function addValidationSection(ArrayNodeDefinition $rootNode) ->end() ->end() ->arrayNode('auto_mapping') + ->info('A collection of namespaces for which auto-mapping will be enabled.') + ->example([ + 'App\\Entity\\' => [], + 'App\\WithSpecificLoaders\\' => ['validator.property_info_loader'], + ]) ->useAttributeAsKey('namespace') ->normalizeKeys(false) ->beforeNormalization() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index f29c72e95a6e2..41d50c26098ca 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -292,6 +292,7 @@ public function load(array $configs, ContainerBuilder $container) $container->removeDefinition('console.command.messenger_failed_messages_retry'); $container->removeDefinition('console.command.messenger_failed_messages_show'); $container->removeDefinition('console.command.messenger_failed_messages_remove'); + $container->removeDefinition('cache.messenger.restart_workers_signal'); } $propertyInfoEnabled = $this->isConfigEnabled($container, $config['property_info']); diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php b/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php index e4a630fb48e72..9baf50ff0572a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php +++ b/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php @@ -37,7 +37,14 @@ public function __construct(KernelInterface $kernel, string $cacheDir = null) $this->kernel = $kernel; $this->cacheDir = $cacheDir; - parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge(['debug' => $kernel->isDebug()], $this->getOptions())); + $isDebug = $kernel->isDebug(); + $options = ['debug' => $isDebug]; + + if ($isDebug) { + $options['stale_if_error'] = 0; + } + + parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge($options, $this->getOptions())); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/LICENSE b/src/Symfony/Bundle/FrameworkBundle/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/LICENSE +++ b/src/Symfony/Bundle/FrameworkBundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 69deb492a2c84..14ed3913b6afb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -414,6 +414,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php index ffa9a923f1f9d..21bc581cecd21 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php @@ -48,7 +48,7 @@ public function parse($name) } // normalize name - $name = str_replace(':/', ':', preg_replace('#/{2,}#', '/', str_replace('\\', '/', $name))); + $name = preg_replace('#/{2,}#', '/', str_replace('\\', '/', $name)); if (false !== strpos($name, '..')) { throw new \RuntimeException(sprintf('Template name "%s" contains invalid characters.', $name)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php index 68db3bb99f482..5c8b88ec6a97a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php @@ -39,7 +39,7 @@ public function testSubscribedServices() 'security.authorization_checker' => '?Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationCheckerInterface', 'templating' => '?Symfony\\Component\\Templating\\EngineInterface', 'twig' => '?Twig\\Environment', - 'doctrine' => '?Doctrine\\Common\\Persistence\\ManagerRegistry', + 'doctrine' => '?Doctrine\\Persistence\\ManagerRegistry', 'form.factory' => '?Symfony\\Component\\Form\\FormFactoryInterface', 'parameter_bag' => '?Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface', 'message_bus' => '?Symfony\\Component\\Messenger\\MessageBusInterface', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTraitTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTraitTest.php index 7586e29818b02..f4d0153275aef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTraitTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTraitTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Controller; +use Doctrine\Persistence\ManagerRegistry; use Fig\Link\Link; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\DependencyInjection\Container; @@ -522,7 +523,7 @@ public function testCreateFormBuilder() public function testGetDoctrine() { - $doctrine = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); + $doctrine = $this->getMockBuilder(ManagerRegistry::class)->getMock(); $container = new Container(); $container->set('doctrine', $doctrine); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_disabled.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_disabled.php new file mode 100644 index 0000000000000..e02542d9778c6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/messenger_disabled.php @@ -0,0 +1,5 @@ +loadFromExtension('framework', [ + 'messenger' => false, +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_disabled.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_disabled.xml new file mode 100644 index 0000000000000..6f57398b30d2b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/messenger_disabled.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_disabled.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_disabled.yml new file mode 100644 index 0000000000000..1b2d2d1a4f475 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/messenger_disabled.yml @@ -0,0 +1,2 @@ +framework: + messenger: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 45a3c02e76c3c..33751dbf2dfb3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -659,9 +659,23 @@ public function testWebLink() $this->assertTrue($container->hasDefinition('web_link.add_link_header_listener')); } + public function testMessengerServicesRemovedWhenDisabled() + { + $container = $this->createContainerFromFile('messenger_disabled'); + $this->assertFalse($container->hasDefinition('console.command.messenger_consume_messages')); + $this->assertFalse($container->hasDefinition('console.command.messenger_debug')); + $this->assertFalse($container->hasDefinition('console.command.messenger_stop_workers')); + $this->assertFalse($container->hasDefinition('console.command.messenger_setup_transports')); + $this->assertFalse($container->hasDefinition('console.command.messenger_failed_messages_retry')); + $this->assertFalse($container->hasDefinition('console.command.messenger_failed_messages_show')); + $this->assertFalse($container->hasDefinition('console.command.messenger_failed_messages_remove')); + $this->assertFalse($container->hasDefinition('cache.messenger.restart_workers_signal')); + } + public function testMessenger() { $container = $this->createContainerFromFile('messenger'); + $this->assertTrue($container->hasDefinition('console.command.messenger_consume_messages')); $this->assertTrue($container->hasAlias('message_bus')); $this->assertTrue($container->getAlias('message_bus')->isPublic()); $this->assertTrue($container->hasAlias('messenger.default_bus')); diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index b554b2be63eab..a05c47a2a7238 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -64,6 +64,7 @@ "twig/twig": "~1.41|~2.10" }, "conflict": { + "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<3.0", "phpdocumentor/type-resolver": "<0.2.1", "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php index b1e653a10975f..461a5ad66d081 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSessionDomainConstraintPass.php @@ -31,7 +31,7 @@ public function process(ContainerBuilder $container) } $sessionOptions = $container->getParameter('session.storage.options'); - $domainRegexp = empty($sessionOptions['cookie_domain']) ? '%s' : sprintf('(?:%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.'))); + $domainRegexp = empty($sessionOptions['cookie_domain']) ? '%%s' : sprintf('(?:%%%%s|(?:.+\.)?%s)', preg_quote(trim($sessionOptions['cookie_domain'], '.'))); if ('auto' === ($sessionOptions['cookie_secure'] ?? null)) { $secureDomainRegexp = sprintf('{^https://%s$}i', $domainRegexp); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index 1e1e97ccb5b3f..92ab5cc21f37a 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -399,7 +399,13 @@ private function addEncodersSection(ArrayNodeDefinition $rootNode) ->performNoDeepMerging() ->beforeNormalization()->ifString()->then(function ($v) { return ['algorithm' => $v]; })->end() ->children() - ->scalarNode('algorithm')->cannotBeEmpty()->end() + ->scalarNode('algorithm') + ->cannotBeEmpty() + ->validate() + ->ifTrue(function ($v) { return !\is_string($v); }) + ->thenInvalid('You must provide a string value.') + ->end() + ->end() ->scalarNode('hash_algorithm')->info('Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms.')->defaultValue('sha512')->end() ->scalarNode('key_length')->defaultValue(40)->end() ->booleanNode('ignore_case')->defaultFalse()->end() diff --git a/src/Symfony/Bundle/SecurityBundle/LICENSE b/src/Symfony/Bundle/SecurityBundle/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Bundle/SecurityBundle/LICENSE +++ b/src/Symfony/Bundle/SecurityBundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php index 7bda8fcc58c8e..183e94451b62c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php @@ -26,13 +26,11 @@ class FirewallMap implements FirewallMapInterface { private $container; private $map; - private $contexts; public function __construct(ContainerInterface $container, iterable $map) { $this->container = $container; $this->map = $map; - $this->contexts = new \SplObjectStorage(); } public function getListeners(Request $request) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php index 3a1046b0c4a17..51f56c220d33c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php @@ -33,7 +33,7 @@ public function testUserChangeClearsCookie() $this->assertNotNull($cookieJar->get('REMEMBERME')); $client->request('GET', '/foo'); - $this->assertSame(200, $client->getResponse()->getStatusCode()); + $this->assertRedirect($client->getResponse(), '/login'); $this->assertNull($cookieJar->get('REMEMBERME')); } } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index a46df92ba1c14..aa565e6d15280 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -24,7 +24,7 @@ "symfony/security-core": "~4.3", "symfony/security-csrf": "~4.2", "symfony/security-guard": "~4.2", - "symfony/security-http": "~4.3.9|^4.4.1" + "symfony/security-http": "~4.3.10|^4.4.3" }, "require-dev": { "symfony/asset": "~3.4|~4.0", diff --git a/src/Symfony/Bundle/TwigBundle/LICENSE b/src/Symfony/Bundle/TwigBundle/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Bundle/TwigBundle/LICENSE +++ b/src/Symfony/Bundle/TwigBundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bundle/TwigBundle/TwigBundle.php b/src/Symfony/Bundle/TwigBundle/TwigBundle.php index bd766c15219e7..5a353833eb27c 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigBundle.php +++ b/src/Symfony/Bundle/TwigBundle/TwigBundle.php @@ -32,7 +32,8 @@ public function build(ContainerBuilder $container) { parent::build($container); - $container->addCompilerPass(new ExtensionPass()); + // ExtensionPass must be run before the FragmentRendererPass as it adds tags that are processed later + $container->addCompilerPass(new ExtensionPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10); $container->addCompilerPass(new TwigEnvironmentPass()); $container->addCompilerPass(new TwigLoaderPass()); $container->addCompilerPass(new ExceptionListenerPass()); diff --git a/src/Symfony/Bundle/WebProfilerBundle/LICENSE b/src/Symfony/Bundle/WebProfilerBundle/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/LICENSE +++ b/src/Symfony/Bundle/WebProfilerBundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/info.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/info.html.twig index 0227532e1208a..43404393ec360 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/info.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/info.html.twig @@ -4,7 +4,7 @@ 'no_token' : { status: 'error', title: (token|default('') == 'latest') ? 'There are no profiles' : 'Token not found', - message: (token|default('') == 'latest') ? 'No profiles found in the database.' : 'Token "' ~ token|default('') ~ '" was not found in the database.' + message: (token|default('') == 'latest') ? 'No profiles found.' : 'Token "' ~ token|default('') ~ '" not found.' } } %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php index 6a43bd2ce5abd..74690f947ec71 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php @@ -11,19 +11,110 @@ namespace Symfony\Bundle\WebProfilerBundle\Tests\Controller; -use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Client; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController; use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler; +use Symfony\Bundle\WebProfilerBundle\Tests\Functional\WebProfilerBundleKernel; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Profiler\Profile; -class ProfilerControllerTest extends TestCase +class ProfilerControllerTest extends WebTestCase { + public function testHomeActionWithProfilerDisabled() + { + $this->expectException(NotFoundHttpException::class); + $this->expectExceptionMessage('The profiler must be enabled.'); + + $urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock(); + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + + $controller = new ProfilerController($urlGenerator, null, $twig, []); + $controller->homeAction(); + } + + public function testHomeActionRedirect() + { + $kernel = new WebProfilerBundleKernel(); + $client = new Client($kernel); + + $client->request('GET', '/_profiler/'); + + $this->assertSame(302, $client->getResponse()->getStatusCode()); + $this->assertSame('/_profiler/empty/search/results?limit=10', $client->getResponse()->getTargetUrl()); + } + + public function testPanelActionWithLatestTokenWhenNoTokensExist() + { + $kernel = new WebProfilerBundleKernel(); + $client = new Client($kernel); + + $client->request('GET', '/_profiler/latest'); + + $this->assertStringContainsString('No profiles found.', $client->getResponse()->getContent()); + } + + public function testPanelActionWithLatestToken() + { + $kernel = new WebProfilerBundleKernel(); + $client = new Client($kernel); + + $client->request('GET', '/'); + $client->request('GET', '/_profiler/latest'); + + $this->assertStringContainsString('kernel:homepageController', $client->getResponse()->getContent()); + } + + public function testPanelActionWithoutValidToken() + { + $kernel = new WebProfilerBundleKernel(); + $client = new Client($kernel); + + $client->request('GET', '/_profiler/this-token-does-not-exist'); + + $this->assertStringContainsString('Token "this-token-does-not-exist" not found.', $client->getResponse()->getContent()); + } + + public function testPanelActionWithWrongPanel() + { + $kernel = new WebProfilerBundleKernel(); + $client = new Client($kernel); + + $client->request('GET', '/'); + $client->request('GET', '/_profiler/latest?panel=this-panel-does-not-exist'); + + $this->assertSame(404, $client->getResponse()->getStatusCode()); + } + + public function testPanelActionWithValidPanelAndToken() + { + $kernel = new WebProfilerBundleKernel(); + $client = new Client($kernel); + + $client->request('GET', '/'); + $crawler = $client->request('GET', '/_profiler/latest?panel=router'); + + $this->assertSame('_', $crawler->filter('.metrics .metric .value')->eq(0)->text()); + $this->assertSame('12', $crawler->filter('.metrics .metric .value')->eq(1)->text()); + } + + public function testToolbarActionWithProfilerDisabled() + { + $this->expectException(NotFoundHttpException::class); + $this->expectExceptionMessage('The profiler must be enabled.'); + + $urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock(); + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + + $controller = new ProfilerController($urlGenerator, null, $twig, []); + $controller->toolbarAction(Request::create('/_wdt/foo-token'), null); + } + /** * @dataProvider getEmptyTokenCases */ - public function testEmptyToken($token) + public function testToolbarActionWithEmptyToken($token) { $urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock(); $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); @@ -111,10 +202,36 @@ public function testReturns404onTokenNotFound($withCsp) $this->assertEquals(404, $response->getStatusCode()); } + public function testSearchBarActionWithProfilerDisabled() + { + $this->expectException(NotFoundHttpException::class); + $this->expectExceptionMessage('The profiler must be enabled.'); + + $urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock(); + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + + $controller = new ProfilerController($urlGenerator, null, $twig, []); + $controller->searchBarAction(Request::create('/_profiler/search_bar')); + } + + public function testSearchBarActionDefaultPage() + { + $kernel = new WebProfilerBundleKernel(); + $client = new Client($kernel); + + $crawler = $client->request('GET', '/_profiler/search_bar'); + + $this->assertSame(200, $client->getResponse()->getStatusCode()); + + foreach (['ip', 'status_code', 'url', 'token', 'start', 'end'] as $searchCriteria) { + $this->assertSame('', $crawler->filter(sprintf('form input[name="%s"]', $searchCriteria))->text()); + } + } + /** * @dataProvider provideCspVariants */ - public function testSearchResult($withCsp) + public function testSearchResultsAction($withCsp) { $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); $profiler = $this @@ -177,6 +294,67 @@ public function testSearchResult($withCsp) $this->assertEquals(200, $response->getStatusCode()); } + public function testSearchActionWithProfilerDisabled() + { + $this->expectException(NotFoundHttpException::class); + $this->expectExceptionMessage('The profiler must be enabled.'); + + $urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock(); + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + + $controller = new ProfilerController($urlGenerator, null, $twig, []); + $controller->searchBarAction(Request::create('/_profiler/search')); + } + + public function testSearchActionWithToken() + { + $kernel = new WebProfilerBundleKernel(); + $client = new Client($kernel); + + $client->request('GET', '/'); + $token = $client->getResponse()->headers->get('x-debug-token'); + $client->request('GET', '/_profiler/search?token='.$token); + + $this->assertSame(302, $client->getResponse()->getStatusCode()); + $this->assertSame('/_profiler/'.$token, $client->getResponse()->getTargetUrl()); + } + + public function testSearchActionWithoutToken() + { + $kernel = new WebProfilerBundleKernel(); + $client = new Client($kernel); + $client->followRedirects(); + + $client->request('GET', '/'); + $token = $client->getResponse()->headers->get('x-debug-token'); + $client->request('GET', '/_profiler/search?ip=&method=GET&status_code=&url=&token=&start=&end=&limit=10'); + + $this->assertStringContainsString('results found', $client->getResponse()->getContent()); + $this->assertStringContainsString(sprintf('%s', $token, $token), $client->getResponse()->getContent()); + } + + public function testPhpinfoActionWithProfilerDisabled() + { + $this->expectException(NotFoundHttpException::class); + $this->expectExceptionMessage('The profiler must be enabled.'); + + $urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock(); + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + + $controller = new ProfilerController($urlGenerator, null, $twig, []); + $controller->phpinfoAction(Request::create('/_profiler/phpinfo')); + } + + public function testPhpinfoAction() + { + $kernel = new WebProfilerBundleKernel(); + $client = new Client($kernel); + + $client->request('GET', '/_profiler/phpinfo'); + + $this->assertStringContainsString('PHP License', $client->getResponse()->getContent()); + } + public function provideCspVariants() { return [ diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php new file mode 100644 index 0000000000000..2490add710a55 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php @@ -0,0 +1,72 @@ +import(__DIR__.'/../../Resources/config/routing/profiler.xml', '/_profiler'); + $routes->import(__DIR__.'/../../Resources/config/routing/wdt.xml', '/_wdt'); + $routes->add('/', 'kernel:homepageController'); + } + + protected function configureContainer(ContainerBuilder $containerBuilder, LoaderInterface $loader) + { + $containerBuilder->loadFromExtension('framework', [ + 'secret' => 'foo-secret', + 'profiler' => ['only_exceptions' => false], + 'session' => ['storage_id' => 'session.storage.mock_file'], + ]); + + $containerBuilder->loadFromExtension('web_profiler', [ + 'toolbar' => true, + 'intercept_redirects' => false, + ]); + + $containerBuilder->loadFromExtension('twig', [ + 'strict_variables' => true, + ]); + } + + public function getCacheDir() + { + return sys_get_temp_dir().'/cache-'.spl_object_hash($this); + } + + public function getLogDir() + { + return sys_get_temp_dir().'/log-'.spl_object_hash($this); + } + + public function homepageController() + { + return new Response('Homepage Controller.'); + } +} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php index 857cde44fdb96..50145dc5da737 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php @@ -105,10 +105,6 @@ protected function mockTwigEnvironment() { $this->twigEnvironment = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); - $this->twigEnvironment->expects($this->any()) - ->method('loadTemplate') - ->willReturn('loadedTemplate'); - if (Environment::MAJOR_VERSION > 1) { $loader = $this->createMock(LoaderInterface::class); $loader diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index cbd0f38dd1fc6..211c4ce7f6b56 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -19,14 +19,16 @@ "php": "^7.1.3", "symfony/config": "^4.2", "symfony/http-kernel": "^4.3", - "symfony/routing": "~3.4|~4.0", + "symfony/routing": "^4.3", "symfony/twig-bundle": "~4.2", - "symfony/var-dumper": "~3.4|~4.0", + "symfony/var-dumper": "^4.3", "twig/twig": "^1.41|^2.10" }, "require-dev": { - "symfony/console": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", + "symfony/browser-kit": "^4.3", + "symfony/console": "^4.3", + "symfony/css-selector": "~3.4|~4.0", + "symfony/framework-bundle": "^4.3", "symfony/stopwatch": "~3.4|~4.0" }, "conflict": { diff --git a/src/Symfony/Bundle/WebServerBundle/LICENSE b/src/Symfony/Bundle/WebServerBundle/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Bundle/WebServerBundle/LICENSE +++ b/src/Symfony/Bundle/WebServerBundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Asset/LICENSE b/src/Symfony/Component/Asset/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Asset/LICENSE +++ b/src/Symfony/Component/Asset/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/BrowserKit/LICENSE b/src/Symfony/Component/BrowserKit/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/BrowserKit/LICENSE +++ b/src/Symfony/Component/BrowserKit/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php index 0217c801e4f7f..aad2bc1bd8d77 100644 --- a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php @@ -61,14 +61,15 @@ public function __construct(array $adapters, int $defaultLifetime = 0) $this->adapterCount = \count($this->adapters); $this->syncItem = \Closure::bind( - static function ($sourceItem, $item) use ($defaultLifetime) { + static function ($sourceItem, $item, $sourceMetadata = null) use ($defaultLifetime) { + $sourceItem->isTaggable = false; + $sourceMetadata = $sourceMetadata ?? $sourceItem->metadata; + unset($sourceMetadata[CacheItem::METADATA_TAGS]); + $item->value = $sourceItem->value; - $item->expiry = $sourceItem->expiry; + $item->expiry = $sourceMetadata[CacheItem::METADATA_EXPIRY] ?? $sourceItem->expiry; $item->isHit = $sourceItem->isHit; - $item->metadata = $sourceItem->metadata; - - $sourceItem->isTaggable = false; - unset($sourceItem->metadata[CacheItem::METADATA_TAGS]); + $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata; if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) { $defaultLifetime = $sourceItem->defaultLifetime; @@ -103,7 +104,7 @@ public function get(string $key, callable $callback, float $beta = null, array & $value = $this->doGet($adapter, $key, $callback, $beta, $metadata); } if (null !== $item) { - ($this->syncItem)($lastItem = $lastItem ?? $item, $item); + ($this->syncItem)($lastItem = $lastItem ?? $item, $item, $metadata); } return $value; diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index c28cd5c609df9..4359953eb77a4 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -61,22 +61,17 @@ static function ($key, $value, $isHit) { * This adapter takes advantage of how PHP stores arrays in its latest versions. * * @param string $file The PHP file were values are cached - * @param CacheItemPoolInterface $fallbackPool Fallback when opcache is disabled + * @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit * * @return CacheItemPoolInterface */ public static function create($file, CacheItemPoolInterface $fallbackPool) { - // Shared memory is available in PHP 7.0+ with OPCache enabled - if (filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) { - if (!$fallbackPool instanceof AdapterInterface) { - $fallbackPool = new ProxyAdapter($fallbackPool); - } - - return new static($file, $fallbackPool); + if (!$fallbackPool instanceof AdapterInterface) { + $fallbackPool = new ProxyAdapter($fallbackPool); } - return $fallbackPool; + return new static($file, $fallbackPool); } /** diff --git a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php index eef9e75b06332..00784b64f5f94 100644 --- a/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php +++ b/src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php @@ -54,7 +54,7 @@ public function process(ContainerBuilder $container) } $seed .= '.'.$container->getParameter('kernel.container_class'); - $pools = []; + $allPools = []; $clearers = []; $attributes = [ 'provider', @@ -119,7 +119,7 @@ public function process(ContainerBuilder $container) $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); } - $pools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + $allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); } $notAliasedCacheClearerId = $this->cacheClearerId; @@ -127,7 +127,7 @@ public function process(ContainerBuilder $container) $this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId); } if ($container->hasDefinition($this->cacheClearerId)) { - $clearers[$notAliasedCacheClearerId] = $pools; + $clearers[$notAliasedCacheClearerId] = $allPools; } foreach ($clearers as $id => $pools) { @@ -145,7 +145,7 @@ public function process(ContainerBuilder $container) } if ($container->hasDefinition('console.command.cache_pool_list')) { - $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, array_keys($pools)); + $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, array_keys($allPools)); } } diff --git a/src/Symfony/Component/Cache/LICENSE b/src/Symfony/Component/Cache/LICENSE index 3c464ca94359b..a7ec70801827a 100644 --- a/src/Symfony/Component/Cache/LICENSE +++ b/src/Symfony/Component/Cache/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2019 Fabien Potencier +Copyright (c) 2016-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Cache/Simple/ApcuCache.php b/src/Symfony/Component/Cache/Simple/ApcuCache.php index c22771e8227c5..f1eb946ff5c39 100644 --- a/src/Symfony/Component/Cache/Simple/ApcuCache.php +++ b/src/Symfony/Component/Cache/Simple/ApcuCache.php @@ -11,7 +11,9 @@ namespace Symfony\Component\Cache\Simple; +use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Traits\ApcuTrait; +use Symfony\Contracts\Cache\CacheInterface; @trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ApcuCache::class, ApcuAdapter::class, CacheInterface::class), E_USER_DEPRECATED); diff --git a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php index 566609359d568..3a4b24469683b 100644 --- a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php +++ b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php @@ -41,18 +41,14 @@ public function __construct(string $file, Psr16CacheInterface $fallbackPool) /** * This adapter takes advantage of how PHP stores arrays in its latest versions. * - * @param string $file The PHP file were values are cached + * @param string $file The PHP file were values are cached + * @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit * * @return Psr16CacheInterface */ public static function create($file, Psr16CacheInterface $fallbackPool) { - // Shared memory is available in PHP 7.0+ with OPCache enabled - if (filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) { - return new static($file, $fallbackPool); - } - - return $fallbackPool; + return new static($file, $fallbackPool); } /** diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php index ac92006404520..74f53804efe69 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php @@ -28,7 +28,7 @@ class ChainAdapterTest extends AdapterTestCase public function createCachePool($defaultLifetime = 0, $testMethod = null) { if ('testGetMetadata' === $testMethod) { - return new ChainAdapter([new FilesystemAdapter('', $defaultLifetime)], $defaultLifetime); + return new ChainAdapter([new FilesystemAdapter('a', $defaultLifetime), new FilesystemAdapter('b', $defaultLifetime)], $defaultLifetime); } return new ChainAdapter([new ArrayAdapter($defaultLifetime), new ExternalAdapter(), new FilesystemAdapter('', $defaultLifetime)], $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php index c4055fb747445..e3a4b72448974 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php @@ -65,6 +65,8 @@ public static function setUpBeforeClass(): void protected function tearDown(): void { + $this->createCachePool()->clear(); + if (file_exists(sys_get_temp_dir().'/symfony-cache')) { FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache'); } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php index d8e20179883d2..73ca191a167da 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php @@ -37,6 +37,8 @@ public static function setUpBeforeClass(): void protected function tearDown(): void { + $this->createCachePool()->clear(); + if (file_exists(sys_get_temp_dir().'/symfony-cache')) { FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache'); } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterAppendOnlyTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterAppendOnlyTest.php new file mode 100644 index 0000000000000..a38a6f9f5c61a --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterAppendOnlyTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Adapter; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\PhpFilesAdapter; + +/** + * @group time-sensitive + */ +class PhpFilesAdapterAppendOnlyTest extends PhpFilesAdapterTest +{ + protected $skippedTests = [ + 'testDefaultLifeTime' => 'PhpFilesAdapter does not allow configuring a default lifetime.', + 'testExpiration' => 'PhpFilesAdapter in append-only mode does not expiration.', + ]; + + public function createCachePool(): CacheItemPoolInterface + { + return new PhpFilesAdapter('sf-cache', 0, null, true); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheTest.php index c1d4ab2d7705d..7dda920d049f5 100644 --- a/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheTest.php +++ b/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheTest.php @@ -57,6 +57,8 @@ public static function setUpBeforeClass(): void protected function tearDown(): void { + $this->createSimpleCache()->clear(); + if (file_exists(sys_get_temp_dir().'/symfony-cache')) { FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache'); } diff --git a/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php b/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php index 7ae814a98a0d2..800f16d108c02 100644 --- a/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php +++ b/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php @@ -44,6 +44,8 @@ public static function setUpBeforeClass(): void protected function tearDown(): void { + $this->createSimpleCache()->clear(); + if (file_exists(sys_get_temp_dir().'/symfony-cache')) { FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache'); } diff --git a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php index 37e1fd1f06b3c..ed7289952411f 100644 --- a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php @@ -26,7 +26,7 @@ trait FilesystemCommonTrait private function init($namespace, $directory) { if (!isset($directory[0])) { - $directory = sys_get_temp_dir().'/symfony-cache'; + $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache'; } else { $directory = realpath($directory) ?: $directory; } @@ -55,8 +55,8 @@ protected function doClear($namespace) { $ok = true; - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) { - $ok = ($file->isDir() || $this->doUnlink($file) || !file_exists($file)) && $ok; + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok; } return $ok; diff --git a/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php b/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php index 4395de0252894..a5ad230fe885f 100644 --- a/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php @@ -29,6 +29,8 @@ trait PhpArrayTrait private $keys; private $values; + private static $valuesCache = []; + /** * Store an array of cached values. * @@ -115,6 +117,7 @@ public function warmUp(array $values) unset($serialized, $value, $dump); @rename($tmpFile, $this->file); + unset(self::$valuesCache[$this->file]); $this->initialize(); } @@ -127,6 +130,7 @@ public function clear() $this->keys = $this->values = []; $cleared = @unlink($this->file) || !file_exists($this->file); + unset(self::$valuesCache[$this->file]); return $this->pool->clear() && $cleared; } @@ -136,12 +140,15 @@ public function clear() */ private function initialize() { - if (!file_exists($this->file)) { + if (isset(self::$valuesCache[$this->file])) { + $values = self::$valuesCache[$this->file]; + } elseif (!file_exists($this->file)) { $this->keys = $this->values = []; return; + } else { + $values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []]; } - $values = (include $this->file) ?: [[], []]; if (2 !== \count($values) || !isset($values[0], $values[1])) { $this->keys = $this->values = []; diff --git a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php index 5ed4d6023ef80..a9a1092ad16bb 100644 --- a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php @@ -35,12 +35,13 @@ trait PhpFilesTrait private $files = []; private static $startTime; + private static $valuesCache = []; public static function isSupported() { self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time(); - return \function_exists('opcache_invalidate') && ('cli' !== \PHP_SAPI || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN)) && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN); + return \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN)); } /** @@ -54,7 +55,7 @@ public function prune() set_error_handler($this->includeHandler); try { - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { try { if (\is_array($expiresAt = include $file)) { $expiresAt = $expiresAt[0]; @@ -100,7 +101,6 @@ protected function doFetch(array $ids) } elseif (!\is_object($value)) { $values[$id] = $value; } elseif (!$value instanceof LazyValue) { - // calling a Closure is for @deprecated BC and should be removed in Symfony 5.0 $values[$id] = $value(); } elseif (false === $values[$id] = include $value->file) { unset($values[$id], $this->values[$id]); @@ -123,14 +123,20 @@ protected function doFetch(array $ids) try { $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); - if (\is_array($expiresAt = include $file)) { + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $this->values[$id]] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } + [$expiresAt, $this->values[$id]] = $expiresAt; } elseif ($now < $expiresAt) { $this->values[$id] = new LazyValue($file); } if ($now >= $expiresAt) { - unset($this->values[$id], $missingIds[$k]); + unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]); } } catch (\ErrorException $e) { unset($missingIds[$k]); @@ -159,7 +165,13 @@ protected function doHave($id) $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); $getExpiry = true; - if (\is_array($expiresAt = include $file)) { + if (isset(self::$valuesCache[$file])) { + [$expiresAt, $value] = self::$valuesCache[$file]; + } elseif (\is_array($expiresAt = include $file)) { + if ($this->appendOnly) { + self::$valuesCache[$file] = $expiresAt; + } + [$expiresAt, $value] = $expiresAt; } elseif ($this->appendOnly) { $value = new LazyValue($file); @@ -211,12 +223,14 @@ protected function doSave(array $values, $lifetime) $value = var_export($value, true); } - if (!$isStaticValue) { + if ($isStaticValue) { + $value = "appendOnly) { + $value = "files[$key] = $this->getFile($key, true); @@ -227,6 +241,7 @@ protected function doSave(array $values, $lifetime) @opcache_invalidate($file, true); @opcache_compile_file($file); } + unset(self::$valuesCache[$file]); } if (!$ok && !is_writable($this->directory)) { @@ -260,6 +275,8 @@ protected function doDelete(array $ids) protected function doUnlink($file) { + unset(self::$valuesCache[$file]); + if (self::isSupported()) { @opcache_invalidate($file, true); } diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php index 84ad86794c1e9..be059f5d17fce 100644 --- a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php +++ b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php @@ -24,13 +24,13 @@ class NodeBuilder implements NodeParentInterface public function __construct() { $this->nodeMapping = [ - 'variable' => __NAMESPACE__.'\\VariableNodeDefinition', - 'scalar' => __NAMESPACE__.'\\ScalarNodeDefinition', - 'boolean' => __NAMESPACE__.'\\BooleanNodeDefinition', - 'integer' => __NAMESPACE__.'\\IntegerNodeDefinition', - 'float' => __NAMESPACE__.'\\FloatNodeDefinition', - 'array' => __NAMESPACE__.'\\ArrayNodeDefinition', - 'enum' => __NAMESPACE__.'\\EnumNodeDefinition', + 'variable' => VariableNodeDefinition::class, + 'scalar' => ScalarNodeDefinition::class, + 'boolean' => BooleanNodeDefinition::class, + 'integer' => IntegerNodeDefinition::class, + 'float' => FloatNodeDefinition::class, + 'array' => ArrayNodeDefinition::class, + 'enum' => EnumNodeDefinition::class, ]; } diff --git a/src/Symfony/Component/Config/LICENSE b/src/Symfony/Component/Config/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Config/LICENSE +++ b/src/Symfony/Component/Config/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Config/README.md b/src/Symfony/Component/Config/README.md index bf400da196b22..0bbde55230a21 100644 --- a/src/Symfony/Component/Config/README.md +++ b/src/Symfony/Component/Config/README.md @@ -8,7 +8,7 @@ may be (YAML, XML, INI files, or for instance a database). Resources --------- - * [Documentation](https://symfony.com/doc/current/components/config/index.html) + * [Documentation](https://symfony.com/doc/current/components/config.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/Config/Resource/ClassExistenceResource.php b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php index ba50c62cd61d3..77cd8e51aabfa 100644 --- a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php +++ b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php @@ -37,7 +37,9 @@ class ClassExistenceResource implements SelfCheckingResourceInterface public function __construct(string $resource, bool $exists = null) { $this->resource = $resource; - $this->exists = $exists; + if (null !== $exists) { + $this->exists = [(bool) $exists, null]; + } } /** @@ -65,9 +67,13 @@ public function isFresh($timestamp) { $loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false); - if (null !== $exists = &self::$existsCache[(int) (0 >= $timestamp)][$this->resource]) { - $exists = $exists || $loaded; - } elseif (!$exists = $loaded) { + if (null !== $exists = &self::$existsCache[$this->resource]) { + if ($loaded) { + $exists = [true, null]; + } elseif (0 >= $timestamp && !$exists[0] && null !== $exists[1]) { + throw new \ReflectionException($exists[1]); + } + } elseif ([false, null] === $exists = [$loaded, null]) { if (!self::$autoloadLevel++) { spl_autoload_register(__CLASS__.'::throwOnRequiredClass'); } @@ -75,16 +81,19 @@ public function isFresh($timestamp) self::$autoloadedClass = ltrim($this->resource, '\\'); try { - $exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false); + $exists[0] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false); } catch (\Exception $e) { + $exists[1] = $e->getMessage(); + try { self::throwOnRequiredClass($this->resource, $e); } catch (\ReflectionException $e) { if (0 >= $timestamp) { - unset(self::$existsCache[1][$this->resource]); throw $e; } } + } catch (\Throwable $e) { + $exists[1] = $e->getMessage(); } finally { self::$autoloadedClass = $autoloadedClass; if (!--self::$autoloadLevel) { @@ -97,7 +106,7 @@ public function isFresh($timestamp) $this->exists = $exists; } - return $this->exists xor !$exists; + return $this->exists[0] xor !$exists[0]; } /** @@ -112,6 +121,16 @@ public function __sleep(): array return ['resource', 'exists']; } + /** + * @internal + */ + public function __wakeup() + { + if (\is_bool($this->exists)) { + $this->exists = [$this->exists, null]; + } + } + /** * Throws a reflection exception when the passed class does not exist but is required. * @@ -147,7 +166,17 @@ public static function throwOnRequiredClass($class, \Exception $previous = null) throw $previous; } - $e = new \ReflectionException(sprintf('Class "%s" not found while loading "%s".', $class, self::$autoloadedClass), 0, $previous); + $message = sprintf('Class "%s" not found.', $class); + + if (self::$autoloadedClass !== $class) { + $message = substr_replace($message, sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0); + } + + if (null !== $previous) { + $message = $previous->getMessage(); + } + + $e = new \ReflectionException($message, 0, $previous); if (null !== $previous) { throw $e; diff --git a/src/Symfony/Component/Config/Tests/Definition/Builder/NodeBuilderTest.php b/src/Symfony/Component/Config/Tests/Definition/Builder/NodeBuilderTest.php index 5cc7cfea4f492..46518c659afbd 100644 --- a/src/Symfony/Component/Config/Tests/Definition/Builder/NodeBuilderTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/Builder/NodeBuilderTest.php @@ -35,7 +35,7 @@ public function testThrowsAnExceptionWhenTheNodeClassIsNotFound() public function testAddingANewNodeType() { - $class = __NAMESPACE__.'\\SomeNodeDefinition'; + $class = SomeNodeDefinition::class; $builder = new BaseNodeBuilder(); $node = $builder @@ -47,7 +47,7 @@ public function testAddingANewNodeType() public function testOverridingAnExistingNodeType() { - $class = __NAMESPACE__.'\\SomeNodeDefinition'; + $class = SomeNodeDefinition::class; $builder = new BaseNodeBuilder(); $node = $builder @@ -66,7 +66,7 @@ public function testNodeTypesAreNotCaseSensitive() $this->assertInstanceOf(\get_class($node1), $node2); - $builder->setNodeClass('CuStOm', __NAMESPACE__.'\\SomeNodeDefinition'); + $builder->setNodeClass('CuStOm', SomeNodeDefinition::class); $node1 = $builder->node('', 'CUSTOM'); $node2 = $builder->node('', 'custom'); diff --git a/src/Symfony/Component/Config/Tests/Fixtures/BadFileName.php b/src/Symfony/Component/Config/Tests/Fixtures/BadFileName.php new file mode 100644 index 0000000000000..0f79bdd523a7f --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Fixtures/BadFileName.php @@ -0,0 +1,9 @@ +isFresh(0); } + public function testBadFileName() + { + $this->expectException('ReflectionException'); + $this->expectExceptionMessage('Mismatch between file name and class name.'); + + $res = new ClassExistenceResource(BadFileName::class, false); + $res->isFresh(0); + } + + public function testBadFileNameBis() + { + $this->expectException('ReflectionException'); + $this->expectExceptionMessage('Mismatch between file name and class name.'); + + $res = new ClassExistenceResource(BadFileName::class, false); + $res->isFresh(0); + } + public function testConditionalClass() { $res = new ClassExistenceResource(ConditionalClass::class, false); diff --git a/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php b/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php index 4653c8d7c776c..d1a47a8ea3376 100644 --- a/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php +++ b/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php @@ -48,7 +48,7 @@ public function testLoadFile() $this->assertStringContainsString('XSD file or callable', $e->getMessage()); } - $mock = $this->getMockBuilder(__NAMESPACE__.'\Validator')->getMock(); + $mock = $this->getMockBuilder(Validator::class)->getMock(); $mock->expects($this->exactly(2))->method('validate')->will($this->onConsecutiveCalls(false, true)); try { @@ -68,7 +68,7 @@ public function testParseWithInvalidValidatorCallable() $this->expectExceptionMessage('The XML is not valid'); $fixtures = __DIR__.'/../Fixtures/Util/'; - $mock = $this->getMockBuilder(__NAMESPACE__.'\Validator')->getMock(); + $mock = $this->getMockBuilder(Validator::class)->getMock(); $mock->expects($this->once())->method('validate')->willReturn(false); XmlUtils::parse(file_get_contents($fixtures.'valid.xml'), [$mock, 'validate']); diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 3640e39bab146..e9d80d76d9be1 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -663,8 +663,13 @@ public function find($name) // filter out aliases for commands which are already on the list if (\count($commands) > 1) { $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands; - $commands = array_unique(array_filter($commands, function ($nameOrAlias) use ($commandList, $commands, &$aliases) { - $commandName = $commandList[$nameOrAlias] instanceof Command ? $commandList[$nameOrAlias]->getName() : $nameOrAlias; + $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) { + if (!$commandList[$nameOrAlias] instanceof Command) { + $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias); + } + + $commandName = $commandList[$nameOrAlias]->getName(); + $aliases[$nameOrAlias] = $commandName; return $commandName === $nameOrAlias || !\in_array($commandName, $commands); @@ -680,10 +685,6 @@ public function find($name) $maxLen = max(Helper::strlen($abbrev), $maxLen); } $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) { - if (!$commandList[$cmd] instanceof Command) { - $commandList[$cmd] = $this->commandLoader->get($cmd); - } - if ($commandList[$cmd]->isHidden()) { return false; } @@ -795,7 +796,7 @@ protected function doRenderException(\Exception $e, OutputInterface $output) } if (false !== strpos($message, "class@anonymous\0")) { - $message = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) { + $message = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:)[0-9a-fA-F]++/', function ($m) { return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0]; }, $message); } diff --git a/src/Symfony/Component/Console/Helper/FormatterHelper.php b/src/Symfony/Component/Console/Helper/FormatterHelper.php index 4ad63856dcd5c..d6eccee8e85ac 100644 --- a/src/Symfony/Component/Console/Helper/FormatterHelper.php +++ b/src/Symfony/Component/Console/Helper/FormatterHelper.php @@ -54,12 +54,12 @@ public function formatBlock($messages, $style, $large = false) foreach ($messages as $message) { $message = OutputFormatter::escape($message); $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); - $len = max($this->strlen($message) + ($large ? 4 : 2), $len); + $len = max(self::strlen($message) + ($large ? 4 : 2), $len); } $messages = $large ? [str_repeat(' ', $len)] : []; for ($i = 0; isset($lines[$i]); ++$i) { - $messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i])); + $messages[] = $lines[$i].str_repeat(' ', $len - self::strlen($lines[$i])); } if ($large) { $messages[] = str_repeat(' ', $len); @@ -83,17 +83,13 @@ public function formatBlock($messages, $style, $large = false) */ public function truncate($message, $length, $suffix = '...') { - $computedLength = $length - $this->strlen($suffix); + $computedLength = $length - self::strlen($suffix); - if ($computedLength > $this->strlen($message)) { + if ($computedLength > self::strlen($message)) { return $message; } - if (false === $encoding = mb_detect_encoding($message, null, true)) { - return substr($message, 0, $length).$suffix; - } - - return mb_substr($message, 0, $length, $encoding).$suffix; + return self::substr($message, 0, $length).$suffix; } /** diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index 4d28a39d18123..25d26112ad0f7 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -162,15 +162,9 @@ protected function writePrompt(OutputInterface $output, Question $question) $message = $question->getQuestion(); if ($question instanceof ChoiceQuestion) { - $maxWidth = max(array_map([$this, 'strlen'], array_keys($question->getChoices()))); - - $messages = (array) $question->getQuestion(); - foreach ($question->getChoices() as $key => $value) { - $width = $maxWidth - $this->strlen($key); - $messages[] = ' ['.$key.str_repeat(' ', $width).'] '.$value; - } - - $output->writeln($messages); + $output->writeln(array_merge([ + $question->getQuestion(), + ], $this->formatChoiceQuestionChoices($question, 'info'))); $message = $question->getPrompt(); } @@ -178,6 +172,26 @@ protected function writePrompt(OutputInterface $output, Question $question) $output->write($message); } + /** + * @param string $tag + * + * @return string[] + */ + protected function formatChoiceQuestionChoices(ChoiceQuestion $question, $tag) + { + $messages = []; + + $maxWidth = max(array_map('self::strlen', array_keys($choices = $question->getChoices()))); + + foreach ($choices as $key => $value) { + $padding = str_repeat(' ', $maxWidth - self::strlen($key)); + + $messages[] = sprintf(" [<$tag>%s$padding] %s", $key, $value); + } + + return $messages; + } + /** * Outputs an error message. */ diff --git a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php index 260c03e20bf85..e4e87b2f99188 100644 --- a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php @@ -68,15 +68,15 @@ protected function writePrompt(OutputInterface $output, Question $question) $output->writeln($text); + $prompt = ' > '; + if ($question instanceof ChoiceQuestion) { - $width = max(array_map('strlen', array_keys($question->getChoices()))); + $output->writeln($this->formatChoiceQuestionChoices($question, 'comment')); - foreach ($question->getChoices() as $key => $value) { - $output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); - } + $prompt = $question->getPrompt(); } - $output->write(' > '); + $output->write($prompt); } /** diff --git a/src/Symfony/Component/Console/LICENSE b/src/Symfony/Component/Console/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Console/LICENSE +++ b/src/Symfony/Component/Console/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Console/README.md b/src/Symfony/Component/Console/README.md index 664a37c0ee7be..3e2fc605e5bfd 100644 --- a/src/Symfony/Component/Console/README.md +++ b/src/Symfony/Component/Console/README.md @@ -7,7 +7,7 @@ interfaces. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/console/index.html) + * [Documentation](https://symfony.com/doc/current/components/console.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/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index ed72c6dfae926..381c9579e62b9 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -605,7 +605,7 @@ public function testFindAlternativeCommands() $this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternatives'); $this->assertRegExp('/afoobar1/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternative : "afoobar1"'); $this->assertRegExp('/foo:bar1/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternative : "foo:bar1"'); - $this->assertNotRegExp('/foo:bar(?>!1)/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, without "foo:bar" alternative'); + $this->assertNotRegExp('/foo:bar(?!1)/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, without "foo:bar" alternative'); } } @@ -615,6 +615,9 @@ public function testFindAlternativeCommandsWithAnAlias() $fooCommand->setAliases(['foo2']); $application = new Application(); + $application->setCommandLoader(new FactoryCommandLoader([ + 'foo3' => static function () use ($fooCommand) { return $fooCommand; }, + ])); $application->add($fooCommand); $result = $application->find('foo'); diff --git a/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php index cbf3b957b3913..467f38b6d45c8 100644 --- a/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php @@ -130,6 +130,49 @@ public function testAskThrowsExceptionOnMissingInput() $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new Question('What\'s your name?')); } + public function testChoiceQuestionPadding() + { + $choiceQuestion = new ChoiceQuestion('qqq', [ + 'foo' => 'foo', + 'żółw' => 'bar', + 'łabądź' => 'baz', + ]); + + (new SymfonyQuestionHelper())->ask( + $this->createStreamableInputInterfaceMock($this->getInputStream("foo\n")), + $output = $this->createOutputInterface(), + $choiceQuestion + ); + + $this->assertOutputContains(<< +EOT + , $output, true); + } + + public function testChoiceQuestionCustomPrompt() + { + $choiceQuestion = new ChoiceQuestion('qqq', ['foo']); + $choiceQuestion->setPrompt(' >ccc> '); + + (new SymfonyQuestionHelper())->ask( + $this->createStreamableInputInterfaceMock($this->getInputStream("foo\n")), + $output = $this->createOutputInterface(), + $choiceQuestion + ); + + $this->assertOutputContains(<<ccc> +EOT + , $output, true); + } + protected function getInputStream($input) { $stream = fopen('php://memory', 'r+', false); @@ -157,10 +200,15 @@ protected function createInputInterfaceMock($interactive = true) return $mock; } - private function assertOutputContains($expected, StreamOutput $output) + private function assertOutputContains($expected, StreamOutput $output, $normalize = false) { rewind($output->getStream()); $stream = stream_get_contents($output->getStream()); + + if ($normalize) { + $stream = str_replace(PHP_EOL, "\n", $stream); + } + $this->assertStringContainsString($expected, $stream); } } diff --git a/src/Symfony/Component/CssSelector/LICENSE b/src/Symfony/Component/CssSelector/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/CssSelector/LICENSE +++ b/src/Symfony/Component/CssSelector/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Debug/Exception/FlattenException.php b/src/Symfony/Component/Debug/Exception/FlattenException.php index 2e97ac39bbd35..71dc4febfc9c0 100644 --- a/src/Symfony/Component/Debug/Exception/FlattenException.php +++ b/src/Symfony/Component/Debug/Exception/FlattenException.php @@ -172,7 +172,7 @@ public function getMessage() public function setMessage($message) { if (false !== strpos($message, "class@anonymous\0")) { - $message = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) { + $message = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:)[0-9a-fA-F]++/', function ($m) { return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0]; }, $message); } diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index 743de65622d44..5608bb270e29e 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -252,7 +252,11 @@ public function getContent(FlattenException $exception) foreach ($e['trace'] as $trace) { $content .= ''; if ($trace['function']) { - $content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); + $content .= sprintf('at %s%s%s', $this->formatClass($trace['class']), $trace['type'], $trace['function']); + + if (isset($trace['args'])) { + $content .= sprintf('(%s)', $this->formatArgs($trace['args'])); + } } if (isset($trace['file']) && isset($trace['line'])) { $content .= $this->formatPath($trace['file'], $trace['line']); diff --git a/src/Symfony/Component/Debug/LICENSE b/src/Symfony/Component/Debug/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Debug/LICENSE +++ b/src/Symfony/Component/Debug/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Debug/README.md b/src/Symfony/Component/Debug/README.md index a1d16175c1a1f..38bc800c587b3 100644 --- a/src/Symfony/Component/Debug/README.md +++ b/src/Symfony/Component/Debug/README.md @@ -6,7 +6,7 @@ The Debug component provides tools to ease debugging PHP code. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/debug/index.html) + * [Documentation](https://symfony.com/doc/current/components/debug.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/Debug/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php index 30cf4a3326dbf..d2e4824e3d0de 100644 --- a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php @@ -63,21 +63,21 @@ public function testThrowingClass() $this->expectException('Exception'); $this->expectExceptionMessage('boo'); try { - class_exists(__NAMESPACE__.'\Fixtures\Throwing'); + class_exists(Fixtures\Throwing::class); $this->fail('Exception expected'); } catch (\Exception $e) { $this->assertSame('boo', $e->getMessage()); } // the second call also should throw - class_exists(__NAMESPACE__.'\Fixtures\Throwing'); + class_exists(Fixtures\Throwing::class); } public function testNameCaseMismatch() { $this->expectException('RuntimeException'); $this->expectExceptionMessage('Case mismatch between loaded and declared class names'); - class_exists(__NAMESPACE__.'\TestingCaseMismatch', true); + class_exists(TestingCaseMismatch::class, true); } public function testFileCaseMismatch() @@ -88,7 +88,7 @@ public function testFileCaseMismatch() $this->markTestSkipped('Can only be run on case insensitive filesystems'); } - class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true); + class_exists(Fixtures\CaseMismatch::class, true); } public function testPsr4CaseMismatch() @@ -110,7 +110,7 @@ public function testNotPsr0Bis() public function testClassAlias() { - $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\ClassAlias', true)); + $this->assertTrue(class_exists(Fixtures\ClassAlias::class, true)); } /** @@ -152,7 +152,7 @@ public function testInterfaceExtendsDeprecatedInterface() $e = error_reporting(0); trigger_error('', E_USER_NOTICE); - class_exists('Test\\'.__NAMESPACE__.'\\NonDeprecatedInterfaceClass', true); + class_exists('Test\\'.NonDeprecatedInterfaceClass::class, true); error_reporting($e); restore_error_handler(); @@ -199,7 +199,7 @@ public function testExtendedFinalClass() require __DIR__.'/Fixtures/FinalClasses.php'; $i = 1; - while (class_exists($finalClass = __NAMESPACE__.'\\Fixtures\\FinalClass'.$i++, false)) { + while (class_exists($finalClass = Fixtures\FinalClass::class.$i++, false)) { spl_autoload_call($finalClass); class_exists('Test\\'.__NAMESPACE__.'\\Extends'.substr($finalClass, strrpos($finalClass, '\\') + 1), true); } @@ -225,7 +225,7 @@ public function testExtendedFinalMethod() set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; }); $e = error_reporting(E_USER_DEPRECATED); - class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true); + class_exists(Fixtures\ExtendedFinalMethod::class, true); error_reporting($e); restore_error_handler(); @@ -244,7 +244,7 @@ public function testExtendedDeprecatedMethodDoesntTriggerAnyNotice() $e = error_reporting(0); trigger_error('', E_USER_NOTICE); - class_exists('Test\\'.__NAMESPACE__.'\\ExtendsAnnotatedClass', true); + class_exists('Test\\'.ExtendsAnnotatedClass::class, true); error_reporting($e); restore_error_handler(); @@ -261,7 +261,7 @@ public function testInternalsUse() set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; }); $e = error_reporting(E_USER_DEPRECATED); - class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true); + class_exists('Test\\'.ExtendsInternals::class, true); error_reporting($e); restore_error_handler(); @@ -280,7 +280,7 @@ public function testExtendedMethodDefinesNewParameters() set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; }); $e = error_reporting(E_USER_DEPRECATED); - class_exists(__NAMESPACE__.'\\Fixtures\SubClassWithAnnotatedParameters', true); + class_exists(Fixtures\SubClassWithAnnotatedParameters::class, true); error_reporting($e); restore_error_handler(); @@ -303,7 +303,7 @@ public function testUseTraitWithInternalMethod() set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; }); $e = error_reporting(E_USER_DEPRECATED); - class_exists('Test\\'.__NAMESPACE__.'\\UseTraitWithInternalMethod', true); + class_exists('Test\\'.UseTraitWithInternalMethod::class, true); error_reporting($e); restore_error_handler(); @@ -317,7 +317,7 @@ public function testVirtualUse() set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; }); $e = error_reporting(E_USER_DEPRECATED); - class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtual', true); + class_exists('Test\\'.ExtendsVirtual::class, true); error_reporting($e); restore_error_handler(); @@ -343,7 +343,7 @@ public function testVirtualUseWithMagicCall() set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; }); $e = error_reporting(E_USER_DEPRECATED); - class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtualMagicCall', true); + class_exists('Test\\'.ExtendsVirtualMagicCall::class, true); error_reporting($e); restore_error_handler(); @@ -353,7 +353,7 @@ class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtualMagicCall', true); public function testEvaluatedCode() { - $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\DefinitionInEvaluatedCode', true)); + $this->assertTrue(class_exists(Fixtures\DefinitionInEvaluatedCode::class, true)); } } @@ -372,11 +372,11 @@ public function findFile($class) { $fixtureDir = __DIR__.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR; - if (__NAMESPACE__.'\TestingUnsilencing' === $class) { + if (TestingUnsilencing::class === $class) { eval('-- parse error --'); - } elseif (__NAMESPACE__.'\TestingStacking' === $class) { + } elseif (TestingStacking::class === $class) { eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }'); - } elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) { + } elseif (TestingCaseMismatch::class === $class) { eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}'); } elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) { return $fixtureDir.'psr4'.\DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php'; @@ -386,55 +386,55 @@ public function findFile($class) return $fixtureDir.'notPsr0Bis.php'; } elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) { eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}'); - } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) { + } elseif ('Test\\'.DeprecatedParentClass::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedParentClass extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}'); - } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedInterfaceClass' === $class) { + } elseif ('Test\\'.DeprecatedInterfaceClass::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\DeprecatedInterface {}'); - } elseif ('Test\\'.__NAMESPACE__.'\NonDeprecatedInterfaceClass' === $class) { + } elseif ('Test\\'.NonDeprecatedInterfaceClass::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class NonDeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\NonDeprecatedInterface {}'); - } elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) { + } elseif ('Test\\'.Float::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class Float {}'); - } elseif (0 === strpos($class, 'Test\\'.__NAMESPACE__.'\ExtendsFinalClass')) { + } elseif (0 === strpos($class, 'Test\\'.ExtendsFinalClass::class)) { $classShortName = substr($class, strrpos($class, '\\') + 1); eval('namespace Test\\'.__NAMESPACE__.'; class '.$classShortName.' extends \\'.__NAMESPACE__.'\Fixtures\\'.substr($classShortName, 7).' {}'); - } elseif ('Test\\'.__NAMESPACE__.'\ExtendsAnnotatedClass' === $class) { + } elseif ('Test\\'.ExtendsAnnotatedClass::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsAnnotatedClass extends \\'.__NAMESPACE__.'\Fixtures\AnnotatedClass { public function deprecatedMethod() { } }'); - } elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternals' === $class) { + } elseif ('Test\\'.ExtendsInternals::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternals extends ExtendsInternalsParent { use \\'.__NAMESPACE__.'\Fixtures\InternalTrait; public function internalMethod() { } }'); - } elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternalsParent' === $class) { + } elseif ('Test\\'.ExtendsInternalsParent::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternalsParent extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { }'); - } elseif ('Test\\'.__NAMESPACE__.'\UseTraitWithInternalMethod' === $class) { + } elseif ('Test\\'.UseTraitWithInternalMethod::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class UseTraitWithInternalMethod { use \\'.__NAMESPACE__.'\Fixtures\TraitWithInternalMethod; }'); - } elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtual' === $class) { + } elseif ('Test\\'.ExtendsVirtual::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtual extends ExtendsVirtualParent implements \\'.__NAMESPACE__.'\Fixtures\VirtualSubInterface { public function ownClassMethod() { } public function classMethod() { } public function sameLineInterfaceMethodNoBraces() { } }'); - } elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualParent' === $class) { + } elseif ('Test\\'.ExtendsVirtualParent::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtualParent extends ExtendsVirtualAbstract { public function ownParentMethod() { } public function traitMethod() { } public function sameLineInterfaceMethod() { } public function staticMethodNoBraces() { } // should be static }'); - } elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualAbstract' === $class) { + } elseif ('Test\\'.ExtendsVirtualAbstract::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; abstract class ExtendsVirtualAbstract extends ExtendsVirtualAbstractBase { public static function staticMethod() { } public function ownAbstractMethod() { } public function interfaceMethod() { } }'); - } elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualAbstractBase' === $class) { + } elseif ('Test\\'.ExtendsVirtualAbstractBase::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; abstract class ExtendsVirtualAbstractBase extends \\'.__NAMESPACE__.'\Fixtures\VirtualClass implements \\'.__NAMESPACE__.'\Fixtures\VirtualInterface { public function ownAbstractBaseMethod() { } }'); - } elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualMagicCall' === $class) { + } elseif ('Test\\'.ExtendsVirtualMagicCall::class === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtualMagicCall extends \\'.__NAMESPACE__.'\Fixtures\VirtualClassMagicCall implements \\'.__NAMESPACE__.'\Fixtures\VirtualInterface { }'); } diff --git a/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php index 5498d9d4ab126..1c3d0fd20b572 100644 --- a/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php +++ b/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php @@ -237,6 +237,10 @@ public function flattenDataProvider() public function testArguments() { + if (\PHP_VERSION_ID >= 70400) { + $this->markTestSkipped('PHP 7.4 removes arguments from exception traces.'); + } + $dh = opendir(__DIR__); $fh = tmpfile(); @@ -299,6 +303,10 @@ function () {}, public function testRecursionInArguments() { + if (\PHP_VERSION_ID >= 70400) { + $this->markTestSkipped('PHP 7.4 removes arguments from exception traces.'); + } + $a = null; $a = ['foo', [2, &$a]]; $exception = $this->createException($a); @@ -310,6 +318,10 @@ public function testRecursionInArguments() public function testTooBigArray() { + if (\PHP_VERSION_ID >= 70400) { + $this->markTestSkipped('PHP 7.4 removes arguments from exception traces.'); + } + $a = []; for ($i = 0; $i < 20; ++$i) { for ($j = 0; $j < 50; ++$j) { diff --git a/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php index f3fd16e2ac1da..555048dcf4c37 100644 --- a/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php @@ -29,6 +29,10 @@ public static function setUpBeforeClass(): void // get class loaders wrapped by DebugClassLoader if ($function[0] instanceof DebugClassLoader) { $function = $function[0]->getClassLoader(); + + if (!\is_array($function)) { + continue; + } } if ($function[0] instanceof ComposerClassLoader) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php index 39b183fe64456..890438bb0b375 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php @@ -49,21 +49,10 @@ public function process(ContainerBuilder $container) } if (class_exists($id) || interface_exists($id, false)) { if (0 === strpos($id, '\\') && 1 < substr_count($id, '\\')) { - throw new RuntimeException(sprintf( - 'The definition for "%s" has no class attribute, and appears to reference a class or interface. ' - .'Please specify the class attribute explicitly or remove the leading backslash by renaming ' - .'the service to "%s" to get rid of this error.', - $id, substr($id, 1) - )); + throw new RuntimeException(sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface. Please specify the class attribute explicitly or remove the leading backslash by renaming the service to "%s" to get rid of this error.', $id, substr($id, 1))); } - throw new RuntimeException(sprintf( - 'The definition for "%s" has no class attribute, and appears to reference a ' - .'class or interface in the global namespace. Leaving out the "class" attribute ' - .'is only allowed for namespaced classes. Please specify the class attribute ' - .'explicitly to get rid of this error.', - $id - )); + throw new RuntimeException(sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface in the global namespace. Leaving out the "class" attribute is only allowed for namespaced classes. Please specify the class attribute explicitly to get rid of this error.', $id)); } throw new RuntimeException(sprintf('The definition for "%s" has no class. If you intend to inject this service dynamically at runtime, please mark it as synthetic=true. If this is an abstract definition solely used by child definitions, please add abstract=true, otherwise specify a class to get rid of this error.', $id)); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php index f49e8cf949985..e65b7b331ea30 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php @@ -64,8 +64,18 @@ public function process(ContainerBuilder $container) if (isset($decoratingDefinitions[$inner])) { $decoratingDefinition = $decoratingDefinitions[$inner]; - $definition->setTags(array_merge($decoratingDefinition->getTags(), $definition->getTags())); - $decoratingDefinition->setTags([]); + + $decoratingTags = $decoratingDefinition->getTags(); + $resetTags = []; + + if (isset($decoratingTags['container.service_locator'])) { + // container.service_locator has special logic and it must not be transferred out to decorators + $resetTags = ['container.service_locator' => $decoratingTags['container.service_locator']]; + unset($decoratingTags['container.service_locator']); + } + + $definition->setTags(array_merge($decoratingTags, $definition->getTags())); + $decoratingDefinition->setTags($resetTags); $decoratingDefinitions[$inner] = $definition; } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 9db617520d8d3..757bd4555b75e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -52,13 +52,13 @@ public function __construct() new ValidateEnvPlaceholdersPass(), new ResolveChildDefinitionsPass(), new RegisterServiceSubscribersPass(), - new DecoratorServicePass(), - new ResolveParameterPlaceHoldersPass(false), + new ResolveParameterPlaceHoldersPass(false, false), new ResolveFactoryClassPass(), new ResolveNamedArgumentsPass(), new AutowireRequiredMethodsPass(), new ResolveBindingsPass(), new ServiceLocatorTagPass(), + new DecoratorServicePass(), new CheckDefinitionValidityPass(), new AutowirePass(false), new ResolveTaggedIteratorArgumentPass(), diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php index aeb96418112c9..14bf000863c9d 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php @@ -11,11 +11,14 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Psr\Container\ContainerInterface as PsrContainerInterface; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Contracts\Service\ServiceProviderInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; /** @@ -105,7 +108,14 @@ protected function processValue($value, $isRoot = false) throw new InvalidArgumentException(sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId)); } - $value->addTag('container.service_subscriber.locator', ['id' => (string) ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId)]); + $locatorRef = ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId); + + $value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]); + + $value->setBindings([ + PsrContainerInterface::class => new BoundArgument($locatorRef, false), + ServiceProviderInterface::class => new BoundArgument($locatorRef, false), + ] + $value->getBindings()); return parent::processValue($value); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php index 23fe4cababb6b..aa688f4dfdcf4 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php @@ -95,6 +95,11 @@ protected function processValue($value, $isRoot = false) if ($value instanceof TypedReference && $value->getType() === (string) $value) { // Already checked $bindings = $this->container->getDefinition($this->currentId)->getBindings(); + $name = $value->getName(); + + if (isset($name, $bindings[$name = $value.' $'.$name])) { + return $this->getBindingValue($bindings[$name]); + } if (isset($bindings[$value->getType()])) { return $this->getBindingValue($bindings[$value->getType()]); @@ -107,6 +112,8 @@ protected function processValue($value, $isRoot = false) return parent::processValue($value, $isRoot); } + $bindingNames = []; + foreach ($bindings as $key => $binding) { list($bindingValue, $bindingId, $used, $bindingType, $file) = $binding->getValues(); if ($used) { @@ -116,7 +123,11 @@ protected function processValue($value, $isRoot = false) $this->unusedBindings[$bindingId] = [$key, $this->currentId, $bindingType, $file]; } - if (preg_match('/^(?:(?:array|bool|float|int|string) )?\$/', $key)) { + if (preg_match('/^(?:(?:array|bool|float|int|string|([^ $]++)) )\$/', $key, $m)) { + $bindingNames[substr($key, \strlen($m[0]))] = $binding; + } + + if (!isset($m[1])) { continue; } @@ -177,11 +188,17 @@ protected function processValue($value, $isRoot = false) continue; } - if (!$typeHint || '\\' !== $typeHint[0] || !isset($bindings[$typeHint = substr($typeHint, 1)])) { + if ($typeHint && '\\' === $typeHint[0] && isset($bindings[$typeHint = substr($typeHint, 1)])) { + $arguments[$key] = $this->getBindingValue($bindings[$typeHint]); + continue; } - $arguments[$key] = $this->getBindingValue($bindings[$typeHint]); + if (isset($bindingNames[$parameter->name])) { + $bindingKey = array_search($binding, $bindings, true); + $argumentType = substr($bindingKey, 0, strpos($bindingKey, ' ')); + $this->errorMessages[] = sprintf('Did you forget to add the type "%s" to argument "$%s" of method "%s::%s()"?', $argumentType, $parameter->name, $reflectionMethod->class, $reflectionMethod->name); + } } if ($arguments !== $call[1]) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php index 0b56476c69ebe..b6db6947af4da 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php @@ -63,9 +63,10 @@ private function processDefinition(ContainerBuilder $container, $id, Definition $instanceofTags = []; $instanceofCalls = []; $instanceofBindings = []; + $reflectionClass = null; foreach ($conditionals as $interface => $instanceofDefs) { - if ($interface !== $class && (!$container->getReflectionClass($class, false))) { + if ($interface !== $class && !(null === $reflectionClass ? $reflectionClass = ($container->getReflectionClass($class, false) ?: false) : $reflectionClass)) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php index 2dec13f15d4af..32eb6a3a76f3e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php @@ -24,10 +24,12 @@ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass { private $bag; private $resolveArrays; + private $throwOnResolveException; - public function __construct(bool $resolveArrays = true) + public function __construct($resolveArrays = true, $throwOnResolveException = true) { $this->resolveArrays = $resolveArrays; + $this->throwOnResolveException = $throwOnResolveException; } /** @@ -61,7 +63,16 @@ public function process(ContainerBuilder $container) protected function processValue($value, $isRoot = false) { if (\is_string($value)) { - $v = $this->bag->resolveValue($value); + try { + $v = $this->bag->resolveValue($value); + } catch (ParameterNotFoundException $e) { + if ($this->throwOnResolveException) { + throw $e; + } + + $v = null; + $this->container->getDefinition($this->currentId)->addError($e->getMessage()); + } return $this->resolveArrays || !$v || !\is_array($v) ? $v : $value; } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 5e2f7d2dd4378..c4bf94cc6989f 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -194,14 +194,14 @@ public function dump(array $options = []) if (!empty($options['file']) && is_dir($dir = \dirname($options['file']))) { // Build a regexp where the first root dirs are mandatory, // but every other sub-dir is optional up to the full path in $dir - // Mandate at least 2 root dirs and not more that 5 optional dirs. + // Mandate at least 1 root dir and not more than 5 optional dirs. $dir = explode(\DIRECTORY_SEPARATOR, realpath($dir)); $i = \count($dir); - if (3 <= $i) { + if (2 + (int) ('\\' === \DIRECTORY_SEPARATOR) <= $i) { $regex = ''; - $lastOptionalDir = $i > 8 ? $i - 5 : 3; + $lastOptionalDir = $i > 8 ? $i - 5 : (2 + (int) ('\\' === \DIRECTORY_SEPARATOR)); $this->targetDirMaxMatches = $i - $lastOptionalDir; while (--$i >= $lastOptionalDir) { diff --git a/src/Symfony/Component/DependencyInjection/Extension/Extension.php b/src/Symfony/Component/DependencyInjection/Extension/Extension.php index 925775aa567c3..5f855357d7af9 100644 --- a/src/Symfony/Component/DependencyInjection/Extension/Extension.php +++ b/src/Symfony/Component/DependencyInjection/Extension/Extension.php @@ -80,6 +80,11 @@ public function getAlias() public function getConfiguration(array $config, ContainerBuilder $container) { $class = \get_class($this); + + if (false !== strpos($class, "\0")) { + return null; // ignore anonymous classes + } + $class = substr_replace($class, '\Configuration', strrpos($class, '\\')); $class = $container->getReflectionClass($class); diff --git a/src/Symfony/Component/DependencyInjection/LICENSE b/src/Symfony/Component/DependencyInjection/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/DependencyInjection/LICENSE +++ b/src/Symfony/Component/DependencyInjection/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php index 07326cae69c06..16ec938ec453a 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php @@ -48,13 +48,7 @@ final public function extension(string $namespace, array $config) { if (!$this->container->hasExtension($namespace)) { $extensions = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); - throw new InvalidArgumentException(sprintf( - 'There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s', - $namespace, - $this->file, - $namespace, - $extensions ? sprintf('"%s"', implode('", "', $extensions)) : 'none' - )); + throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s', $namespace, $this->file, $namespace, $extensions ? sprintf('"%s"', implode('", "', $extensions)) : 'none')); } $this->container->loadFromExtension($namespace, static::processValue($config)); diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/CallTrait.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/CallTrait.php index 8e6b17a19d289..282054063b08e 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/CallTrait.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/CallTrait.php @@ -18,16 +18,17 @@ trait CallTrait /** * Adds a method to call after service initialization. * - * @param string $method The method name to call - * @param array $arguments An array of arguments to pass to the method call + * @param string $method The method name to call + * @param array $arguments An array of arguments to pass to the method call + * @param bool $returnsClone Whether the call returns the service instance or not * * @return $this * * @throws InvalidArgumentException on empty $method param */ - final public function call($method, array $arguments = []) + final public function call($method, array $arguments = [], $returnsClone = false) { - $this->definition->addMethodCall($method, static::processValue($arguments, true)); + $this->definition->addMethodCall($method, static::processValue($arguments, true), $returnsClone); return $this; } diff --git a/src/Symfony/Component/DependencyInjection/README.md b/src/Symfony/Component/DependencyInjection/README.md index 932647f94a903..cb2d4a11c5886 100644 --- a/src/Symfony/Component/DependencyInjection/README.md +++ b/src/Symfony/Component/DependencyInjection/README.md @@ -7,7 +7,7 @@ way objects are constructed in your application. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/dependency_injection/index.html) + * [Documentation](https://symfony.com/doc/current/components/dependency_injection.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/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index e036059f53534..a220edd49339a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -40,7 +40,7 @@ public function testProcess() $container = new ContainerBuilder(); $container->register(Foo::class); - $barDefinition = $container->register('bar', __NAMESPACE__.'\Bar'); + $barDefinition = $container->register('bar', Bar::class); $barDefinition->setAutowired(true); (new ResolveClassPass())->process($container); @@ -55,7 +55,7 @@ public function testProcessNotExistingActionParam() $container = new ContainerBuilder(); $container->register(Foo::class); - $barDefinition = $container->register(__NAMESPACE__.'EslaAction', __NAMESPACE__.'\ElsaAction'); + $barDefinition = $container->register(ElsaAction::class, ElsaAction::class); $barDefinition->setAutowired(true); (new ResolveClassPass())->process($container); @@ -63,7 +63,7 @@ public function testProcessNotExistingActionParam() (new AutowirePass())->process($container); $this->fail('AutowirePass should have thrown an exception'); } catch (AutowiringFailedException $e) { - $this->assertSame('Cannot autowire service "Symfony\Component\DependencyInjection\Tests\CompilerEslaAction": argument "$notExisting" of method "Symfony\Component\DependencyInjection\Tests\Compiler\ElsaAction::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotExisting" but this class was not found.', (string) $e->getMessage()); + $this->assertSame('Cannot autowire service "Symfony\Component\DependencyInjection\Tests\Compiler\ElsaAction": argument "$notExisting" of method "__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotExisting" but this class was not found.', (string) $e->getMessage()); } } @@ -86,7 +86,7 @@ public function testProcessAutowireParent() $container = new ContainerBuilder(); $container->register(B::class); - $cDefinition = $container->register('c', __NAMESPACE__.'\C'); + $cDefinition = $container->register('c', C::class); $cDefinition->setAutowired(true); (new ResolveClassPass())->process($container); @@ -103,7 +103,7 @@ public function testProcessAutowireInterface() $container = new ContainerBuilder(); $container->register(F::class); - $gDefinition = $container->register('g', __NAMESPACE__.'\G'); + $gDefinition = $container->register('g', G::class); $gDefinition->setAutowired(true); (new ResolveClassPass())->process($container); @@ -119,9 +119,9 @@ public function testCompleteExistingDefinition() { $container = new ContainerBuilder(); - $container->register('b', __NAMESPACE__.'\B'); + $container->register('b', B::class); $container->register(DInterface::class, F::class); - $hDefinition = $container->register('h', __NAMESPACE__.'\H')->addArgument(new Reference('b')); + $hDefinition = $container->register('h', H::class)->addArgument(new Reference('b')); $hDefinition->setAutowired(true); (new ResolveClassPass())->process($container); @@ -138,7 +138,7 @@ public function testCompleteExistingDefinitionWithNotDefinedArguments() $container->register(B::class); $container->register(DInterface::class, F::class); - $hDefinition = $container->register('h', __NAMESPACE__.'\H')->addArgument('')->addArgument(''); + $hDefinition = $container->register('h', H::class)->addArgument('')->addArgument(''); $hDefinition->setAutowired(true); (new ResolveClassPass())->process($container); @@ -153,7 +153,7 @@ public function testPrivateConstructorThrowsAutowireException() { $container = new ContainerBuilder(); - $container->autowire('private_service', __NAMESPACE__.'\PrivateConstructor'); + $container->autowire('private_service', PrivateConstructor::class); $pass = new AutowirePass(true); try { @@ -168,10 +168,10 @@ public function testTypeCollision() { $container = new ContainerBuilder(); - $container->register('c1', __NAMESPACE__.'\CollisionA'); - $container->register('c2', __NAMESPACE__.'\CollisionB'); - $container->register('c3', __NAMESPACE__.'\CollisionB'); - $aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired'); + $container->register('c1', CollisionA::class); + $container->register('c2', CollisionB::class); + $container->register('c3', CollisionB::class); + $aDefinition = $container->register('a', CannotBeAutowired::class); $aDefinition->setAutowired(true); $pass = new AutowirePass(); @@ -187,9 +187,9 @@ public function testTypeNotGuessable() { $container = new ContainerBuilder(); - $container->register('a1', __NAMESPACE__.'\Foo'); - $container->register('a2', __NAMESPACE__.'\Foo'); - $aDefinition = $container->register('a', __NAMESPACE__.'\NotGuessableArgument'); + $container->register('a1', Foo::class); + $container->register('a2', Foo::class); + $aDefinition = $container->register('a', NotGuessableArgument::class); $aDefinition->setAutowired(true); $pass = new AutowirePass(); @@ -205,9 +205,9 @@ public function testTypeNotGuessableWithSubclass() { $container = new ContainerBuilder(); - $container->register('a1', __NAMESPACE__.'\B'); - $container->register('a2', __NAMESPACE__.'\B'); - $aDefinition = $container->register('a', __NAMESPACE__.'\NotGuessableArgumentForSubclass'); + $container->register('a1', B::class); + $container->register('a2', B::class); + $aDefinition = $container->register('a', NotGuessableArgumentForSubclass::class); $aDefinition->setAutowired(true); $pass = new AutowirePass(); @@ -223,7 +223,7 @@ public function testTypeNotGuessableNoServicesFound() { $container = new ContainerBuilder(); - $aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired'); + $aDefinition = $container->register('a', CannotBeAutowired::class); $aDefinition->setAutowired(true); $pass = new AutowirePass(); @@ -239,10 +239,10 @@ public function testTypeNotGuessableWithTypeSet() { $container = new ContainerBuilder(); - $container->register('a1', __NAMESPACE__.'\Foo'); - $container->register('a2', __NAMESPACE__.'\Foo'); + $container->register('a1', Foo::class); + $container->register('a2', Foo::class); $container->register(Foo::class, Foo::class); - $aDefinition = $container->register('a', __NAMESPACE__.'\NotGuessableArgument'); + $aDefinition = $container->register('a', NotGuessableArgument::class); $aDefinition->setAutowired(true); $pass = new AutowirePass(); @@ -256,10 +256,10 @@ public function testWithTypeSet() { $container = new ContainerBuilder(); - $container->register('c1', __NAMESPACE__.'\CollisionA'); - $container->register('c2', __NAMESPACE__.'\CollisionB'); + $container->register('c1', CollisionA::class); + $container->register('c2', CollisionB::class); $container->setAlias(CollisionInterface::class, 'c2'); - $aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired'); + $aDefinition = $container->register('a', CannotBeAutowired::class); $aDefinition->setAutowired(true); $pass = new AutowirePass(); @@ -273,7 +273,7 @@ public function testServicesAreNotAutoCreated() { $container = new ContainerBuilder(); - $coopTilleulsDefinition = $container->register('coop_tilleuls', __NAMESPACE__.'\LesTilleuls'); + $coopTilleulsDefinition = $container->register('coop_tilleuls', LesTilleuls::class); $coopTilleulsDefinition->setAutowired(true); $pass = new AutowirePass(); @@ -306,7 +306,7 @@ public function testOptionalParameter() $container->register(A::class); $container->register(Foo::class); - $optDefinition = $container->register('opt', __NAMESPACE__.'\OptionalParameter'); + $optDefinition = $container->register('opt', OptionalParameter::class); $optDefinition->setAutowired(true); (new ResolveClassPass())->process($container); @@ -323,7 +323,7 @@ public function testDontTriggerAutowiring() $container = new ContainerBuilder(); $container->register(Foo::class); - $container->register('bar', __NAMESPACE__.'\Bar'); + $container->register('bar', Bar::class); (new ResolveClassPass())->process($container); (new AutowirePass())->process($container); @@ -335,7 +335,7 @@ public function testClassNotFoundThrowsException() { $container = new ContainerBuilder(); - $aDefinition = $container->register('a', __NAMESPACE__.'\BadTypeHintedArgument'); + $aDefinition = $container->register('a', BadTypeHintedArgument::class); $aDefinition->setAutowired(true); $container->register(Dunglas::class, Dunglas::class); @@ -353,7 +353,7 @@ public function testParentClassNotFoundThrowsException() { $container = new ContainerBuilder(); - $aDefinition = $container->register('a', __NAMESPACE__.'\BadParentTypeHintedArgument'); + $aDefinition = $container->register('a', BadParentTypeHintedArgument::class); $aDefinition->setAutowired(true); $container->register(Dunglas::class, Dunglas::class); @@ -372,8 +372,8 @@ public function testDontUseAbstractServices() $container = new ContainerBuilder(); $container->register(Foo::class)->setAbstract(true); - $container->register('foo', __NAMESPACE__.'\Foo'); - $container->register('bar', __NAMESPACE__.'\Bar')->setAutowired(true); + $container->register('foo', Foo::class); + $container->register('bar', Bar::class)->setAutowired(true); (new ResolveClassPass())->process($container); try { @@ -391,7 +391,7 @@ public function testSomeSpecificArgumentsAreSet() $container->register('foo', Foo::class); $container->register(A::class); $container->register(Dunglas::class); - $container->register('multiple', __NAMESPACE__.'\MultipleArguments') + $container->register('multiple', MultipleArguments::class) ->setAutowired(true) // set the 2nd (index 1) argument only: autowire the first and third // args are: A, Foo, Dunglas @@ -421,7 +421,7 @@ public function testScalarArgsCannotBeAutowired() $container->register(A::class); $container->register(Dunglas::class); - $container->register('arg_no_type_hint', __NAMESPACE__.'\MultipleArguments') + $container->register('arg_no_type_hint', MultipleArguments::class) ->setArguments([1 => 'foo']) ->setAutowired(true); @@ -440,7 +440,7 @@ public function testNoTypeArgsCannotBeAutowired() $container->register(A::class); $container->register(Dunglas::class); - $container->register('arg_no_type_hint', __NAMESPACE__.'\MultipleArguments') + $container->register('arg_no_type_hint', MultipleArguments::class) ->setAutowired(true); (new ResolveClassPass())->process($container); @@ -458,7 +458,7 @@ public function testOptionalScalarNotReallyOptionalUsesDefaultValue() $container->register(A::class); $container->register(Lille::class); - $definition = $container->register('not_really_optional_scalar', __NAMESPACE__.'\MultipleArgumentsOptionalScalarNotReallyOptional') + $definition = $container->register('not_really_optional_scalar', MultipleArgumentsOptionalScalarNotReallyOptional::class) ->setAutowired(true); (new ResolveClassPass())->process($container); @@ -473,7 +473,7 @@ public function testOptionalScalarArgsDontMessUpOrder() $container->register(A::class); $container->register(Lille::class); - $container->register('with_optional_scalar', __NAMESPACE__.'\MultipleArgumentsOptionalScalar') + $container->register('with_optional_scalar', MultipleArgumentsOptionalScalar::class) ->setAutowired(true); (new ResolveClassPass())->process($container); @@ -497,7 +497,7 @@ public function testOptionalScalarArgsNotPassedIfLast() $container->register(A::class); $container->register(Lille::class); - $container->register('with_optional_scalar_last', __NAMESPACE__.'\MultipleArgumentsOptionalScalarLast') + $container->register('with_optional_scalar_last', MultipleArgumentsOptionalScalarLast::class) ->setAutowired(true); (new ResolveClassPass())->process($container); @@ -624,9 +624,9 @@ public function testIgnoreServiceWithClassNotExisting() { $container = new ContainerBuilder(); - $container->register('class_not_exist', __NAMESPACE__.'\OptionalServiceClass'); + $container->register('class_not_exist', OptionalServiceClass::class); - $barDefinition = $container->register('bar', __NAMESPACE__.'\Bar'); + $barDefinition = $container->register('bar', Bar::class); $barDefinition->setAutowired(true); $container->register(Foo::class, Foo::class); @@ -680,8 +680,8 @@ public function testProcessDoesNotTriggerDeprecations() { $container = new ContainerBuilder(); $container->register('deprecated', 'Symfony\Component\DependencyInjection\Tests\Fixtures\DeprecatedClass')->setDeprecated(true); - $container->register('foo', __NAMESPACE__.'\Foo'); - $container->register('bar', __NAMESPACE__.'\Bar')->setAutowired(true); + $container->register('foo', Foo::class); + $container->register('bar', Bar::class)->setAutowired(true); $pass = new AutowirePass(); try { @@ -702,7 +702,7 @@ public function testEmptyStringIsKept() $container->register(A::class); $container->register(Lille::class); - $container->register('foo', __NAMESPACE__.'\MultipleArgumentsOptionalScalar') + $container->register('foo', MultipleArgumentsOptionalScalar::class) ->setAutowired(true) ->setArguments(['', '']); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php index d8d7587c8d425..721426b024dc4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php @@ -167,6 +167,25 @@ public function testProcessMovesTagsFromDecoratedDefinitionToDecoratingDefinitio $this->assertEquals(['bar' => ['attr' => 'baz']], $container->getDefinition('deco2')->getTags()); } + public function testProcessLeavesServiceLocatorTagOnOriginalDefinition() + { + $container = new ContainerBuilder(); + $container + ->register('foo') + ->setTags(['container.service_locator' => [0 => []], 'bar' => ['attr' => 'baz']]) + ; + $container + ->register('baz') + ->setTags(['foobar' => ['attr' => 'bar']]) + ->setDecoratedService('foo') + ; + + $this->process($container); + + $this->assertEquals(['container.service_locator' => [0 => []]], $container->getDefinition('baz.inner')->getTags()); + $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar']], $container->getDefinition('baz')->getTags()); + } + protected function process(ContainerBuilder $container) { $repeatedPass = new DecoratorServicePass(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index fc300fde21c11..1865dd6772f08 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -24,6 +24,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass; +use Symfony\Contracts\Service\ServiceProviderInterface; /** * This class tests the integration of the different compiler passes. @@ -142,6 +143,29 @@ public function testCanDecorateServiceSubscriber() $this->assertInstanceOf(DecoratedServiceSubscriber::class, $container->get(ServiceSubscriberStub::class)); } + public function testCanDecorateServiceLocator() + { + $container = new ContainerBuilder(); + + $container->register('foo', 'stdClass')->setPublic(true); + + $container->register(ServiceLocator::class) + ->addTag('container.service_locator') + ->setArguments([[new Reference('foo')]]) + ; + + $container->register(DecoratedServiceLocator::class) + ->setDecoratedService(ServiceLocator::class) + ->setPublic(true) + ->setArguments([new Reference(DecoratedServiceLocator::class.'.inner')]) + ; + + $container->compile(); + + $this->assertInstanceOf(DecoratedServiceLocator::class, $container->get(DecoratedServiceLocator::class)); + $this->assertSame($container->get('foo'), $container->get(DecoratedServiceLocator::class)->get('foo')); + } + /** * @dataProvider getYamlCompileTests */ @@ -416,6 +440,34 @@ class DecoratedServiceSubscriber { } +class DecoratedServiceLocator implements ServiceProviderInterface +{ + /** + * @var ServiceLocator + */ + private $locator; + + public function __construct(ServiceLocator $locator) + { + $this->locator = $locator; + } + + public function get($id) + { + return $this->locator->get($id); + } + + public function has($id): bool + { + return $this->locator->has($id); + } + + public function getProvidedServices(): array + { + return $this->locator->getProvidedServices(); + } +} + class IntegrationTestStub extends IntegrationTestStubParent { } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php index e78d468d66d90..212bd11ec01a4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php @@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\AutowirePass; use Symfony\Component\DependencyInjection\Compiler\RegisterServiceSubscribersPass; +use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveServiceSubscribersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -235,4 +236,32 @@ public static function getSubscribedServices() ]; $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); } + + public function testBinding() + { + $container = new ContainerBuilder(); + + $container->register('foo', TestServiceSubscriber::class) + ->addMethodCall('setServiceProvider') + ->addTag('container.service_subscriber') + ; + + (new RegisterServiceSubscribersPass())->process($container); + (new ResolveBindingsPass())->process($container); + + $foo = $container->getDefinition('foo'); + $locator = $container->getDefinition((string) $foo->getMethodCalls()[0][1][0]); + + $this->assertFalse($locator->isPublic()); + $this->assertSame(ServiceLocator::class, $locator->getClass()); + + $expected = [ + TestServiceSubscriber::class => new ServiceClosureArgument(new TypedReference(TestServiceSubscriber::class, TestServiceSubscriber::class)), + 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')), + ]; + + $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php index 551c30175954e..e35ff1edf056e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php @@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy; @@ -80,7 +81,10 @@ public function testTypedReferenceSupport() { $container = new ContainerBuilder(); - $bindings = [CaseSensitiveClass::class => new BoundArgument(new Reference('foo'))]; + $bindings = [ + CaseSensitiveClass::class => new BoundArgument(new Reference('foo')), + CaseSensitiveClass::class.' $c' => new BoundArgument(new Reference('bar')), + ]; // Explicit service id $definition1 = $container->register('def1', NamedArgumentsDummy::class); @@ -91,11 +95,16 @@ public function testTypedReferenceSupport() $definition2->addArgument(new TypedReference(CaseSensitiveClass::class, CaseSensitiveClass::class)); $definition2->setBindings($bindings); + $definition3 = $container->register('def3', NamedArgumentsDummy::class); + $definition3->addArgument(new TypedReference(CaseSensitiveClass::class, CaseSensitiveClass::class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, 'c')); + $definition3->setBindings($bindings); + $pass = new ResolveBindingsPass(); $pass->process($container); $this->assertEquals([$typedRef], $container->getDefinition('def1')->getArguments()); $this->assertEquals([new Reference('foo')], $container->getDefinition('def2')->getArguments()); + $this->assertEquals([new Reference('bar')], $container->getDefinition('def3')->getArguments()); } public function testScalarSetter() @@ -149,4 +158,19 @@ public function testSyntheticServiceWithBind() $this->assertSame([1 => 'bar'], $container->getDefinition(NamedArgumentsDummy::class)->getArguments()); } + + public function testEmptyBindingTypehint() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Did you forget to add the type "string" to argument "$apiKey" of method "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy::__construct()"?'); + + $container = new ContainerBuilder(); + $bindings = [ + 'string $apiKey' => new BoundArgument('foo'), + ]; + $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); + $definition->setBindings($bindings); + $pass = new ResolveBindingsPass(); + $pass->process($container); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveParameterPlaceHoldersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveParameterPlaceHoldersPassTest.php index a81634d9d9e7f..c6eadfb821f62 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveParameterPlaceHoldersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveParameterPlaceHoldersPassTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Compiler\ResolveParameterPlaceHoldersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; class ResolveParameterPlaceHoldersPassTest extends TestCase { @@ -71,6 +72,31 @@ public function testBindingsShouldBeResolved() $this->assertSame($this->container->getParameterBag()->resolveValue('%env(BAZ)%'), $boundValue); } + public function testParameterNotFoundExceptionsIsThrown() + { + $this->expectException(ParameterNotFoundException::class); + $this->expectExceptionMessage('The service "baz_service_id" has a dependency on a non-existent parameter "non_existent_param".'); + + $containerBuilder = new ContainerBuilder(); + $definition = $containerBuilder->register('baz_service_id'); + $definition->setArgument(0, '%non_existent_param%'); + + $pass = new ResolveParameterPlaceHoldersPass(); + $pass->process($containerBuilder); + } + + public function testParameterNotFoundExceptionsIsNotThrown() + { + $containerBuilder = new ContainerBuilder(); + $definition = $containerBuilder->register('baz_service_id'); + $definition->setArgument(0, '%non_existent_param%'); + + $pass = new ResolveParameterPlaceHoldersPass(true, false); + $pass->process($containerBuilder); + + $this->assertCount(1, $definition->getErrors()); + } + private function createContainerBuilder() { $containerBuilder = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index fb3b2e99628d2..23152b89206b5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1245,7 +1245,7 @@ public function testAutowiring() $container = new ContainerBuilder(); $container->register(A::class)->setPublic(true); - $bDefinition = $container->register('b', __NAMESPACE__.'\B'); + $bDefinition = $container->register('b', B::class); $bDefinition->setAutowired(true); $bDefinition->setPublic(true); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php index 7fc3842af3a49..e040dec0a945e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php @@ -2,6 +2,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures; +use Symfony\Contracts\Service\ServiceProviderInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; class TestServiceSubscriber implements ServiceSubscriberInterface @@ -10,6 +11,10 @@ public function __construct($container) { } + public function setServiceProvider(ServiceProviderInterface $container) + { + } + public static function getSubscribedServices() { return [ diff --git a/src/Symfony/Component/DomCrawler/LICENSE b/src/Symfony/Component/DomCrawler/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/DomCrawler/LICENSE +++ b/src/Symfony/Component/DomCrawler/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index 5c41d81d96554..0c104051ba3f0 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -35,7 +35,7 @@ final class Dotenv private $data; private $end; private $values; - private $usePutenv = true; + private $usePutenv; /** * @var bool If `putenv()` should be used to define environment variables or not. @@ -277,7 +277,10 @@ private function lexValue() $this->cursor += 1 + $len; } elseif ('"' === $this->data[$this->cursor]) { $value = ''; - ++$this->cursor; + + if (++$this->cursor === $this->end) { + throw $this->createFormatException('Missing quote to end the value'); + } while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) { $value .= $this->data[$this->cursor]; @@ -458,7 +461,7 @@ private function resolveVariables($value, array $loadedVars) } elseif (isset($this->values[$name])) { $value = $this->values[$name]; } else { - $value = ''; + $value = (string) getenv($name); } if (!$matches['opening_brace'] && isset($matches['closing_brace'])) { diff --git a/src/Symfony/Component/Dotenv/LICENSE b/src/Symfony/Component/Dotenv/LICENSE index 3c464ca94359b..a7ec70801827a 100644 --- a/src/Symfony/Component/Dotenv/LICENSE +++ b/src/Symfony/Component/Dotenv/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2019 Fabien Potencier +Copyright (c) 2016-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php index e2b387ebc4ecd..e86e0ccb54e9c 100644 --- a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php +++ b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php @@ -40,6 +40,7 @@ public function getEnvDataWithFormatErrors() ['FOO', "Missing = in the environment variable declaration in \".env\" at line 1.\n...FOO...\n ^ line 1 offset 3"], ['FOO="foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo...\n ^ line 1 offset 8"], ['FOO=\'foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo...\n ^ line 1 offset 8"], + ["FOO=\"foo\nBAR=\"bar\"", "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo\\nBAR=\"bar\"...\n ^ line 1 offset 18"], ['FOO=\'foo'."\n", "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo\\n...\n ^ line 1 offset 9"], ['export FOO', "Unable to unset an environment variable in \".env\" at line 1.\n...export FOO...\n ^ line 1 offset 10"], ['FOO=${FOO', "Unclosed braces on variable expansion in \".env\" at line 1.\n...FOO=\${FOO...\n ^ line 1 offset 9"], @@ -431,6 +432,20 @@ public function testGetVariablesValueFromEnvFirst() } } + public function testGetVariablesValueFromGetenv() + { + putenv('Foo=Bar'); + + $dotenv = new Dotenv(true); + + try { + $values = $dotenv->parse('Foo=${Foo}'); + $this->assertSame('Bar', $values['Foo']); + } finally { + putenv('Foo'); + } + } + /** * @group legacy * @expectedDeprecation The default value of "$usePutenv" argument of "%s" will be changed from "true" to "false" in Symfony 5.0. You should define its value explicitly. diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcher.php b/src/Symfony/Component/EventDispatcher/EventDispatcher.php index f7c66309827a4..221fdc8f53bae 100644 --- a/src/Symfony/Component/EventDispatcher/EventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/EventDispatcher.php @@ -270,7 +270,7 @@ private function sortListeners(string $eventName) $this->sorted[$eventName] = []; foreach ($this->listeners[$eventName] as &$listeners) { - foreach ($listeners as $k => $listener) { + foreach ($listeners as $k => &$listener) { if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { $listener[0] = $listener[0](); } diff --git a/src/Symfony/Component/EventDispatcher/LICENSE b/src/Symfony/Component/EventDispatcher/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/EventDispatcher/LICENSE +++ b/src/Symfony/Component/EventDispatcher/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/EventDispatcher/README.md b/src/Symfony/Component/EventDispatcher/README.md index 185c3fecf8fee..e0d38eed017f8 100644 --- a/src/Symfony/Component/EventDispatcher/README.md +++ b/src/Symfony/Component/EventDispatcher/README.md @@ -8,7 +8,7 @@ them. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/event_dispatcher/index.html) + * [Documentation](https://symfony.com/doc/current/components/event_dispatcher.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/ExpressionLanguage/LICENSE b/src/Symfony/Component/ExpressionLanguage/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/ExpressionLanguage/LICENSE +++ b/src/Symfony/Component/ExpressionLanguage/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php index 0af4f16623e0c..11245f9a86e2a 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php @@ -147,8 +147,16 @@ public function evaluate($functions, $values) case '*': return $left * $right; case '/': + if (0 == $right) { + throw new \DivisionByZeroError('Division by zero'); + } + return $left / $right; case '%': + if (0 == $right) { + throw new \DivisionByZeroError('Modulo by zero'); + } + return $left % $right; case 'matches': return preg_match($right, $left); diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 41c84e73564a9..7a2f93a6f3e7f 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -212,7 +212,7 @@ public function chmod($files, $mode, $umask = 0000, $recursive = false) * Change the owner of an array of files or directories. * * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner - * @param string $user The new owner user name + * @param string|int $user A user name or number * @param bool $recursive Whether change the owner recursively or not * * @throws IOException When the change fails @@ -239,7 +239,7 @@ public function chown($files, $user, $recursive = false) * Change the group of an array of files or directories. * * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group - * @param string $group The group name + * @param string|int $group A group name or number * @param bool $recursive Whether change the group recursively or not * * @throws IOException When the change fails diff --git a/src/Symfony/Component/Filesystem/LICENSE b/src/Symfony/Component/Filesystem/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Filesystem/LICENSE +++ b/src/Symfony/Component/Filesystem/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Filesystem/README.md b/src/Symfony/Component/Filesystem/README.md index 877ab3543f32d..cb03d43c15dd2 100644 --- a/src/Symfony/Component/Filesystem/README.md +++ b/src/Symfony/Component/Filesystem/README.md @@ -6,7 +6,7 @@ The Filesystem component provides basic utilities for the filesystem. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/filesystem/index.html) + * [Documentation](https://symfony.com/doc/current/components/filesystem.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/Finder/LICENSE b/src/Symfony/Component/Finder/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Finder/LICENSE +++ b/src/Symfony/Component/Finder/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php b/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php index acb8d02b8c5a9..a4e8b8d41c0e6 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php @@ -31,7 +31,7 @@ public function configureOptions(OptionsResolver $resolver) */ public function getParent() { - return __NAMESPACE__.'\DateType'; + return DateType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 9866ab2456c80..4be88149770f8 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -373,12 +373,12 @@ private function addSubForm(FormBuilderInterface $builder, string $name, ChoiceV ]; if ($options['multiple']) { - $choiceType = __NAMESPACE__.'\CheckboxType'; + $choiceType = CheckboxType::class; // The user can check 0 or more checkboxes. If required // is true, they are required to check all of them. $choiceOpts['required'] = false; } else { - $choiceType = __NAMESPACE__.'\RadioType'; + $choiceType = RadioType::class; } $builder->add($name, $choiceType, $choiceOpts); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php index fc36cca6da937..b441f08ce6fc4 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php @@ -94,7 +94,7 @@ public function configureOptions(OptionsResolver $resolver) 'prototype' => true, 'prototype_data' => null, 'prototype_name' => '__name__', - 'entry_type' => __NAMESPACE__.'\TextType', + 'entry_type' => TextType::class, 'entry_options' => [], 'delete_empty' => false, ]); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php index e15cd01c22181..1bc10ec8340e9 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php @@ -59,7 +59,7 @@ public function configureOptions(OptionsResolver $resolver) */ public function getParent() { - return __NAMESPACE__.'\ChoiceType'; + return ChoiceType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php index 486bdc7b20206..d1d71c32a7ce3 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php @@ -59,7 +59,7 @@ public function configureOptions(OptionsResolver $resolver) */ public function getParent() { - return __NAMESPACE__.'\ChoiceType'; + return ChoiceType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index 4e84b71a7c8e4..3f27bced3f01b 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -171,8 +171,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'time' => $timeParts, ]), ])) - ->add('date', __NAMESPACE__.'\DateType', $dateOptions) - ->add('time', __NAMESPACE__.'\TimeType', $timeOptions) + ->add('date', DateType::class, $dateOptions) + ->add('time', TimeType::class, $timeOptions) ; } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php b/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php index 2434778c760c4..1bc1019ab9f26 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/EmailType.php @@ -20,7 +20,7 @@ class EmailType extends AbstractType */ public function getParent() { - return __NAMESPACE__.'\TextType'; + return TextType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php index 4a0cbe1f8f459..7146de0108714 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php @@ -59,7 +59,7 @@ public function configureOptions(OptionsResolver $resolver) */ public function getParent() { - return __NAMESPACE__.'\ChoiceType'; + return ChoiceType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php index 41e016df1008b..5ce654bb231f7 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php @@ -59,7 +59,7 @@ public function configureOptions(OptionsResolver $resolver) */ public function getParent() { - return __NAMESPACE__.'\ChoiceType'; + return ChoiceType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php b/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php index 5a5b2605a4222..a11708084feb7 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/PasswordType.php @@ -44,7 +44,7 @@ public function configureOptions(OptionsResolver $resolver) */ public function getParent() { - return __NAMESPACE__.'\TextType'; + return TextType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php b/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php index 7c0e8608478bc..471075b9a6bbc 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php @@ -20,7 +20,7 @@ class RadioType extends AbstractType */ public function getParent() { - return __NAMESPACE__.'\CheckboxType'; + return CheckboxType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php b/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php index a69633c540407..36ecdb96da4ee 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/RangeType.php @@ -20,7 +20,7 @@ class RangeType extends AbstractType */ public function getParent() { - return __NAMESPACE__.'\TextType'; + return TextType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php b/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php index 5f9ee5329cc09..ffb7520a582b6 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php @@ -47,7 +47,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ - 'type' => __NAMESPACE__.'\TextType', + 'type' => TextType::class, 'options' => [], 'first_options' => [], 'second_options' => [], diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ResetType.php b/src/Symfony/Component/Form/Extension/Core/Type/ResetType.php index 16978ec5f1433..ce8013d1b09dc 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ResetType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ResetType.php @@ -26,7 +26,7 @@ class ResetType extends AbstractType implements ButtonTypeInterface */ public function getParent() { - return __NAMESPACE__.'\ButtonType'; + return ButtonType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php b/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php index 4766ad094cd8f..c817a26d025b6 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/SearchType.php @@ -20,7 +20,7 @@ class SearchType extends AbstractType */ public function getParent() { - return __NAMESPACE__.'\TextType'; + return TextType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php b/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php index 38666325f374a..78ed618c843d1 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php @@ -33,7 +33,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) */ public function getParent() { - return __NAMESPACE__.'\ButtonType'; + return ButtonType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php b/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php index e4127f932cfc6..7db19d8aedc65 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php @@ -30,7 +30,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) */ public function getParent() { - return __NAMESPACE__.'\TextType'; + return TextType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index ccf51d1fb5f27..53347946a0652 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -102,7 +102,7 @@ public function configureOptions(OptionsResolver $resolver) */ public function getParent() { - return __NAMESPACE__.'\ChoiceType'; + return ChoiceType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php b/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php index 39b9d2d20828c..b0392a9849b03 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/UrlType.php @@ -56,7 +56,7 @@ public function configureOptions(OptionsResolver $resolver) */ public function getParent() { - return __NAMESPACE__.'\TextType'; + return TextType::class; } /** diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index 5acc1adc16b54..80d12c8101b9b 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -30,7 +30,7 @@ class FormValidator extends ConstraintValidator public function validate($form, Constraint $formConstraint) { if (!$formConstraint instanceof Form) { - throw new UnexpectedTypeException($formConstraint, __NAMESPACE__.'\Form'); + throw new UnexpectedTypeException($formConstraint, Form::class); } if (!$form instanceof FormInterface) { diff --git a/src/Symfony/Component/Form/LICENSE b/src/Symfony/Component/Form/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Form/LICENSE +++ b/src/Symfony/Component/Form/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Form/README.md b/src/Symfony/Component/Form/README.md index 3fe6f8bbf3206..5519cd00058dd 100644 --- a/src/Symfony/Component/Form/README.md +++ b/src/Symfony/Component/Form/README.md @@ -6,7 +6,7 @@ The Form component allows you to easily create, process and reuse HTML forms. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/form/index.html) + * [Documentation](https://symfony.com/doc/current/components/form.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/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index 36965c114db73..ffb1c17a2c89d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -133,7 +133,10 @@ public function testDontValidateIfParentWithoutValidConstraint() $parent->add($form); $form->setData($object); + $parent->submit([]); + $this->assertTrue($form->isSubmitted()); + $this->assertTrue($form->isSynchronized()); $this->expectNoValidate(); $this->validator->validate($form, new Form()); @@ -188,10 +191,15 @@ public function testDontValidateIfNoValidationGroups() 'validation_groups' => [], ]) ->setData($object) + ->setCompound(true) + ->setDataMapper(new PropertyPathMapper()) ->getForm(); $form->setData($object); + $form->submit([]); + $this->assertTrue($form->isSubmitted()); + $this->assertTrue($form->isSynchronized()); $this->expectNoValidate(); $this->validator->validate($form, new Form()); @@ -214,6 +222,8 @@ public function testDontValidateConstraintsIfNoValidationGroups() // Launch transformer $form->submit('foo'); + $this->assertTrue($form->isSubmitted()); + $this->assertTrue($form->isSynchronized()); $this->expectNoValidate(); $this->validator->validate($form, new Form()); @@ -236,6 +246,8 @@ public function testDontValidateChildConstraintsIfCallableNoValidationGroups() $form->add($child); $form->submit([]); + $this->assertTrue($form->isSubmitted()); + $this->assertTrue($form->isSynchronized()); $this->expectNoValidate(); $this->validator->validate($form, new Form()); @@ -264,6 +276,8 @@ function () { throw new TransformationFailedException(); } // Launch transformer $form->submit('foo'); + $this->assertTrue($form->isSubmitted()); + $this->assertFalse($form->isSynchronized()); $this->expectNoValidate(); $this->validator->validate($form, new Form()); @@ -299,6 +313,8 @@ function () { throw new TransformationFailedException(); } // Launch transformer $form->submit('foo'); + $this->assertTrue($form->isSubmitted()); + $this->assertFalse($form->isSynchronized()); $this->expectNoValidate(); $this->validator->validate($form, new Form()); @@ -410,6 +426,8 @@ function () { throw new TransformationFailedException(); } // Launch transformer $form->submit(['child' => 'foo']); + $this->assertTrue($form->isSubmitted()); + $this->assertFalse($form->isSynchronized()); $this->expectNoValidate(); $this->validator->validate($form, new Form()); @@ -615,7 +633,10 @@ public function testDontWalkScalars() $form = $this->getBuilder() ->setData('scalar') ->getForm(); + $form->submit('foo'); + $this->assertTrue($form->isSubmitted()); + $this->assertTrue($form->isSynchronized()); $this->expectNoValidate(); $this->validator->validate($form, new Form()); @@ -633,6 +654,8 @@ public function testViolationIfExtraData() $form->submit(['foo' => 'bar']); + $this->assertTrue($form->isSubmitted()); + $this->assertTrue($form->isSynchronized()); $this->expectNoValidate(); $this->validator->validate($form, new Form()); @@ -654,6 +677,8 @@ public function testViolationFormatIfMultipleExtraFields() $form->submit(['foo' => 'bar', 'baz' => 'qux', 'quux' => 'quuz']); + $this->assertTrue($form->isSubmitted()); + $this->assertTrue($form->isSynchronized()); $this->expectNoValidate(); $this->validator->validate($form, new Form()); diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FooSubType.php b/src/Symfony/Component/Form/Tests/Fixtures/FooSubType.php index e4a4f3612e19d..61aa451236d7f 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/FooSubType.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/FooSubType.php @@ -17,6 +17,6 @@ class FooSubType extends AbstractType { public function getParent() { - return __NAMESPACE__.'\FooType'; + return FooType::class; } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php index fbe1601d5bf4d..70c710e922fdf 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBarExtension.php @@ -30,6 +30,6 @@ public function getAllowedOptionValues() public static function getExtendedTypes(): iterable { - return [__NAMESPACE__.'\FooType']; + return [FooType::class]; } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php index 250c122f9cc25..a11f158844732 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php +++ b/src/Symfony/Component/Form/Tests/Fixtures/FooTypeBazExtension.php @@ -23,6 +23,6 @@ public function buildForm(FormBuilderInterface $builder, array $options) public static function getExtendedTypes(): iterable { - return [__NAMESPACE__.'\FooType']; + return [FooType::class]; } } diff --git a/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php b/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php index 210b6657aa142..2ca3ecdcdaa79 100644 --- a/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php @@ -350,12 +350,12 @@ public function testBlockPrefixDefaultsToFQCNIfNoName($typeClass, $blockPrefix) public function provideTypeClassBlockPrefixTuples() { return [ - [__NAMESPACE__.'\Fixtures\FooType', 'foo'], - [__NAMESPACE__.'\Fixtures\Foo', 'foo'], - [__NAMESPACE__.'\Fixtures\Type', 'type'], - [__NAMESPACE__.'\Fixtures\FooBarHTMLType', 'foo_bar_html'], + [Fixtures\FooType::class, 'foo'], + [Fixtures\Foo::class, 'foo'], + [Fixtures\Type::class, 'type'], + [Fixtures\FooBarHTMLType::class, 'foo_bar_html'], [__NAMESPACE__.'\Fixtures\Foo1Bar2Type', 'foo1_bar2'], - [__NAMESPACE__.'\Fixtures\FBooType', 'f_boo'], + [Fixtures\FBooType::class, 'f_boo'], ]; } diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 683b08da461ff..727f878ccc0d7 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -113,26 +113,9 @@ public function request(string $method, string $url, array $options = []): Respo $url = implode('', $url); if (!isset($options['normalized_headers']['user-agent'])) { - $options['normalized_headers']['user-agent'][] = $options['headers'][] = 'User-Agent: Symfony HttpClient/Curl'; + $options['headers'][] = 'User-Agent: Symfony HttpClient/Curl'; } - if ($pushedResponse = $this->multi->pushedResponses[$url] ?? null) { - unset($this->multi->pushedResponses[$url]); - - if (self::acceptPushForRequest($method, $options, $pushedResponse)) { - $this->logger && $this->logger->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url)); - - // Reinitialize the pushed response with request's options - $pushedResponse->response->__construct($this->multi, $url, $options, $this->logger); - - return $pushedResponse->response; - } - - $this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s".', $url)); - } - - $this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url)); - $curlopts = [ CURLOPT_URL => $url, CURLOPT_TCP_NODELAY => true, @@ -211,8 +194,8 @@ public function request(string $method, string $url, array $options = []): Respo $curlopts[CURLOPT_NOSIGNAL] = true; } - if (!isset($options['normalized_headers']['accept-encoding']) && CURL_VERSION_LIBZ & self::$curlVersion['features']) { - $curlopts[CURLOPT_ENCODING] = 'gzip'; // Expose only one encoding, some servers mess up when more are provided + if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) { + $options['headers'][] = 'Accept-Encoding: gzip'; // Expose only one encoding, some servers mess up when more are provided } foreach ($options['headers'] as $header) { @@ -267,7 +250,26 @@ public function request(string $method, string $url, array $options = []): Respo $curlopts[file_exists($options['bindto']) ? CURLOPT_UNIX_SOCKET_PATH : CURLOPT_INTERFACE] = $options['bindto']; } - $ch = curl_init(); + if ($pushedResponse = $this->multi->pushedResponses[$url] ?? null) { + unset($this->multi->pushedResponses[$url]); + + if (self::acceptPushForRequest($method, $options, $pushedResponse)) { + $this->logger && $this->logger->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url)); + + // Reinitialize the pushed response with request's options + $ch = $pushedResponse->handle; + $pushedResponse = $pushedResponse->response; + $pushedResponse->__construct($this->multi, $url, $options, $this->logger); + } else { + $this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s".', $url)); + $pushedResponse = null; + } + } + + if (!$pushedResponse) { + $ch = curl_init(); + $this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url)); + } foreach ($curlopts as $opt => $value) { if (null !== $value && !curl_setopt($ch, $opt, $value) && CURLOPT_CERTINFO !== $opt) { @@ -279,7 +281,7 @@ public function request(string $method, string $url, array $options = []): Respo } } - return new CurlResponse($this->multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host)); + return $pushedResponse ?? new CurlResponse($this->multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host)); } /** @@ -369,7 +371,7 @@ private static function handlePush($parent, $pushed, array $requestHeaders, Curl $url .= $headers[':path'][0]; $logger && $logger->debug(sprintf('Queueing pushed response: "%s"', $url)); - $multi->pushedResponses[$url] = new PushedResponse(new CurlResponse($multi, $pushed), $headers, $multi->openHandles[(int) $parent][1] ?? []); + $multi->pushedResponses[$url] = new PushedResponse(new CurlResponse($multi, $pushed), $headers, $multi->openHandles[(int) $parent][1] ?? [], $pushed); return CURL_PUSH_OK; } diff --git a/src/Symfony/Component/HttpClient/Internal/NativeClientState.php b/src/Symfony/Component/HttpClient/Internal/NativeClientState.php index e82ce4c853d21..6578929dc5466 100644 --- a/src/Symfony/Component/HttpClient/Internal/NativeClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/NativeClientState.php @@ -11,8 +11,6 @@ namespace Symfony\Component\HttpClient\Internal; -use Symfony\Component\HttpClient\Response\NativeResponse; - /** * Internal representation of the native client's state. * @@ -24,8 +22,6 @@ final class NativeClientState extends ClientState { /** @var int */ public $id; - /** @var NativeResponse[] */ - public $pendingResponses = []; /** @var int */ public $maxHostConnections = PHP_INT_MAX; /** @var int */ diff --git a/src/Symfony/Component/HttpClient/Internal/PushedResponse.php b/src/Symfony/Component/HttpClient/Internal/PushedResponse.php index 6f8e8fda3a6ba..08fca60dcb964 100644 --- a/src/Symfony/Component/HttpClient/Internal/PushedResponse.php +++ b/src/Symfony/Component/HttpClient/Internal/PushedResponse.php @@ -29,10 +29,13 @@ final class PushedResponse public $parentOptions = []; - public function __construct(CurlResponse $response, array $requestHeaders, array $parentOptions) + public $handle; + + public function __construct(CurlResponse $response, array $requestHeaders, array $parentOptions, $handle) { $this->response = $response; $this->requestHeaders = $requestHeaders; $this->parentOptions = $parentOptions; + $this->handle = $handle; } } diff --git a/src/Symfony/Component/HttpClient/LICENSE b/src/Symfony/Component/HttpClient/LICENSE index 3f853aaf35fe1..69d925ba7511e 100644 --- a/src/Symfony/Component/HttpClient/LICENSE +++ b/src/Symfony/Component/HttpClient/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2019 Fabien Potencier +Copyright (c) 2018-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index 8098ddf23feb9..06b1f3ad7121d 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -77,7 +77,7 @@ public function request(string $method, string $url, array $options = []): Respo $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; } - if ($gzipEnabled = \extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) { + if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) { // gzip is the most widely available algo, no need to deal with deflate $options['headers'][] = 'Accept-Encoding: gzip'; } @@ -170,7 +170,7 @@ public function request(string $method, string $url, array $options = []): Respo $context = [ 'http' => [ - 'protocol_version' => $options['http_version'] ?: '1.1', + 'protocol_version' => min($options['http_version'] ?: '1.1', '1.1'), 'method' => $method, 'content' => $options['body'], 'ignore_errors' => true, @@ -210,7 +210,7 @@ public function request(string $method, string $url, array $options = []): Respo $context = stream_context_create($context, ['notification' => $notification]); self::configureHeadersAndProxy($context, $host, $options['headers'], $proxy, $noProxy); - return new NativeResponse($this->multi, $context, implode('', $url), $options, $gzipEnabled, $info, $resolveRedirect, $onProgress, $this->logger); + return new NativeResponse($this->multi, $context, implode('', $url), $options, $info, $resolveRedirect, $onProgress, $this->logger); } /** @@ -340,7 +340,7 @@ private static function createRedirectResolver(array $options, string $host, ?ar }); if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) { - $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) { + $redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) { return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:'); }); } diff --git a/src/Symfony/Component/HttpClient/Psr18Client.php b/src/Symfony/Component/HttpClient/Psr18Client.php index 9ec7a0849028b..a251d4283652e 100644 --- a/src/Symfony/Component/HttpClient/Psr18Client.php +++ b/src/Symfony/Component/HttpClient/Psr18Client.php @@ -105,7 +105,7 @@ public function sendRequest(RequestInterface $request): ResponseInterface /** * @internal */ -trait Psr18ExceptionTrait +class Psr18NetworkException extends \RuntimeException implements NetworkExceptionInterface { private $request; @@ -121,18 +121,21 @@ public function getRequest(): RequestInterface } } -/** - * @internal - */ -class Psr18NetworkException extends \RuntimeException implements NetworkExceptionInterface -{ - use Psr18ExceptionTrait; -} - /** * @internal */ class Psr18RequestException extends \InvalidArgumentException implements RequestExceptionInterface { - use Psr18ExceptionTrait; + private $request; + + public function __construct(TransportExceptionInterface $e, RequestInterface $request) + { + parent::__construct($e->getMessage(), 0, $e); + $this->request = $request; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } } diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php index 13320acfbba91..51b815b47a1c4 100644 --- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php +++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php @@ -52,6 +52,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null, $this->id = $id = (int) $ch; $this->logger = $logger; + $this->shouldBuffer = $options['buffer'] ?? true; $this->timeout = $options['timeout'] ?? null; $this->info['http_method'] = $method; $this->info['user_data'] = $options['user_data'] ?? null; @@ -65,30 +66,25 @@ public function __construct(CurlClientState $multi, $ch, array $options = null, curl_setopt($ch, CURLOPT_PRIVATE, \in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true) && 1.0 < (float) ($options['http_version'] ?? 1.1) ? 'H2' : 'H0'); // H = headers + retry counter } - if (null === $content = &$this->content) { - $content = ($options['buffer'] ?? true) ? fopen('php://temp', 'w+') : null; - } else { - // Move the pushed response to the activity list - if (ftell($content)) { - rewind($content); - $multi->handlesActivity[$id][] = stream_get_contents($content); - } - $content = ($options['buffer'] ?? true) ? $content : null; - } - curl_setopt($ch, CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int { return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger); }); if (null === $options) { // Pushed response: buffer until requested - curl_setopt($ch, CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use (&$content): int { - return fwrite($content, $data); + curl_setopt($ch, CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { + $multi->handlesActivity[$id][] = $data; + curl_pause($ch, CURLPAUSE_RECV); + + return \strlen($data); }); return; } + $this->inflate = !isset($options['normalized_headers']['accept-encoding']); + curl_pause($ch, CURLPAUSE_CONT); + if ($onProgress = $options['on_progress']) { $url = isset($info['url']) ? ['url' => $info['url']] : []; curl_setopt($ch, CURLOPT_NOPROGRESS, false); @@ -108,33 +104,16 @@ public function __construct(CurlClientState $multi, $ch, array $options = null, }); } - curl_setopt($ch, CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use (&$content, $multi, $id): int { + curl_setopt($ch, CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int { $multi->handlesActivity[$id][] = $data; - return null !== $content ? fwrite($content, $data) : \strlen($data); + return \strlen($data); }); $this->initializer = static function (self $response) { - if (null !== $response->info['error']) { - throw new TransportException($response->info['error']); - } - $waitFor = curl_getinfo($ch = $response->handle, CURLINFO_PRIVATE); - if ('H' === $waitFor[0] || 'D' === $waitFor[0]) { - try { - foreach (self::stream([$response]) as $chunk) { - if ($chunk->isFirst()) { - break; - } - } - } catch (\Throwable $e) { - // Persist timeouts thrown during initialization - $response->info['error'] = $e->getMessage(); - $response->close(); - throw $e; - } - } + return 'H' === $waitFor[0] || 'D' === $waitFor[0]; }; // Schedule the request in a non-blocking way @@ -221,6 +200,7 @@ public function __destruct() */ private function close(): void { + $this->inflate = null; unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]); curl_multi_remove_handle($this->multi->handle, $this->handle); curl_setopt_array($this->handle, [ diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php index 4cb8be180b48d..01c3f605a7bbd 100644 --- a/src/Symfony/Component/HttpClient/Response/MockResponse.php +++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php @@ -93,6 +93,7 @@ public function cancel(): void */ protected function close(): void { + $this->inflate = null; $this->body = []; } @@ -104,16 +105,9 @@ public static function fromRequest(string $method, string $url, array $options, $response = new self([]); $response->requestOptions = $options; $response->id = ++self::$idSequence; - $response->content = ($options['buffer'] ?? true) ? fopen('php://temp', 'w+') : null; + $response->shouldBuffer = $options['buffer'] ?? true; $response->initializer = static function (self $response) { - if (null !== $response->info['error']) { - throw new TransportException($response->info['error']); - } - - if (\is_array($response->body[0] ?? null)) { - // Consume the first chunk if it's not yielded yet - self::stream([$response])->current(); - } + return \is_array($response->body[0] ?? null); }; $response->info['redirect_count'] = 0; @@ -186,11 +180,6 @@ protected static function perform(ClientState $multi, array &$responses): void } else { // Data or timeout chunk $multi->handlesActivity[$id][] = $chunk; - - if (\is_string($chunk) && null !== $response->content) { - // Buffer response body - fwrite($response->content, $chunk); - } } } } diff --git a/src/Symfony/Component/HttpClient/Response/NativeResponse.php b/src/Symfony/Component/HttpClient/Response/NativeResponse.php index b8af399a94b10..f8c19fbf8c7d3 100644 --- a/src/Symfony/Component/HttpClient/Response/NativeResponse.php +++ b/src/Symfony/Component/HttpClient/Response/NativeResponse.php @@ -32,14 +32,13 @@ final class NativeResponse implements ResponseInterface private $onProgress; private $remaining; private $buffer; - private $inflate; private $multi; private $debugBuffer; /** * @internal */ - public function __construct(NativeClientState $multi, $context, string $url, $options, bool $gzipEnabled, array &$info, callable $resolveRedirect, ?callable $onProgress, ?LoggerInterface $logger) + public function __construct(NativeClientState $multi, $context, string $url, array $options, array &$info, callable $resolveRedirect, ?callable $onProgress, ?LoggerInterface $logger) { $this->multi = $multi; $this->id = (int) $context; @@ -50,27 +49,17 @@ public function __construct(NativeClientState $multi, $context, string $url, $op $this->info = &$info; $this->resolveRedirect = $resolveRedirect; $this->onProgress = $onProgress; - $this->content = $options['buffer'] ? fopen('php://temp', 'w+') : null; + $this->inflate = !isset($options['normalized_headers']['accept-encoding']); + $this->shouldBuffer = $options['buffer'] ?? true; - // Temporary resources to dechunk/inflate the response stream + // Temporary resource to dechunk the response stream $this->buffer = fopen('php://temp', 'w+'); - $this->inflate = $gzipEnabled ? inflate_init(ZLIB_ENCODING_GZIP) : null; $info['user_data'] = $options['user_data']; ++$multi->responseCount; $this->initializer = static function (self $response) { - if (null !== $response->info['error']) { - throw new TransportException($response->info['error']); - } - - if (null === $response->remaining) { - foreach (self::stream([$response]) as $chunk) { - if ($chunk->isFirst()) { - break; - } - } - } + return null === $response->remaining; }; } @@ -158,14 +147,14 @@ private function open(): void restore_error_handler(); } - stream_set_blocking($h, false); - $this->context = $this->resolveRedirect = null; - - if (isset($context['ssl']['peer_certificate_chain'])) { + if (isset($context['ssl']['capture_peer_cert_chain']) && isset(($context = stream_context_get_options($this->context))['ssl']['peer_certificate_chain'])) { $this->info['peer_certificate_chain'] = $context['ssl']['peer_certificate_chain']; } - // Create dechunk and inflate buffers + stream_set_blocking($h, false); + $this->context = $this->resolveRedirect = null; + + // Create dechunk buffers if (isset($this->headers['content-length'])) { $this->remaining = (int) $this->headers['content-length'][0]; } elseif ('chunked' === ($this->headers['transfer-encoding'][0] ?? null)) { @@ -175,10 +164,6 @@ private function open(): void $this->remaining = -2; } - if ($this->inflate && 'gzip' !== ($this->headers['content-encoding'][0] ?? null)) { - $this->inflate = null; - } - $this->multi->handlesActivity[$this->id] = [new FirstChunk()]; if ('HEAD' === $context['http']['method'] || \in_array($this->info['http_code'], [204, 304], true)) { @@ -188,7 +173,7 @@ private function open(): void return; } - $this->multi->openHandles[$this->id] = [$h, $this->buffer, $this->inflate, $this->content, $this->onProgress, &$this->remaining, &$this->info]; + $this->multi->openHandles[$this->id] = [$h, $this->buffer, $this->onProgress, &$this->remaining, &$this->info]; } /** @@ -209,11 +194,7 @@ private static function schedule(self $response, array &$runningResponses): void $runningResponses[$i] = [$response->multi, []]; } - if (null === $response->remaining) { - $response->multi->pendingResponses[] = $response; - } else { - $runningResponses[$i][1][$response->id] = $response; - } + $runningResponses[$i][1][$response->id] = $response; if (null === $response->buffer) { // Response already completed @@ -232,15 +213,15 @@ private static function perform(NativeClientState $multi, array &$responses = nu $multi->handles = []; } - foreach ($multi->openHandles as $i => [$h, $buffer, $inflate, $content, $onProgress]) { + foreach ($multi->openHandles as $i => [$h, $buffer, $onProgress]) { $hasActivity = false; - $remaining = &$multi->openHandles[$i][5]; - $info = &$multi->openHandles[$i][6]; + $remaining = &$multi->openHandles[$i][3]; + $info = &$multi->openHandles[$i][4]; $e = null; // Read incoming buffer and write it to the dechunk one try { - while ($remaining && '' !== $data = (string) fread($h, 0 > $remaining ? 16372 : $remaining)) { + if ($remaining && '' !== $data = (string) fread($h, 0 > $remaining ? 16372 : $remaining)) { fwrite($buffer, $data); $hasActivity = true; $multi->sleep = false; @@ -268,16 +249,8 @@ private static function perform(NativeClientState $multi, array &$responses = nu rewind($buffer); ftruncate($buffer, 0); - if (null !== $inflate && false === $data = @inflate_add($inflate, $data)) { - $e = new TransportException('Error while processing content unencoding.'); - } - - if ('' !== $data && null === $e) { + if (null === $e) { $multi->handlesActivity[$i][] = $data; - - if (null !== $content && \strlen($data) !== fwrite($content, $data)) { - $e = new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($data))); - } } } @@ -315,25 +288,30 @@ private static function perform(NativeClientState $multi, array &$responses = nu return; } - if ($multi->pendingResponses && \count($multi->handles) < $multi->maxHostConnections) { - // Open the next pending request - this is a blocking operation so we do only one of them - /** @var self $response */ - $response = array_shift($multi->pendingResponses); - $response->open(); - $responses[$response->id] = $response; - $multi->sleep = false; - self::perform($response->multi); - - if (null !== $response->handle) { - $multi->handles[] = $response->handle; + // Create empty activity lists to tell ResponseTrait::stream() we still have pending requests + foreach ($responses as $i => $response) { + if (null === $response->remaining && null !== $response->buffer) { + $multi->handlesActivity[$i] = []; } } - if ($multi->pendingResponses) { - // Create empty activity list to tell ResponseTrait::stream() we still have pending requests - $response = $multi->pendingResponses[0]; - $responses[$response->id] = $response; - $multi->handlesActivity[$response->id] = []; + if (\count($multi->handles) >= $multi->maxHostConnections) { + return; + } + + // Open the next pending request - this is a blocking operation so we do only one of them + foreach ($responses as $i => $response) { + if (null === $response->remaining && null !== $response->buffer) { + $response->open(); + $multi->sleep = false; + self::perform($multi); + + if (null !== $response->handle) { + $multi->handles[] = $response->handle; + } + + break; + } } } diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php index 52e413a07ad33..0452e9e7917eb 100644 --- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php @@ -39,11 +39,6 @@ trait ResponseTrait */ private $initializer; - /** - * @var resource A php://temp stream typically - */ - private $content; - private $info = [ 'response_headers' => [], 'http_code' => 0, @@ -54,6 +49,9 @@ trait ResponseTrait private $handle; private $id; private $timeout; + private $inflate; + private $shouldBuffer; + private $content; private $finalInfo; private $offset = 0; private $jsonData; @@ -64,8 +62,7 @@ trait ResponseTrait public function getStatusCode(): int { if ($this->initializer) { - ($this->initializer)($this); - $this->initializer = null; + self::initialize($this); } return $this->info['http_code']; @@ -77,8 +74,7 @@ public function getStatusCode(): int public function getHeaders(bool $throw = true): array { if ($this->initializer) { - ($this->initializer)($this); - $this->initializer = null; + self::initialize($this); } if ($throw) { @@ -94,8 +90,7 @@ public function getHeaders(bool $throw = true): array public function getContent(bool $throw = true): string { if ($this->initializer) { - ($this->initializer)($this); - $this->initializer = null; + self::initialize($this); } if ($throw) { @@ -201,10 +196,34 @@ abstract protected static function perform(ClientState $multi, array &$responses */ abstract protected static function select(ClientState $multi, float $timeout): int; + private static function initialize(self $response): void + { + if (null !== $response->info['error']) { + throw new TransportException($response->info['error']); + } + + try { + if (($response->initializer)($response)) { + foreach (self::stream([$response]) as $chunk) { + if ($chunk->isFirst()) { + break; + } + } + } + } catch (\Throwable $e) { + // Persist timeouts thrown during initialization + $response->info['error'] = $e->getMessage(); + $response->close(); + throw $e; + } + + $response->initializer = null; + } + private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers, string &$debug = ''): void { foreach ($responseHeaders as $h) { - if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([12345]\d\d) .*#', $h, $m)) { + if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([12345]\d\d)(?: |$)#', $h, $m)) { if ($headers) { $debug .= "< \r\n"; $headers = []; @@ -246,8 +265,7 @@ private function checkStatusCode() private function doDestruct() { if ($this->initializer && null === $this->info['error']) { - ($this->initializer)($this); - $this->initializer = null; + self::initialize($this); $this->checkStatusCode(); } } @@ -299,6 +317,16 @@ public static function stream(iterable $responses, float $timeout = null): \Gene $isTimeout = false; if (\is_string($chunk = array_shift($multi->handlesActivity[$j]))) { + if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate, $chunk)) { + $multi->handlesActivity[$j] = [null, new TransportException('Error while processing content unencoding.')]; + continue; + } + + if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) { + $multi->handlesActivity[$j] = [null, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($chunk)))]; + continue; + } + $response->offset += \strlen($chunk); $chunk = new DataChunk($response->offset, $chunk); } elseif (null === $chunk) { @@ -326,6 +354,9 @@ public static function stream(iterable $responses, float $timeout = null): \Gene $response->logger->info(sprintf('Response: "%s %s"', $info['http_code'], $info['url'])); } + $response->inflate = \extension_loaded('zlib') && $response->inflate && 'gzip' === ($response->headers['content-encoding'][0] ?? null) ? inflate_init(ZLIB_ENCODING_GZIP) : null; + $response->content = $response->shouldBuffer ? fopen('php://temp', 'w+') : null; + yield $response => $chunk; if ($response->initializer && null === $response->info['error']) { diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index c14ff333e8b4a..b36d991ac23e5 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -217,7 +217,7 @@ public function prepare(Request $request) } if ('x-accel-redirect' === strtolower($type)) { // Do X-Accel-Mapping substitutions. - // @link http://wiki.nginx.org/X-accel#X-Accel-Redirect + // @link https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-redirect $parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',='); foreach ($parts as $part) { list($pathPrefix, $location) = $part; diff --git a/src/Symfony/Component/HttpFoundation/LICENSE b/src/Symfony/Component/HttpFoundation/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/HttpFoundation/LICENSE +++ b/src/Symfony/Component/HttpFoundation/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/HttpFoundation/README.md b/src/Symfony/Component/HttpFoundation/README.md index 8907f0b967896..ac98f9b80ad5d 100644 --- a/src/Symfony/Component/HttpFoundation/README.md +++ b/src/Symfony/Component/HttpFoundation/README.md @@ -7,7 +7,7 @@ specification. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/http_foundation/index.html) + * [Documentation](https://symfony.com/doc/current/components/http_foundation.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/HttpFoundation/ResponseHeaderBag.php b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php index cf44d0eceba8e..833b61ea4bc27 100644 --- a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php @@ -267,13 +267,13 @@ public function makeDisposition($disposition, $filename, $filenameFallback = '') */ protected function computeCacheControlValue() { - if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) { - return 'no-cache, private'; - } - if (!$this->cacheControl) { + if ($this->has('Last-Modified') || $this->has('Expires')) { + return 'private, must-revalidate'; // allows for heuristic expiration (RFC 7234 Section 4.2.2) in the case of "Last-Modified" + } + // conservative by default - return 'private, must-revalidate'; + return 'no-cache, private'; } $header = $this->getCacheControlHeader(); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 979cb783a882f..93c9ce2d16bf1 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -218,7 +218,7 @@ public function createTable() // - trailing space removal // - case-insensitivity // - language processing like é == e - $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB"; + $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB"; break; case 'sqlite': $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index 202f3a5a6a166..a2a7e01854dd0 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -403,13 +403,11 @@ public function setOptions(array $options) * ini_set('session.save_path', '/tmp'); * * or pass in a \SessionHandler instance which configures session.save_handler in the - * constructor, for a template see NativeFileSessionHandler or use handlers in - * composer package drak/native-session + * constructor, for a template see NativeFileSessionHandler. * * @see https://php.net/session-set-save-handler * @see https://php.net/sessionhandlerinterface * @see https://php.net/sessionhandler - * @see https://github.com/zikula/NativeSession * * @param \SessionHandlerInterface|null $saveHandler * diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php index dc4f7105f4d8e..8457e557d7ded 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php @@ -51,9 +51,9 @@ public function testCacheControlHeader() $this->assertTrue($bag->hasCacheControlDirective('public')); $bag = new ResponseHeaderBag(['ETag' => 'abcde']); - $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); $this->assertTrue($bag->hasCacheControlDirective('private')); - $this->assertTrue($bag->hasCacheControlDirective('must-revalidate')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); $this->assertFalse($bag->hasCacheControlDirective('max-age')); $bag = new ResponseHeaderBag(['Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT']); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index cb4e8200bebed..19ad79fca29b2 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -353,6 +353,10 @@ protected function lookup(Request $request, $catch = false) return $this->validate($request, $entry, $catch); } + if ($entry->headers->hasCacheControlDirective('no-cache')) { + return $this->validate($request, $entry, $catch); + } + $this->record($request, 'fresh'); $entry->headers->set('Age', $entry->getAge()); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php index 3bdf0f5199891..aee689e1cea3d 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php @@ -110,8 +110,6 @@ public function update(Response $response) $response->headers->set('Age', $this->age); if ($this->isNotCacheableResponseEmbedded) { - $response->setExpires($response->getDate()); - if ($this->flagDirectives['no-store']) { $response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate'); } else { diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 2274f3fb61c1f..9c31bbbd3f8d3 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -73,11 +73,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '4.3.9'; - const VERSION_ID = 40309; + const VERSION = '4.3.10'; + const VERSION_ID = 40310; const MAJOR_VERSION = 4; const MINOR_VERSION = 3; - const RELEASE_VERSION = 9; + const RELEASE_VERSION = 10; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '01/2020'; diff --git a/src/Symfony/Component/HttpKernel/LICENSE b/src/Symfony/Component/HttpKernel/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/HttpKernel/LICENSE +++ b/src/Symfony/Component/HttpKernel/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/HttpKernel/README.md b/src/Symfony/Component/HttpKernel/README.md index cc5e74b6bca3b..abdaf513f9cde 100644 --- a/src/Symfony/Component/HttpKernel/README.md +++ b/src/Symfony/Component/HttpKernel/README.md @@ -9,7 +9,7 @@ an advanced CMS system (Drupal). Resources --------- - * [Documentation](https://symfony.com/doc/current/components/http_kernel/index.html) + * [Documentation](https://symfony.com/doc/current/components/http_kernel.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/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php b/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php index f77b6759afa6a..ba8ab56604a0b 100644 --- a/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php @@ -48,7 +48,7 @@ public function testSignature2() $this->assertEquals([ new ArgumentMetadata('foo', self::class, false, true, null, true), - new ArgumentMetadata('bar', __NAMESPACE__.'\FakeClassThatDoesNotExist', false, true, null, true), + new ArgumentMetadata('bar', FakeClassThatDoesNotExist::class, false, true, null, true), new ArgumentMetadata('baz', 'Fake\ImportedAndFake', false, true, null, true), ], $arguments); } @@ -58,7 +58,7 @@ public function testSignature3() $arguments = $this->factory->createArgumentMetadata([$this, 'signature3']); $this->assertEquals([ - new ArgumentMetadata('bar', __NAMESPACE__.'\FakeClassThatDoesNotExist', false, false, null), + new ArgumentMetadata('bar', FakeClassThatDoesNotExist::class, false, false, null), new ArgumentMetadata('baz', 'Fake\ImportedAndFake', false, false, null), ], $arguments); } diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php index 42215ea0f7619..269b6211ee31f 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php @@ -99,7 +99,7 @@ public function provideControllerCallables() '"Regular" callable', [$this, 'testControllerInspection'], [ - 'class' => __NAMESPACE__.'\RequestDataCollectorTest', + 'class' => RequestDataCollectorTest::class, 'method' => 'testControllerInspection', 'file' => __FILE__, 'line' => $r1->getStartLine(), diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/DisallowRobotsIndexingListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/DisallowRobotsIndexingListenerTest.php index 53b317b7611ce..6534ebf4e2122 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/DisallowRobotsIndexingListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DisallowRobotsIndexingListenerTest.php @@ -24,8 +24,9 @@ class DisallowRobotsIndexingListenerTest extends TestCase /** * @dataProvider provideResponses */ - public function testInvoke(?string $expected, Response $response): void + public function testInvoke(?string $expected, array $responseArgs) { + $response = new Response(...$responseArgs); $listener = new DisallowRobotsIndexingListener(); $event = new ResponseEvent($this->createMock(HttpKernelInterface::class), $this->createMock(Request::class), KernelInterface::MASTER_REQUEST, $response); @@ -37,11 +38,11 @@ public function testInvoke(?string $expected, Response $response): void public function provideResponses(): iterable { - yield 'No header' => ['noindex', new Response()]; + yield 'No header' => ['noindex', []]; yield 'Header already set' => [ 'something else', - new Response('', 204, ['X-Robots-Tag' => 'something else']), + ['', 204, ['X-Robots-Tag' => 'something else']], ]; } } diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php index 15eb11496d9e6..ca2e03ebc57fc 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php @@ -443,6 +443,22 @@ public function testCachesResponsesWithExplicitNoCacheDirective() $this->assertTrue($this->response->headers->has('Age')); } + public function testRevalidatesResponsesWithNoCacheDirectiveEvenIfFresh() + { + $this->setNextResponse(200, ['Cache-Control' => 'public, no-cache, max-age=10', 'ETag' => 'some-etag'], 'OK'); + $this->request('GET', '/'); // warm the cache + + sleep(5); + + $this->setNextResponse(304, ['Cache-Control' => 'public, no-cache, max-age=10', 'ETag' => 'some-etag']); + $this->request('GET', '/'); + + $this->assertHttpKernelIsCalled(); // no-cache -> MUST have revalidated at origin + $this->assertTraceContains('valid'); + $this->assertEquals('OK', $this->response->getContent()); + $this->assertEquals(0, $this->response->getAge()); + } + public function testCachesResponsesWithAnExpirationHeader() { $time = \DateTime::createFromFormat('U', time() + 5); @@ -1242,7 +1258,6 @@ public function testEsiCacheForceValidation() $this->request('GET', '/', [], [], true); $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent()); $this->assertNull($this->response->getTtl()); - $this->assertTrue($this->response->mustRevalidate()); $this->assertTrue($this->response->headers->hasCacheControlDirective('private')); $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache')); } @@ -1273,7 +1288,6 @@ public function testEsiCacheForceValidationForHeadRequests() // This can neither be cached nor revalidated, so it should be private/no cache $this->assertEmpty($this->response->getContent()); $this->assertNull($this->response->getTtl()); - $this->assertTrue($this->response->mustRevalidate()); $this->assertTrue($this->response->headers->hasCacheControlDirective('private')); $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache')); } diff --git a/src/Symfony/Component/Inflector/LICENSE b/src/Symfony/Component/Inflector/LICENSE index f03153cc4a232..2749b15672205 100644 --- a/src/Symfony/Component/Inflector/LICENSE +++ b/src/Symfony/Component/Inflector/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2019 Fabien Potencier +Copyright (c) 2012-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Intl/LICENSE b/src/Symfony/Component/Intl/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Intl/LICENSE +++ b/src/Symfony/Component/Intl/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Ldap/LICENSE b/src/Symfony/Component/Ldap/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Ldap/LICENSE +++ b/src/Symfony/Component/Ldap/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Lock/LICENSE b/src/Symfony/Component/Lock/LICENSE index 3c464ca94359b..a7ec70801827a 100644 --- a/src/Symfony/Component/Lock/LICENSE +++ b/src/Symfony/Component/Lock/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2019 Fabien Potencier +Copyright (c) 2016-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/LICENSE b/src/Symfony/Component/Mailer/Bridge/Amazon/LICENSE index 1a1869751d250..4bf0fef4ff3b0 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/LICENSE +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019 Fabien Potencier +Copyright (c) 2019-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Mailer/Bridge/Google/LICENSE b/src/Symfony/Component/Mailer/Bridge/Google/LICENSE index 1a1869751d250..4bf0fef4ff3b0 100644 --- a/src/Symfony/Component/Mailer/Bridge/Google/LICENSE +++ b/src/Symfony/Component/Mailer/Bridge/Google/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019 Fabien Potencier +Copyright (c) 2019-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/Api/MandrillTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/Api/MandrillTransport.php index c8e313e7b7391..34395ad9221e2 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/Api/MandrillTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/Api/MandrillTransport.php @@ -79,10 +79,14 @@ private function getPayload(Email $email, SmtpEnvelope $envelope): array 'type' => $headers->get('Content-Type')->getBody(), ]; + if ($name = $headers->getHeaderParameter('Content-Disposition', 'name')) { + $att['name'] = $name; + } + if ('inline' === $disposition) { - $payload['images'][] = $att; + $payload['message']['images'][] = $att; } else { - $payload['attachments'][] = $att; + $payload['message']['attachments'][] = $att; } } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php index ea8bcf4dbbba5..ed9bea8d80ebf 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php @@ -42,7 +42,7 @@ protected function doSend(SentMessage $message): void 'json' => [ 'key' => $this->key, 'to' => $this->stringifyAddresses($envelope->getRecipients()), - 'from_email' => $envelope->getSender()->toString(), + 'from_email' => $envelope->getSender()->getAddress(), 'raw_message' => $message->toString(), ], ]); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/LICENSE b/src/Symfony/Component/Mailer/Bridge/Mailchimp/LICENSE index 1a1869751d250..4bf0fef4ff3b0 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/LICENSE +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019 Fabien Potencier +Copyright (c) 2019-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/LICENSE b/src/Symfony/Component/Mailer/Bridge/Mailgun/LICENSE index 1a1869751d250..4bf0fef4ff3b0 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/LICENSE +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019 Fabien Potencier +Copyright (c) 2019-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/LICENSE b/src/Symfony/Component/Mailer/Bridge/Postmark/LICENSE index 1a1869751d250..4bf0fef4ff3b0 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/LICENSE +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019 Fabien Potencier +Copyright (c) 2019-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Http/Api/SendgridTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Http/Api/SendgridTransport.php index 27a530f3099a9..f4415cc8e6fa5 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Http/Api/SendgridTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Http/Api/SendgridTransport.php @@ -115,7 +115,7 @@ private function getAttachments(Email $email): array $disposition = $headers->getHeaderBody('Content-Disposition'); $att = [ - 'content' => $attachment->bodyToString(), + 'content' => str_replace("\r\n", '', $attachment->bodyToString()), 'type' => $headers->get('Content-Type')->getBody(), 'filename' => $filename, 'disposition' => $disposition, diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/LICENSE b/src/Symfony/Component/Mailer/Bridge/Sendgrid/LICENSE index 1a1869751d250..4bf0fef4ff3b0 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/LICENSE +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019 Fabien Potencier +Copyright (c) 2019-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Http/Api/SendgridTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Http/Api/SendgridTransportTest.php index e6cb1a9718ea6..534868383f5be 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Http/Api/SendgridTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Http/Api/SendgridTransportTest.php @@ -58,4 +58,52 @@ public function testSend() $mailer->send($email); } + + public function testLineBreaksInEncodedAttachment() + { + $email = new Email(); + $email->from('foo@example.com') + ->to('bar@example.com') + // even if content doesn't include new lines, the base64 encoding performed later may add them + ->attach('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod', 'lorem.txt'); + + $response = $this->createMock(ResponseInterface::class); + + $response + ->expects($this->once()) + ->method('getStatusCode') + ->willReturn(202); + + $httpClient = $this->createMock(HttpClientInterface::class); + + $httpClient + ->expects($this->once()) + ->method('request') + ->with('POST', 'https://api.sendgrid.com/v3/mail/send', [ + 'json' => [ + 'personalizations' => [ + [ + 'to' => [['email' => 'bar@example.com']], + 'subject' => null, + ], + ], + 'from' => ['email' => 'foo@example.com'], + 'content' => [], + 'attachments' => [ + [ + 'content' => 'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2Q=', + 'filename' => 'lorem.txt', + 'type' => 'application/octet-stream', + 'disposition' => 'attachment', + ], + ], + ], + 'auth_bearer' => 'foo', + ]) + ->willReturn($response); + + $mailer = new SendgridTransport('foo', $httpClient); + + $mailer->send($email); + } } diff --git a/src/Symfony/Component/Mailer/LICENSE b/src/Symfony/Component/Mailer/LICENSE index 1a1869751d250..4bf0fef4ff3b0 100644 --- a/src/Symfony/Component/Mailer/LICENSE +++ b/src/Symfony/Component/Mailer/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019 Fabien Potencier +Copyright (c) 2019-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Messenger/Command/DebugCommand.php b/src/Symfony/Component/Messenger/Command/DebugCommand.php index 2fba6f02f5122..08b76d1c19410 100644 --- a/src/Symfony/Component/Messenger/Command/DebugCommand.php +++ b/src/Symfony/Component/Messenger/Command/DebugCommand.php @@ -108,9 +108,9 @@ private function formatConditions(array $options): string $optionsMapping = []; foreach ($options as $key => $value) { - $optionsMapping[] = ' '.$key.'='.$value; + $optionsMapping[] = $key.'='.$value; } - return ' (when'.implode(', ', $optionsMapping).')'; + return ' (when '.implode(', ', $optionsMapping).')'; } } diff --git a/src/Symfony/Component/Messenger/Command/SetupTransportsCommand.php b/src/Symfony/Component/Messenger/Command/SetupTransportsCommand.php index 1adac92a25419..7848252223908 100644 --- a/src/Symfony/Component/Messenger/Command/SetupTransportsCommand.php +++ b/src/Symfony/Component/Messenger/Command/SetupTransportsCommand.php @@ -59,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $io = new SymfonyStyle($input, $output); $transportNames = $this->transportNames; - // do we want to setup only one transport? + // do we want to set up only one transport? if ($transport = $input->getArgument('transport')) { if (!$this->transportLocator->has($transport)) { throw new \RuntimeException(sprintf('The "%s" transport does not exist.', $transport)); @@ -71,7 +71,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $transport = $this->transportLocator->get($transportName); if ($transport instanceof SetupableTransportInterface) { $transport->setup(); - $io->success(sprintf('The "%s" transport was setup successfully.', $transportName)); + $io->success(sprintf('The "%s" transport was set up successfully.', $transportName)); } else { $io->note(sprintf('The "%s" transport does not support setup.', $transportName)); } diff --git a/src/Symfony/Component/Messenger/Handler/MessageSubscriberInterface.php b/src/Symfony/Component/Messenger/Handler/MessageSubscriberInterface.php index f1fe29c568c6b..64ac1d513470d 100644 --- a/src/Symfony/Component/Messenger/Handler/MessageSubscriberInterface.php +++ b/src/Symfony/Component/Messenger/Handler/MessageSubscriberInterface.php @@ -30,7 +30,7 @@ interface MessageSubscriberInterface extends MessageHandlerInterface * It can also change the priority per classes. * * yield FirstMessage::class => ['priority' => 0]; - * yield SecondMessage::class => ['priority => -10]; + * yield SecondMessage::class => ['priority' => -10]; * * It can also specify a method, a priority, a bus and/or a transport per message: * diff --git a/src/Symfony/Component/Messenger/LICENSE b/src/Symfony/Component/Messenger/LICENSE index 3f853aaf35fe1..69d925ba7511e 100644 --- a/src/Symfony/Component/Messenger/LICENSE +++ b/src/Symfony/Component/Messenger/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2019 Fabien Potencier +Copyright (c) 2018-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Messenger/Middleware/SendMessageMiddleware.php b/src/Symfony/Component/Messenger/Middleware/SendMessageMiddleware.php index 7d25cd5735297..4ed900e1fb684 100644 --- a/src/Symfony/Component/Messenger/Middleware/SendMessageMiddleware.php +++ b/src/Symfony/Component/Messenger/Middleware/SendMessageMiddleware.php @@ -39,7 +39,13 @@ class SendMessageMiddleware implements MiddlewareInterface public function __construct(SendersLocatorInterface $sendersLocator, EventDispatcherInterface $eventDispatcher = null) { $this->sendersLocator = $sendersLocator; - $this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher); + + if (null !== $eventDispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher); + } else { + $this->eventDispatcher = $eventDispatcher; + } + $this->logger = new NullLogger(); } diff --git a/src/Symfony/Component/Messenger/README.md b/src/Symfony/Component/Messenger/README.md index 245224f5c9e7a..972d13b473f9a 100644 --- a/src/Symfony/Component/Messenger/README.md +++ b/src/Symfony/Component/Messenger/README.md @@ -12,6 +12,7 @@ are not covered by Symfony's Resources --------- + * [Documentation](https://symfony.com/doc/current/components/messenger.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/Messenger/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Messenger/Tests/Command/DebugCommandTest.php index 02812adb47a1e..e637d51dd3126 100644 --- a/src/Symfony/Component/Messenger/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Messenger/Tests/Command/DebugCommandTest.php @@ -40,7 +40,7 @@ public function testOutput() { $command = new DebugCommand([ 'command_bus' => [ - DummyCommand::class => [[DummyCommandHandler::class, []]], + DummyCommand::class => [[DummyCommandHandler::class, ['option1' => '1', 'option2' => '2']]], MultipleBusesMessage::class => [[MultipleBusesMessageHandler::class, []]], ], 'query_bus' => [ @@ -62,12 +62,12 @@ public function testOutput() The following messages can be dispatched: - --------------------------------------------------------------------------------------- - Symfony\Component\Messenger\Tests\Fixtures\DummyCommand - handled by Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler - Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage - handled by Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler - --------------------------------------------------------------------------------------- + ----------------------------------------------------------------------------------------------------------- + Symfony\Component\Messenger\Tests\Fixtures\DummyCommand + handled by Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler (when option1=1, option2=2) + Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage + handled by Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler + ----------------------------------------------------------------------------------------------------------- query_bus --------- diff --git a/src/Symfony/Component/Messenger/Tests/Command/SetupTransportsCommandTest.php b/src/Symfony/Component/Messenger/Tests/Command/SetupTransportsCommandTest.php index b3fdf50b7a3c4..edc28f5ea867a 100644 --- a/src/Symfony/Component/Messenger/Tests/Command/SetupTransportsCommandTest.php +++ b/src/Symfony/Component/Messenger/Tests/Command/SetupTransportsCommandTest.php @@ -42,7 +42,7 @@ public function testReceiverNames() $tester->execute([]); $display = $tester->getDisplay(); - $this->assertStringContainsString('The "amqp" transport was setup successfully.', $display); + $this->assertStringContainsString('The "amqp" transport was set up successfully.', $display); $this->assertStringContainsString('The "other_transport" transport does not support setup.', $display); } @@ -66,7 +66,7 @@ public function testReceiverNameArgument() $tester->execute(['transport' => 'amqp']); $display = $tester->getDisplay(); - $this->assertStringContainsString('The "amqp" transport was setup successfully.', $display); + $this->assertStringContainsString('The "amqp" transport was set up successfully.', $display); } public function testReceiverNameArgumentNotFound() diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php index 614cbb82e7913..489020a6dcf9b 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php @@ -227,7 +227,7 @@ public function testItSetupsTheConnectionWithDefaults() ); $amqpExchange->expects($this->once())->method('declareExchange'); - $amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => []]); + $amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => [], 'delivery_mode' => 2]); $amqpQueue->expects($this->once())->method('declareQueue'); $amqpQueue->expects($this->once())->method('bind')->with(self::DEFAULT_EXCHANGE_NAME, null); @@ -250,7 +250,7 @@ public function testItSetupsTheConnection() $factory->method('createQueue')->will($this->onConsecutiveCalls($amqpQueue0, $amqpQueue1)); $amqpExchange->expects($this->once())->method('declareExchange'); - $amqpExchange->expects($this->once())->method('publish')->with('body', 'routing_key', AMQP_NOPARAM, ['headers' => []]); + $amqpExchange->expects($this->once())->method('publish')->with('body', 'routing_key', AMQP_NOPARAM, ['headers' => [], 'delivery_mode' => 2]); $amqpQueue0->expects($this->once())->method('declareQueue'); $amqpQueue0->expects($this->exactly(2))->method('bind')->withConsecutive( [self::DEFAULT_EXCHANGE_NAME, 'binding_key0'], @@ -373,7 +373,7 @@ public function testItDelaysTheMessage() $delayQueue->expects($this->once())->method('declareQueue'); $delayQueue->expects($this->once())->method('bind')->with('delays', 'delay_messages__5000'); - $delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages__5000', AMQP_NOPARAM, ['headers' => ['x-some-headers' => 'foo']]); + $delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages__5000', AMQP_NOPARAM, ['headers' => ['x-some-headers' => 'foo'], 'delivery_mode' => 2]); $connection = Connection::fromDsn('amqp://localhost', [], $factory); $connection->publish('{}', ['x-some-headers' => 'foo'], 5000); @@ -415,7 +415,7 @@ public function testItDelaysTheMessageWithADifferentRoutingKeyAndTTLs() $delayQueue->expects($this->once())->method('declareQueue'); $delayQueue->expects($this->once())->method('bind')->with('delays', 'delay_messages__120000'); - $delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages__120000', AMQP_NOPARAM, ['headers' => []]); + $delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages__120000', AMQP_NOPARAM, ['headers' => [], 'delivery_mode' => 2]); $connection->publish('{}', [], 120000); } @@ -447,12 +447,27 @@ public function testAmqpStampHeadersAreUsed() $amqpExchange = $this->createMock(\AMQPExchange::class) ); - $amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => ['Foo' => 'X', 'Bar' => 'Y']]); + $amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => ['Foo' => 'X', 'Bar' => 'Y'], 'delivery_mode' => 2]); $connection = Connection::fromDsn('amqp://localhost', [], $factory); $connection->publish('body', ['Foo' => 'X'], 0, new AmqpStamp(null, AMQP_NOPARAM, ['headers' => ['Bar' => 'Y']])); } + public function testAmqpStampDelireryModeIsUsed() + { + $factory = new TestAmqpFactory( + $this->createMock(\AMQPConnection::class), + $this->createMock(\AMQPChannel::class), + $this->createMock(\AMQPQueue::class), + $amqpExchange = $this->createMock(\AMQPExchange::class) + ); + + $amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => [], 'delivery_mode' => 1]); + + $connection = Connection::fromDsn('amqp://localhost', [], $factory); + $connection->publish('body', [], 0, new AmqpStamp(null, AMQP_NOPARAM, ['delivery_mode' => 1])); + } + public function testItCanPublishWithTheDefaultRoutingKey() { $factory = new TestAmqpFactory( @@ -519,7 +534,7 @@ public function testItDelaysTheMessageWithTheInitialSuppliedRoutingKeyAsArgument $delayQueue->expects($this->once())->method('declareQueue'); $delayQueue->expects($this->once())->method('bind')->with('delays', 'delay_messages_routing_key_120000'); - $delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages_routing_key_120000', AMQP_NOPARAM, ['headers' => []]); + $delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages_routing_key_120000', AMQP_NOPARAM, ['headers' => [], 'delivery_mode' => 2]); $connection->publish('{}', [], 120000, new AmqpStamp('routing_key')); } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php index 81baaac8d96a2..5d9f68a982a83 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php @@ -177,6 +177,17 @@ public function testGetNonBlocking() $redis->del('messenger-getnonblocking'); } + public function testJsonError() + { + $redis = new \Redis(); + $connection = Connection::fromDsn('redis://localhost/json-error', [], $redis); + try { + $connection->add("\xB1\x31", []); + } catch (TransportException $e) { + } + $this->assertSame('Malformed UTF-8 characters, possibly incorrectly encoded', $e->getMessage()); + } + public function testLastErrorGetsCleared() { $redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock(); diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php index de147413978f1..51cc3877d7ffa 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php @@ -231,6 +231,7 @@ private function publishOnExchange(\AMQPExchange $exchange, string $body, string { $attributes = $amqpStamp ? $amqpStamp->getAttributes() : []; $attributes['headers'] = array_merge($attributes['headers'] ?? [], $headers); + $attributes['delivery_mode'] = $attributes['delivery_mode'] ?? 2; $exchange->publish( $body, @@ -334,7 +335,7 @@ public function get(string $queueName): ?\AMQPEnvelope } } catch (\AMQPQueueException $e) { if (404 === $e->getCode() && $this->shouldSetup()) { - // If we get a 404 for the queue, it means we need to setup the exchange & queue. + // If we get a 404 for the queue, it means we need to set up the exchange & queue. $this->setupExchangeAndQueues(); return $this->get(); diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php index caa13082d9257..338b969e0376f 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Messenger\Transport\Doctrine; -use Doctrine\Common\Persistence\ConnectionRegistry; +use Doctrine\Persistence\ConnectionRegistry; use Symfony\Bridge\Doctrine\RegistryInterface; use Symfony\Component\Messenger\Exception\TransportException; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php index 6ded724ca5bfa..b9515e7ad0cc1 100644 --- a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php @@ -185,9 +185,16 @@ public function add(string $body, array $headers): void } try { - $added = $this->connection->xadd($this->stream, '*', ['message' => json_encode( - ['body' => $body, 'headers' => $headers] - )]); + $message = json_encode([ + 'body' => $body, + 'headers' => $headers, + ]); + + if (false === $message) { + throw new TransportException(json_last_error_msg()); + } + + $added = $this->connection->xadd($this->stream, '*', ['message' => $message]); } catch (\RedisException $e) { throw new TransportException($e->getMessage(), 0, $e); } diff --git a/src/Symfony/Component/Messenger/Worker.php b/src/Symfony/Component/Messenger/Worker.php index 6205baefd43bd..fbd8e56f42b8a 100644 --- a/src/Symfony/Component/Messenger/Worker.php +++ b/src/Symfony/Component/Messenger/Worker.php @@ -52,7 +52,13 @@ public function __construct(array $receivers, MessageBusInterface $bus, array $r $this->receivers = $receivers; $this->bus = $bus; $this->retryStrategies = $retryStrategies; - $this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher); + + if (null !== $eventDispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher); + } else { + $this->eventDispatcher = $eventDispatcher; + } + $this->logger = $logger; } diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json index 728c9fb63baba..78825318a260e 100644 --- a/src/Symfony/Component/Messenger/composer.json +++ b/src/Symfony/Component/Messenger/composer.json @@ -37,6 +37,7 @@ "symfony/var-dumper": "~3.4|~4.0" }, "conflict": { + "doctrine/persistence": "<1.3", "symfony/event-dispatcher": "<4.3", "symfony/debug": "<4.1" }, diff --git a/src/Symfony/Component/Mime/LICENSE b/src/Symfony/Component/Mime/LICENSE index 9a9a61b1c6c10..d53be68356dbf 100644 --- a/src/Symfony/Component/Mime/LICENSE +++ b/src/Symfony/Component/Mime/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2019 Fabien Potencier +Copyright (c) 2010-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/OptionsResolver/LICENSE b/src/Symfony/Component/OptionsResolver/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/OptionsResolver/LICENSE +++ b/src/Symfony/Component/OptionsResolver/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Process/LICENSE b/src/Symfony/Component/Process/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Process/LICENSE +++ b/src/Symfony/Component/Process/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 5d02c60069ec7..8cc3e768a4fd0 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -135,7 +135,7 @@ class Process implements \IteratorAggregate * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input * @param int|float|null $timeout The timeout in seconds or null to disable * - * @throws RuntimeException When proc_open is not installed + * @throws LogicException When proc_open is not installed */ public function __construct($command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) { @@ -188,7 +188,7 @@ public function __construct($command, string $cwd = null, array $env = null, $in * * @return static * - * @throws RuntimeException When proc_open is not installed + * @throws LogicException When proc_open is not installed */ public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60) { @@ -223,9 +223,11 @@ public function __clone() * * @return int The exit status code * - * @throws RuntimeException When process can't be launched - * @throws RuntimeException When process stopped after receiving signal - * @throws LogicException In case a callback is provided and output has been disabled + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException In case a callback is provided and output has been disabled * * @final */ @@ -390,9 +392,9 @@ public function restart(callable $callback = null, array $env = []) * * @return int The exitcode of the process * - * @throws RuntimeException When process timed out - * @throws RuntimeException When process stopped after receiving signal - * @throws LogicException When process is not yet started + * @throws ProcessTimedOutException When process timed out + * @throws ProcessSignaledException When process stopped after receiving signal + * @throws LogicException When process is not yet started */ public function wait(callable $callback = null) { @@ -403,7 +405,7 @@ public function wait(callable $callback = null) if (null !== $callback) { if (!$this->processPipes->haveReadSupport()) { $this->stop(0); - throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait"'); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait"'); } $this->callback = $this->buildCallback($callback); } @@ -433,8 +435,9 @@ public function wait(callable $callback = null) * from the output in real-time while writing the standard input to the process. * It allows to have feedback from the independent process during execution. * - * @throws RuntimeException When process timed out - * @throws LogicException When process is not yet started + * @throws RuntimeException When process timed out + * @throws LogicException When process is not yet started + * @throws ProcessTimedOutException In case the timeout was reached */ public function waitUntil(callable $callback): bool { @@ -443,7 +446,7 @@ public function waitUntil(callable $callback): bool if (!$this->processPipes->haveReadSupport()) { $this->stop(0); - throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".'); + throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".'); } $callback = $this->buildCallback($callback); diff --git a/src/Symfony/Component/PropertyAccess/LICENSE b/src/Symfony/Component/PropertyAccess/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/PropertyAccess/LICENSE +++ b/src/Symfony/Component/PropertyAccess/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index ab8054ddcc6a2..b64bc5d3328df 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -174,14 +174,14 @@ public function setValue(&$objectOrArray, $propertyPath, $value) $value = $zval[self::VALUE]; } } catch (\TypeError $e) { - self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath); + self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath, $e); // It wasn't thrown in this class so rethrow it throw $e; } } - private static function throwInvalidArgumentException($message, $trace, $i, $propertyPath) + private static function throwInvalidArgumentException($message, $trace, $i, $propertyPath, \Throwable $previous = null) { // the type mismatch is not caused by invalid arguments (but e.g. by an incompatible return type hint of the writer method) if (0 !== strpos($message, 'Argument ')) { @@ -195,7 +195,7 @@ private static function throwInvalidArgumentException($message, $trace, $i, $pro $type = substr($message, 2 + $j, strpos($message, ' given', $j) - $j - 2); $message = substr($message, $pos, $j - $pos); - throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $message, 'NULL' === $type ? 'null' : $type, $propertyPath)); + throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $message, 'NULL' === $type ? 'null' : $type, $propertyPath), 0, $previous); } } diff --git a/src/Symfony/Component/PropertyAccess/README.md b/src/Symfony/Component/PropertyAccess/README.md index 1959fd9e93492..891528d29cb94 100644 --- a/src/Symfony/Component/PropertyAccess/README.md +++ b/src/Symfony/Component/PropertyAccess/README.md @@ -7,7 +7,7 @@ object or array using a simple string notation. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/property_access/index.html) + * [Documentation](https://symfony.com/doc/current/components/property_access.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/PropertyInfo/Extractor/PhpDocExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php index 3e3054dbb15e0..6c176031cff9e 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php @@ -196,6 +196,8 @@ private function getDocBlockFromProperty(string $class, string $property): ?DocB return $this->docBlockFactory->create($reflectionProperty, $this->contextFactory->createFromReflector($reflectionProperty->getDeclaringClass())); } catch (\InvalidArgumentException $e) { return null; + } catch (\RuntimeException $e) { + return null; } } @@ -232,6 +234,8 @@ private function getDocBlockFromMethod(string $class, string $ucFirstProperty, i return [$this->docBlockFactory->create($reflectionMethod, $this->contextFactory->createFromReflector($reflectionMethod)), $prefix]; } catch (\InvalidArgumentException $e) { return null; + } catch (\RuntimeException $e) { + return null; } } } diff --git a/src/Symfony/Component/PropertyInfo/LICENSE b/src/Symfony/Component/PropertyInfo/LICENSE index 4cd8bdd3007da..5612f967a232e 100644 --- a/src/Symfony/Component/PropertyInfo/LICENSE +++ b/src/Symfony/Component/PropertyInfo/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2019 Fabien Potencier +Copyright (c) 2015-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Routing/Generator/CompiledUrlGenerator.php b/src/Symfony/Component/Routing/Generator/CompiledUrlGenerator.php index 41cd5893ae2f3..adcc99e31015a 100644 --- a/src/Symfony/Component/Routing/Generator/CompiledUrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/CompiledUrlGenerator.php @@ -40,7 +40,6 @@ public function generate($name, $parameters = [], $referenceType = self::ABSOLUT if (null !== $locale) { do { if (($this->compiledRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) { - unset($parameters['_locale']); $name .= '.'.$locale; break; } @@ -53,6 +52,14 @@ public function generate($name, $parameters = [], $referenceType = self::ABSOLUT list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = $this->compiledRoutes[$name]; + if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) { + if (!\in_array('_locale', $variables, true)) { + unset($parameters['_locale']); + } elseif (!isset($parameters['_locale'])) { + $parameters['_locale'] = $defaults['_locale']; + } + } + return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); } } diff --git a/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php b/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php index 3869ffda4eebe..8526d81bc0761 100644 --- a/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php +++ b/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php @@ -120,7 +120,6 @@ public function generate($name, $parameters = [], $referenceType = self::ABSOLUT if (null !== $locale && null !== $name) { do { if ((self::$declaredRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) { - unset($parameters['_locale']); $name .= '.'.$locale; break; } @@ -133,6 +132,14 @@ public function generate($name, $parameters = [], $referenceType = self::ABSOLUT list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = self::$declaredRoutes[$name]; + if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) { + if (!\in_array('_locale', $variables, true)) { + unset($parameters['_locale']); + } elseif (!isset($parameters['_locale'])) { + $parameters['_locale'] = $defaults['_locale']; + } + } + return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); } EOF; diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index 8be593bf98b6f..72870669964ab 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -134,7 +134,6 @@ public function generate($name, $parameters = [], $referenceType = self::ABSOLUT if (null !== $locale) { do { if (null !== ($route = $this->routes->get($name.'.'.$locale)) && $route->getDefault('_canonical_route') === $name) { - unset($parameters['_locale']); break; } } while (false !== $locale = strstr($locale, '_', true)); @@ -147,7 +146,18 @@ public function generate($name, $parameters = [], $referenceType = self::ABSOLUT // the Route has a cache of its own and is not recompiled as long as it does not get modified $compiledRoute = $route->compile(); - return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes()); + $defaults = $route->getDefaults(); + $variables = $compiledRoute->getVariables(); + + if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) { + if (!\in_array('_locale', $variables, true)) { + unset($parameters['_locale']); + } elseif (!isset($parameters['_locale'])) { + $parameters['_locale'] = $defaults['_locale']; + } + } + + return $this->doGenerate($variables, $defaults, $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes()); } /** diff --git a/src/Symfony/Component/Routing/LICENSE b/src/Symfony/Component/Routing/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Routing/LICENSE +++ b/src/Symfony/Component/Routing/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Routing/README.md b/src/Symfony/Component/Routing/README.md index 88fb1fde5a7bd..a16d9d7fcbbbb 100644 --- a/src/Symfony/Component/Routing/README.md +++ b/src/Symfony/Component/Routing/README.md @@ -6,7 +6,7 @@ The Routing component maps an HTTP request to a set of configuration variables. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/routing/index.html) + * [Documentation](https://symfony.com/doc/current/components/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/Router.php b/src/Symfony/Component/Routing/Router.php index c4fb3e2151bdb..3f91cd64b2450 100644 --- a/src/Symfony/Component/Routing/Router.php +++ b/src/Symfony/Component/Routing/Router.php @@ -97,6 +97,8 @@ class Router implements RouterInterface, RequestMatcherInterface */ private $expressionLanguageProviders = []; + private static $cache = []; + /** * @param LoaderInterface $loader A LoaderInterface instance * @param mixed $resource The main resource to load @@ -289,7 +291,7 @@ public function getMatcher() return $this->matcher; } - $compiled = is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true) && (UrlMatcher::class === $this->options['matcher_base_class'] || RedirectableUrlMatcher::class === $this->options['matcher_base_class']); + $compiled = is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true) && (UrlMatcher::class === $this->options['matcher_base_class'] || RedirectableUrlMatcher::class === $this->options['matcher_base_class']) && is_a($this->options['matcher_dumper_class'], CompiledUrlMatcherDumper::class, true); if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) { $routes = $this->getRouteCollection(); @@ -325,7 +327,7 @@ function (ConfigCacheInterface $cache) { ); if ($compiled) { - return $this->matcher = new $this->options['matcher_class'](require $cache->getPath(), $this->context); + return $this->matcher = new $this->options['matcher_class'](self::getCompiledRoutes($cache->getPath()), $this->context); } if (!class_exists($this->options['matcher_cache_class'], false)) { @@ -346,7 +348,7 @@ public function getGenerator() return $this->generator; } - $compiled = is_a($this->options['generator_class'], CompiledUrlGenerator::class, true) && UrlGenerator::class === $this->options['generator_base_class']; + $compiled = is_a($this->options['generator_class'], CompiledUrlGenerator::class, true) && UrlGenerator::class === $this->options['generator_base_class'] && is_a($this->options['generator_dumper_class'], CompiledUrlGeneratorDumper::class, true); if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) { $routes = $this->getRouteCollection(); @@ -369,7 +371,7 @@ function (ConfigCacheInterface $cache) { ); if ($compiled) { - $this->generator = new $this->options['generator_class'](require $cache->getPath(), $this->context, $this->logger, $this->defaultLocale); + $this->generator = new $this->options['generator_class'](self::getCompiledRoutes($cache->getPath()), $this->context, $this->logger, $this->defaultLocale); } else { if (!class_exists($this->options['generator_cache_class'], false)) { require_once $cache->getPath(); @@ -396,8 +398,8 @@ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterfac */ protected function getGeneratorDumperInstance() { - // For BC, fallback to PhpGeneratorDumper if the UrlGenerator and UrlGeneratorDumper are not consistent with each other - if (is_a($this->options['generator_class'], CompiledUrlGenerator::class, true) !== is_a($this->options['generator_dumper_class'], CompiledUrlGeneratorDumper::class, true)) { + // For BC, fallback to PhpGeneratorDumper (which is the old default value) if the old UrlGenerator is used with the new default CompiledUrlGeneratorDumper + if (!is_a($this->options['generator_class'], CompiledUrlGenerator::class, true) && is_a($this->options['generator_dumper_class'], CompiledUrlGeneratorDumper::class, true)) { return new PhpGeneratorDumper($this->getRouteCollection()); } @@ -409,8 +411,8 @@ protected function getGeneratorDumperInstance() */ protected function getMatcherDumperInstance() { - // For BC, fallback to PhpMatcherDumper if the UrlMatcher and UrlMatcherDumper are not consistent with each other - if (is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true) !== is_a($this->options['matcher_dumper_class'], CompiledUrlMatcherDumper::class, true)) { + // For BC, fallback to PhpMatcherDumper (which is the old default value) if the old UrlMatcher is used with the new default CompiledUrlMatcherDumper + if (!is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true) && is_a($this->options['matcher_dumper_class'], CompiledUrlMatcherDumper::class, true)) { return new PhpMatcherDumper($this->getRouteCollection()); } @@ -442,4 +444,21 @@ private function checkDeprecatedOption($key) @trigger_error(sprintf('Option "%s" given to router %s is deprecated since Symfony 4.3.', $key, static::class), E_USER_DEPRECATED); } } + + private static function getCompiledRoutes(string $path): array + { + if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN))) { + self::$cache = null; + } + + if (null === self::$cache) { + return require $path; + } + + if (isset(self::$cache[$path])) { + return self::$cache[$path]; + } + + return self::$cache[$path] = require $path; + } } diff --git a/src/Symfony/Component/Routing/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php b/src/Symfony/Component/Routing/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php index 521f0f126cda7..de4776ff78331 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php @@ -131,9 +131,9 @@ public function testDumpWithRouteNotFoundLocalizedRoutes() public function testDumpWithFallbackLocaleLocalizedRoutes() { - $this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_canonical_route', 'test')); - $this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_canonical_route', 'test')); - $this->routeCollection->add('test.fr', (new Route('/tester/est/amusant'))->setDefault('_canonical_route', 'test')); + $this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'test')); + $this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_locale', 'nl')->setDefault('_canonical_route', 'test')); + $this->routeCollection->add('test.fr', (new Route('/tester/est/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'test')); $code = $this->generatorDumper->dump(); file_put_contents($this->testTmpFilepath, $code); @@ -231,4 +231,29 @@ public function testDumpWithSchemeRequirement() $this->assertEquals('https://localhost/app.php/testing', $absoluteUrl); $this->assertEquals('/app.php/testing', $relativeUrl); } + + public function testDumpWithLocalizedRoutesPreserveTheGoodLocaleInTheUrl() + { + $this->routeCollection->add('foo.en', (new Route('/{_locale}/foo'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'foo')); + $this->routeCollection->add('foo.fr', (new Route('/{_locale}/foo'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'foo')); + $this->routeCollection->add('fun.en', (new Route('/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'fun')); + $this->routeCollection->add('fun.fr', (new Route('/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'fun')); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump()); + + $requestContext = new RequestContext(); + $requestContext->setParameter('_locale', 'fr'); + + $compiledUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, $requestContext, null, null); + + $this->assertSame('/fr/foo', $compiledUrlGenerator->generate('foo')); + $this->assertSame('/en/foo', $compiledUrlGenerator->generate('foo.en')); + $this->assertSame('/en/foo', $compiledUrlGenerator->generate('foo', ['_locale' => 'en'])); + $this->assertSame('/en/foo', $compiledUrlGenerator->generate('foo.fr', ['_locale' => 'en'])); + + $this->assertSame('/amusant', $compiledUrlGenerator->generate('fun')); + $this->assertSame('/fun', $compiledUrlGenerator->generate('fun.en')); + $this->assertSame('/fun', $compiledUrlGenerator->generate('fun', ['_locale' => 'en'])); + $this->assertSame('/amusant', $compiledUrlGenerator->generate('fun.fr', ['_locale' => 'en'])); + } } diff --git a/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php index 5e81b8f5d5755..1787144780f43 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php @@ -140,9 +140,9 @@ public function testDumpWithRouteNotFoundLocalizedRoutes() public function testDumpWithFallbackLocaleLocalizedRoutes() { - $this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_canonical_route', 'test')); - $this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_canonical_route', 'test')); - $this->routeCollection->add('test.fr', (new Route('/tester/est/amusant'))->setDefault('_canonical_route', 'test')); + $this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'test')); + $this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_locale', 'nl')->setDefault('_canonical_route', 'test')); + $this->routeCollection->add('test.fr', (new Route('/tester/est/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'test')); $code = $this->generatorDumper->dump([ 'class' => 'FallbackLocaleLocalizedProjectUrlGenerator', @@ -250,4 +250,32 @@ public function testDumpWithSchemeRequirement() $this->assertEquals('https://localhost/app.php/testing', $absoluteUrl); $this->assertEquals('/app.php/testing', $relativeUrl); } + + public function testDumpWithLocalizedRoutesPreserveTheGoodLocaleInTheUrl() + { + $this->routeCollection->add('foo.en', (new Route('/{_locale}/foo'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'foo')); + $this->routeCollection->add('foo.fr', (new Route('/{_locale}/foo'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'foo')); + $this->routeCollection->add('fun.en', (new Route('/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'fun')); + $this->routeCollection->add('fun.fr', (new Route('/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'fun')); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump([ + 'class' => 'PreserveTheGoodLocaleInTheUrlGenerator', + ])); + include $this->testTmpFilepath; + + $requestContext = new RequestContext(); + $requestContext->setParameter('_locale', 'fr'); + + $phpGenerator = new \PreserveTheGoodLocaleInTheUrlGenerator($requestContext); + + $this->assertSame('/fr/foo', $phpGenerator->generate('foo')); + $this->assertSame('/en/foo', $phpGenerator->generate('foo.en')); + $this->assertSame('/en/foo', $phpGenerator->generate('foo', ['_locale' => 'en'])); + $this->assertSame('/en/foo', $phpGenerator->generate('foo.fr', ['_locale' => 'en'])); + + $this->assertSame('/amusant', $phpGenerator->generate('fun')); + $this->assertSame('/fun', $phpGenerator->generate('fun.en')); + $this->assertSame('/fun', $phpGenerator->generate('fun', ['_locale' => 'en'])); + $this->assertSame('/amusant', $phpGenerator->generate('fun.fr', ['_locale' => 'en'])); + } } diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index a768384747684..01215da2c064e 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -236,6 +236,29 @@ public function testGenerateWithOverriddenParameterLocaleFromRequestContext() ); } + public function testDumpWithLocalizedRoutesPreserveTheGoodLocaleInTheUrl() + { + $routeCollection = new RouteCollection(); + + $routeCollection->add('foo.en', (new Route('/{_locale}/foo'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'foo')); + $routeCollection->add('foo.fr', (new Route('/{_locale}/foo'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'foo')); + $routeCollection->add('fun.en', (new Route('/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'fun')); + $routeCollection->add('fun.fr', (new Route('/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'fun')); + + $urlGenerator = $this->getGenerator($routeCollection); + $urlGenerator->getContext()->setParameter('_locale', 'fr'); + + $this->assertSame('/app.php/fr/foo', $urlGenerator->generate('foo')); + $this->assertSame('/app.php/en/foo', $urlGenerator->generate('foo.en')); + $this->assertSame('/app.php/en/foo', $urlGenerator->generate('foo', ['_locale' => 'en'])); + $this->assertSame('/app.php/en/foo', $urlGenerator->generate('foo.fr', ['_locale' => 'en'])); + + $this->assertSame('/app.php/amusant', $urlGenerator->generate('fun')); + $this->assertSame('/app.php/fun', $urlGenerator->generate('fun.en')); + $this->assertSame('/app.php/fun', $urlGenerator->generate('fun', ['_locale' => 'en'])); + $this->assertSame('/app.php/amusant', $urlGenerator->generate('fun.fr', ['_locale' => 'en'])); + } + public function testGenerateWithoutRoutes() { $this->expectException('Symfony\Component\Routing\Exception\RouteNotFoundException'); diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php index f8a101972368f..438247ca1d557 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php @@ -54,7 +54,7 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke throw new BadCredentialsException('The presented password cannot be empty.'); } - if (!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) { + if (null === $user->getPassword() || !$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) { throw new BadCredentialsException('The presented password is invalid.'); } } diff --git a/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php b/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php index 8509844f9a17b..28361a24ba892 100644 --- a/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php +++ b/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php @@ -71,17 +71,13 @@ private function decideAffirmative(TokenInterface $token, array $attributes, $ob $deny = 0; foreach ($this->voters as $voter) { $result = $voter->vote($token, $object, $attributes); - switch ($result) { - case VoterInterface::ACCESS_GRANTED: - return true; - case VoterInterface::ACCESS_DENIED: - ++$deny; - - break; + if (VoterInterface::ACCESS_GRANTED === $result) { + return true; + } - default: - break; + if (VoterInterface::ACCESS_DENIED === $result) { + ++$deny; } } @@ -113,16 +109,10 @@ private function decideConsensus(TokenInterface $token, array $attributes, $obje foreach ($this->voters as $voter) { $result = $voter->vote($token, $object, $attributes); - switch ($result) { - case VoterInterface::ACCESS_GRANTED: - ++$grant; - - break; - - case VoterInterface::ACCESS_DENIED: - ++$deny; - - break; + if (VoterInterface::ACCESS_GRANTED === $result) { + ++$grant; + } elseif (VoterInterface::ACCESS_DENIED === $result) { + ++$deny; } } @@ -154,17 +144,12 @@ private function decideUnanimous(TokenInterface $token, array $attributes, $obje foreach ($attributes as $attribute) { $result = $voter->vote($token, $object, [$attribute]); - switch ($result) { - case VoterInterface::ACCESS_GRANTED: - ++$grant; - - break; - - case VoterInterface::ACCESS_DENIED: - return false; + if (VoterInterface::ACCESS_DENIED === $result) { + return false; + } - default: - break; + if (VoterInterface::ACCESS_GRANTED === $result) { + ++$grant; } } } diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/TraceableVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/TraceableVoter.php index e5ce1f696be36..81ec62c14677b 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/TraceableVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/TraceableVoter.php @@ -31,7 +31,12 @@ class TraceableVoter implements VoterInterface public function __construct(VoterInterface $voter, EventDispatcherInterface $eventDispatcher) { $this->voter = $voter; - $this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher); + + if (class_exists(LegacyEventDispatcherProxy::class)) { + $this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher); + } else { + $this->eventDispatcher = $eventDispatcher; + } } public function vote(TokenInterface $token, $subject, array $attributes) diff --git a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php index 3efc8c6d48bb5..479a5731c2547 100644 --- a/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/UserPasswordEncoder.php @@ -42,6 +42,10 @@ public function encodePassword(UserInterface $user, $plainPassword) */ public function isPasswordValid(UserInterface $user, $raw) { + if (null === $user->getPassword()) { + return false; + } + $encoder = $this->encoderFactory->getEncoder($user); return $encoder->isPasswordValid($user->getPassword(), $raw, $user->getSalt()); diff --git a/src/Symfony/Component/Security/Core/LICENSE b/src/Symfony/Component/Security/Core/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Security/Core/LICENSE +++ b/src/Symfony/Component/Security/Core/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Security/Core/README.md b/src/Symfony/Component/Security/Core/README.md index ede185bd3b3f5..70476d9e7f2b2 100644 --- a/src/Symfony/Component/Security/Core/README.md +++ b/src/Symfony/Component/Security/Core/README.md @@ -9,7 +9,7 @@ the Java Spring framework. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/security/index.html) + * [Documentation](https://symfony.com/doc/current/components/security.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/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php index bb0576fb4c1a2..7f46b1bcae3d5 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\User\User; class DaoAuthenticationProviderTest extends TestCase { @@ -151,7 +152,7 @@ public function testCheckAuthenticationWhenCredentialsAre0() $method->invoke( $provider, - $this->getMockBuilder('Symfony\\Component\\Security\\Core\\User\\UserInterface')->getMock(), + new User('username', 'password'), $token ); } @@ -175,7 +176,7 @@ public function testCheckAuthenticationWhenCredentialsAreNotValid() ->willReturn('foo') ; - $method->invoke($provider, $this->getMockBuilder('Symfony\\Component\\Security\\Core\\User\\UserInterface')->getMock(), $token); + $method->invoke($provider, new User('username', 'password'), $token); } public function testCheckAuthenticationDoesNotReauthenticateWhenPasswordHasChanged() @@ -247,7 +248,7 @@ public function testCheckAuthentication() ->willReturn('foo') ; - $method->invoke($provider, $this->getMockBuilder('Symfony\\Component\\Security\\Core\\User\\UserInterface')->getMock(), $token); + $method->invoke($provider, new User('username', 'password'), $token); } protected function getSupportedToken() diff --git a/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php b/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php index 1592bcd2fe8e8..cf4909dfe8829 100644 --- a/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php @@ -68,24 +68,49 @@ public function testRefreshUser() $provider1 = $this->getProvider(); $provider1 ->expects($this->once()) - ->method('refreshUser') - ->willThrowException(new UnsupportedUserException('unsupported')) + ->method('supportsClass') + ->willReturn(false) ; $provider2 = $this->getProvider(); + $provider2 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider2 + ->expects($this->once()) + ->method('refreshUser') + ->willThrowException(new UnsupportedUserException('unsupported')) + ; + + $provider3 = $this->getProvider(); + $provider3 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + + $provider3 ->expects($this->once()) ->method('refreshUser') ->willReturn($account = $this->getAccount()) ; - $provider = new ChainUserProvider([$provider1, $provider2]); + $provider = new ChainUserProvider([$provider1, $provider2, $provider3]); $this->assertSame($account, $provider->refreshUser($this->getAccount())); } public function testRefreshUserAgain() { $provider1 = $this->getProvider(); + $provider1 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider1 ->expects($this->once()) ->method('refreshUser') @@ -93,6 +118,12 @@ public function testRefreshUserAgain() ; $provider2 = $this->getProvider(); + $provider2 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider2 ->expects($this->once()) ->method('refreshUser') @@ -107,6 +138,12 @@ public function testRefreshUserThrowsUnsupportedUserException() { $this->expectException('Symfony\Component\Security\Core\Exception\UnsupportedUserException'); $provider1 = $this->getProvider(); + $provider1 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider1 ->expects($this->once()) ->method('refreshUser') @@ -114,6 +151,12 @@ public function testRefreshUserThrowsUnsupportedUserException() ; $provider2 = $this->getProvider(); + $provider2 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider2 ->expects($this->once()) ->method('refreshUser') @@ -171,6 +214,12 @@ public function testSupportsClassWhenNotSupported() public function testAcceptsTraversable() { $provider1 = $this->getProvider(); + $provider1 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider1 ->expects($this->once()) ->method('refreshUser') @@ -178,6 +227,12 @@ public function testAcceptsTraversable() ; $provider2 = $this->getProvider(); + $provider2 + ->expects($this->once()) + ->method('supportsClass') + ->willReturn(true) + ; + $provider2 ->expects($this->once()) ->method('refreshUser') diff --git a/src/Symfony/Component/Security/Core/User/ChainUserProvider.php b/src/Symfony/Component/Security/Core/User/ChainUserProvider.php index 4106ad190afea..3bb55a2133245 100644 --- a/src/Symfony/Component/Security/Core/User/ChainUserProvider.php +++ b/src/Symfony/Component/Security/Core/User/ChainUserProvider.php @@ -73,6 +73,10 @@ public function refreshUser(UserInterface $user) foreach ($this->providers as $provider) { try { + if (!$provider->supportsClass(\get_class($user))) { + continue; + } + return $provider->refreshUser($user); } catch (UnsupportedUserException $e) { // try next one diff --git a/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php index 41559a3adcd17..24b032484fa1a 100644 --- a/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php +++ b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php @@ -36,7 +36,7 @@ public function __construct(TokenStorageInterface $tokenStorage, EncoderFactoryI public function validate($password, Constraint $constraint) { if (!$constraint instanceof UserPassword) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UserPassword'); + throw new UnexpectedTypeException($constraint, UserPassword::class); } if (null === $password || '' === $password) { @@ -53,7 +53,7 @@ public function validate($password, Constraint $constraint) $encoder = $this->encoderFactory->getEncoder($user); - if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) { + if (null === $user->getPassword() || !$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) { $this->context->addViolation($constraint->message); } } diff --git a/src/Symfony/Component/Security/Csrf/LICENSE b/src/Symfony/Component/Security/Csrf/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Security/Csrf/LICENSE +++ b/src/Symfony/Component/Security/Csrf/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Security/Csrf/README.md b/src/Symfony/Component/Security/Csrf/README.md index aff72a0c32240..15b9ace238fb9 100644 --- a/src/Symfony/Component/Security/Csrf/README.md +++ b/src/Symfony/Component/Security/Csrf/README.md @@ -7,7 +7,7 @@ The Security CSRF (cross-site request forgery) component provides a class Resources --------- - * [Documentation](https://symfony.com/doc/current/components/security/index.html) + * [Documentation](https://symfony.com/doc/current/components/security.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/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index 25de3ce44079c..94a1efb469cd3 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -51,7 +51,7 @@ class GuardAuthenticationListener implements ListenerInterface * @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider * @param LoggerInterface $logger A LoggerInterface instance */ - public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, $guardAuthenticators, LoggerInterface $logger = null) + public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, iterable $guardAuthenticators, LoggerInterface $logger = null) { if (empty($providerKey)) { throw new \InvalidArgumentException('$providerKey must not be empty.'); diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index b39d9b2700c72..39a102015fb91 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -46,7 +46,13 @@ class GuardAuthenticatorHandler public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null, array $statelessProviderKeys = []) { $this->tokenStorage = $tokenStorage; - $this->dispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher); + + if (null !== $eventDispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher); + } else { + $this->dispatcher = $eventDispatcher; + } + $this->statelessProviderKeys = $statelessProviderKeys; } diff --git a/src/Symfony/Component/Security/Guard/LICENSE b/src/Symfony/Component/Security/Guard/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Security/Guard/LICENSE +++ b/src/Symfony/Component/Security/Guard/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 625ac5e6fe51d..d4d23dfb0d44f 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -45,7 +45,7 @@ class GuardAuthenticationProvider implements AuthenticationProviderInterface * @param UserProviderInterface $userProvider The user provider * @param string $providerKey The provider (i.e. firewall) key */ - public function __construct($guardAuthenticators, UserProviderInterface $userProvider, string $providerKey, UserCheckerInterface $userChecker) + public function __construct(iterable $guardAuthenticators, UserProviderInterface $userProvider, string $providerKey, UserCheckerInterface $userChecker) { $this->guardAuthenticators = $guardAuthenticators; $this->userProvider = $userProvider; diff --git a/src/Symfony/Component/Security/Guard/README.md b/src/Symfony/Component/Security/Guard/README.md index ce706223901b3..40083a48d7682 100644 --- a/src/Symfony/Component/Security/Guard/README.md +++ b/src/Symfony/Component/Security/Guard/README.md @@ -8,7 +8,7 @@ total control. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/security/index.html) + * [Documentation](https://symfony.com/doc/current/components/security.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/Security/Http/Firewall/AbstractAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php index 6b358990733b3..1929e4cfc3d66 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php @@ -93,7 +93,13 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM 'require_previous_session' => true, ], $options); $this->logger = $logger; - $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + + if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + } else { + $this->dispatcher = $dispatcher; + } + $this->httpUtils = $httpUtils; } diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php index 500ae43e498bd..6a3f1377c716f 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php @@ -52,7 +52,12 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM $this->authenticationManager = $authenticationManager; $this->providerKey = $providerKey; $this->logger = $logger; - $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + + if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + } else { + $this->dispatcher = $dispatcher; + } } /** diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index 1059dd6c5d8c0..ce84e656f4cb0 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -66,7 +66,13 @@ public function __construct(TokenStorageInterface $tokenStorage, iterable $userP $this->userProviders = $userProviders; $this->sessionKey = '_security_'.$contextKey; $this->logger = $logger; - $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + + if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + } else { + $this->dispatcher = $dispatcher; + } + $this->trustResolver = $trustResolver ?: new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class); } @@ -175,12 +181,17 @@ protected function refreshUser(TokenInterface $token) $userNotFoundByProvider = false; $userDeauthenticated = false; + $userClass = \get_class($user); foreach ($this->userProviders as $provider) { if (!$provider instanceof UserProviderInterface) { throw new \InvalidArgumentException(sprintf('User provider "%s" must implement "%s".', \get_class($provider), UserProviderInterface::class)); } + if (!$provider->supportsClass($userClass)) { + continue; + } + try { $refreshedUser = $provider->refreshUser($user); $newToken = clone $token; @@ -244,7 +255,7 @@ protected function refreshUser(TokenInterface $token) return null; } - throw new \RuntimeException(sprintf('There is no user provider for user "%s".', \get_class($user))); + throw new \RuntimeException(sprintf('There is no user provider for user "%s".', $userClass)); } private function safelyUnserialize($serializedToken) diff --git a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php index ebc03db862952..78841ecce0850 100644 --- a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php @@ -49,7 +49,13 @@ public function __construct(TokenStorageInterface $tokenStorage, RememberMeServi $this->rememberMeServices = $rememberMeServices; $this->authenticationManager = $authenticationManager; $this->logger = $logger; - $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + + if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + } else { + $this->dispatcher = $dispatcher; + } + $this->catchExceptions = $catchExceptions; $this->sessionStrategy = null === $sessionStrategy ? new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE) : $sessionStrategy; } diff --git a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php index 2c444e823b6fe..69f773269c5b2 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php @@ -65,7 +65,13 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM $this->providerKey = $providerKey; $this->simpleAuthenticator = $simpleAuthenticator; $this->logger = $logger; - $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + + if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + } else { + $this->dispatcher = $dispatcher; + } + $this->trustResolver = $trustResolver ?: new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class); } diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index 4b82fa1f8c5b7..f605a279aadcc 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -70,7 +70,13 @@ public function __construct(TokenStorageInterface $tokenStorage, UserProviderInt $this->usernameParameter = $usernameParameter; $this->role = $role; $this->logger = $logger; - $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + + if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + } else { + $this->dispatcher = $dispatcher; + } + $this->stateless = $stateless; } diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php index 8606899a7ed70..7089c89dcb051 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php @@ -69,7 +69,13 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM $this->successHandler = $successHandler; $this->failureHandler = $failureHandler; $this->logger = $logger; - $this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher); + + if (null !== $eventDispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->eventDispatcher = LegacyEventDispatcherProxy::decorate($eventDispatcher); + } else { + $this->eventDispatcher = $eventDispatcher; + } + $this->options = array_merge(['username_path' => 'username', 'password_path' => 'password'], $options); $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } diff --git a/src/Symfony/Component/Security/Http/LICENSE b/src/Symfony/Component/Security/Http/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Security/Http/LICENSE +++ b/src/Symfony/Component/Security/Http/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Security/Http/README.md b/src/Symfony/Component/Security/Http/README.md index 5be2111830116..dbac8c659c112 100644 --- a/src/Symfony/Component/Security/Http/README.md +++ b/src/Symfony/Component/Security/Http/README.md @@ -9,7 +9,7 @@ the Java Spring framework. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/security/index.html) + * [Documentation](https://symfony.com/doc/current/components/security.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/Security/Http/RememberMe/AbstractRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php index cf98bc7766942..00caffb02e7a7 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php @@ -93,6 +93,10 @@ public function getSecret() */ final public function autoLogin(Request $request): ?TokenInterface { + if (($cookie = $request->attributes->get(self::COOKIE_ATTR_NAME)) && null === $cookie->getValue()) { + return null; + } + if (null === $cookie = $request->cookies->get($this->options['name'])) { return null; } diff --git a/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php index 26901c2439657..6db2a78330d78 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php @@ -91,10 +91,10 @@ protected function onLoginSuccess(Request $request, Response $response, TokenInt /** * Generates the cookie value. * - * @param string $class - * @param string $username The username - * @param int $expires The Unix timestamp when the cookie expires - * @param string $password The encoded password + * @param string $class + * @param string $username The username + * @param int $expires The Unix timestamp when the cookie expires + * @param string|null $password The encoded password * * @return string */ @@ -113,10 +113,10 @@ protected function generateCookieValue($class, $username, $expires, $password) /** * Generates a hash for the cookie to ensure it is not being tampered with. * - * @param string $class - * @param string $username The username - * @param int $expires The Unix timestamp when the cookie expires - * @param string $password The encoded password + * @param string $class + * @param string $username The username + * @param int $expires The Unix timestamp when the cookie expires + * @param string|null $password The encoded password * * @return string */ diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php index a0d5f16aa8440..e37667552c2fe 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php @@ -254,7 +254,7 @@ public function testIfTokenIsDeauthenticated() { $tokenStorage = new TokenStorage(); $refreshedUser = new User('foobar', 'baz'); - $this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)]); + $this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)]); $this->assertNull($tokenStorage->getToken()); } @@ -276,7 +276,7 @@ public function testRememberMeGetsCanceledIfTokenIsDeauthenticated() $rememberMeServices = $this->createMock(RememberMeServicesInterface::class); $rememberMeServices->expects($this->once())->method('loginFail'); - $this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], null, $rememberMeServices); + $this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)], null, $rememberMeServices); $this->assertNull($tokenStorage->getToken()); } @@ -285,7 +285,7 @@ public function testTryAllUserProvidersUntilASupportingUserProviderIsFound() { $tokenStorage = new TokenStorage(); $refreshedUser = new User('foobar', 'baz'); - $this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], $refreshedUser); + $this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)], $refreshedUser); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); } @@ -294,7 +294,7 @@ public function testNextSupportingUserProviderIsTriedIfPreviousSupportingUserPro { $tokenStorage = new TokenStorage(); $refreshedUser = new User('foobar', 'baz'); - $this->handleEventWithPreviousSession($tokenStorage, [new SupportingUserProvider(), new SupportingUserProvider($refreshedUser)], $refreshedUser); + $this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)], $refreshedUser); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); } @@ -302,7 +302,7 @@ public function testNextSupportingUserProviderIsTriedIfPreviousSupportingUserPro public function testTokenIsSetToNullIfNoUserWasLoadedByTheRegisteredUserProviders() { $tokenStorage = new TokenStorage(); - $this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider()]); + $this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider()]); $this->assertNull($tokenStorage->getToken()); } @@ -310,14 +310,14 @@ public function testTokenIsSetToNullIfNoUserWasLoadedByTheRegisteredUserProvider public function testRuntimeExceptionIsThrownIfNoSupportingUserProviderWasRegistered() { $this->expectException('RuntimeException'); - $this->handleEventWithPreviousSession(new TokenStorage(), [new NotSupportingUserProvider(), new NotSupportingUserProvider()]); + $this->handleEventWithPreviousSession(new TokenStorage(), [new NotSupportingUserProvider(false), new NotSupportingUserProvider(true)]); } public function testAcceptsProvidersAsTraversable() { $tokenStorage = new TokenStorage(); $refreshedUser = new User('foobar', 'baz'); - $this->handleEventWithPreviousSession($tokenStorage, new \ArrayObject([new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)]), $refreshedUser); + $this->handleEventWithPreviousSession($tokenStorage, new \ArrayObject([new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)]), $refreshedUser); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); } @@ -343,7 +343,7 @@ public function testDeauthenticatedEvent() $this->assertNotEquals($event->getRefreshedToken()->getUser(), $user); }); - $listener = new ContextListener($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], 'context_key', null, $eventDispatcher); + $listener = new ContextListener($tokenStorage, [new NotSupportingUserProvider(true), new NotSupportingUserProvider(false), new SupportingUserProvider($refreshedUser)], 'context_key', null, $eventDispatcher); $listener(new RequestEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), $request, HttpKernelInterface::MASTER_REQUEST)); $this->assertNull($tokenStorage->getToken()); @@ -398,6 +398,14 @@ private function handleEventWithPreviousSession(TokenStorageInterface $tokenStor class NotSupportingUserProvider implements UserProviderInterface { + /** @var bool */ + private $throwsUnsupportedException; + + public function __construct($throwsUnsupportedException) + { + $this->throwsUnsupportedException = $throwsUnsupportedException; + } + public function loadUserByUsername($username) { throw new UsernameNotFoundException(); @@ -405,7 +413,11 @@ public function loadUserByUsername($username) public function refreshUser(UserInterface $user) { - throw new UnsupportedUserException(); + if ($this->throwsUnsupportedException) { + throw new UnsupportedUserException(); + } + + return $user; } public function supportsClass($class) diff --git a/src/Symfony/Component/Security/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php b/src/Symfony/Component/Security/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php index 8dc2042f12c09..cf70ed4cb1618 100644 --- a/src/Symfony/Component/Security/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php +++ b/src/Symfony/Component/Security/Http/Tests/RememberMe/AbstractRememberMeServicesTest.php @@ -39,6 +39,17 @@ public function testAutoLoginReturnsNullWhenNoCookie() $this->assertNull($service->autoLogin(new Request())); } + public function testAutoLoginReturnsNullAfterLoginFail() + { + $service = $this->getService(null, ['name' => 'foo', 'path' => null, 'domain' => null]); + + $request = new Request(); + $request->cookies->set('foo', 'foo'); + + $service->loginFail($request); + $this->assertNull($service->autoLogin($request)); + } + /** * @group legacy */ diff --git a/src/Symfony/Component/Security/LICENSE b/src/Symfony/Component/Security/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Security/LICENSE +++ b/src/Symfony/Component/Security/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Security/README.md b/src/Symfony/Component/Security/README.md index 87ce3d9ea7cf9..21f17f6458300 100644 --- a/src/Symfony/Component/Security/README.md +++ b/src/Symfony/Component/Security/README.md @@ -11,7 +11,7 @@ roles. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/security/index.html) + * [Documentation](https://symfony.com/doc/current/components/security.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/Serializer/LICENSE b/src/Symfony/Component/Serializer/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Serializer/LICENSE +++ b/src/Symfony/Component/Serializer/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php index d22a3463ccdad..e75131921f5cb 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php @@ -106,13 +106,7 @@ public function denormalize($data, $type, $format = null, array $context = []) $dateTimeErrors = \DateTime::class === $type ? \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors(); - throw new NotNormalizableValueException(sprintf( - 'Parsing datetime string "%s" using format "%s" resulted in %d errors:'."\n".'%s', - $data, - $dateTimeFormat, - $dateTimeErrors['error_count'], - implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])) - )); + throw new NotNormalizableValueException(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors:'."\n".'%s', $data, $dateTimeFormat, $dateTimeErrors['error_count'], implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])))); } try { diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php index 118e9856f5e9a..7b68fa604ee24 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php @@ -103,8 +103,14 @@ protected function extractAttributes($object, $format = null, array $context = [ } } + $checkPropertyInitialization = \PHP_VERSION_ID >= 70400; + // properties foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) { + if ($checkPropertyInitialization && !$reflProperty->isInitialized($object)) { + continue; + } + if ($reflProperty->isStatic() || !$this->isAllowedAttribute($object, $reflProperty->name, $format, $context)) { continue; } diff --git a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php index 83f63daa51315..c0df2b2ce6e38 100644 --- a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php @@ -101,9 +101,20 @@ protected function extractAttributes($object, $format = null, array $context = [ { $reflectionObject = new \ReflectionObject($object); $attributes = []; + $checkPropertyInitialization = \PHP_VERSION_ID >= 70400; do { foreach ($reflectionObject->getProperties() as $property) { + if ($checkPropertyInitialization) { + if (!$property->isPublic()) { + $property->setAccessible(true); + } + + if (!$property->isInitialized($object)) { + continue; + } + } + if (!$this->isAllowedAttribute($reflectionObject->getName(), $property->name, $format, $context)) { continue; } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Dummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Dummy.php new file mode 100644 index 0000000000000..e8cebd9b45e95 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Dummy.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +/** + * @author Valentin Udaltsov + */ +final class Php74Dummy +{ + public string $uninitializedProperty; + + public string $initializedProperty = 'defaultValue'; +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index a45722423f8e8..0b62abfac1830 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -40,7 +40,7 @@ class AbstractObjectNormalizerTest extends TestCase public function testDenormalize() { $normalizer = new AbstractObjectNormalizerDummy(); - $normalizedData = $normalizer->denormalize(['foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'], __NAMESPACE__.'\Dummy'); + $normalizedData = $normalizer->denormalize(['foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'], Dummy::class); $this->assertSame('foo', $normalizedData->foo); $this->assertNull($normalizedData->bar); @@ -50,12 +50,12 @@ public function testDenormalize() public function testInstantiateObjectDenormalizer() { $data = ['foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz']; - $class = __NAMESPACE__.'\Dummy'; + $class = Dummy::class; $context = []; $normalizer = new AbstractObjectNormalizerDummy(); - $this->assertInstanceOf(__NAMESPACE__.'\Dummy', $normalizer->instantiateObject($data, $class, $context, new \ReflectionClass($class), [])); + $this->assertInstanceOf(Dummy::class, $normalizer->instantiateObject($data, $class, $context, new \ReflectionClass($class), [])); } public function testDenormalizeWithExtraAttributes() @@ -66,7 +66,7 @@ public function testDenormalizeWithExtraAttributes() $normalizer = new AbstractObjectNormalizerDummy($factory); $normalizer->denormalize( ['fooFoo' => 'foo', 'fooBar' => 'bar'], - __NAMESPACE__.'\Dummy', + Dummy::class, 'any', ['allow_extra_attributes' => false] ); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php index a8302be91ed76..fbceb90cffd45 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php @@ -68,7 +68,7 @@ public function testSupportsValidArray() { $this->serializer->expects($this->once()) ->method('supportsDenormalization') - ->with($this->anything(), __NAMESPACE__.'\ArrayDummy', $this->anything()) + ->with($this->anything(), ArrayDummy::class, $this->anything()) ->willReturn(true); $this->assertTrue( @@ -104,7 +104,7 @@ public function testSupportsNoArray() $this->assertFalse( $this->denormalizer->supportsDenormalization( ['foo' => 'one', 'bar' => 'two'], - __NAMESPACE__.'\ArrayDummy' + ArrayDummy::class ) ); } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php index f58d43ce1eb36..c5c47185eee3d 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -68,7 +68,7 @@ protected function setUp(): void private function createNormalizer(array $defaultContext = []) { - $this->serializer = $this->getMockBuilder(__NAMESPACE__.'\SerializerNormalizer')->getMock(); + $this->serializer = $this->getMockBuilder(SerializerNormalizer::class)->getMock(); $this->normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext); $this->normalizer->setSerializer($this->serializer); } @@ -157,7 +157,7 @@ public function testConstructorDenormalize() { $obj = $this->normalizer->denormalize( ['foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'], - __NAMESPACE__.'\GetConstructorDummy', 'any'); + GetConstructorDummy::class, 'any'); $this->assertEquals('foo', $obj->getFoo()); $this->assertEquals('bar', $obj->getBar()); $this->assertTrue($obj->isBaz()); @@ -167,7 +167,7 @@ public function testConstructorDenormalizeWithNullArgument() { $obj = $this->normalizer->denormalize( ['foo' => 'foo', 'bar' => null, 'baz' => true], - __NAMESPACE__.'\GetConstructorDummy', 'any'); + GetConstructorDummy::class, 'any'); $this->assertEquals('foo', $obj->getFoo()); $this->assertNull($obj->getBar()); $this->assertTrue($obj->isBaz()); @@ -177,7 +177,7 @@ public function testConstructorDenormalizeWithMissingOptionalArgument() { $obj = $this->normalizer->denormalize( ['foo' => 'test', 'baz' => [1, 2, 3]], - __NAMESPACE__.'\GetConstructorOptionalArgsDummy', 'any'); + GetConstructorOptionalArgsDummy::class, 'any'); $this->assertEquals('test', $obj->getFoo()); $this->assertEquals([], $obj->getBar()); $this->assertEquals([1, 2, 3], $obj->getBaz()); @@ -187,7 +187,7 @@ public function testConstructorDenormalizeWithOptionalDefaultArgument() { $obj = $this->normalizer->denormalize( ['bar' => 'test'], - __NAMESPACE__.'\GetConstructorArgsWithDefaultValueDummy', 'any'); + GetConstructorArgsWithDefaultValueDummy::class, 'any'); $this->assertEquals([], $obj->getFoo()); $this->assertEquals('test', $obj->getBar()); } @@ -215,14 +215,14 @@ public function testConstructorWithObjectDenormalize() $data->bar = 'bar'; $data->baz = true; $data->fooBar = 'foobar'; - $obj = $this->normalizer->denormalize($data, __NAMESPACE__.'\GetConstructorDummy', 'any'); + $obj = $this->normalizer->denormalize($data, GetConstructorDummy::class, 'any'); $this->assertEquals('foo', $obj->getFoo()); $this->assertEquals('bar', $obj->getBar()); } public function testConstructorWArgWithPrivateMutator() { - $obj = $this->normalizer->denormalize(['foo' => 'bar'], __NAMESPACE__.'\ObjectConstructorArgsWithPrivateMutatorDummy', 'any'); + $obj = $this->normalizer->denormalize(['foo' => 'bar'], ObjectConstructorArgsWithPrivateMutatorDummy::class, 'any'); $this->assertEquals('bar', $obj->getFoo()); } @@ -479,7 +479,7 @@ public function testNoStaticGetSetSupport() public function testPrivateSetter() { - $obj = $this->normalizer->denormalize(['foo' => 'foobar'], __NAMESPACE__.'\ObjectWithPrivateSetterDummy'); + $obj = $this->normalizer->denormalize(['foo' => 'foobar'], ObjectWithPrivateSetterDummy::class); $this->assertEquals('bar', $obj->getFoo()); } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index aacce2dee98c5..f1d55096da6f9 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -33,6 +33,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\OtherSerializedNameDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy; use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder; use Symfony\Component\Serializer\Tests\Normalizer\Features\AttributesTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksObject; @@ -79,7 +80,7 @@ protected function setUp(): void private function createNormalizer(array $defaultContext = [], ClassMetadataFactoryInterface $classMetadataFactory = null) { - $this->serializer = $this->getMockBuilder(__NAMESPACE__.'\ObjectSerializerNormalizer')->getMock(); + $this->serializer = $this->getMockBuilder(ObjectSerializerNormalizer::class)->getMock(); $this->normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext); $this->normalizer->setSerializer($this->serializer); } @@ -114,6 +115,18 @@ public function testNormalize() ); } + /** + * @requires PHP 7.4 + */ + public function testNormalizeObjectWithUninitializedProperties() + { + $obj = new Php74Dummy(); + $this->assertEquals( + ['initializedProperty' => 'defaultValue'], + $this->normalizer->normalize($obj, 'any') + ); + } + public function testDenormalize() { $obj = $this->normalizer->denormalize( @@ -146,7 +159,7 @@ public function testConstructorDenormalize() { $obj = $this->normalizer->denormalize( ['foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'], - __NAMESPACE__.'\ObjectConstructorDummy', 'any'); + ObjectConstructorDummy::class, 'any'); $this->assertEquals('foo', $obj->getFoo()); $this->assertEquals('bar', $obj->bar); $this->assertTrue($obj->isBaz()); @@ -156,7 +169,7 @@ public function testConstructorDenormalizeWithNullArgument() { $obj = $this->normalizer->denormalize( ['foo' => 'foo', 'bar' => null, 'baz' => true], - __NAMESPACE__.'\ObjectConstructorDummy', 'any'); + ObjectConstructorDummy::class, 'any'); $this->assertEquals('foo', $obj->getFoo()); $this->assertNull($obj->bar); $this->assertTrue($obj->isBaz()); @@ -166,7 +179,7 @@ public function testConstructorDenormalizeWithMissingOptionalArgument() { $obj = $this->normalizer->denormalize( ['foo' => 'test', 'baz' => [1, 2, 3]], - __NAMESPACE__.'\ObjectConstructorOptionalArgsDummy', 'any'); + ObjectConstructorOptionalArgsDummy::class, 'any'); $this->assertEquals('test', $obj->getFoo()); $this->assertEquals([], $obj->bar); $this->assertEquals([1, 2, 3], $obj->getBaz()); @@ -176,7 +189,7 @@ public function testConstructorDenormalizeWithOptionalDefaultArgument() { $obj = $this->normalizer->denormalize( ['bar' => 'test'], - __NAMESPACE__.'\ObjectConstructorArgsWithDefaultValueDummy', 'any'); + ObjectConstructorArgsWithDefaultValueDummy::class, 'any'); $this->assertEquals([], $obj->getFoo()); $this->assertEquals('test', $obj->getBar()); } @@ -188,7 +201,7 @@ public function testConstructorWithObjectDenormalize() $data->bar = 'bar'; $data->baz = true; $data->fooBar = 'foobar'; - $obj = $this->normalizer->denormalize($data, __NAMESPACE__.'\ObjectConstructorDummy', 'any'); + $obj = $this->normalizer->denormalize($data, ObjectConstructorDummy::class, 'any'); $this->assertEquals('foo', $obj->getFoo()); $this->assertEquals('bar', $obj->bar); } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php index 9a8eb3fb38c09..99dc413c052d2 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php @@ -29,6 +29,7 @@ use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\GroupDummyChild; +use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy; use Symfony\Component\Serializer\Tests\Fixtures\PropertyCircularReferenceDummy; use Symfony\Component\Serializer\Tests\Fixtures\PropertySiblingHolder; use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksObject; @@ -86,11 +87,23 @@ public function testNormalize() ); } + /** + * @requires PHP 7.4 + */ + public function testNormalizeObjectWithUninitializedProperties() + { + $obj = new Php74Dummy(); + $this->assertEquals( + ['initializedProperty' => 'defaultValue'], + $this->normalizer->normalize($obj, 'any') + ); + } + public function testDenormalize() { $obj = $this->normalizer->denormalize( ['foo' => 'foo', 'bar' => 'bar'], - __NAMESPACE__.'\PropertyDummy', + PropertyDummy::class, 'any' ); $this->assertEquals('foo', $obj->foo); @@ -129,7 +142,7 @@ public function testConstructorDenormalize() { $obj = $this->normalizer->denormalize( ['foo' => 'foo', 'bar' => 'bar'], - __NAMESPACE__.'\PropertyConstructorDummy', + PropertyConstructorDummy::class, 'any' ); $this->assertEquals('foo', $obj->getFoo()); @@ -140,7 +153,7 @@ public function testConstructorDenormalizeWithNullArgument() { $obj = $this->normalizer->denormalize( ['foo' => null, 'bar' => 'bar'], - __NAMESPACE__.'\PropertyConstructorDummy', ' + PropertyConstructorDummy::class, ' any' ); $this->assertNull($obj->getFoo()); @@ -367,13 +380,13 @@ public function testDenormalizeNonExistingAttribute() { $this->assertEquals( new PropertyDummy(), - $this->normalizer->denormalize(['non_existing' => true], __NAMESPACE__.'\PropertyDummy') + $this->normalizer->denormalize(['non_existing' => true], PropertyDummy::class) ); } public function testDenormalizeShouldIgnoreStaticProperty() { - $obj = $this->normalizer->denormalize(['outOfScope' => true], __NAMESPACE__.'\PropertyDummy'); + $obj = $this->normalizer->denormalize(['outOfScope' => true], PropertyDummy::class); $this->assertEquals(new PropertyDummy(), $obj); $this->assertEquals('out_of_scope', PropertyDummy::$outOfScope); diff --git a/src/Symfony/Component/Stopwatch/LICENSE b/src/Symfony/Component/Stopwatch/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Stopwatch/LICENSE +++ b/src/Symfony/Component/Stopwatch/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Templating/LICENSE b/src/Symfony/Component/Templating/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Templating/LICENSE +++ b/src/Symfony/Component/Templating/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Templating/README.md b/src/Symfony/Component/Templating/README.md index 58e2a0a5c6b83..2b8ecad873964 100644 --- a/src/Symfony/Component/Templating/README.md +++ b/src/Symfony/Component/Templating/README.md @@ -12,7 +12,7 @@ layouts. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/templating/index.html) + * [Documentation](https://symfony.com/doc/current/components/templating.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/Translation/DataCollector/TranslationDataCollector.php b/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php index 891101d858f63..222ff9ea4995e 100644 --- a/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php +++ b/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use Symfony\Component\Translation\DataCollectorTranslator; +use Symfony\Component\VarDumper\Cloner\Data; /** * @author Abdellatif Ait boudad diff --git a/src/Symfony/Component/Translation/LICENSE b/src/Symfony/Component/Translation/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Translation/LICENSE +++ b/src/Symfony/Component/Translation/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Translation/Loader/PhpFileLoader.php b/src/Symfony/Component/Translation/Loader/PhpFileLoader.php index a0050e8db1e19..0991c3d3a28c6 100644 --- a/src/Symfony/Component/Translation/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/PhpFileLoader.php @@ -18,11 +18,25 @@ */ class PhpFileLoader extends FileLoader { + private static $cache = []; + /** * {@inheritdoc} */ protected function loadResource($resource) { - return require $resource; + if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN))) { + self::$cache = null; + } + + if (null === self::$cache) { + return require $resource; + } + + if (isset(self::$cache[$resource])) { + return self::$cache[$resource]; + } + + return self::$cache[$resource] = require $resource; } } diff --git a/src/Symfony/Component/Translation/MessageCatalogue.php b/src/Symfony/Component/Translation/MessageCatalogue.php index 1dc50ea0a50e4..3c0f41adfa90e 100644 --- a/src/Symfony/Component/Translation/MessageCatalogue.php +++ b/src/Symfony/Component/Translation/MessageCatalogue.php @@ -156,7 +156,9 @@ public function add($messages, $domain = 'messages') if (!isset($this->messages[$domain])) { $this->messages[$domain] = $messages; } else { - $this->messages[$domain] = array_replace($this->messages[$domain], $messages); + foreach ($messages as $id => $message) { + $this->messages[$domain][$id] = $message; + } } } diff --git a/src/Symfony/Component/Translation/README.md b/src/Symfony/Component/Translation/README.md index 46f3d1f2f25e6..e80a70cad033e 100644 --- a/src/Symfony/Component/Translation/README.md +++ b/src/Symfony/Component/Translation/README.md @@ -6,7 +6,7 @@ The Translation component provides tools to internationalize your application. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/translation/index.html) + * [Documentation](https://symfony.com/doc/current/components/translation.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/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php index bfb1faa53eb35..d7c5b45e00a3c 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -270,15 +270,38 @@ public function testTransWithIcuRootFallbackLocale() $this->assertSame('bar', $translator->trans('bar')); } - public function testTransWithFallbackLocaleBis() + /** + * @dataProvider getFallbackLocales + */ + public function testTransWithFallbackLocaleBis($expectedLocale, $locale) { - $translator = new Translator('en_US'); + $translator = new Translator($locale); $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', ['foo' => 'foofoo'], 'en_US'); - $translator->addResource('array', ['bar' => 'foobar'], 'en'); + $translator->addResource('array', ['foo' => 'foofoo'], $locale); + $translator->addResource('array', ['bar' => 'foobar'], $expectedLocale); $this->assertEquals('foobar', $translator->trans('bar')); } + public function getFallbackLocales() + { + $locales = [ + ['en', 'en_US'], + ['en', 'en-US'], + ['sl_Latn_IT', 'sl_Latn_IT_nedis'], + ['sl_Latn', 'sl_Latn_IT'], + ]; + + if (\function_exists('locale_parse')) { + $locales[] = ['sl_Latn_IT', 'sl-Latn-IT-nedis']; + $locales[] = ['sl_Latn', 'sl-Latn-IT']; + } else { + $locales[] = ['sl-Latn-IT', 'sl-Latn-IT-nedis']; + $locales[] = ['sl-Latn', 'sl-Latn-IT']; + } + + return $locales; + } + public function testTransWithFallbackLocaleTer() { $translator = new Translator('fr_FR'); diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index d9241d5a82625..2faf3ac96b0b1 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -451,10 +451,17 @@ protected function computeFallbackLocales($locale) while ($locale) { $parent = $parentLocales[$locale] ?? null; - if (!$parent && false !== strrchr($locale, '_')) { - $locale = substr($locale, 0, -\strlen(strrchr($locale, '_'))); - } elseif ('root' !== $parent) { - $locale = $parent; + if ($parent) { + $locale = 'root' !== $parent ? $parent : null; + } elseif (\function_exists('locale_parse')) { + $localeSubTags = locale_parse($locale); + $locale = null; + if (1 < \count($localeSubTags)) { + array_pop($localeSubTags); + $locale = locale_compose($localeSubTags) ?: null; + } + } elseif ($i = strrpos($locale, '_') ?: strrpos($locale, '-')) { + $locale = substr($locale, 0, $i); } else { $locale = null; } diff --git a/src/Symfony/Component/Validator/ConstraintValidator.php b/src/Symfony/Component/Validator/ConstraintValidator.php index 93cca2ea74a5e..458351fe2ee00 100644 --- a/src/Symfony/Component/Validator/ConstraintValidator.php +++ b/src/Symfony/Component/Validator/ConstraintValidator.php @@ -87,19 +87,12 @@ protected function formatValue($value, $format = 0) { if (($format & self::PRETTY_DATE) && $value instanceof \DateTimeInterface) { if (class_exists('IntlDateFormatter')) { - $locale = \Locale::getDefault(); - $formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, $value->getTimezone()); - - // neither the native nor the stub IntlDateFormatter support - // DateTimeImmutable as of yet - if (!$value instanceof \DateTime) { - $value = new \DateTime( - $value->format('Y-m-d H:i:s.u e'), - $value->getTimezone() - ); - } - - return $formatter->format($value); + $formatter = new \IntlDateFormatter(\Locale::getDefault(), \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, 'UTC'); + + return $formatter->format(new \DateTime( + $value->format('Y-m-d H:i:s.u'), + new \DateTimeZone('UTC') + )); } return $value->format('Y-m-d H:i:s'); diff --git a/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php b/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php index cd96bc9df75f8..f04db3cc2d1b9 100644 --- a/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php +++ b/src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php @@ -40,7 +40,7 @@ public function __construct(PropertyAccessorInterface $propertyAccessor = null) public function validate($value, Constraint $constraint) { if (!$constraint instanceof AbstractComparison) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\AbstractComparison'); + throw new UnexpectedTypeException($constraint, AbstractComparison::class); } if (null === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/AllValidator.php b/src/Symfony/Component/Validator/Constraints/AllValidator.php index 5bacdf8b95ff2..611ac8deae3e3 100644 --- a/src/Symfony/Component/Validator/Constraints/AllValidator.php +++ b/src/Symfony/Component/Validator/Constraints/AllValidator.php @@ -27,7 +27,7 @@ class AllValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof All) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\All'); + throw new UnexpectedTypeException($constraint, All::class); } if (null === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/BicValidator.php b/src/Symfony/Component/Validator/Constraints/BicValidator.php index bc306d35e805e..f40923af9780c 100644 --- a/src/Symfony/Component/Validator/Constraints/BicValidator.php +++ b/src/Symfony/Component/Validator/Constraints/BicValidator.php @@ -61,7 +61,7 @@ public function __construct(PropertyAccessor $propertyAccessor = null) public function validate($value, Constraint $constraint) { if (!$constraint instanceof Bic) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Bic'); + throw new UnexpectedTypeException($constraint, Bic::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/BlankValidator.php b/src/Symfony/Component/Validator/Constraints/BlankValidator.php index ca999b5aa3961..3fd7fcc9fe1c6 100644 --- a/src/Symfony/Component/Validator/Constraints/BlankValidator.php +++ b/src/Symfony/Component/Validator/Constraints/BlankValidator.php @@ -26,7 +26,7 @@ class BlankValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Blank) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Blank'); + throw new UnexpectedTypeException($constraint, Blank::class); } if ('' !== $value && null !== $value) { diff --git a/src/Symfony/Component/Validator/Constraints/CallbackValidator.php b/src/Symfony/Component/Validator/Constraints/CallbackValidator.php index b0ba21acb1776..9bb242f0de52e 100644 --- a/src/Symfony/Component/Validator/Constraints/CallbackValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CallbackValidator.php @@ -29,7 +29,7 @@ class CallbackValidator extends ConstraintValidator public function validate($object, Constraint $constraint) { if (!$constraint instanceof Callback) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Callback'); + throw new UnexpectedTypeException($constraint, Callback::class); } $method = $constraint->callback; diff --git a/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php b/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php index f62fb03997f28..478047fb8f272 100644 --- a/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php @@ -99,7 +99,7 @@ class CardSchemeValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof CardScheme) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\CardScheme'); + throw new UnexpectedTypeException($constraint, CardScheme::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/ChoiceValidator.php b/src/Symfony/Component/Validator/Constraints/ChoiceValidator.php index d1ef66eeda795..2cff8c8be61e9 100644 --- a/src/Symfony/Component/Validator/Constraints/ChoiceValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ChoiceValidator.php @@ -32,7 +32,7 @@ class ChoiceValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Choice) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Choice'); + throw new UnexpectedTypeException($constraint, Choice::class); } if (!\is_array($constraint->choices) && !$constraint->callback) { diff --git a/src/Symfony/Component/Validator/Constraints/CollectionValidator.php b/src/Symfony/Component/Validator/Constraints/CollectionValidator.php index cbc3e198a35bf..7cea7d01df963 100644 --- a/src/Symfony/Component/Validator/Constraints/CollectionValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CollectionValidator.php @@ -27,7 +27,7 @@ class CollectionValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Collection) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Collection'); + throw new UnexpectedTypeException($constraint, Collection::class); } if (null === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/CountValidator.php b/src/Symfony/Component/Validator/Constraints/CountValidator.php index 301358bcf714c..5c40e47b211de 100644 --- a/src/Symfony/Component/Validator/Constraints/CountValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CountValidator.php @@ -27,7 +27,7 @@ class CountValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Count) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Count'); + throw new UnexpectedTypeException($constraint, Count::class); } if (null === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/CountryValidator.php b/src/Symfony/Component/Validator/Constraints/CountryValidator.php index 21dd62b034e19..d8289e0e52b9b 100644 --- a/src/Symfony/Component/Validator/Constraints/CountryValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CountryValidator.php @@ -31,7 +31,7 @@ class CountryValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Country) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Country'); + throw new UnexpectedTypeException($constraint, Country::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/CurrencyValidator.php b/src/Symfony/Component/Validator/Constraints/CurrencyValidator.php index a6352c3263498..fdb8128d16a68 100644 --- a/src/Symfony/Component/Validator/Constraints/CurrencyValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CurrencyValidator.php @@ -32,7 +32,7 @@ class CurrencyValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Currency) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Currency'); + throw new UnexpectedTypeException($constraint, Currency::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php b/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php index 16abf6f671ce4..6dd4fa7455ced 100644 --- a/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/DateTimeValidator.php @@ -27,7 +27,7 @@ class DateTimeValidator extends DateValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof DateTime) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\DateTime'); + throw new UnexpectedTypeException($constraint, DateTime::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/DateValidator.php b/src/Symfony/Component/Validator/Constraints/DateValidator.php index 93b1f7d074e18..b900d2c0511f7 100644 --- a/src/Symfony/Component/Validator/Constraints/DateValidator.php +++ b/src/Symfony/Component/Validator/Constraints/DateValidator.php @@ -45,7 +45,7 @@ public static function checkDate($year, $month, $day) public function validate($value, Constraint $constraint) { if (!$constraint instanceof Date) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Date'); + throw new UnexpectedTypeException($constraint, Date::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/EmailValidator.php b/src/Symfony/Component/Validator/Constraints/EmailValidator.php index b7306d2b0e174..037b90a8e276c 100644 --- a/src/Symfony/Component/Validator/Constraints/EmailValidator.php +++ b/src/Symfony/Component/Validator/Constraints/EmailValidator.php @@ -68,7 +68,7 @@ public function __construct($defaultMode = Email::VALIDATION_MODE_LOOSE) public function validate($value, Constraint $constraint) { if (!$constraint instanceof Email) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Email'); + throw new UnexpectedTypeException($constraint, Email::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php b/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php index b72a83365b7b7..294ba4639da86 100644 --- a/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php @@ -36,7 +36,7 @@ public function __construct($propertyAccessor = null, ExpressionLanguage $expres public function validate($value, Constraint $constraint) { if (!$constraint instanceof Expression) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Expression'); + throw new UnexpectedTypeException($constraint, Expression::class); } $variables = $constraint->values; diff --git a/src/Symfony/Component/Validator/Constraints/FileValidator.php b/src/Symfony/Component/Validator/Constraints/FileValidator.php index 145607964d7ff..0a4b8789e0719 100644 --- a/src/Symfony/Component/Validator/Constraints/FileValidator.php +++ b/src/Symfony/Component/Validator/Constraints/FileValidator.php @@ -42,7 +42,7 @@ class FileValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof File) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\File'); + throw new UnexpectedTypeException($constraint, File::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/GreaterThanOrEqualValidator.php b/src/Symfony/Component/Validator/Constraints/GreaterThanOrEqualValidator.php index e196e688f3476..290408ac0cc3f 100644 --- a/src/Symfony/Component/Validator/Constraints/GreaterThanOrEqualValidator.php +++ b/src/Symfony/Component/Validator/Constraints/GreaterThanOrEqualValidator.php @@ -24,7 +24,7 @@ class GreaterThanOrEqualValidator extends AbstractComparisonValidator */ protected function compareValues($value1, $value2) { - return $value1 >= $value2; + return null === $value2 || $value1 >= $value2; } /** diff --git a/src/Symfony/Component/Validator/Constraints/GreaterThanValidator.php b/src/Symfony/Component/Validator/Constraints/GreaterThanValidator.php index 9029e8fc46a80..062503ab3f426 100644 --- a/src/Symfony/Component/Validator/Constraints/GreaterThanValidator.php +++ b/src/Symfony/Component/Validator/Constraints/GreaterThanValidator.php @@ -24,7 +24,7 @@ class GreaterThanValidator extends AbstractComparisonValidator */ protected function compareValues($value1, $value2) { - return $value1 > $value2; + return null === $value2 || $value1 > $value2; } /** diff --git a/src/Symfony/Component/Validator/Constraints/IbanValidator.php b/src/Symfony/Component/Validator/Constraints/IbanValidator.php index e927f013aa052..63f63f900ec5f 100644 --- a/src/Symfony/Component/Validator/Constraints/IbanValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IbanValidator.php @@ -143,7 +143,7 @@ class IbanValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Iban) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Iban'); + throw new UnexpectedTypeException($constraint, Iban::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/ImageValidator.php b/src/Symfony/Component/Validator/Constraints/ImageValidator.php index d41054c28bbfb..7e7df8129aac2 100644 --- a/src/Symfony/Component/Validator/Constraints/ImageValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ImageValidator.php @@ -31,7 +31,7 @@ class ImageValidator extends FileValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Image) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Image'); + throw new UnexpectedTypeException($constraint, Image::class); } $violations = \count($this->context->getViolations()); diff --git a/src/Symfony/Component/Validator/Constraints/IpValidator.php b/src/Symfony/Component/Validator/Constraints/IpValidator.php index 0f7d383587189..f965a31d4b9c1 100644 --- a/src/Symfony/Component/Validator/Constraints/IpValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IpValidator.php @@ -30,7 +30,7 @@ class IpValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Ip) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Ip'); + throw new UnexpectedTypeException($constraint, Ip::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/IsFalseValidator.php b/src/Symfony/Component/Validator/Constraints/IsFalseValidator.php index 95245fb97bd9d..79c42348fec68 100644 --- a/src/Symfony/Component/Validator/Constraints/IsFalseValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IsFalseValidator.php @@ -26,7 +26,7 @@ class IsFalseValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof IsFalse) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\IsFalse'); + throw new UnexpectedTypeException($constraint, IsFalse::class); } if (null === $value || false === $value || 0 === $value || '0' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/IsNullValidator.php b/src/Symfony/Component/Validator/Constraints/IsNullValidator.php index 01b9d1e40a05e..b6e78170f92de 100644 --- a/src/Symfony/Component/Validator/Constraints/IsNullValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IsNullValidator.php @@ -26,7 +26,7 @@ class IsNullValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof IsNull) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\IsNull'); + throw new UnexpectedTypeException($constraint, IsNull::class); } if (null !== $value) { diff --git a/src/Symfony/Component/Validator/Constraints/IsTrueValidator.php b/src/Symfony/Component/Validator/Constraints/IsTrueValidator.php index 89aa337d2c054..6088f6d7a7a64 100644 --- a/src/Symfony/Component/Validator/Constraints/IsTrueValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IsTrueValidator.php @@ -26,7 +26,7 @@ class IsTrueValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof IsTrue) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\IsTrue'); + throw new UnexpectedTypeException($constraint, IsTrue::class); } if (null === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/IsbnValidator.php b/src/Symfony/Component/Validator/Constraints/IsbnValidator.php index 53687ddc80477..5b59f3c7a8251 100644 --- a/src/Symfony/Component/Validator/Constraints/IsbnValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IsbnValidator.php @@ -33,7 +33,7 @@ class IsbnValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Isbn) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Isbn'); + throw new UnexpectedTypeException($constraint, Isbn::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/IssnValidator.php b/src/Symfony/Component/Validator/Constraints/IssnValidator.php index 21b6403494c3b..8766b077ba3b7 100644 --- a/src/Symfony/Component/Validator/Constraints/IssnValidator.php +++ b/src/Symfony/Component/Validator/Constraints/IssnValidator.php @@ -32,7 +32,7 @@ class IssnValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Issn) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Issn'); + throw new UnexpectedTypeException($constraint, Issn::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/LanguageValidator.php b/src/Symfony/Component/Validator/Constraints/LanguageValidator.php index 4f9b118d8416c..c204712306709 100644 --- a/src/Symfony/Component/Validator/Constraints/LanguageValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LanguageValidator.php @@ -31,7 +31,7 @@ class LanguageValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Language) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Language'); + throw new UnexpectedTypeException($constraint, Language::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/LengthValidator.php b/src/Symfony/Component/Validator/Constraints/LengthValidator.php index f3cf245cf41dd..7cc0ae3e9cd15 100644 --- a/src/Symfony/Component/Validator/Constraints/LengthValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LengthValidator.php @@ -27,7 +27,7 @@ class LengthValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Length) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Length'); + throw new UnexpectedTypeException($constraint, Length::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/LessThanOrEqualValidator.php b/src/Symfony/Component/Validator/Constraints/LessThanOrEqualValidator.php index 54281eef5a673..f7f4c8be5f326 100644 --- a/src/Symfony/Component/Validator/Constraints/LessThanOrEqualValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LessThanOrEqualValidator.php @@ -24,7 +24,7 @@ class LessThanOrEqualValidator extends AbstractComparisonValidator */ protected function compareValues($value1, $value2) { - return $value1 <= $value2; + return null === $value2 || $value1 <= $value2; } /** diff --git a/src/Symfony/Component/Validator/Constraints/LessThanValidator.php b/src/Symfony/Component/Validator/Constraints/LessThanValidator.php index ef7535fc99143..64e107547ac83 100644 --- a/src/Symfony/Component/Validator/Constraints/LessThanValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LessThanValidator.php @@ -24,7 +24,7 @@ class LessThanValidator extends AbstractComparisonValidator */ protected function compareValues($value1, $value2) { - return $value1 < $value2; + return null === $value2 || $value1 < $value2; } /** diff --git a/src/Symfony/Component/Validator/Constraints/LocaleValidator.php b/src/Symfony/Component/Validator/Constraints/LocaleValidator.php index f965a0fcf21e8..ec2c7c8f03d17 100644 --- a/src/Symfony/Component/Validator/Constraints/LocaleValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LocaleValidator.php @@ -31,7 +31,7 @@ class LocaleValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Locale) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Locale'); + throw new UnexpectedTypeException($constraint, Locale::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/LuhnValidator.php b/src/Symfony/Component/Validator/Constraints/LuhnValidator.php index 888e29cb7f10c..0f568fa6e4958 100644 --- a/src/Symfony/Component/Validator/Constraints/LuhnValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LuhnValidator.php @@ -40,7 +40,7 @@ class LuhnValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Luhn) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Luhn'); + throw new UnexpectedTypeException($constraint, Luhn::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/NotBlankValidator.php b/src/Symfony/Component/Validator/Constraints/NotBlankValidator.php index d358cc90f0410..86af06123d76c 100644 --- a/src/Symfony/Component/Validator/Constraints/NotBlankValidator.php +++ b/src/Symfony/Component/Validator/Constraints/NotBlankValidator.php @@ -27,7 +27,7 @@ class NotBlankValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof NotBlank) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\NotBlank'); + throw new UnexpectedTypeException($constraint, NotBlank::class); } if ($constraint->allowNull && null === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/NotNullValidator.php b/src/Symfony/Component/Validator/Constraints/NotNullValidator.php index d6f620713e6a6..d02fcc43988a6 100644 --- a/src/Symfony/Component/Validator/Constraints/NotNullValidator.php +++ b/src/Symfony/Component/Validator/Constraints/NotNullValidator.php @@ -26,7 +26,7 @@ class NotNullValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof NotNull) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\NotNull'); + throw new UnexpectedTypeException($constraint, NotNull::class); } if (null === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/RangeValidator.php b/src/Symfony/Component/Validator/Constraints/RangeValidator.php index ea7d2778088c3..43394601d7279 100644 --- a/src/Symfony/Component/Validator/Constraints/RangeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/RangeValidator.php @@ -27,7 +27,7 @@ class RangeValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Range) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Range'); + throw new UnexpectedTypeException($constraint, Range::class); } if (null === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/RegexValidator.php b/src/Symfony/Component/Validator/Constraints/RegexValidator.php index 5b8be6ec1d2b1..7fadf7682b056 100644 --- a/src/Symfony/Component/Validator/Constraints/RegexValidator.php +++ b/src/Symfony/Component/Validator/Constraints/RegexValidator.php @@ -30,7 +30,7 @@ class RegexValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Regex) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Regex'); + throw new UnexpectedTypeException($constraint, Regex::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/TimeValidator.php b/src/Symfony/Component/Validator/Constraints/TimeValidator.php index 799dbc3108e0c..fdfd6e654559c 100644 --- a/src/Symfony/Component/Validator/Constraints/TimeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/TimeValidator.php @@ -45,7 +45,7 @@ public static function checkTime($hour, $minute, $second) public function validate($value, Constraint $constraint) { if (!$constraint instanceof Time) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Time'); + throw new UnexpectedTypeException($constraint, Time::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/TypeValidator.php b/src/Symfony/Component/Validator/Constraints/TypeValidator.php index 206836d3617fc..ecd7cd8cf7e37 100644 --- a/src/Symfony/Component/Validator/Constraints/TypeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/TypeValidator.php @@ -26,7 +26,7 @@ class TypeValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Type) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Type'); + throw new UnexpectedTypeException($constraint, Type::class); } if (null === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/UrlValidator.php b/src/Symfony/Component/Validator/Constraints/UrlValidator.php index a15a92313f9c4..901699168a9be 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-]+:)?([\_\.\pL\pN-]+)@)? # basic auth ( ([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name | # or @@ -46,7 +46,7 @@ class UrlValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Url) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Url'); + throw new UnexpectedTypeException($constraint, Url::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/UuidValidator.php b/src/Symfony/Component/Validator/Constraints/UuidValidator.php index c5de675b1ca5a..330b91ed6ae20 100644 --- a/src/Symfony/Component/Validator/Constraints/UuidValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UuidValidator.php @@ -68,7 +68,7 @@ class UuidValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Uuid) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Uuid'); + throw new UnexpectedTypeException($constraint, Uuid::class); } if (null === $value || '' === $value) { diff --git a/src/Symfony/Component/Validator/Constraints/ValidValidator.php b/src/Symfony/Component/Validator/Constraints/ValidValidator.php index 695ec822540ed..85eabb80ae8e2 100644 --- a/src/Symfony/Component/Validator/Constraints/ValidValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ValidValidator.php @@ -23,7 +23,7 @@ class ValidValidator extends ConstraintValidator public function validate($value, Constraint $constraint) { if (!$constraint instanceof Valid) { - throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Valid'); + throw new UnexpectedTypeException($constraint, Valid::class); } if (null === $value) { diff --git a/src/Symfony/Component/Validator/LICENSE b/src/Symfony/Component/Validator/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Validator/LICENSE +++ b/src/Symfony/Component/Validator/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf index 12f26fdc88688..79dab63d7de58 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf @@ -334,6 +334,38 @@ This value should be valid JSON. هذه القيمة يجب أن تكون صالحة ل JSON. + + This collection should contain only unique elements. + يجب أن تحتوي هذه المجموعة علي عناصر فريدة فقط. + + + This value should be positive. + يجب أن تكون هذه القيمة موجبة. + + + This value should be either positive or zero. + يجب أن تكون هذه القيمة إما موجبة او صفر. + + + This value should be negative. + يجب أن تكون هذه القيمة سالبة. + + + This value should be either negative or zero. + يجب أن تكون هذه القيمة إما سالبة او صفر. + + + This value is not a valid timezone. + هذه القيمة ليست منطقة زمنية صحيحة. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + تم تسريب كلمة المرور هذه في خرق للبيانات، ويجب عدم استخدامها. يرجي استخدام كلمة مرور أخري. + + + This value should be between {{ min }} and {{ max }}. + يجب أن تكون هذه القيمة بين {{ min }} و {{ max }}. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf index 018dd1233ac61..20dff43c6d904 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sr_Latn.xlf @@ -24,11 +24,11 @@ You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. - Morate odabrati bar {{ limit }} mogućnost.|Morate odabrati bar {{ limit }} mogućnosti. + Morate odabrati bar {{ limit }} mogućnost.|Morate odabrati bar {{ limit }} mogućnosti.|Morate odabrati bar {{ limit }} mogućnosti. You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. - Morate odabrati najviše {{ limit }} mogućnost.|Morate odabrati najviše {{ limit }} mogućnosti. + Morate odabrati najviše {{ limit }} mogućnost.|Morate odabrati najviše {{ limit }} mogućnosti.|Morate odabrati najviše {{ limit }} mogućnosti. One or more of the given values is invalid. @@ -76,7 +76,7 @@ This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. - Vrednost je predugačka. Trebalo bi da ima {{ limit }} karakter ili manje.|Vrednost je predugačka. Trebalo bi da ima {{ limit }} karaktera ili manje. + Vrednost je predugačka. Trebalo bi da ima {{ limit }} karakter ili manje.|Vrednost je predugačka. Trebalo bi da ima {{ limit }} karaktera ili manje.|Vrednost je predugačka. Trebalo bi da ima {{ limit }} karaktera ili manje. This value should be {{ limit }} or more. @@ -84,7 +84,7 @@ This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. - Vrednost je prekratka. Trebalo bi da ima {{ limit }} karakter ili više.|Vrednost je prekratka. Trebalo bi da ima {{ limit }} karaktera ili više. + Vrednost je prekratka. Trebalo bi da ima {{ limit }} karakter ili više.|Vrednost je prekratka. Trebalo bi da ima {{ limit }} karaktera ili više.|Vrednost je prekratka. Trebalo bi da ima {{ limit }} karaktera ili više. This value should not be blank. @@ -180,7 +180,7 @@ This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. - Vrednost bi trebalo da ima tačno {{ limit }} karakter.|Vrednost bi trebalo da ima tačno {{ limit }} karaktera. + Vrednost bi trebalo da ima tačno {{ limit }} karakter.|Vrednost bi trebalo da ima tačno {{ limit }} karaktera.|Vrednost bi trebalo da ima tačno {{ limit }} karaktera. The file was only partially uploaded. @@ -204,15 +204,15 @@ This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. - Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata. + Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili više elemenata. This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. - Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata. + Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata.|Ova kolekcija bi trebalo da sadrži {{ limit }} ili manje elemenata. This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. - Ova kolekcija bi trebalo da sadrži tačno {{ limit }} element.|Ova kolekcija bi trebalo da sadrži tačno {{ limit }} elemenata. + Ova kolekcija bi trebalo da sadrži tačno {{ limit }} element.|Ova kolekcija bi trebalo da sadrži tačno {{ limit }} elementa.|Ova kolekcija bi trebalo da sadrži tačno {{ limit }} elemenata. Invalid card number. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.zh_TW.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.zh_TW.xlf index d9d5f2f622b43..7cef875f5812e 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.zh_TW.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.zh_TW.xlf @@ -278,6 +278,94 @@ This value should not be identical to {{ compared_value_type }} {{ compared_value }}. 該值不應與 {{ compared_value_type }} {{ compared_value }} 相同。 + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + 圖像格式過大 ({{ ratio }})。 最大允許尺寸 {{ max_ratio }}。 + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + 圖像格式過小 ({{ ratio }})。最小尺寸 {{ min_ratio }}。 + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + 方形圖像 ({{ width }}x{{ height }}px)。不接受方形圖像。 + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + 紀念冊布局圖像 ({{ width }}x{{ height }}px)。 不接受紀念冊布局圖像。 + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + 書籍布局圖像 ({{ width }}x{{ height }}px)。不接受圖像書籍布局。 + + + An empty file is not allowed. + 不接受空白文件。 + + + The host could not be resolved. + 未找到服務器。 + + + This value does not match the expected {{ charset }} charset. + 該數值不符合預期 {{ charset }} 符號編碼。 + + + This is not a valid Business Identifier Code (BIC). + 無效企業識別碼 (BIC)。 + + + Error. + 錯誤。 + + + This is not a valid UUID. + 無效的通用唯壹標識符 (UUID)。 + + + This value should be a multiple of {{ compared_value }}. + 該值必須是倍數 {{ compared_value }}。 + + + This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. + 該企業識別碼 (BIC) 與銀行賬戶國際編號不壹致 (IBAN) {{ iban }}。 + + + This value should be valid JSON. + 該數值必須序列化為JSON格式。 + + + This collection should contain only unique elements. + 該集合應僅包含唯壹元素。 + + + This value should be positive. + 數值應為正數。 + + + This value should be either positive or zero. + 數值應或未正數,或為零。 + + + This value should be negative. + 數值應為負數。 + + + This value should be either negative or zero. + 數值應或未負數,或為零。 + + + This value is not a valid timezone. + 無效時區。 + + + This password has been leaked in a data breach, it must not be used. Please use another password. + 依據您的密碼,發生數據泄露,請勿使用改密碼。請更換密碼。 + + + This value should be between {{ min }} and {{ max }}. + 該數值應在 {{ min }} 和 {{ max }} 之間。 + diff --git a/src/Symfony/Component/Validator/Tests/ConstraintValidatorTest.php b/src/Symfony/Component/Validator/Tests/ConstraintValidatorTest.php index 96af6f13eb4e7..6ca3eab41fd6e 100644 --- a/src/Symfony/Component/Validator/Tests/ConstraintValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/ConstraintValidatorTest.php @@ -27,6 +27,9 @@ public function testFormatValue($expected, $value, $format = 0) public function formatValueProvider() { + $defaultTimezone = date_default_timezone_get(); + date_default_timezone_set('Europe/Moscow'); // GMT+3 + $data = [ ['true', true], ['false', false], @@ -36,10 +39,15 @@ public function formatValueProvider() ['array', []], ['object', $toString = new TestToStringObject()], ['ccc', $toString, ConstraintValidator::OBJECT_TO_STRING], - ['object', $dateTime = (new \DateTimeImmutable('@0'))->setTimezone(new \DateTimeZone('UTC'))], - [class_exists(\IntlDateFormatter::class) ? 'Jan 1, 1970, 12:00 AM' : '1970-01-01 00:00:00', $dateTime, ConstraintValidator::PRETTY_DATE], + ['object', $dateTime = new \DateTimeImmutable('1971-02-02T08:00:00UTC')], + [class_exists(\IntlDateFormatter::class) ? 'Oct 4, 2019, 11:02 AM' : '2019-10-04 11:02:03', new \DateTimeImmutable('2019-10-04T11:02:03+09:00'), ConstraintValidator::PRETTY_DATE], + [class_exists(\IntlDateFormatter::class) ? 'Feb 2, 1971, 8:00 AM' : '1971-02-02 08:00:00', $dateTime, ConstraintValidator::PRETTY_DATE], + [class_exists(\IntlDateFormatter::class) ? 'Jan 1, 1970, 6:00 AM' : '1970-01-01 06:00:00', new \DateTimeImmutable('1970-01-01T06:00:00Z'), ConstraintValidator::PRETTY_DATE], + [class_exists(\IntlDateFormatter::class) ? 'Jan 1, 1970, 3:00 PM' : '1970-01-01 15:00:00', (new \DateTimeImmutable('1970-01-01T23:00:00'))->setTimezone(new \DateTimeZone('America/New_York')), ConstraintValidator::PRETTY_DATE], ]; + date_default_timezone_set($defaultTimezone); + return $data; } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php b/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php index b02e57cfa2358..3e8a9dcd7b829 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php @@ -53,10 +53,7 @@ protected static function addPhp5Dot5Comparisons(array $comparisons) foreach ($comparison as $i => $value) { if ($value instanceof \DateTime) { - $comparison[$i] = new \DateTimeImmutable( - $value->format('Y-m-d H:i:s.u e'), - $value->getTimezone() - ); + $comparison[$i] = new \DateTimeImmutable($value->format('Y-m-d H:i:s.u e')); $add = true; } elseif ('DateTime' === $value) { $comparison[$i] = 'DateTimeImmutable'; @@ -237,6 +234,31 @@ public function throwsOnInvalidStringDatesProvider() ]; } + /** + * @dataProvider provideComparisonsToNullValueAtPropertyPath + */ + public function testCompareWithNullValueAtPropertyAt($dirtyValue, $dirtyValueAsString, $isValid) + { + $constraint = $this->createConstraint(['propertyPath' => 'value']); + $constraint->message = 'Constraint Message'; + + $object = new ComparisonTest_Class(null); + $this->setObject($object); + + $this->validator->validate($dirtyValue, $constraint); + + if ($isValid) { + $this->assertNoViolation(); + } else { + $this->buildViolation('Constraint Message') + ->setParameter('{{ value }}', $dirtyValueAsString) + ->setParameter('{{ compared_value }}', 'null') + ->setParameter('{{ compared_value_type }}', 'NULL') + ->setCode($this->getErrorCode()) + ->assertRaised(); + } + } + /** * @return array */ @@ -258,6 +280,8 @@ public function provideAllInvalidComparisons() */ abstract public function provideInvalidComparisons(); + abstract public function provideComparisonsToNullValueAtPropertyPath(); + /** * @param array|null $options Options for the constraint * diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php index 99a969d51d671..4dff952f197a0 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/DivisibleByValidatorTest.php @@ -97,4 +97,9 @@ public function throwsOnNonNumericValuesProvider() [\ArrayIterator::class, new \ArrayIterator(), 12], ]; } + + public function provideComparisonsToNullValueAtPropertyPath() + { + $this->markTestSkipped('DivisibleByValidator rejects null values.'); + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php index c1eb2f93ad754..880dbd7795c2e 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php @@ -75,4 +75,11 @@ public function provideInvalidComparisons() [new ComparisonTest_Class(4), '4', new ComparisonTest_Class(5), '5', __NAMESPACE__.'\ComparisonTest_Class'], ]; } + + public function provideComparisonsToNullValueAtPropertyPath() + { + return [ + [5, '5', false], + ]; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php index d8d8eab8bdeee..043c02e7a72d4 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php @@ -78,4 +78,11 @@ public function provideInvalidComparisons() ['b', '"b"', 'c', '"c"', 'string'], ]; } + + public function provideComparisonsToNullValueAtPropertyPath() + { + return [ + [5, '5', true], + ]; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php index e678496c41e68..119c162edb5e2 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php @@ -80,4 +80,11 @@ public function provideInvalidComparisons() ['22', '"22"', '22', '"22"', 'string'], ]; } + + public function provideComparisonsToNullValueAtPropertyPath() + { + return [ + [5, '5', true], + ]; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php index c96ac16a91930..1d3662f49ae45 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php @@ -93,4 +93,11 @@ public function provideInvalidComparisons() [new ComparisonTest_Class(4), '4', new ComparisonTest_Class(5), '5', __NAMESPACE__.'\ComparisonTest_Class'], ]; } + + public function provideComparisonsToNullValueAtPropertyPath() + { + return [ + [5, '5', false], + ]; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php index b77deff6163d6..9311706e7df38 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php @@ -81,4 +81,11 @@ public function provideInvalidComparisons() ['c', '"c"', 'b', '"b"', 'string'], ]; } + + public function provideComparisonsToNullValueAtPropertyPath() + { + return [ + [5, '5', true], + ]; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php index 47490ad97bc31..4f22ed385a123 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorWithNegativeOrZeroConstraintTest.php @@ -108,4 +108,12 @@ public function testThrowsOnInvalidStringDates(AbstractComparison $constraint, $ { $this->markTestSkipped('The compared value cannot be an invalid string date because it is hardcoded to 0.'); } + + /** + * @dataProvider provideComparisonsToNullValueAtPropertyPath + */ + public function testCompareWithNullValueAtPropertyAt($dirtyValue, $dirtyValueAsString, $isValid) + { + $this->markTestSkipped('PropertyPath option is not used in NegativeOrZero constraint'); + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php index 7d209ed5d4719..c40389440df50 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php @@ -79,4 +79,11 @@ public function provideInvalidComparisons() ['333', '"333"', '22', '"22"', 'string'], ]; } + + public function provideComparisonsToNullValueAtPropertyPath() + { + return [ + [5, '5', true], + ]; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php index c73811b09c017..ce71d57cfa94c 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorWithNegativeConstraintTest.php @@ -108,4 +108,12 @@ public function testThrowsOnInvalidStringDates(AbstractComparison $constraint, $ { $this->markTestSkipped('The compared value cannot be an invalid string date because it is hardcoded to 0.'); } + + /** + * @dataProvider provideComparisonsToNullValueAtPropertyPath + */ + public function testCompareWithNullValueAtPropertyAt($dirtyValue, $dirtyValueAsString, $isValid) + { + $this->markTestSkipped('PropertyPath option is not used in Negative constraint'); + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php index 810f7a175f104..8577159583def 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php @@ -75,4 +75,11 @@ public function provideInvalidComparisons() [new ComparisonTest_Class(5), '5', new ComparisonTest_Class(5), '5', __NAMESPACE__.'\ComparisonTest_Class'], ]; } + + public function provideComparisonsToNullValueAtPropertyPath() + { + return [ + [5, '5', true], + ]; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php index 0cb9ec543114c..f14c5bd0dce8a 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php @@ -93,4 +93,11 @@ public function provideInvalidComparisons() return $comparisons; } + + public function provideComparisonsToNullValueAtPropertyPath() + { + return [ + [5, '5', true], + ]; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php index b00746d7a161a..e7bc62381ec84 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php @@ -152,9 +152,11 @@ public function getValidUrls() ['http://☎.com/'], ['http://username:password@symfony.com'], ['http://user.name:password@symfony.com'], + ['http://user_name:pass_word@symfony.com'], ['http://username:pass.word@symfony.com'], ['http://user.name:pass.word@symfony.com'], ['http://user-name@symfony.com'], + ['http://user_name@symfony.com'], ['http://symfony.com?'], ['http://symfony.com?query=1'], ['http://symfony.com/?query=1'], diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php index 26d0ce3c80942..a8788e0dfdf6b 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php @@ -33,7 +33,7 @@ protected function tearDown(): void public function testLoadClassMetadataReturnsTrueIfSuccessful() { $loader = new StaticMethodLoader('loadMetadata'); - $metadata = new ClassMetadata(__NAMESPACE__.'\StaticLoaderEntity'); + $metadata = new ClassMetadata(StaticLoaderEntity::class); $this->assertTrue($loader->loadClassMetadata($metadata)); } @@ -49,7 +49,7 @@ public function testLoadClassMetadataReturnsFalseIfNotSuccessful() public function testLoadClassMetadata() { $loader = new StaticMethodLoader('loadMetadata'); - $metadata = new ClassMetadata(__NAMESPACE__.'\StaticLoaderEntity'); + $metadata = new ClassMetadata(StaticLoaderEntity::class); $loader->loadClassMetadata($metadata); @@ -59,12 +59,12 @@ public function testLoadClassMetadata() public function testLoadClassMetadataDoesNotRepeatLoadWithParentClasses() { $loader = new StaticMethodLoader('loadMetadata'); - $metadata = new ClassMetadata(__NAMESPACE__.'\StaticLoaderDocument'); + $metadata = new ClassMetadata(StaticLoaderDocument::class); $loader->loadClassMetadata($metadata); $this->assertCount(0, $metadata->getConstraints()); $loader = new StaticMethodLoader('loadMetadata'); - $metadata = new ClassMetadata(__NAMESPACE__.'\BaseStaticLoaderDocument'); + $metadata = new ClassMetadata(BaseStaticLoaderDocument::class); $loader->loadClassMetadata($metadata); $this->assertCount(1, $metadata->getConstraints()); } @@ -72,7 +72,7 @@ public function testLoadClassMetadataDoesNotRepeatLoadWithParentClasses() public function testLoadClassMetadataIgnoresInterfaces() { $loader = new StaticMethodLoader('loadMetadata'); - $metadata = new ClassMetadata(__NAMESPACE__.'\StaticLoaderInterface'); + $metadata = new ClassMetadata(StaticLoaderInterface::class); $loader->loadClassMetadata($metadata); @@ -82,7 +82,7 @@ public function testLoadClassMetadataIgnoresInterfaces() public function testLoadClassMetadataInAbstractClasses() { $loader = new StaticMethodLoader('loadMetadata'); - $metadata = new ClassMetadata(__NAMESPACE__.'\AbstractStaticLoader'); + $metadata = new ClassMetadata(AbstractStaticLoader::class); $loader->loadClassMetadata($metadata); @@ -95,7 +95,7 @@ public function testLoadClassMetadataIgnoresAbstractMethods() // strict standards error error_reporting(0); - $metadata = new ClassMetadata(__NAMESPACE__.'\AbstractStaticMethodLoader'); + $metadata = new ClassMetadata(AbstractStaticMethodLoader::class); $loader = new StaticMethodLoader('loadMetadata'); $loader->loadClassMetadata($metadata); diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php index 884e84de9b89d..9a2b685b8a905 100644 --- a/src/Symfony/Component/VarDumper/Caster/Caster.php +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -48,6 +48,15 @@ class Caster */ public static function castObject($obj, $class, $hasDebugInfo = false) { + if ($hasDebugInfo) { + try { + $debugInfo = $obj->__debugInfo(); + } catch (\Exception $e) { + // ignore failing __debugInfo() + $hasDebugInfo = false; + } + } + $a = $obj instanceof \Closure ? [] : (array) $obj; if ($obj instanceof \__PHP_Incomplete_Class) { @@ -83,7 +92,7 @@ public static function castObject($obj, $class, $hasDebugInfo = false) } } - if ($hasDebugInfo && \is_array($debugInfo = $obj->__debugInfo())) { + if ($hasDebugInfo && \is_array($debugInfo)) { foreach ($debugInfo as $k => $v) { if (!isset($k[0]) || "\0" !== $k[0]) { $k = self::PREFIX_VIRTUAL.$k; diff --git a/src/Symfony/Component/VarDumper/Caster/ClassStub.php b/src/Symfony/Component/VarDumper/Caster/ClassStub.php index 0b9329dbe903f..afafda57530bc 100644 --- a/src/Symfony/Component/VarDumper/Caster/ClassStub.php +++ b/src/Symfony/Component/VarDumper/Caster/ClassStub.php @@ -56,7 +56,7 @@ public function __construct(string $identifier, $callable = null) } if (false !== strpos($identifier, "class@anonymous\0")) { - $this->value = $identifier = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) { + $this->value = $identifier = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:)[0-9a-fA-F]++/', function ($m) { return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0]; }, $identifier); } diff --git a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php index ec168c8da9c92..ad02aec2fe3b6 100644 --- a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php @@ -282,7 +282,7 @@ private static function filterExceptionArray($xClass, array $a, $xPrefix, $filte unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']); if (isset($a[Caster::PREFIX_PROTECTED.'message']) && false !== strpos($a[Caster::PREFIX_PROTECTED.'message'], "class@anonymous\0")) { - $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) { + $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:)[0-9a-fA-F]++/', function ($m) { return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0]; }, $a[Caster::PREFIX_PROTECTED.'message']); } diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 79519a7fb2a12..779e0626c6ab6 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -46,6 +46,7 @@ abstract class AbstractCloner implements ClonerInterface 'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'], 'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'], 'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'], + 'Doctrine\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], diff --git a/src/Symfony/Component/VarDumper/Dumper/ContextProvider/CliContextProvider.php b/src/Symfony/Component/VarDumper/Dumper/ContextProvider/CliContextProvider.php index e7f8ccf17f194..38f878971c53f 100644 --- a/src/Symfony/Component/VarDumper/Dumper/ContextProvider/CliContextProvider.php +++ b/src/Symfony/Component/VarDumper/Dumper/ContextProvider/CliContextProvider.php @@ -25,7 +25,7 @@ public function getContext(): ?array } return [ - 'command_line' => $commandLine = implode(' ', $_SERVER['argv']), + 'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []), 'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']), ]; } diff --git a/src/Symfony/Component/VarDumper/LICENSE b/src/Symfony/Component/VarDumper/LICENSE index cf8b3ebe87145..684fbf94df83c 100644 --- a/src/Symfony/Component/VarDumper/LICENSE +++ b/src/Symfony/Component/VarDumper/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2019 Fabien Potencier +Copyright (c) 2014-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php index a0b8e1d1b3663..7ff2e1d2e6e49 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php @@ -120,7 +120,7 @@ public function testClosureCasterExcludingVerbosity() public function testReflectionParameter() { - $var = new \ReflectionParameter(__NAMESPACE__.'\reflectionParameterFixture', 0); + $var = new \ReflectionParameter(reflectionParameterFixture::class, 0); $this->assertDumpMatchesFormat( <<<'EOTXT' diff --git a/src/Symfony/Component/VarExporter/LICENSE b/src/Symfony/Component/VarExporter/LICENSE index 3f853aaf35fe1..69d925ba7511e 100644 --- a/src/Symfony/Component/VarExporter/LICENSE +++ b/src/Symfony/Component/VarExporter/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2019 Fabien Potencier +Copyright (c) 2018-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/WebLink/LICENSE b/src/Symfony/Component/WebLink/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/WebLink/LICENSE +++ b/src/Symfony/Component/WebLink/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Workflow/LICENSE b/src/Symfony/Component/Workflow/LICENSE index cf8b3ebe87145..684fbf94df83c 100644 --- a/src/Symfony/Component/Workflow/LICENSE +++ b/src/Symfony/Component/Workflow/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2019 Fabien Potencier +Copyright (c) 2014-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php index 53fef69274acc..625086b2f1032 100644 --- a/src/Symfony/Component/Workflow/Workflow.php +++ b/src/Symfony/Component/Workflow/Workflow.php @@ -43,7 +43,13 @@ public function __construct(Definition $definition, MarkingStoreInterface $marki { $this->definition = $definition; $this->markingStore = $markingStore ?: new MultipleStateMarkingStore(); - $this->dispatcher = null !== $dispatcher ? LegacyEventDispatcherProxy::decorate($dispatcher) : null; + + if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) { + $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + } else { + $this->dispatcher = $dispatcher; + } + $this->name = $name; } diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php index 5175ae4d115f5..0b56779c68f10 100644 --- a/src/Symfony/Component/Yaml/Dumper.php +++ b/src/Symfony/Component/Yaml/Dumper.php @@ -70,7 +70,7 @@ public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): $blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : ''; $output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator); - foreach (preg_split('/\n|\r\n/', $value) as $row) { + foreach (explode("\n", $value) as $row) { $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row); } @@ -80,6 +80,19 @@ public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): if ($value instanceof TaggedValue) { $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); + if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : ''; + $output .= sprintf(" |%s\n", $blockIndentationIndicator); + + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row); + } + + continue; + } + if ($inline - 1 <= 0 || null === $value->getValue() || is_scalar($value->getValue())) { $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; } else { diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index 1fbfeed7ba7dc..cd8a4a7009724 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -434,6 +434,11 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); } + if ('!php/const' === $key) { + $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false, []); + $key = self::evaluateScalar($key, $flags); + } + if (false === $i = strpos($mapping, ':', $i)) { break; } @@ -612,11 +617,11 @@ private static function evaluateScalar(string $scalar, int $flags, array $refere // Optimize for returning strings. // no break case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]): + if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { + $scalar = str_replace('_', '', (string) $scalar); + } + switch (true) { - case Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar): - $scalar = str_replace('_', '', (string) $scalar); - // omitting the break / return as integers are handled in the next case - // no break case ctype_digit($scalar): $raw = $scalar; $cast = (int) $scalar; @@ -626,7 +631,7 @@ private static function evaluateScalar(string $scalar, int $flags, array $refere $raw = $scalar; $cast = (int) $scalar; - return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw); + return '0' == $scalar[1] ? -octdec(substr($scalar, 1)) : (($raw === (string) $cast) ? $cast : $raw); case is_numeric($scalar): case Parser::preg_match(self::getHexRegex(), $scalar): $scalar = str_replace('_', '', $scalar); @@ -669,6 +674,10 @@ private static function parseTag(string $value, int &$i, int $flags): ?string $nextOffset = $i + $tagLength + 1; $nextOffset += strspn($value, ' ', $nextOffset); + if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) { + throw new ParseException(sprintf('Using the unquoted scalar value "!" is not supported. You must quote it.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + // Is followed by a scalar and is a built-in tag if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) { // Manage in {@link self::evaluateScalar()} diff --git a/src/Symfony/Component/Yaml/LICENSE b/src/Symfony/Component/Yaml/LICENSE index a677f43763ca4..9e936ec0448b8 100644 --- a/src/Symfony/Component/Yaml/LICENSE +++ b/src/Symfony/Component/Yaml/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2004-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/Yaml/README.md b/src/Symfony/Component/Yaml/README.md index 0d324881ce5e4..b914e7836c7a5 100644 --- a/src/Symfony/Component/Yaml/README.md +++ b/src/Symfony/Component/Yaml/README.md @@ -6,7 +6,7 @@ The Yaml component loads and dumps YAML files. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/yaml/index.html) + * [Documentation](https://symfony.com/doc/current/components/yaml.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/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index 0ac0de0f3427c..4ccb30dce85a3 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -487,6 +487,39 @@ public function testDumpingNotInlinedNullTaggedValue() $this->assertSame($expected, $this->dumper->dump($data, 2)); } + public function testDumpingMultiLineStringAsScalarBlockTaggedValue() + { + $data = [ + 'foo' => new TaggedValue('bar', "foo\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz"), + ]; + $expected = <<assertSame($expected, $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + } + + public function testDumpingInlinedMultiLineIfRnBreakLineInTaggedValue() + { + $data = [ + 'data' => [ + 'foo' => new TaggedValue('bar', "foo\r\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz"), + ], + ]; + + $this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block_for_tagged_values.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + } + public function testDumpMultiLineStringAsScalarBlock() { $data = [ diff --git a/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_for_tagged_values.yml b/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_for_tagged_values.yml new file mode 100644 index 0000000000000..f8c9112fd52a5 --- /dev/null +++ b/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_for_tagged_values.yml @@ -0,0 +1,2 @@ +data: + foo: !bar "foo\r\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz" diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index a18adfff6fa2d..315620e30243f 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -59,6 +59,7 @@ public function getTestsForParsePhpConstants() ['!php/const PHP_INT_MAX', PHP_INT_MAX], ['[!php/const PHP_INT_MAX]', [PHP_INT_MAX]], ['{ foo: !php/const PHP_INT_MAX }', ['foo' => PHP_INT_MAX]], + ['{ !php/const PHP_INT_MAX: foo }', [PHP_INT_MAX => 'foo']], ['!php/const NULL', null], ]; } @@ -720,4 +721,85 @@ public function testUnfinishedInlineMap() $this->expectExceptionMessage('Unexpected end of line, expected one of ",}" at line 1 (near "{abc: \'def\'").'); Inline::parse("{abc: 'def'"); } + + /** + * @dataProvider getTestsForOctalNumbers + */ + public function testParseOctalNumbers($expected, $yaml) + { + self::assertSame($expected, Inline::parse($yaml)); + } + + public function getTestsForOctalNumbers() + { + return [ + 'positive octal number' => [28, '034'], + 'negative octal number' => [-28, '-034'], + ]; + } + + /** + * @dataProvider unquotedExclamationMarkThrowsProvider + */ + 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 "/'); + + Inline::parse($value); + } + + public function unquotedExclamationMarkThrowsProvider() + { + return [ + ['!'], + ['! '], + ['! '], + [' ! '], + ['[!]'], + ['[! ]'], + ['[! ]'], + ['[!, "foo"]'], + ['["foo", !, "ccc"]'], + ['{foo: !}'], + ['{foo: !}'], + ['{foo: !, bar: "ccc"}'], + ['{bar: "ccc", foo: ! }'], + ['!]]]'], + ['!}'], + ['!,}foo,]'], + ['! [!]'], + ]; + } + + /** + * @dataProvider quotedExclamationMarkProvider + */ + public function testQuotedExclamationMark($expected, string $value) + { + $this->assertSame($expected, Inline::parse($value)); + } + + // This provider should stay consistent with unquotedExclamationMarkThrowsProvider + public function quotedExclamationMarkProvider() + { + return [ + ['!', '"!"'], + ['! ', '"! "'], + [' !', '" !"'], + [' ! ', '" ! "'], + [['!'], '["!"]'], + [['! '], '["! "]'], + [['!', 'foo'], '["!", "foo"]'], + [['foo', '!', 'ccc'], '["foo", "!", "ccc"]'], + [['foo' => '!'], '{foo: "!"}'], + [['foo' => ' !'], '{foo: " !"}'], + [['foo' => '!', 'bar' => 'ccc'], '{foo: "!", bar: "ccc"}'], + [['bar' => 'ccc', 'foo' => '! '], '{bar: "ccc", foo: "! "}'], + ['!]]]', '"!]]]"'], + ['!}', '"!}"'], + ['!,}foo,]', '"!,}foo,]"'], + [['!'], '! ["!"]'], + ]; + } } diff --git a/src/Symfony/Contracts/Cache/LICENSE b/src/Symfony/Contracts/Cache/LICENSE index 3f853aaf35fe1..69d925ba7511e 100644 --- a/src/Symfony/Contracts/Cache/LICENSE +++ b/src/Symfony/Contracts/Cache/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2019 Fabien Potencier +Copyright (c) 2018-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Contracts/EventDispatcher/LICENSE b/src/Symfony/Contracts/EventDispatcher/LICENSE index 3f853aaf35fe1..69d925ba7511e 100644 --- a/src/Symfony/Contracts/EventDispatcher/LICENSE +++ b/src/Symfony/Contracts/EventDispatcher/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2019 Fabien Potencier +Copyright (c) 2018-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Contracts/HttpClient/LICENSE b/src/Symfony/Contracts/HttpClient/LICENSE index 3f853aaf35fe1..69d925ba7511e 100644 --- a/src/Symfony/Contracts/HttpClient/LICENSE +++ b/src/Symfony/Contracts/HttpClient/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2019 Fabien Potencier +Copyright (c) 2018-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Contracts/LICENSE b/src/Symfony/Contracts/LICENSE index 3f853aaf35fe1..69d925ba7511e 100644 --- a/src/Symfony/Contracts/LICENSE +++ b/src/Symfony/Contracts/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2019 Fabien Potencier +Copyright (c) 2018-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Contracts/Service/LICENSE b/src/Symfony/Contracts/Service/LICENSE index 3f853aaf35fe1..69d925ba7511e 100644 --- a/src/Symfony/Contracts/Service/LICENSE +++ b/src/Symfony/Contracts/Service/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2019 Fabien Potencier +Copyright (c) 2018-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Contracts/Translation/LICENSE b/src/Symfony/Contracts/Translation/LICENSE index 3f853aaf35fe1..69d925ba7511e 100644 --- a/src/Symfony/Contracts/Translation/LICENSE +++ b/src/Symfony/Contracts/Translation/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2019 Fabien Potencier +Copyright (c) 2018-2020 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal