diff --git a/CHANGELOG-4.0.md b/CHANGELOG-4.0.md index cbd0ed892c1db..9abb4a61a6634 100644 --- a/CHANGELOG-4.0.md +++ b/CHANGELOG-4.0.md @@ -7,6 +7,41 @@ in 4.0 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.0.0...v4.0.1 +* 4.0.9 (2018-04-30) + + * bug #27074 [Debug][WebProfilerBundle] Fix setting file link format (lyrixx, nicolas-grekas) + * bug #27088 ResolveBindingsPass: Don't throw error for unused service, missing parent class (weaverryan) + * bug #27086 [PHPUnitBridge] Add an implementation just for php 7.0 (greg0ire) + * bug #26138 [HttpKernel] Catch HttpExceptions when templating is not installed (cilefen) + * bug #27007 [Cache] TagAwareAdapterInterface::invalidateTags() should commit deferred items (nicolas-grekas) + * bug #27067 [HttpFoundation] Fix setting session-related ini settings (e-moe) + * bug #27061 [HttpKernel] Don't clean legacy containers that are still loaded (nicolas-grekas) + * bug #27064 [VarDumper] Fix HtmlDumper classes match (ogizanagi) + * bug #27016 [Security][Guard] GuardAuthenticationProvider::authenticate cannot return null (biomedia-thomas) + * bug #26831 [Bridge/Doctrine] count(): Parameter must be an array or an object that implements Countable (gpenverne) + * bug #27044 [Security] Skip user checks if not implementing UserInterface (chalasr) + * bug #27025 [DI] Add check of internal type to ContainerBuilder::getReflectionClass (upyx) + * bug #26994 [PhpUnitBridge] Add type hints (greg0ire) + * bug #26014 [Security] Fixed being logged out on failed attempt in guard (iltar) + * bug #25348 [HttpFoundation] Send cookies using header() to fix "SameSite" ones (nicolas-grekas, cvilleger) + * bug #26910 Use new PHP7.2 functions in hasColorSupport (johnstevenson) + * bug #26999 [VarDumper] Fix dumping of SplObjectStorage (corphi) + * bug #25841 [DoctrineBridge] Fix bug when indexBy is meta key in PropertyInfo\DoctrineExtractor (insekticid) + * bug #26983 [TwigBridge] [Bootstrap 4] Fix PercentType error rendering. (alexismarquis) + * bug #26980 [TwigBundle] fix formatting arguments in plaintext format (xabbuh) + * bug #26886 Don't assume that file binary exists on *nix OS (teohhanhui) + * bug #26959 [Console] Fix PSR exception context key (scaytrase) + * bug #26899 [Routing] Fix loading multiple class annotations for invokable classes (1ed) + * bug #26643 Fix that ESI/SSI processing can turn a "private" response "public" (mpdude) + * bug #26932 [Form] Fixed trimming choice values (HeahDude) + * bug #26922 [TwigBundle] fix rendering exception stack traces (xabbuh) + * bug #26773 [HttpKernel] Make ServiceValueResolver work if controller namespace starts with a backslash in routing (mathieutu) + * bug #26870 Add d-block to bootstrap 4 alerts (Normunds) + * bug #26857 [HttpKernel] Dont create mock cookie for new sessions in tests (nicolas-grekas) + * bug #26875 [Console] Don't go past exact matches when autocompleting (nicolas-grekas) + * bug #26823 [Validator] Fix LazyLoadingMetadataFactory with PSR6Cache for non classname if tested values isn't existing class (Pascal Montoya, pmontoya) + * bug #26834 [Yaml] Throw parse error on unfinished inline map (nicolas-grekas) + * 4.0.8 (2018-04-06) * bug #26802 [Security] register custom providers on ExpressionLanguage directly (dmaicher) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1cb4373ec2030..bc4ebf6f78139 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -13,21 +13,21 @@ Symfony is the result of the work of many people who made the code better - Jordi Boggiano (seldaek) - Victor Berchet (victor) - Kévin Dunglas (dunglas) + - Robin Chalas (chalas_r) - Johannes S (johannes) - Jakub Zalas (jakubzalas) - - Robin Chalas (chalas_r) - Kris Wallsmith (kriswallsmith) - Ryan Weaver (weaverryan) - Javier Eguiluz (javier.eguiluz) - Maxime Steinhausser (ogizanagi) - - Hugo Hamon (hhamon) - Grégoire Pineau (lyrixx) + - Hugo Hamon (hhamon) - Abdellatif Ait boudad (aitboudad) - Romain Neutron (romain) - Pascal Borreli (pborreli) - Wouter De Jong (wouterj) - - Joseph Bielawski (stloyd) - Roland Franssen (ro0) + - Joseph Bielawski (stloyd) - Karma Dordrak (drak) - Lukas Kahwe Smith (lsmith) - Martin Hasoň (hason) @@ -39,32 +39,32 @@ Symfony is the result of the work of many people who made the code better - Eriksen Costa (eriksencosta) - Guilhem Niot (energetick) - Sarah Khalil (saro0h) + - Samuel ROZE (sroze) - Jonathan Wage (jwage) - Hamza Amrouche (simperfit) - Diego Saint Esteben (dosten) + - Yonel Ceruto (yonelceruto) - Alexandre Salomé (alexandresalome) + - Iltar van der Berg (kjarli) - William Durand (couac) - ornicar - Francis Besset (francisbesset) - - Iltar van der Berg (kjarli) - stealth35 ‏ (stealth35) - Alexander Mols (asm89) - - Yonel Ceruto (yonelceruto) - Bulat Shakirzyanov (avalanche123) - Peter Rehm (rpet) - - Saša Stamenković (umpirsky) - Matthias Pigulla (mpdude) - - Samuel ROZE (sroze) + - Saša Stamenković (umpirsky) + - Pierre du Plessis (pierredup) - Henrik Bjørnskov (henrikbjorn) - Dany Maillard (maidmaid) - Miha Vrhovnik - - Pierre du Plessis (pierredup) - Tobias Nyholm (tobias) - Diego Saint Esteben (dii3g0) - - Konstantin Kudryashov (everzet) - Kevin Bond (kbond) - - Bilal Amarni (bamarni) + - Konstantin Kudryashov (everzet) - Alexander M. Turek (derrabus) + - Bilal Amarni (bamarni) - Jérémy DERUSSÉ (jderusse) - Florin Patan (florinpatan) - Mathieu Piot (mpiot) @@ -81,13 +81,14 @@ Symfony is the result of the work of many people who made the code better - Deni - Henrik Westphal (snc) - Dariusz Górecki (canni) - - Douglas Greenshields (shieldo) - Issei Murasawa (issei_m) + - Douglas Greenshields (shieldo) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) - Graham Campbell (graham) - Daniel Holmes (dholmes) + - David Maicher (dmaicher) - Dariusz Ruminski - Toni Uebernickel (havvg) - Bart van den Burg (burgov) @@ -95,21 +96,20 @@ Symfony is the result of the work of many people who made the code better - Jérôme Tamarelle (gromnan) - John Wards (johnwards) - Fran Moreno (franmomu) + - Vladimir Reznichenko (kalessil) - Antoine Hérault (herzult) - Paráda József (paradajozsef) - - David Maicher (dmaicher) - - Vladimir Reznichenko (kalessil) - Arnaud Le Blanc (arnaud-lb) - Maxime STEINHAUSSER - Michal Piotrowski (eventhorizon) - Tim Nagel (merk) - Brice BERNARD (brikou) - Baptiste Clavié (talus) + - Grégoire Paris (greg0ire) - marc.weistroff - lenar - Alexander Schwenn (xelaris) - Włodzimierz Gajda (gajdaw) - - Grégoire Paris (greg0ire) - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) - Colin Frei @@ -122,17 +122,17 @@ Symfony is the result of the work of many people who made the code better - Fabien Pennequin (fabienpennequin) - Gordon Franke (gimler) - Eric GELOEN (gelo) + - Lars Strojny (lstrojny) - Daniel Wehner (dawehner) - Tugdual Saunier (tucksaun) + - Javier Spagnoletti (phansys) - Théo FIDRY (theofidry) - Robert Schönthal (digitalkaoz) - Florian Lonqueu-Brochard (florianlb) - Sebastiaan Stok (sstok) - Stefano Sala (stefano.sala) - Evgeniy (ewgraf) - - Lars Strojny (lstrojny) - Alex Pott - - Javier Spagnoletti (phansys) - Vincent AUBERT (vincent) - Juti Noppornpitak (shiroyuki) - Tigran Azatyan (tigranazatyan) @@ -148,7 +148,9 @@ Symfony is the result of the work of many people who made the code better - Julien Falque (julienfalque) - Rafael Dohms (rdohms) - Arnaud Kleinpeter (nanocom) + - gadelat (gadelat) - jwdeitch + - Teoh Han Hui (teohhanhui) - Mikael Pajunen - Joel Wurtz (brouznouf) - Valentin Udaltsov (vudaltsov) @@ -158,9 +160,7 @@ Symfony is the result of the work of many people who made the code better - Richard van Laak (rvanlaak) - Richard Shank (iampersistent) - Thomas Rabaix (rande) - - gadelat (gadelat) - Rouven Weßling (realityking) - - Teoh Han Hui (teohhanhui) - Clemens Tolboom - Helmer Aaviksoo - Hiromi Hishida (77web) @@ -186,6 +186,7 @@ Symfony is the result of the work of many people who made the code better - Gabriel Ostrolucký - Mario A. Alvarez Garcia (nomack84) - Dennis Benkert (denderello) + - DQNEO - SpacePossum - Benjamin Dulau (dbenjamin) - Mathieu Lemoine (lemoinem) @@ -223,6 +224,7 @@ Symfony is the result of the work of many people who made the code better - Niels Keurentjes (curry684) - Eugene Wissner - Julien Brochet (mewt) + - Leo Feyer - Tristan Darricau (nicofuma) - Michaël Perrin (michael.perrin) - Marcel Beerta (mazen) @@ -269,7 +271,6 @@ Symfony is the result of the work of many people who made the code better - Ray - Tyson Andre - Nikolay Labinskiy (e-moe) - - Leo Feyer - Chekote - Thomas Adam - Albert Casademont (acasademont) @@ -277,7 +278,6 @@ Symfony is the result of the work of many people who made the code better - Jhonny Lidfors (jhonne) - Diego Agulló (aeoris) - Andreas Schempp (aschempp) - - DQNEO - jdhoek - Pavel Batanov (scaytrase) - Bob den Otter (bopp) @@ -292,6 +292,7 @@ Symfony is the result of the work of many people who made the code better - Roumen Damianoff (roumen) - Antonio J. García Lagar (ajgarlag) - Kim Hemsø Rasmussen (kimhemsoe) + - Florent Mata (fmata) - Wouter Van Hecke - Jérôme Parmentier (lctrs) - Michael Babker (mbabker) @@ -299,6 +300,7 @@ Symfony is the result of the work of many people who made the code better - Michael Holm (hollo) - Marc Weistroff (futurecat) - Christian Schmidt + - Maxime Veber (nek-) - Edi Modrić (emodric) - Chad Sikorra (chadsikorra) - Chris Smith (cs278) @@ -326,6 +328,7 @@ Symfony is the result of the work of many people who made the code better - John Bafford (jbafford) - Adrian Rudnik (kreischweide) - Francesc Rosàs (frosas) + - Romain Pierre (romain-pierre) - Massimiliano Arione (garak) - Julien Galenski (ruian) - Bongiraud Dominique @@ -356,8 +359,8 @@ Symfony is the result of the work of many people who made the code better - Damien Alexandre (damienalexandre) - Felix Labrecque - Yaroslav Kiliba - - Maxime Veber (nek-) - Terje Bråten + - Mathieu Lechat - Robbert Klarenbeek (robbertkl) - JhonnyL - David Badura (davidbadura) @@ -371,13 +374,14 @@ Symfony is the result of the work of many people who made the code better - Philipp Kräutli (pkraeutli) - Kirill chEbba Chebunin (chebba) - Greg Thornton (xdissent) - - Florent Mata (fmata) + - Gary PEGEOT (gary-p) - Costin Bereveanu (schniper) - Loïc Chardonnet (gnusat) - Marek Kalnik (marekkalnik) - Vyacheslav Salakhutdinov (megazoll) - Hassan Amouhzi - Tamas Szijarto + - Michele Locati - Pavel Volokitin (pvolok) - Arthur de Moulins (4rthem) - Nicolas Dewez (nicolas_dewez) @@ -420,9 +424,11 @@ Symfony is the result of the work of many people who made the code better - Jeanmonod David (jeanmonod) - Christopher Davis (chrisguitarguy) - Jan Schumann + - Christian Schmidt - Niklas Fiekas - Markus Bachmann (baachi) - lancergr + - Zan Baldwin - Mihai Stancu - Olivier Dolbeau (odolbeau) - Jan Rosier (rosier) @@ -435,7 +441,9 @@ Symfony is the result of the work of many people who made the code better - Florian Pfitzer (marmelatze) - Asier Illarramendi (doup) - Andreas Braun + - Boris Vujicic (boris.vujicic) - Chris Sedlmayr (catchamonkey) + - Mateusz Sip (mateusz_sip) - Seb Koelen - Christoph Mewes (xrstf) - Vitaliy Tverdokhlib (vitaliytv) @@ -456,6 +464,7 @@ Symfony is the result of the work of many people who made the code better - Adam Harvey - Anton Bakai - Alex Bakhturin + - insekticid - Alexander Obuhovich (aik099) - boombatower - Fabrice Bernhard (fabriceb) @@ -467,6 +476,7 @@ Symfony is the result of the work of many people who made the code better - Roman Lapin (memphys) - Yoshio HANAWA - Gladhon + - Haralan Dobrev (hkdobrev) - Sebastian Bergmann - Miroslav Sustek - Sullivan SENECHAL (soullivaneuh) @@ -499,9 +509,7 @@ Symfony is the result of the work of many people who made the code better - Andrew Udvare (audvare) - alexpods - Arjen van der Meijden - - Michele Locati - Dariusz Ruminski - - Mathieu Lechat - Erik Trapman (eriktrapman) - De Cock Xavier (xdecock) - Almog Baku (almogbaku) @@ -550,7 +558,6 @@ Symfony is the result of the work of many people who made the code better - Disquedur - Michiel Boeckaert (milio) - Geoffrey Tran (geoff) - - Romain Pierre (romain-pierre) - David Prévot - Jan Behrens - Mantas Var (mvar) @@ -564,7 +571,6 @@ Symfony is the result of the work of many people who made the code better - aubx - Marvin Butkereit - Ricky Su (ricky) - - Zan Baldwin - Gildas Quéméner (gquemener) - Charles-Henri Bruyand - Max Rath (drak3) @@ -577,6 +583,7 @@ Symfony is the result of the work of many people who made the code better - Andre Rømcke (andrerom) - Nahuel Cuesta (ncuesta) - Chris Boden (cboden) + - Christophe Villeger (seragan) - Stefan Gehrig (sgehrig) - Hany el-Kerdany - Wang Jingyu @@ -588,14 +595,13 @@ Symfony is the result of the work of many people who made the code better - Javier López (loalf) - Reinier Kip - Geoffrey Brier (geoffrey-brier) - - Boris Vujicic (boris.vujicic) + - Vladimir Tsykun - Dustin Dobervich (dustin10) - dantleech - Anne-Sophie Bachelard (annesophie) - Sebastian Marek (proofek) - Erkhembayar Gantulga (erheme318) - Michal Trojanowski - - Mateusz Sip - David Fuhr - Kamil Kokot (pamil) - Aurimas Niekis (gcds) @@ -603,6 +609,7 @@ Symfony is the result of the work of many people who made the code better - mcfedr (mcfedr) - Rostyslav Kinash - Maciej Malarz (malarzm) + - Pascal Luna (skalpa) - Daisuke Ohata - Vincent Simonin - Alex Bogomazov (alebo) @@ -623,6 +630,7 @@ Symfony is the result of the work of many people who made the code better - Denis Brumann (dbrumann) - Quentin de Longraye (quentinus95) - Chris Heng (gigablah) + - Shaun Simmons (simshaun) - Richard Bradley - Ulumuddin Yunus (joenoez) - Johann Saunier (prophet777) @@ -670,7 +678,6 @@ Symfony is the result of the work of many people who made the code better - twifty - Indra Gunawan (guind) - Peter Ward - - insekticid - Julien DIDIER (juliendidier) - Dominik Ritter (dritter) - Sebastian Grodzicki (sgrodzicki) @@ -765,6 +772,7 @@ Symfony is the result of the work of many people who made the code better - Maksim Kotlyar (makasim) - Neil Ferreira - Nathanael Noblet (gnat) + - Indra Gunawan (indragunawan) - Dmitry Parnas (parnas) - Paul LE CORRE - Emanuele Iannone @@ -876,6 +884,7 @@ Symfony is the result of the work of many people who made the code better - Sander Marechal - Radosław Benkel - jean pasqualini (darkilliant) + - Ross Motley (rossmotley) - ttomor - Mei Gwilym (meigwilym) - Michael H. Arieli (excelwebzone) @@ -895,6 +904,7 @@ Symfony is the result of the work of many people who made the code better - Zachary Tong (polyfractal) - Ashura - Hryhorii Hrebiniuk + - johnstevenson - Dennis Fridrich (dfridrich) - hamza - dantleech @@ -938,6 +948,7 @@ Symfony is the result of the work of many people who made the code better - mlazovla - Max Beutel - Antanas Arvasevicius + - Thomas - Maximilian Berghoff (electricmaxxx) - nacho - Piotr Antosik (antek88) @@ -951,6 +962,7 @@ Symfony is the result of the work of many people who made the code better - Ken Marfilla (marfillaster) - benatespina (benatespina) - Denis Kop + - Jean-Guilhem Rouel (jean-gui) - jfcixmedia - Nikita Konstantinov - Martijn Evers @@ -982,7 +994,6 @@ Symfony is the result of the work of many people who made the code better - Olaf Klischat - orlovv - Peter Smeets (darkspartan) - - Haralan Dobrev (hkdobrev) - Jhonny Lidfors (jhonny) - Julien Bianchi (jubianchi) - Robert Meijers @@ -1001,6 +1012,7 @@ Symfony is the result of the work of many people who made the code better - Jeremy Bush - wizhippo - Viacheslav Sychov + - Helmut Hummel (helhum) - Matt Brunt - Carlos Ortega Huetos - rpg600 @@ -1025,6 +1037,7 @@ Symfony is the result of the work of many people who made the code better - rchoquet - gitlost - Taras Girnyk + - Anthony GRASSIOT (antograssiot) - Eduardo García Sanz (coma) - James Gilliland - fduch (fduch) @@ -1054,6 +1067,7 @@ Symfony is the result of the work of many people who made the code better - Peter Thompson (petert82) - Felicitus - Krzysztof Przybyszewski + - alexpozzi - Paul Matthews - Jakub Kisielewski - Vacheslav Silyutin @@ -1078,6 +1092,7 @@ Symfony is the result of the work of many people who made the code better - Tobias Stöckler - Mario Young - Ilia (aliance) + - Grégoire Penverne (gpenverne) - Mo Di (modi) - Pablo Schläpfer - Jelte Steijaert (jelte) @@ -1098,6 +1113,7 @@ Symfony is the result of the work of many people who made the code better - Lars Ambrosius Wallenborn (larsborn) - Oriol Mangas Abellan (oriolman) - Sebastian Göttschkes (sgoettschkes) + - Sergey (upyx) - Tatsuya Tsuruoka - Ross Tuck - Kévin Gomez (kevin) @@ -1121,6 +1137,7 @@ Symfony is the result of the work of many people who made the code better - Grzegorz Zdanowski (kiler129) - sl_toto (sl_toto) - Walter Dal Mut (wdalmut) + - abluchet - Matthieu - Albin Kerouaton - Sébastien HOUZÉ @@ -1133,9 +1150,11 @@ Symfony is the result of the work of many people who made the code better - Berat Doğan - Guillaume LECERF - Juanmi Rodriguez Cerón + - Sergey Yastrebov - Andy Raines - Anthony Ferrara - Klaas Cuvelier (kcuvelier) + - Mathieu TUDISCO (mathieutu) - markusu49 - Steve Frécinaux - Jules Lamur @@ -1178,6 +1197,7 @@ Symfony is the result of the work of many people who made the code better - Tadcka - Beth Binkovitz - Gonzalo Míguez + - Philipp Cordes - Pierre Rineau - Romain Geissler - Adrien Moiruad @@ -1190,6 +1210,7 @@ Symfony is the result of the work of many people who made the code better - Jay Severson - René Kerner - Nathaniel Catchpole + - - Adrien Samson (adriensamson) - Samuel Gordalina (gordalina) - Max Romanovsky (maxromanovsky) @@ -1226,6 +1247,7 @@ Symfony is the result of the work of many people who made the code better - Ergie Gonzaga - Matthew J Mucklo - AnrDaemon + - Smaine Milianni (ismail1432) - fdgdfg (psampaz) - Stéphane Seng - Maxwell Vandervelde @@ -1237,6 +1259,7 @@ Symfony is the result of the work of many people who made the code better - Simon Neidhold - Valentin VALCIU - Jeremiah VALERIE + - Julien Menth - Kevin Dew - James Cowgill - 1ma (jautenim) @@ -1293,7 +1316,6 @@ Symfony is the result of the work of many people who made the code better - ryunosuke - zenmate - victoria - - Christian Schmidt - Francisco Facioni (fran6co) - Iwan van Staveren (istaveren) - Povilas S. (povilas) @@ -1362,7 +1384,6 @@ Symfony is the result of the work of many people who made the code better - Christopher Parotat - 蝦米 - Grayson Koonce (breerly) - - Indra Gunawan (indragunawan) - Karim Cassam Chenaï (ka) - Michal Kurzeja (mkurzeja) - Nicolas Bastien (nicolas_bastien) @@ -1371,10 +1392,13 @@ Symfony is the result of the work of many people who made the code better - Oleksii Zhurbytskyi - Andy Stanberry - Felix Marezki + - Normunds - Luiz “Felds” Liscia - Thomas Rothe - nietonfir - alefranz + - David Barratt + - Pavel.Batanov - avi123 - alsar - Aarón Nieves Fernández @@ -1446,6 +1470,7 @@ Symfony is the result of the work of many people who made the code better - Edvinas Klovas - Drew Butler - Peter Breuls + - Chansig - Tischoi - J Bruni - Alexey Prilipko @@ -1530,6 +1555,7 @@ Symfony is the result of the work of many people who made the code better - Erik van Wingerden - Valouleloup - Dane Powell + - mweimerskirch - Gerrit Drost - Linnaea Von Lavia - Simon Mönch @@ -1602,6 +1628,7 @@ Symfony is the result of the work of many people who made the code better - Gabriel Moreira - Alexey Popkov - ChS + - Alexis MARQUIS - Joseph Deray - Damian Sromek - Ben @@ -1633,6 +1660,7 @@ Symfony is the result of the work of many people who made the code better - temperatur - Cas - Dusan Kasan + - Karolis - Myke79 - Brian Debuire - Piers Warmers @@ -1643,12 +1671,12 @@ Symfony is the result of the work of many people who made the code better - jc - BenjaminBeck - Aurelijus Rožėnas - - Vladimir Tsykun - Jordan Hoff - znerol - Christian Eikermann - Kai Eichinger - Antonio Angelino + - Pascal Montoya - Matt Fields - Niklas Keller - Vladimir Sazhin @@ -1703,7 +1731,6 @@ Symfony is the result of the work of many people who made the code better - Neophy7e - bokonet - Arrilot - - Shaun Simmons - Markus Staab - Pierre-Louis LAUNAY - djama @@ -1804,6 +1831,7 @@ Symfony is the result of the work of many people who made the code better - Arash Tabriziyan (ghost098) - ibasaw (ibasaw) - Vladislav Krupenkin (ideea) + - Peter Orosz (ill_logical) - Imangazaliev Muhammad (imangazaliev) - j0k (j0k) - joris de wit (jdewit) @@ -1832,6 +1860,7 @@ Symfony is the result of the work of many people who made the code better - emilienbouard (neime) - Nicholas Byfleet (nickbyfleet) - Tomas Norkūnas (norkunas) + - Marco Petersen (ocrampete16) - ollie harridge (ollietb) - Paul Andrieux (paulandrieux) - Paweł Szczepanek (pauluz) @@ -1855,7 +1884,6 @@ Symfony is the result of the work of many people who made the code better - Bruno Ziegler (sfcoder) - Andrea Giuliano (shark) - Schuyler Jager (sjager) - - Pascal Luna (skalpa) - Volker (skydiablo) - Serkan Yildiz (srknyldz) - Julien Sanchez (sumbobyboys) @@ -1886,6 +1914,7 @@ Symfony is the result of the work of many people who made the code better - Ben Scott - Dionysis Arvanitis - Sergey Fedotov + - Konstantin Scheumann - Michael - fh-github@fholzhauer.de - AbdElKader Bouadjadja @@ -1896,6 +1925,7 @@ Symfony is the result of the work of many people who made the code better - Zander Baldwin - Philipp Scheit - max + - Ahmad Mayahi (ahmadmayahi) - Mohamed Karnichi (amiral) - Andrew Carter (andrewcarteruk) - Adam Elsodaney (archfizz) diff --git a/appveyor.yml b/appveyor.yml index a6575052a974b..a901ad79397c2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -45,7 +45,7 @@ install: - copy /Y .composer\* %APPDATA%\Composer\ - php .github/build-packages.php "HEAD^" src\Symfony\Bridge\PhpUnit - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) - - php -dmemory_limit=-1 composer.phar update --no-progress --no-suggest --ansi + - php composer.phar update --no-progress --no-suggest --ansi - php phpunit install test_script: diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php index e1e4e4226982c..3f3a5c8ac40f4 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php @@ -95,10 +95,7 @@ public function getIdValue($object) } if (!$this->om->contains($object)) { - throw new RuntimeException( - 'Entities passed to the choice field must be managed. Maybe '. - 'persist them in the entity manager?' - ); + throw new RuntimeException(sprintf('Entity of type "%s" passed to the choice field must be managed. Maybe you forget to persist it in the entity manager?', get_class($object))); } $this->om->initializeObject($object); diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index aa1d95ecc6bb7..2a41422e00bd0 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -95,9 +95,19 @@ public function getTypes($class, $property, array $context = array()) if (isset($associationMapping['indexBy'])) { $indexProperty = $associationMapping['indexBy']; + /** @var ClassMetadataInfo $subMetadata */ $subMetadata = $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']); $typeOfField = $subMetadata->getTypeOfField($indexProperty); + if (null === $typeOfField) { + $associationMapping = $subMetadata->getAssociationMapping($indexProperty); + + /** @var ClassMetadataInfo $subMetadata */ + $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($indexProperty); + $subMetadata = $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']); + $typeOfField = $subMetadata->getTypeOfField($indexProperty); + } + $collectionKeyType = $this->getPhpType($typeOfField); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index fd6c23279c83d..f212ab25e6b8f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -61,6 +61,7 @@ public function testGetProperties() 'foo', 'bar', 'indexedBar', + 'indexedFoo', ), $this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy') ); @@ -141,6 +142,14 @@ public function typesProvider() new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') ))), + array('indexedFoo', array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Doctrine\Common\Collections\Collection', + true, + new Type(Type::BUILTIN_TYPE_STRING), + new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + ))), array('simpleArray', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))), array('customFoo', null), array('notMapped', null), diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php index 793be8f9aae8b..9517f6cc40d31 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php @@ -14,6 +14,7 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\OneToMany; use Doctrine\ORM\Mapping\ManyToMany; use Doctrine\ORM\Mapping\ManyToOne; @@ -45,6 +46,11 @@ class DoctrineDummy */ protected $indexedBar; + /** + * @OneToMany(targetEntity="DoctrineRelation", mappedBy="foo", indexBy="foo") + */ + protected $indexedFoo; + /** * @Column(type="guid") */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php index 6e94e028faa7d..85660d3d6b66c 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php @@ -14,6 +14,7 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\ManyToOne; /** * @Entity @@ -32,4 +33,10 @@ class DoctrineRelation * @Column(type="guid") */ protected $rguid; + + /** + * @Column(type="guid") + * @ManyToOne(targetEntity="DoctrineDummy", inversedBy="indexedFoo") + */ + protected $foo; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 878e19ccb5cd9..efc611859a2b5 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -630,6 +630,34 @@ public function testEntityManagerNullObject() $this->validator->validate($entity, $constraint); } + public function testValidateUniquenessOnNullResult() + { + $repository = $this->createRepositoryMock(); + $repository + ->method('find') + ->will($this->returnValue(null)) + ; + + $this->em = $this->createEntityManagerMock($repository); + $this->registry = $this->createRegistryMock($this->em); + $this->validator = $this->createValidator(); + $this->validator->initialize($this->context); + + $constraint = new UniqueEntity(array( + 'message' => 'myMessage', + 'fields' => array('name'), + 'em' => self::EM_NAME, + )); + + $entity = new SingleIntIdEntity(1, null); + + $this->em->persist($entity); + $this->em->flush(); + + $this->validator->validate($entity, $constraint); + $this->assertNoViolation(); + } + public function testValidateInheritanceUniqueness() { $constraint = new UniqueEntity(array( diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index e03ef3c555b37..004de6f67e5a1 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -148,15 +148,23 @@ public function validate($entity, Constraint $constraint) */ if ($result instanceof \Iterator) { $result->rewind(); - } elseif (is_array($result)) { + if ($result instanceof \Countable && 1 < \count($result)) { + $result = array($result->current(), $result->current()); + } else { + $result = $result->current(); + $result = null === $result ? array() : array($result); + } + } elseif (\is_array($result)) { reset($result); + } else { + $result = null === $result ? array() : array($result); } /* If no entity matched the query criteria or a single entity matched, * which is the same as the entity being validated, the criteria is * unique. */ - if (0 === count($result) || (1 === count($result) && $entity === ($result instanceof \Iterator ? $result->current() : current($result)))) { + if (!$result || (1 === \count($result) && current($result) === $entity)) { return; } diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 22992da68051e..29b1960798b8c 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -297,17 +297,38 @@ public static function collectDeprecations($outputFile) }); } + /** + * Returns true if STDOUT is defined and supports colorization. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @return bool + */ private static function hasColorSupport() { - if ('\\' === DIRECTORY_SEPARATOR) { - return - defined('STDOUT') && function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(STDOUT) - || '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD + if (!defined('STDOUT')) { + return false; + } + + if (DIRECTORY_SEPARATOR === '\\') { + return (function_exists('sapi_windows_vt100_support') + && sapi_windows_vt100_support(STDOUT)) || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); } - return defined('STDOUT') && function_exists('posix_isatty') && @posix_isatty(STDOUT); + if (function_exists('stream_isatty')) { + return stream_isatty(STDOUT); + } + + if (function_exists('posix_isatty')) { + return posix_isatty(STDOUT); + } + + $stat = fstat(STDOUT); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/Command.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php similarity index 78% rename from src/Symfony/Bridge/PhpUnit/Legacy/Command.php rename to src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php index 0aec8ab67f33e..d4b5ea26d8cd8 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/Command.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php @@ -16,13 +16,13 @@ * * @internal */ -class Command extends \PHPUnit_TextUI_Command +class CommandForV5 extends \PHPUnit_TextUI_Command { /** * {@inheritdoc} */ protected function createRunner() { - return new TestRunner($this->arguments['loader']); + return new TestRunnerForV5($this->arguments['loader']); } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php new file mode 100644 index 0000000000000..fc717ef415cb3 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +use PHPUnit\TextUI\Command as BaseCommand; +use PHPUnit\TextUI\TestRunner as BaseRunner; +use Symfony\Bridge\PhpUnit\TextUI\TestRunner; + +/** + * {@inheritdoc} + * + * @internal + */ +class CommandForV6 extends BaseCommand +{ + /** + * {@inheritdoc} + */ + protected function createRunner(): BaseRunner + { + return new TestRunner($this->arguments['loader']); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunner.php b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV5.php similarity index 95% rename from src/Symfony/Bridge/PhpUnit/Legacy/TestRunner.php rename to src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV5.php index 8a57416bd241f..7897861cf52f7 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunner.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV5.php @@ -16,7 +16,7 @@ * * @internal */ -class TestRunner extends \PHPUnit_TextUI_TestRunner +class TestRunnerForV5 extends \PHPUnit_TextUI_TestRunner { /** * {@inheritdoc} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV6.php new file mode 100644 index 0000000000000..6da7c65448532 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV6.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +use PHPUnit\TextUI\TestRunner as BaseRunner; +use Symfony\Bridge\PhpUnit\SymfonyTestsListener; + +/** + * {@inheritdoc} + * + * @internal + */ +class TestRunnerForV6 extends BaseRunner +{ + /** + * {@inheritdoc} + */ + protected function handleConfiguration(array &$arguments) + { + $listener = new SymfonyTestsListener(); + + parent::handleConfiguration($arguments); + + $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); + + $registeredLocally = false; + + foreach ($arguments['listeners'] as $registeredListener) { + if ($registeredListener instanceof SymfonyTestsListener) { + $registeredListener->globalListenerDisabled(); + $registeredLocally = true; + break; + } + } + + if (!$registeredLocally) { + $arguments['listeners'][] = $listener; + } + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV7.php new file mode 100644 index 0000000000000..a175fb65d7f5a --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV7.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +use PHPUnit\TextUI\TestRunner as BaseRunner; +use Symfony\Bridge\PhpUnit\SymfonyTestsListener; + +/** + * {@inheritdoc} + * + * @internal + */ +class TestRunnerForV7 extends BaseRunner +{ + /** + * {@inheritdoc} + */ + protected function handleConfiguration(array &$arguments): void + { + $listener = new SymfonyTestsListener(); + + parent::handleConfiguration($arguments); + + $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); + + $registeredLocally = false; + + foreach ($arguments['listeners'] as $registeredListener) { + if ($registeredListener instanceof SymfonyTestsListener) { + $registeredListener->globalListenerDisabled(); + $registeredLocally = true; + break; + } + } + + if (!$registeredLocally) { + $arguments['listeners'][] = $listener; + } + } +} diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php index 869a8a8dbe85d..4a26fc7fad278 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php @@ -11,24 +11,14 @@ namespace Symfony\Bridge\PhpUnit\TextUI; -use PHPUnit\TextUI\Command as BaseCommand; - if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\Command', 'Symfony\Bridge\PhpUnit\TextUI\Command'); + class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV5', 'Symfony\Bridge\PhpUnit\TextUI\Command'); } else { - /** - * {@inheritdoc} - * - * @internal - */ - class Command extends BaseCommand + class_alias('Symfony\Bridge\PhpUnit\Legacy\CommandForV6', 'Symfony\Bridge\PhpUnit\TextUI\Command'); +} + +if (false) { + class Command { - /** - * {@inheritdoc} - */ - protected function createRunner() - { - return new TestRunner($this->arguments['loader']); - } } } diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php index 4e1fdca4d54be..cda59209790d5 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php @@ -11,45 +11,16 @@ namespace Symfony\Bridge\PhpUnit\TextUI; -use PHPUnit\TextUI\TestRunner as BaseRunner; -use Symfony\Bridge\PhpUnit\SymfonyTestsListener; - if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunner', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner'); + class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunnerForV5', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner'); +} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { + class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunnerForV6', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner'); } else { - /** - * {@inheritdoc} - * - * @internal - */ - class TestRunner extends BaseRunner - { - /** - * {@inheritdoc} - */ - protected function handleConfiguration(array &$arguments) - { - $listener = new SymfonyTestsListener(); - - $result = parent::handleConfiguration($arguments); - - $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); - - $registeredLocally = false; - - foreach ($arguments['listeners'] as $registeredListener) { - if ($registeredListener instanceof SymfonyTestsListener) { - $registeredListener->globalListenerDisabled(); - $registeredLocally = true; - break; - } - } - - if (!$registeredLocally) { - $arguments['listeners'][] = $listener; - } + class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunnerForV7', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner'); +} - return $result; - } +if (false) { + class TestRunner + { } } diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index a549cd7da4fb7..a824eae8f2ab4 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -72,7 +72,7 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ stream_copy_to_stream($remoteZipStream, fopen("$PHPUNIT_VERSION.zip", 'wb')); } else { @unlink("$PHPUNIT_VERSION.zip"); - passthru("wget https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip"); + passthru("wget -q https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip"); } if (!class_exists('ZipArchive')) { throw new \Exception('simple-phpunit requires the "zip" PHP extension to be installed and enabled in order to uncompress the downloaded PHPUnit packages.'); diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index bbc5e3a32299b..c0982fab00a24 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -181,7 +181,9 @@ public function formatFile($file, $line, $text = null) } } - $text = "$text at line $line"; + if (0 < $line) { + $text .= ' at line '.$line; + } if (false !== $link = $this->getFileLink($file, $line)) { return sprintf('%s', htmlspecialchars($link, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset), $text); diff --git a/src/Symfony/Bridge/Twig/README.md b/src/Symfony/Bridge/Twig/README.md index eb084147c37f8..602f5a54c3dd6 100644 --- a/src/Symfony/Bridge/Twig/README.md +++ b/src/Symfony/Bridge/Twig/README.md @@ -1,7 +1,7 @@ Twig Bridge =========== -Provides integration for [Twig](http://twig.sensiolabs.org/) with various +Provides integration for [Twig](https://twig.symfony.com/) with various Symfony components. Resources diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig index df88a00011883..43c031b933366 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig @@ -106,8 +106,7 @@ {%- endblock dateinterval_widget %} {% block percent_widget -%} -
- {% set valid = true %} +
{{- block('form_widget_simple') -}}
% @@ -267,7 +266,7 @@ {% block form_errors -%} {%- if errors|length > 0 -%} - + {%- for error in errors -%} {{ 'Error'|trans({}, 'validators') }} {{ error.message }} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 936d42a9afde9..921458f5fc2b4 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -23,7 +23,7 @@ "symfony/asset": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", - "symfony/form": "^3.4.7|^4.0.7", + "symfony/form": "^3.4.9|^4.0.9", "symfony/http-foundation": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", "symfony/polyfill-intl-icu": "~1.0", @@ -41,7 +41,7 @@ "symfony/workflow": "~3.4|~4.0" }, "conflict": { - "symfony/form": "<3.4.7|<4.0.7,>=4.0", + "symfony/form": "<3.4.9|<4.0.9,>=4.0", "symfony/console": "<3.4" }, "suggest": { diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php index cd1e4806b71b6..c8a887ca5c733 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplateFinder.php @@ -97,11 +97,10 @@ private function findTemplatesInFolder($dir) private function findTemplatesInBundle(BundleInterface $bundle) { $name = $bundle->getName(); - $templates = array_merge( + $templates = array_unique(array_merge( $this->findTemplatesInFolder($bundle->getPath().'/Resources/views'), $this->findTemplatesInFolder($this->rootDir.'/'.$name.'/views') - ); - $templates = array_unique($templates); + )); foreach ($templates as $i => $template) { $templates[$i] = $template->set('bundle', $name); diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index f25bb71240b9b..11220fe3d53c4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -61,6 +61,9 @@ class FrameworkBundle extends Bundle { public function boot() { + if (!ini_get('xdebug.file_link_format') && !get_cfg_var('xdebug.file_link_format')) { + ini_set('xdebug.file_link_format', $this->container->getParameter('debug.file_link_format')); + } ErrorHandler::register(null, false)->throwAt($this->container->getParameter('debug.error_handler.throw_at'), true); if ($this->container->getParameter('kernel.http_method_override')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index 0622c4196c104..565aef68fd45c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -67,6 +67,15 @@ + + + + null + + %kernel.debug% + %kernel.charset% + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php index fa610e5c06ea2..209c77fa54fc7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/CodeHelper.php @@ -157,7 +157,7 @@ public function formatFile($file, $line, $text = null) $file = trim($file); $fileStr = $file; if (0 === strpos($fileStr, $this->rootDir)) { - $fileStr = str_replace($this->rootDir, '', str_replace('\\', '/', $fileStr)); + $fileStr = str_replace(array('\\', $this->rootDir), array('/', ''), $fileStr); $fileStr = htmlspecialchars($fileStr, $flags, $this->charset); $fileStr = sprintf('kernel.root_dir/%s', htmlspecialchars($this->rootDir, $flags, $this->charset), $fileStr); } diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.txt.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.txt.twig index fb1e49d826eb5..538355cbe17f1 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.txt.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/trace.txt.twig @@ -1,5 +1,5 @@ {% if trace.function %} -at {{ trace.class ~ trace.type ~ trace.function }}({{ trace.args|format_args }}) +at {{ trace.class ~ trace.type ~ trace.function }}({{ trace.args|format_args_as_text }}) {%- endif -%} {% if trace.file|default('') is not empty and trace.line|default('') is not empty %} {{- trace.function ? '\n (' : 'at '}}{{ trace.file|format_file(trace.line)|striptags|replace({ (' at line ' ~ trace.line): '' }) }}:{{ trace.line }}{{ trace.function ? ')' }} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.txt.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.txt.twig index 96ecd44480819..88a2d6922d6c3 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.txt.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces.txt.twig @@ -1,5 +1,4 @@ {% if exception.trace|length %} -
 {{ exception.class }}:
 {% if exception.message is not empty %}
     {{- exception.message }}
@@ -8,5 +7,4 @@
 {% for trace in exception.trace %}
   {{ include('@Twig/Exception/trace.txt.twig', { trace: trace }, with_context = false) }}
 {% endfor %}
-
{% endif %} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces_text.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces_text.html.twig index ffe7f31ad5ec4..318a5bbeeb0c7 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces_text.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/traces_text.html.twig @@ -17,7 +17,13 @@ - {{ include('@Twig/Exception/traces.txt.twig', { exception: exception }, with_context = false) }} + {% if exception.trace|length %} +
+                {%- filter escape('html') -%}
+                    {{- include('@Twig/Exception/traces.txt.twig', { exception: exception, format: 'html' }, with_context = false) }}
+                {% endfilter %}
+                
+ {% endif %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php index 98536397a18ea..a77ba08b51402 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\WebProfilerBundle\Controller; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Symfony\Component\HttpKernel\Profiler\Profiler; use Symfony\Component\Debug\ExceptionHandler; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -30,11 +31,12 @@ class ExceptionController protected $debug; protected $profiler; - public function __construct(Profiler $profiler = null, Environment $twig, bool $debug) + public function __construct(Profiler $profiler = null, Environment $twig, bool $debug, FileLinkFormatter $fileLinkFormat = null) { $this->profiler = $profiler; $this->twig = $twig; $this->debug = $debug; + $this->fileLinkFormat = $fileLinkFormat; } /** @@ -58,7 +60,7 @@ public function showAction($token) $template = $this->getTemplate(); if (!$this->twig->getLoader()->exists($template)) { - $handler = new ExceptionHandler($this->debug, $this->twig->getCharset()); + $handler = new ExceptionHandler($this->debug, $this->twig->getCharset(), $this->fileLinkFormat); return new Response($handler->getContent($exception), 200, array('Content-Type' => 'text/html')); } @@ -98,7 +100,7 @@ public function cssAction($token) $template = $this->getTemplate(); if (!$this->templateExists($template)) { - $handler = new ExceptionHandler($this->debug, $this->twig->getCharset()); + $handler = new ExceptionHandler($this->debug, $this->twig->getCharset(), $this->fileLinkFormat); return new Response($handler->getStylesheet($exception), 200, array('Content-Type' => 'text/css')); } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml index 8858593fc0810..6e8014fb3d8e8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml @@ -26,6 +26,7 @@ %kernel.debug% + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig index 99e12f0f00fb8..6e5b9608b4f43 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig @@ -100,7 +100,7 @@ Resources {% if 'Silex' == collector.applicationname %} - + Read Silex Docs {% else %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.html.twig index 9b482f1f0aa96..58e1fe355621f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.html.twig @@ -8,7 +8,7 @@ {% block body %}
-

{{ file }} line {{ line }}

+

{{ file }}{% if 0 < line %} line {{ line }}{% endif %}

Open in your IDE?
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php index 4069cdd6c9268..43f33dc11ffa9 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php @@ -31,6 +31,7 @@ class WebProfilerExtensionTest extends TestCase public static function assertSaneContainer(Container $container, $message = '', $knownPrivates = array()) { $errors = array(); + $knownPrivates[] = 'debug.file_link_formatter.url_format'; foreach ($container->getServiceIds() as $id) { if (in_array($id, $knownPrivates, true)) { // for BC with 3.4 continue; diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 758d3a8ec2e24..3afc982089018 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -233,7 +233,7 @@ public function commit() if (true === $e || array() === $e) { continue; } - if (is_array($e) || 1 === count($values)) { + if (\is_array($e) || 1 === \count($values)) { foreach (is_array($e) ? $e : array_keys($values) as $id) { $ok = false; $v = $values[$id]; diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php index 910df0fd38ed5..98b0cc24693b4 100644 --- a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php @@ -30,13 +30,13 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn { private $adapters = array(); private $adapterCount; - private $saveUp; + private $syncItem; /** - * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items - * @param int $maxLifetime The max lifetime of items propagated from lower adapters to upper ones + * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items + * @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones */ - public function __construct(array $adapters, int $maxLifetime = 0) + public function __construct(array $adapters, int $defaultLifetime = 0) { if (!$adapters) { throw new InvalidArgumentException('At least one adapter must be specified.'); @@ -55,16 +55,20 @@ public function __construct(array $adapters, int $maxLifetime = 0) } $this->adapterCount = count($this->adapters); - $this->saveUp = \Closure::bind( - function ($adapter, $item) use ($maxLifetime) { - $origDefaultLifetime = $item->defaultLifetime; + $this->syncItem = \Closure::bind( + function ($sourceItem, $item) use ($defaultLifetime) { + $item->value = $sourceItem->value; + $item->expiry = $sourceItem->expiry; + $item->isHit = $sourceItem->isHit; - if (0 < $maxLifetime && ($origDefaultLifetime <= 0 || $maxLifetime < $origDefaultLifetime)) { - $item->defaultLifetime = $maxLifetime; + if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) { + $defaultLifetime = $sourceItem->defaultLifetime; + } + if (0 < $defaultLifetime && ($item->defaultLifetime <= 0 || $defaultLifetime < $item->defaultLifetime)) { + $item->defaultLifetime = $defaultLifetime; } - $adapter->save($item); - $item->defaultLifetime = $origDefaultLifetime; + return $item; }, null, CacheItem::class @@ -76,18 +80,21 @@ function ($adapter, $item) use ($maxLifetime) { */ public function getItem($key) { - $saveUp = $this->saveUp; + $syncItem = $this->syncItem; + $misses = array(); foreach ($this->adapters as $i => $adapter) { $item = $adapter->getItem($key); if ($item->isHit()) { while (0 <= --$i) { - $saveUp($this->adapters[$i], $item); + $this->adapters[$i]->save($syncItem($item, $misses[$i])); } return $item; } + + $misses[$i] = $item; } return $item; @@ -104,6 +111,7 @@ public function getItems(array $keys = array()) private function generateItems($items, $adapterIndex) { $missing = array(); + $misses = array(); $nextAdapterIndex = $adapterIndex + 1; $nextAdapter = isset($this->adapters[$nextAdapterIndex]) ? $this->adapters[$nextAdapterIndex] : null; @@ -112,17 +120,18 @@ private function generateItems($items, $adapterIndex) yield $k => $item; } else { $missing[] = $k; + $misses[$k] = $item; } } if ($missing) { - $saveUp = $this->saveUp; + $syncItem = $this->syncItem; $adapter = $this->adapters[$adapterIndex]; $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex); foreach ($items as $k => $item) { if ($item->isHit()) { - $saveUp($adapter, $item); + $adapter->save($syncItem($item, $misses[$k])); } yield $k => $item; diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index 96a41fe235d93..ca5ef743d2285 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -82,7 +82,7 @@ public static function create($file, CacheItemPoolInterface $fallbackPool) */ public function getItem($key) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } if (null === $this->values) { @@ -97,7 +97,7 @@ public function getItem($key) if ('N;' === $value) { $value = null; - } elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) { + } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { try { $e = null; $value = unserialize($value); @@ -121,7 +121,7 @@ public function getItem($key) public function getItems(array $keys = array()) { foreach ($keys as $key) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } } @@ -137,7 +137,7 @@ public function getItems(array $keys = array()) */ public function hasItem($key) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } if (null === $this->values) { @@ -152,7 +152,7 @@ public function hasItem($key) */ public function deleteItem($key) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } if (null === $this->values) { @@ -171,7 +171,7 @@ public function deleteItems(array $keys) $fallbackKeys = array(); foreach ($keys as $key) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } @@ -235,7 +235,7 @@ private function generateItems(array $keys): \Generator if ('N;' === $value) { yield $key => $f($key, null, true); - } elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) { + } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { try { yield $key => $f($key, unserialize($value), true); } catch (\Error $e) { diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index 72bbc143cedff..67f9477ae3632 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -33,11 +33,14 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R private $getTagsByKey; private $invalidateTags; private $tags; + private $knownTagVersions = array(); + private $knownTagVersionsTtl; - public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null) + public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, $knownTagVersionsTtl = 0.15) { $this->pool = $itemsPool; $this->tags = $tagsPool ?: $itemsPool; + $this->knownTagVersionsTtl = $knownTagVersionsTtl; $this->createCacheItem = \Closure::bind( function ($key, $value, CacheItem $protoItem) { $item = new CacheItem(); @@ -87,8 +90,7 @@ function ($deferred) { ); $this->invalidateTags = \Closure::bind( function (AdapterInterface $tagsAdapter, array $tags) { - foreach ($tagsAdapter->getItems($tags) as $v) { - $v->set(1 + (int) $v->get()); + foreach ($tags as $v) { $v->defaultLifetime = 0; $v->expiry = null; $tagsAdapter->saveDeferred($v); @@ -106,14 +108,42 @@ function (AdapterInterface $tagsAdapter, array $tags) { */ public function invalidateTags(array $tags) { - foreach ($tags as $k => $tag) { - if ('' !== $tag && is_string($tag)) { - $tags[$k] = $tag.static::TAGS_PREFIX; + $ok = true; + $tagsByKey = array(); + $invalidatedTags = array(); + foreach ($tags as $tag) { + CacheItem::validateKey($tag); + $invalidatedTags[$tag] = 0; + } + + if ($this->deferred) { + $items = $this->deferred; + foreach ($items as $key => $item) { + if (!$this->pool->saveDeferred($item)) { + unset($this->deferred[$key]); + $ok = false; + } } + + $f = $this->getTagsByKey; + $tagsByKey = $f($items); + $this->deferred = array(); + } + + $tagVersions = $this->getTagVersions($tagsByKey, $invalidatedTags); + $f = $this->createCacheItem; + + foreach ($tagsByKey as $key => $tags) { + $this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key])); + } + $ok = $this->pool->commit() && $ok; + + if ($invalidatedTags) { + $f = $this->invalidateTags; + $ok = $f($this->tags, $invalidatedTags) && $ok; } - $f = $this->invalidateTags; - return $f($this->tags, $tags); + return $ok; } /** @@ -132,7 +162,7 @@ public function hasItem($key) } foreach ($this->getTagVersions(array($itemTags)) as $tag => $version) { - if ($itemTags[$tag] !== $version) { + if ($itemTags[$tag] !== $version && 1 !== $itemTags[$tag] - $version) { return false; } } @@ -161,7 +191,7 @@ public function getItems(array $keys = array()) $tagKeys = array(); foreach ($keys as $key) { - if ('' !== $key && is_string($key)) { + if ('' !== $key && \is_string($key)) { $key = static::TAGS_PREFIX.$key; $tagKeys[$key] = $key; } @@ -202,7 +232,7 @@ public function deleteItem($key) public function deleteItems(array $keys) { foreach ($keys as $key) { - if ('' !== $key && is_string($key)) { + if ('' !== $key && \is_string($key)) { $keys[] = static::TAGS_PREFIX.$key; } } @@ -241,29 +271,7 @@ public function saveDeferred(CacheItemInterface $item) */ public function commit() { - $ok = true; - - if ($this->deferred) { - $items = $this->deferred; - foreach ($items as $key => $item) { - if (!$this->pool->saveDeferred($item)) { - unset($this->deferred[$key]); - $ok = false; - } - } - - $f = $this->getTagsByKey; - $tagsByKey = $f($items); - $this->deferred = array(); - $tagVersions = $this->getTagVersions($tagsByKey); - $f = $this->createCacheItem; - - foreach ($tagsByKey as $key => $tags) { - $this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key])); - } - } - - return $this->pool->commit() && $ok; + return $this->invalidateTags(array()); } public function __destruct() @@ -294,7 +302,7 @@ private function generateItems($items, array $tagKeys) foreach ($itemTags as $key => $tags) { foreach ($tags as $tag => $version) { - if ($tagVersions[$tag] !== $version) { + if ($tagVersions[$tag] !== $version && 1 !== $version - $tagVersions[$tag]) { unset($itemTags[$key]); continue 2; } @@ -310,23 +318,54 @@ private function generateItems($items, array $tagKeys) } } - private function getTagVersions(array $tagsByKey) + private function getTagVersions(array $tagsByKey, array &$invalidatedTags = array()) { - $tagVersions = array(); + $tagVersions = $invalidatedTags; foreach ($tagsByKey as $tags) { $tagVersions += $tags; } - if ($tagVersions) { - $tags = array(); - foreach ($tagVersions as $tag => $version) { - $tagVersions[$tag] = $tag.static::TAGS_PREFIX; - $tags[$tag.static::TAGS_PREFIX] = $tag; + if (!$tagVersions) { + return array(); + } + + if (!$fetchTagVersions = 1 !== \func_num_args()) { + foreach ($tagsByKey as $tags) { + foreach ($tags as $tag => $version) { + if ($tagVersions[$tag] > $version) { + $tagVersions[$tag] = $version; + } + } + } + } + + $now = microtime(true); + $tags = array(); + foreach ($tagVersions as $tag => $version) { + $tags[$tag.static::TAGS_PREFIX] = $tag; + if ($fetchTagVersions || !isset($this->knownTagVersions[$tag])) { + continue; + } + $version -= $this->knownTagVersions[$tag][1]; + if ((0 !== $version && 1 !== $version) || $this->knownTagVersionsTtl > $now - $this->knownTagVersions[$tag][0]) { + // reuse previously fetched tag versions up to the ttl, unless we are storing items or a potential miss arises + $fetchTagVersions = true; + } else { + $this->knownTagVersions[$tag][1] += $version; } - foreach ($this->tags->getItems($tagVersions) as $tag => $version) { - $tagVersions[$tags[$tag]] = $version->get() ?: 0; + } + + if (!$fetchTagVersions) { + return $tagVersions; + } + + foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) { + $tagVersions[$tag = $tags[$tag]] = $version->get() ?: 0; + if (isset($invalidatedTags[$tag])) { + $invalidatedTags[$tag] = $version->set(++$tagVersions[$tag]); } + $this->knownTagVersions[$tag] = array($now, $tagVersions[$tag]); } return $tagVersions; diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php index 93ffea495e88f..cecaa126d9129 100644 --- a/src/Symfony/Component/Cache/CacheItem.php +++ b/src/Symfony/Component/Cache/CacheItem.php @@ -89,7 +89,7 @@ public function expiresAfter($time) $this->expiry = $this->defaultLifetime > 0 ? time() + $this->defaultLifetime : null; } elseif ($time instanceof \DateInterval) { $this->expiry = (int) \DateTime::createFromFormat('U', time())->add($time)->format('U'); - } elseif (is_int($time)) { + } elseif (\is_int($time)) { $this->expiry = $time + time(); } else { throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', is_object($time) ? get_class($time) : gettype($time))); @@ -109,17 +109,17 @@ public function expiresAfter($time) */ public function tag($tags) { - if (!is_array($tags)) { + if (!\is_array($tags)) { $tags = array($tags); } foreach ($tags as $tag) { - if (!is_string($tag)) { + if (!\is_string($tag)) { throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', is_object($tag) ? get_class($tag) : gettype($tag))); } if (isset($this->tags[$tag])) { continue; } - if (!isset($tag[0])) { + if ('' === $tag) { throw new InvalidArgumentException('Cache tag length must be greater than zero'); } if (false !== strpbrk($tag, '{}()/\@:')) { @@ -152,10 +152,10 @@ public function getPreviousTags() */ public static function validateKey($key) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', is_object($key) ? get_class($key) : gettype($key))); } - if (!isset($key[0])) { + if ('' === $key) { throw new InvalidArgumentException('Cache key length must be greater than zero'); } if (false !== strpbrk($key, '{}()/\@:')) { diff --git a/src/Symfony/Component/Cache/Simple/AbstractCache.php b/src/Symfony/Component/Cache/Simple/AbstractCache.php index 51d2d32ba6ed6..d708496337d38 100644 --- a/src/Symfony/Component/Cache/Simple/AbstractCache.php +++ b/src/Symfony/Component/Cache/Simple/AbstractCache.php @@ -75,7 +75,7 @@ public function getMultiple($keys, $default = null) { if ($keys instanceof \Traversable) { $keys = iterator_to_array($keys, false); - } elseif (!is_array($keys)) { + } elseif (!\is_array($keys)) { throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', is_object($keys) ? get_class($keys) : gettype($keys))); } $ids = array(); @@ -99,13 +99,13 @@ public function getMultiple($keys, $default = null) */ public function setMultiple($values, $ttl = null) { - if (!is_array($values) && !$values instanceof \Traversable) { + if (!\is_array($values) && !$values instanceof \Traversable) { throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', is_object($values) ? get_class($values) : gettype($values))); } $valuesById = array(); foreach ($values as $key => $value) { - if (is_int($key)) { + if (\is_int($key)) { $key = (string) $key; } $valuesById[$this->getId($key)] = $value; @@ -122,7 +122,7 @@ public function setMultiple($values, $ttl = null) return true; } $keys = array(); - foreach (is_array($e) ? $e : array_keys($valuesById) as $id) { + foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) { $keys[] = substr($id, strlen($this->namespace)); } CacheItem::log($this->logger, 'Failed to save values', array('keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null)); @@ -137,7 +137,7 @@ public function deleteMultiple($keys) { if ($keys instanceof \Traversable) { $keys = iterator_to_array($keys, false); - } elseif (!is_array($keys)) { + } elseif (!\is_array($keys)) { throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', is_object($keys) ? get_class($keys) : gettype($keys))); } @@ -152,7 +152,7 @@ private function normalizeTtl($ttl) if ($ttl instanceof \DateInterval) { $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U'); } - if (is_int($ttl)) { + if (\is_int($ttl)) { return 0 < $ttl ? $ttl : false; } diff --git a/src/Symfony/Component/Cache/Simple/ArrayCache.php b/src/Symfony/Component/Cache/Simple/ArrayCache.php index 452b42853ef2c..d1ef583125b8b 100644 --- a/src/Symfony/Component/Cache/Simple/ArrayCache.php +++ b/src/Symfony/Component/Cache/Simple/ArrayCache.php @@ -57,7 +57,7 @@ public function getMultiple($keys, $default = null) { if ($keys instanceof \Traversable) { $keys = iterator_to_array($keys, false); - } elseif (!is_array($keys)) { + } elseif (!\is_array($keys)) { throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', is_object($keys) ? get_class($keys) : gettype($keys))); } foreach ($keys as $key) { @@ -72,7 +72,7 @@ public function getMultiple($keys, $default = null) */ public function deleteMultiple($keys) { - if (!is_array($keys) && !$keys instanceof \Traversable) { + if (!\is_array($keys) && !$keys instanceof \Traversable) { throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', is_object($keys) ? get_class($keys) : gettype($keys))); } foreach ($keys as $key) { @@ -97,13 +97,13 @@ public function set($key, $value, $ttl = null) */ public function setMultiple($values, $ttl = null) { - if (!is_array($values) && !$values instanceof \Traversable) { + if (!\is_array($values) && !$values instanceof \Traversable) { throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', is_object($values) ? get_class($values) : gettype($values))); } $valuesArray = array(); foreach ($values as $key => $value) { - is_int($key) || CacheItem::validateKey($key); + \is_int($key) || CacheItem::validateKey($key); $valuesArray[$key] = $value; } if (false === $ttl = $this->normalizeTtl($ttl)) { @@ -139,7 +139,7 @@ private function normalizeTtl($ttl) if ($ttl instanceof \DateInterval) { $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U'); } - if (is_int($ttl)) { + if (\is_int($ttl)) { return 0 < $ttl ? $ttl : false; } diff --git a/src/Symfony/Component/Cache/Simple/ChainCache.php b/src/Symfony/Component/Cache/Simple/ChainCache.php index 64cca2e91c246..db97fc7de4337 100644 --- a/src/Symfony/Component/Cache/Simple/ChainCache.php +++ b/src/Symfony/Component/Cache/Simple/ChainCache.php @@ -58,7 +58,7 @@ public function __construct(array $caches, int $defaultLifetime = 0) */ public function get($key, $default = null) { - $miss = null !== $default && is_object($default) ? $default : $this->miss; + $miss = null !== $default && \is_object($default) ? $default : $this->miss; foreach ($this->caches as $i => $cache) { $value = $cache->get($key, $miss); @@ -80,7 +80,7 @@ public function get($key, $default = null) */ public function getMultiple($keys, $default = null) { - $miss = null !== $default && is_object($default) ? $default : $this->miss; + $miss = null !== $default && \is_object($default) ? $default : $this->miss; return $this->generateItems($this->caches[0]->getMultiple($keys, $miss), 0, $miss, $default); } diff --git a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php index 0dba1f4895df7..3feb9f0b1a004 100644 --- a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php +++ b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php @@ -61,7 +61,7 @@ public static function create($file, CacheInterface $fallbackPool) */ public function get($key, $default = null) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } if (null === $this->values) { @@ -75,7 +75,7 @@ public function get($key, $default = null) if ('N;' === $value) { $value = null; - } elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) { + } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { try { $e = null; $value = unserialize($value); @@ -97,11 +97,11 @@ public function getMultiple($keys, $default = null) { if ($keys instanceof \Traversable) { $keys = iterator_to_array($keys, false); - } elseif (!is_array($keys)) { + } elseif (!\is_array($keys)) { throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', is_object($keys) ? get_class($keys) : gettype($keys))); } foreach ($keys as $key) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } } @@ -117,7 +117,7 @@ public function getMultiple($keys, $default = null) */ public function has($key) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } if (null === $this->values) { @@ -132,7 +132,7 @@ public function has($key) */ public function delete($key) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } if (null === $this->values) { @@ -147,7 +147,7 @@ public function delete($key) */ public function deleteMultiple($keys) { - if (!is_array($keys) && !$keys instanceof \Traversable) { + if (!\is_array($keys) && !$keys instanceof \Traversable) { throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', is_object($keys) ? get_class($keys) : gettype($keys))); } @@ -155,7 +155,7 @@ public function deleteMultiple($keys) $fallbackKeys = array(); foreach ($keys as $key) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } @@ -181,7 +181,7 @@ public function deleteMultiple($keys) */ public function set($key, $value, $ttl = null) { - if (!is_string($key)) { + if (!\is_string($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } if (null === $this->values) { @@ -196,7 +196,7 @@ public function set($key, $value, $ttl = null) */ public function setMultiple($values, $ttl = null) { - if (!is_array($values) && !$values instanceof \Traversable) { + if (!\is_array($values) && !$values instanceof \Traversable) { throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', is_object($values) ? get_class($values) : gettype($values))); } @@ -204,7 +204,7 @@ public function setMultiple($values, $ttl = null) $fallbackValues = array(); foreach ($values as $key => $value) { - if (!is_string($key) && !is_int($key)) { + if (!\is_string($key) && !\is_int($key)) { throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key))); } @@ -232,7 +232,7 @@ private function generateItems(array $keys, $default) if ('N;' === $value) { yield $key => null; - } elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) { + } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { try { yield $key => unserialize($value); } catch (\Error $e) { diff --git a/src/Symfony/Component/Cache/Simple/Psr6Cache.php b/src/Symfony/Component/Cache/Simple/Psr6Cache.php index 81f14d4a7045c..4a71346d2ce52 100644 --- a/src/Symfony/Component/Cache/Simple/Psr6Cache.php +++ b/src/Symfony/Component/Cache/Simple/Psr6Cache.php @@ -38,7 +38,7 @@ public function __construct(CacheItemPoolInterface $pool) if ($pool instanceof AbstractAdapter) { $this->createCacheItem = \Closure::bind( function ($key, $value, $allowInt = false) { - if ($allowInt && is_int($key)) { + if ($allowInt && \is_int($key)) { $key = (string) $key; } else { CacheItem::validateKey($key); @@ -121,7 +121,7 @@ public function getMultiple($keys, $default = null) { if ($keys instanceof \Traversable) { $keys = iterator_to_array($keys, false); - } elseif (!is_array($keys)) { + } elseif (!\is_array($keys)) { throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', is_object($keys) ? get_class($keys) : gettype($keys))); } @@ -146,7 +146,7 @@ public function getMultiple($keys, $default = null) */ public function setMultiple($values, $ttl = null) { - $valuesIsArray = is_array($values); + $valuesIsArray = \is_array($values); if (!$valuesIsArray && !$values instanceof \Traversable) { throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', is_object($values) ? get_class($values) : gettype($values))); } @@ -166,7 +166,7 @@ public function setMultiple($values, $ttl = null) $items = $this->pool->getItems($items); } else { foreach ($values as $key => $value) { - if (is_int($key)) { + if (\is_int($key)) { $key = (string) $key; } $items[$key] = $this->pool->getItem($key)->set($value); @@ -199,7 +199,7 @@ public function deleteMultiple($keys) { if ($keys instanceof \Traversable) { $keys = iterator_to_array($keys, false); - } elseif (!is_array($keys)) { + } elseif (!\is_array($keys)) { throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', is_object($keys) ? get_class($keys) : gettype($keys))); } diff --git a/src/Symfony/Component/Cache/Simple/TraceableCache.php b/src/Symfony/Component/Cache/Simple/TraceableCache.php index 756403bf1430e..181934eff5012 100644 --- a/src/Symfony/Component/Cache/Simple/TraceableCache.php +++ b/src/Symfony/Component/Cache/Simple/TraceableCache.php @@ -37,7 +37,7 @@ public function __construct(CacheInterface $pool) */ public function get($key, $default = null) { - $miss = null !== $default && is_object($default) ? $default : $this->miss; + $miss = null !== $default && \is_object($default) ? $default : $this->miss; $event = $this->start(__FUNCTION__); try { $value = $this->pool->get($key, $miss); @@ -109,7 +109,7 @@ public function setMultiple($values, $ttl = null) } }; $values = $values(); - } elseif (is_array($values)) { + } elseif (\is_array($values)) { $event->result['keys'] = array_keys($values); } @@ -125,7 +125,7 @@ public function setMultiple($values, $ttl = null) */ public function getMultiple($keys, $default = null) { - $miss = null !== $default && is_object($default) ? $default : $this->miss; + $miss = null !== $default && \is_object($default) ? $default : $this->miss; $event = $this->start(__FUNCTION__); try { $result = $this->pool->getMultiple($keys, $miss); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php index 0e4e07a16d51b..094fa9558bfd5 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAdapterTest.php @@ -71,6 +71,22 @@ public function testInvalidateTags() $this->assertTrue($pool->getItem('foo')->isHit()); } + public function testInvalidateCommits() + { + $pool1 = $this->createCachePool(); + + $foo = $pool1->getItem('foo'); + $foo->tag('tag'); + + $pool1->saveDeferred($foo->set('foo')); + $pool1->invalidateTags(array('tag')); + + $pool2 = $this->createCachePool(); + $foo = $pool2->getItem('foo'); + + $this->assertTrue($foo->isHit()); + } + public function testTagsAreCleanedOnSave() { $pool = $this->createCachePool(); diff --git a/src/Symfony/Component/Cache/Traits/AbstractTrait.php b/src/Symfony/Component/Cache/Traits/AbstractTrait.php index d7af3b559e0ee..92999a2f3c34d 100644 --- a/src/Symfony/Component/Cache/Traits/AbstractTrait.php +++ b/src/Symfony/Component/Cache/Traits/AbstractTrait.php @@ -241,7 +241,7 @@ private function getId($key) if (null === $this->maxIdLength) { return $this->namespace.$this->namespaceVersion.$key; } - if (strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { + if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { $id = $this->namespace.$this->namespaceVersion.substr_replace(base64_encode(hash('sha256', $key, true)), ':', -22); } diff --git a/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php b/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php index ae634d6baa295..e90492b3a14d3 100644 --- a/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php @@ -66,15 +66,15 @@ public function warmUp(array $values) EOF; foreach ($values as $key => $value) { - CacheItem::validateKey(is_int($key) ? (string) $key : $key); + CacheItem::validateKey(\is_int($key) ? (string) $key : $key); - if (null === $value || is_object($value)) { + if (null === $value || \is_object($value)) { try { $value = serialize($value); } catch (\Exception $e) { throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, get_class($value)), 0, $e); } - } elseif (is_array($value)) { + } elseif (\is_array($value)) { try { $serialized = serialize($value); $unserialized = unserialize($serialized); @@ -85,12 +85,12 @@ public function warmUp(array $values) if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) { $value = $serialized; } - } elseif (is_string($value)) { + } elseif (\is_string($value)) { // Serialize strings if they could be confused with serialized objects or arrays if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { $value = serialize($value); } - } elseif (!is_scalar($value)) { + } elseif (!\is_scalar($value)) { throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, gettype($value))); } diff --git a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php index c800e1a1798e5..32bbeb71237e2 100644 --- a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php @@ -96,7 +96,7 @@ protected function doFetch(array $ids) foreach ($values as $id => $value) { if ('N;' === $value) { $values[$id] = null; - } elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) { + } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) { $values[$id] = parent::unserialize($value); } } @@ -122,21 +122,21 @@ protected function doSave(array $values, $lifetime) $allowCompile = 'cli' !== PHP_SAPI || ini_get('opcache.enable_cli'); foreach ($values as $key => $value) { - if (null === $value || is_object($value)) { + if (null === $value || \is_object($value)) { $value = serialize($value); - } elseif (is_array($value)) { + } elseif (\is_array($value)) { $serialized = serialize($value); $unserialized = parent::unserialize($serialized); // Store arrays serialized if they contain any objects or references if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) { $value = $serialized; } - } elseif (is_string($value)) { + } elseif (\is_string($value)) { // Serialize strings if they could be confused with serialized objects or arrays if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { $value = serialize($value); } - } elseif (!is_scalar($value)) { + } elseif (!\is_scalar($value)) { throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, gettype($value))); } diff --git a/src/Symfony/Component/Cache/Traits/ProxyTrait.php b/src/Symfony/Component/Cache/Traits/ProxyTrait.php index 06dba7e59bf7e..d9e085b9ea887 100644 --- a/src/Symfony/Component/Cache/Traits/ProxyTrait.php +++ b/src/Symfony/Component/Cache/Traits/ProxyTrait.php @@ -16,6 +16,8 @@ /** * @author Nicolas Grekas + * + * @internal */ trait ProxyTrait { diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index ac8b5a5fccda2..b8e05d9e417c9 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -240,7 +240,7 @@ protected function doClear($namespace) $cursor = null; do { $keys = $host instanceof \Predis\Client ? $host->scan($cursor, 'MATCH', $namespace.'*', 'COUNT', 1000) : $host->scan($cursor, $namespace.'*', 1000); - if (isset($keys[1]) && is_array($keys[1])) { + if (isset($keys[1]) && \is_array($keys[1])) { $cursor = $keys[0]; $keys = $keys[1]; } diff --git a/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php index 35c87c2207da6..f60323becd88e 100644 --- a/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php @@ -121,7 +121,7 @@ private function getInputOptionData(InputOption $option) { return array( 'name' => '--'.$option->getName(), - 'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '', + 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', 'accept_value' => $option->acceptValue(), 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), diff --git a/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php index 106bff5114992..d52ba55342fa0 100644 --- a/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php @@ -70,7 +70,7 @@ protected function describeInputOption(InputOption $option, array $options = arr { $name = '--'.$option->getName(); if ($option->getShortcut()) { - $name .= '|-'.implode('|-', explode('|', $option->getShortcut())).''; + $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).''; } $this->write( diff --git a/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php index 0d239868c4c99..a2eaca3b18ff2 100644 --- a/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php @@ -218,7 +218,7 @@ private function getInputOptionDocument(InputOption $option): \DOMDocument $pos = strpos($option->getShortcut(), '|'); if (false !== $pos) { $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); - $objectXML->setAttribute('shortcuts', '-'.implode('|-', explode('|', $option->getShortcut()))); + $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut())); } else { $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); } diff --git a/src/Symfony/Component/Console/EventListener/ErrorListener.php b/src/Symfony/Component/Console/EventListener/ErrorListener.php index 3774f9e6666d4..909d6ea3a1ddb 100644 --- a/src/Symfony/Component/Console/EventListener/ErrorListener.php +++ b/src/Symfony/Component/Console/EventListener/ErrorListener.php @@ -40,10 +40,10 @@ public function onConsoleError(ConsoleErrorEvent $event) $error = $event->getError(); if (!$inputString = $this->getInputString($event)) { - return $this->logger->error('An error occurred while using the console. Message: "{message}"', array('error' => $error, 'message' => $error->getMessage())); + return $this->logger->error('An error occurred while using the console. Message: "{message}"', array('exception' => $error, 'message' => $error->getMessage())); } - $this->logger->error('Error thrown while running command "{command}". Message: "{message}"', array('error' => $error, 'command' => $inputString, 'message' => $error->getMessage())); + $this->logger->error('Error thrown while running command "{command}". Message: "{message}"', array('exception' => $error, 'command' => $inputString, 'message' => $error->getMessage())); } public function onConsoleTerminate(ConsoleTerminateEvent $event) diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index dc088a0565ae8..46e1383e786d9 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -261,7 +261,7 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu foreach ($autocomplete as $value) { // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) - if (0 === strpos($value, $ret) && $i !== strlen($value)) { + if (0 === strpos($value, $ret)) { $matches[$numMatches++] = $value; } } diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php index e4c855cae1706..184e369b86a52 100644 --- a/src/Symfony/Component/Console/Input/InputOption.php +++ b/src/Symfony/Component/Console/Input/InputOption.php @@ -195,7 +195,7 @@ public function getDescription() * * @return bool */ - public function equals(InputOption $option) + public function equals(self $option) { return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() diff --git a/src/Symfony/Component/Console/Output/StreamOutput.php b/src/Symfony/Component/Console/Output/StreamOutput.php index 0f3d2aa06a172..476b2fa0dc05c 100644 --- a/src/Symfony/Component/Console/Output/StreamOutput.php +++ b/src/Symfony/Component/Console/Output/StreamOutput.php @@ -83,31 +83,34 @@ protected function doWrite($message, $newline) * * Colorization is disabled if not supported by the stream: * - * - the stream is redirected (eg php file.php >log) - * - Windows without VT100 support, Ansicon, ConEmu, Mintty - * - non tty consoles + * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo + * terminals via named pipes, so we can only check the environment. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler * * @return bool true if the stream supports colorization, false otherwise */ protected function hasColorSupport() { - if (function_exists('stream_isatty') && !@stream_isatty($this->stream)) { - return false; - } if (DIRECTORY_SEPARATOR === '\\') { - if (function_exists('sapi_windows_vt100_support')) { - $vt100Enabled = @sapi_windows_vt100_support($this->stream); - } else { - $vt100Enabled = '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD; - } - - return - $vt100Enabled + return (function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($this->stream)) || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); } - return function_exists('posix_isatty') && @posix_isatty($this->stream); + if (function_exists('stream_isatty')) { + return @stream_isatty($this->stream); + } + + if (function_exists('posix_isatty')) { + return @posix_isatty($this->stream); + } + + $stat = @fstat($this->stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } } diff --git a/src/Symfony/Component/Console/Tests/EventListener/ErrorListenerTest.php b/src/Symfony/Component/Console/Tests/EventListener/ErrorListenerTest.php index 17eaae09084c8..3794a2660ab5f 100644 --- a/src/Symfony/Component/Console/Tests/EventListener/ErrorListenerTest.php +++ b/src/Symfony/Component/Console/Tests/EventListener/ErrorListenerTest.php @@ -34,7 +34,7 @@ public function testOnConsoleError() $logger ->expects($this->once()) ->method('error') - ->with('Error thrown while running command "{command}". Message: "{message}"', array('error' => $error, 'command' => 'test:run --foo=baz buzz', 'message' => 'An error occurred')) + ->with('Error thrown while running command "{command}". Message: "{message}"', array('exception' => $error, 'command' => 'test:run --foo=baz buzz', 'message' => 'An error occurred')) ; $listener = new ErrorListener($logger); @@ -49,7 +49,7 @@ public function testOnConsoleErrorWithNoCommandAndNoInputString() $logger ->expects($this->once()) ->method('error') - ->with('An error occurred while using the console. Message: "{message}"', array('error' => $error, 'message' => 'An error occurred')) + ->with('An error occurred while using the console. Message: "{message}"', array('exception' => $error, 'message' => 'An error occurred')) ; $listener = new ErrorListener($logger); diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 3b8c07d39379b..ea5268cae3d65 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -157,6 +157,29 @@ public function testAskWithAutocompleteWithNonSequentialKeys() $this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } + public function testAskWithAutocompleteWithExactMatch() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + $inputStream = $this->getInputStream("b\n"); + + $possibleChoices = array( + 'a' => 'berlin', + 'b' => 'copenhagen', + 'c' => 'amsterdam', + ); + + $dialog = new QuestionHelper(); + $dialog->setHelperSet(new HelperSet(array(new FormatterHelper()))); + + $question = new ChoiceQuestion('Please select a city', $possibleChoices); + $question->setMaxAttempts(1); + + $this->assertSame('b', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + } + public function testAutocompleteWithTrailingBackslash() { if (!$this->hasSttyAvailable()) { diff --git a/src/Symfony/Component/Console/composer.json b/src/Symfony/Component/Console/composer.json index b11991944435f..371818fc5fd17 100644 --- a/src/Symfony/Component/Console/composer.json +++ b/src/Symfony/Component/Console/composer.json @@ -31,7 +31,7 @@ "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "", - "psr/log": "For using the console logger" + "psr/log-implementation": "For using the console logger" }, "conflict": { "symfony/dependency-injection": "<3.4", diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index a6ae71f8f1091..a67637ea6e655 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -40,7 +40,7 @@ public function __construct(bool $debug = true, string $charset = null, $fileLin { $this->debug = $debug; $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; - $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->fileLinkFormat = $fileLinkFormat; } /** @@ -355,13 +355,29 @@ private function formatClass($class) private function formatPath($path, $line) { $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); - $fmt = $this->fileLinkFormat; + $fmt = $this->fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + + if (!$fmt) { + return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); + } + + if (\is_string($fmt)) { + $i = strpos($f = $fmt, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: strlen($f); + $fmt = array(substr($f, 0, $i)) + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE); + + for ($i = 1; isset($fmt[$i]); ++$i) { + if (0 === strpos($path, $k = $fmt[$i++])) { + $path = substr_replace($path, $fmt[$i], 0, strlen($k)); + break; + } + } - if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $path, '%l' => $line)) : $fmt->format($path, $line)) { - return sprintf('in %s (line %d)', $this->escapeHtml($link), $file, $line); + $link = strtr($fmt[0], array('%f' => $path, '%l' => $line)); + } else { + $link = $fmt->format($path, $line); } - return sprintf('in %s (line %d)', $this->escapeHtml($path), $file, $line); + return sprintf('in %s%s', $this->escapeHtml($link), $file, 0 < $line ? ' line '.$line : ''); } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index a798482656ca6..605d36079b192 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -179,7 +179,10 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a if ($parameter->isOptional()) { continue; } - throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" must have a type-hint or be given a value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method)); + $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false); + $type = $type ? sprintf('is type-hinted "%s"', $type) : 'has no type-hint'; + + throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type)); } // specifically pass the default value diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php index 73ca29d35f424..c82a974360674 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\DependencyInjection\Reference; @@ -88,8 +89,14 @@ protected function processValue($value, $isRoot = false) $calls = $value->getMethodCalls(); - if ($constructor = $this->getConstructor($value, false)) { - $calls[] = array($constructor, $value->getArguments()); + try { + if ($constructor = $this->getConstructor($value, false)) { + $calls[] = array($constructor, $value->getArguments()); + } + } catch (RuntimeException $e) { + $this->container->getDefinition($this->currentId)->addError($e->getMessage()); + + return parent::processValue($value, $isRoot); } foreach ($calls as $i => $call) { diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index f28726c11a55f..d9657ce479d31 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -123,6 +123,20 @@ class ContainerBuilder extends Container implements TaggedContainerInterface private $removedIds = array(); private $alreadyLoading = array(); + private static $internalTypes = array( + 'int' => true, + 'float' => true, + 'string' => true, + 'bool' => true, + 'resource' => true, + 'object' => true, + 'array' => true, + 'null' => true, + 'callable' => true, + 'iterable' => true, + 'mixed' => true, + ); + public function __construct(ParameterBagInterface $parameterBag = null) { parent::__construct($parameterBag); @@ -321,6 +335,11 @@ public function getReflectionClass(?string $class, bool $throw = true): ?\Reflec if (!$class = $this->getParameterBag()->resolveValue($class)) { return null; } + + if (isset(self::$internalTypes[$class])) { + return null; + } + $resource = null; try { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 84fab467cee43..9e51b65eed64d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -373,6 +373,7 @@ public function testSomeSpecificArgumentsAreSet() // args are: A, Foo, Dunglas ->setArguments(array( 1 => new Reference('foo'), + 3 => array('bar'), )); (new ResolveClassPass())->process($container); @@ -384,6 +385,7 @@ public function testSomeSpecificArgumentsAreSet() new TypedReference(A::class, A::class, MultipleArguments::class), new Reference('foo'), new TypedReference(Dunglas::class, Dunglas::class, MultipleArguments::class), + array('bar'), ), $definition->getArguments() ); @@ -391,12 +393,30 @@ public function testSomeSpecificArgumentsAreSet() /** * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException - * @expectedExceptionMessage Cannot autowire service "arg_no_type_hint": argument "$foo" of method "Symfony\Component\DependencyInjection\Tests\Compiler\MultipleArguments::__construct()" must have a type-hint or be given a value explicitly. + * @expectedExceptionMessage Cannot autowire service "arg_no_type_hint": argument "$bar" of method "Symfony\Component\DependencyInjection\Tests\Compiler\MultipleArguments::__construct()" is type-hinted "array", you should configure its value explicitly. */ public function testScalarArgsCannotBeAutowired() { $container = new ContainerBuilder(); + $container->register(A::class); + $container->register(Dunglas::class); + $container->register('arg_no_type_hint', __NAMESPACE__.'\MultipleArguments') + ->setArguments(array(1 => 'foo')) + ->setAutowired(true); + + (new ResolveClassPass())->process($container); + (new AutowirePass())->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException + * @expectedExceptionMessage Cannot autowire service "arg_no_type_hint": argument "$foo" of method "Symfony\Component\DependencyInjection\Tests\Compiler\MultipleArguments::__construct()" has no type-hint, you should configure its value explicitly. + */ + public function testNoTypeArgsCannotBeAutowired() + { + $container = new ContainerBuilder(); + $container->register(A::class); $container->register(Dunglas::class); $container->register('arg_no_type_hint', __NAMESPACE__.'\MultipleArguments') diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 1b719938a9d0f..1f808d1ca9a4b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -880,6 +880,23 @@ public function testGetReflectionClass() $this->assertSame('BarMissingClass', (string) end($resources)); } + public function testGetReflectionClassOnInternalTypes() + { + $container = new ContainerBuilder(); + + $this->assertNull($container->getReflectionClass('int')); + $this->assertNull($container->getReflectionClass('float')); + $this->assertNull($container->getReflectionClass('string')); + $this->assertNull($container->getReflectionClass('bool')); + $this->assertNull($container->getReflectionClass('resource')); + $this->assertNull($container->getReflectionClass('object')); + $this->assertNull($container->getReflectionClass('array')); + $this->assertNull($container->getReflectionClass('null')); + $this->assertNull($container->getReflectionClass('callable')); + $this->assertNull($container->getReflectionClass('iterable')); + $this->assertNull($container->getReflectionClass('mixed')); + } + public function testCompilesClassDefinitionsOfLazyServices() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php index 04c17ee188fa7..77f8f61337b1b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -181,7 +181,7 @@ public function __construct(A $k) } class MultipleArguments { - public function __construct(A $k, $foo, Dunglas $dunglas) + public function __construct(A $k, $foo, Dunglas $dunglas, array $bar) { } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index e536efac37ce3..f4590b29a4bcc 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -319,6 +319,7 @@ public function configureOptions(OptionsResolver $resolver) // See https://github.com/symfony/symfony/pull/5582 'data_class' => null, 'choice_translation_domain' => true, + 'trim' => false, )); $resolver->setNormalizer('placeholder', $placeholderNormalizer); diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap4HorizontalLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap4HorizontalLayoutTest.php index c906c6549ad2f..016792e0edaf6 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap4HorizontalLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap4HorizontalLayoutTest.php @@ -32,7 +32,7 @@ public function testRow() [ ./label[@for="name"] [ - ./span[@class="alert alert-danger"] + ./span[@class="alert alert-danger d-block"] [./span[@class="mb-0 d-block"] [./span[.="[trans]Error[/trans]"]] [./span[.="[trans]Error![/trans]"]] diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php index beabaa21cdb1a..2cadba0ba9e67 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php @@ -32,7 +32,7 @@ public function testRow() [ ./label[@for="name"] [ - ./span[@class="alert alert-danger"] + ./span[@class="alert alert-danger d-block"] [./span[@class="mb-0 d-block"] [./span[.="[trans]Error[/trans]"]] [./span[.="[trans]Error![/trans]"]] @@ -161,7 +161,7 @@ public function testErrors() $this->assertMatchesXpath($html, '/span - [@class="alert alert-danger"] + [@class="alert alert-danger d-block"] [ ./span[@class="mb-0 d-block"] [./span[.="[trans]Error[/trans]"]] diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 5ae3c53689ea6..9cbe941581418 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -1880,4 +1880,176 @@ public function invalidNestedValueTestMatrix() 'multiple, expanded' => array(true, true, array(array())), ); } + + public function testInheritTranslationDomainFromParent() + { + $view = $this->factory + ->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE, null, array( + 'translation_domain' => 'domain', + )) + ->add('child', static::TESTED_TYPE) + ->getForm() + ->createView(); + + $this->assertEquals('domain', $view['child']->vars['translation_domain']); + } + + public function testPassTranslationDomainToView() + { + $view = $this->factory->create(static::TESTED_TYPE, null, array( + 'translation_domain' => 'domain', + )) + ->createView(); + + $this->assertSame('domain', $view->vars['translation_domain']); + } + + public function testPreferOwnTranslationDomain() + { + $view = $this->factory + ->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE, null, array( + 'translation_domain' => 'parent_domain', + )) + ->add('child', static::TESTED_TYPE, array( + 'translation_domain' => 'domain', + )) + ->getForm() + ->createView(); + + $this->assertEquals('domain', $view['child']->vars['translation_domain']); + } + + public function testDefaultTranslationDomain() + { + $view = $this->factory->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE) + ->add('child', static::TESTED_TYPE) + ->getForm() + ->createView(); + + $this->assertNull($view['child']->vars['translation_domain']); + } + + public function testPassMultipartFalseToView() + { + $view = $this->factory->create(static::TESTED_TYPE, null) + ->createView(); + + $this->assertFalse($view->vars['multipart']); + } + + public function testPassLabelToView() + { + $view = $this->factory->createNamed('__test___field', static::TESTED_TYPE, null, array( + 'label' => 'My label', + )) + ->createView(); + + $this->assertSame('My label', $view->vars['label']); + } + + public function testPassIdAndNameToViewWithGrandParent() + { + $builder = $this->factory->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE) + ->add('child', FormTypeTest::TESTED_TYPE); + $builder->get('child')->add('grand_child', static::TESTED_TYPE); + $view = $builder->getForm()->createView(); + + $this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->vars['id']); + $this->assertEquals('grand_child', $view['child']['grand_child']->vars['name']); + $this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->vars['full_name']); + } + + public function testPassIdAndNameToViewWithParent() + { + $view = $this->factory->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE) + ->add('child', static::TESTED_TYPE) + ->getForm() + ->createView(); + + $this->assertEquals('parent_child', $view['child']->vars['id']); + $this->assertEquals('child', $view['child']->vars['name']); + $this->assertEquals('parent[child]', $view['child']->vars['full_name']); + } + + public function testPassDisabledAsOption() + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'disabled' => true, + )); + + $this->assertTrue($form->isDisabled()); + } + + public function testPassIdAndNameToView() + { + $view = $this->factory->createNamed('name', static::TESTED_TYPE, null) + ->createView(); + + $this->assertEquals('name', $view->vars['id']); + $this->assertEquals('name', $view->vars['name']); + $this->assertEquals('name', $view->vars['full_name']); + } + + public function testStripLeadingUnderscoresAndDigitsFromId() + { + $view = $this->factory->createNamed('_09name', static::TESTED_TYPE, null) + ->createView(); + + $this->assertEquals('name', $view->vars['id']); + $this->assertEquals('_09name', $view->vars['name']); + $this->assertEquals('_09name', $view->vars['full_name']); + } + + /** + * @dataProvider provideTrimCases + */ + public function testTrimIsDisabled($multiple, $expanded) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'multiple' => $multiple, + 'expanded' => $expanded, + 'choices' => array( + 'a' => '1', + ), + )); + + $submittedData = ' 1'; + + $form->submit($multiple ? (array) $submittedData : $submittedData); + + // When the choice does not exist the transformation fails + $this->assertFalse($form->isSynchronized()); + $this->assertNull($form->getData()); + } + + /** + * @dataProvider provideTrimCases + */ + public function testSubmitValueWithWhiteSpace($multiple, $expanded) + { + $valueWhitWhiteSpace = '1 '; + + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'multiple' => $multiple, + 'expanded' => $expanded, + 'choices' => array( + 'a' => $valueWhitWhiteSpace, + ), + )); + + $form->submit($multiple ? (array) $valueWhitWhiteSpace : $valueWhitWhiteSpace); + + $this->assertTrue($form->isSynchronized()); + $this->assertSame($multiple ? (array) $valueWhitWhiteSpace : $valueWhitWhiteSpace, $form->getData()); + } + + public function provideTrimCases() + { + return array( + 'Simple' => array(false, false), + 'Multiple' => array(true, false), + 'Simple expanded' => array(false, true), + 'Multiple expanded' => array(true, true), + ); + } } diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json index acbb82aa8fe49..481a1333d1982 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json @@ -21,7 +21,8 @@ "compound", "data_class", "empty_data", - "error_bubbling" + "error_bubbling", + "trim" ] }, "parent": { @@ -43,7 +44,6 @@ "property_path", "required", "translation_domain", - "trim", "upload_max_size_message" ] }, diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt index 6d698a6171f15..d8f6d47f54cb2 100644 --- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt @@ -11,7 +11,7 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice") choice_name data_class attr csrf_message choice_translation_domain empty_data auto_initialize csrf_protection choice_value error_bubbling block_name csrf_token_id - choices by_reference csrf_token_manager + choices trim by_reference csrf_token_manager expanded data group_by disabled multiple inherit_data @@ -24,7 +24,6 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice") property_path required translation_domain - trim upload_max_size_message --------------------------- -------------------- ------------------------- ----------------------- diff --git a/src/Symfony/Component/HttpFoundation/Cookie.php b/src/Symfony/Component/HttpFoundation/Cookie.php index 78b67bb3d5d44..cc9de5adf7fcd 100644 --- a/src/Symfony/Component/HttpFoundation/Cookie.php +++ b/src/Symfony/Component/HttpFoundation/Cookie.php @@ -145,12 +145,12 @@ public function __toString() $str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'='; if ('' === (string) $this->getValue()) { - $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001'; + $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0'; } else { $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue()); if (0 !== $this->getExpiresTime()) { - $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; max-age='.$this->getMaxAge(); + $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge(); } } @@ -224,7 +224,9 @@ public function getExpiresTime() */ public function getMaxAge() { - return 0 !== $this->expire ? $this->expire - time() : 0; + $maxAge = $this->expire - time(); + + return 0 >= $maxAge ? 0 : $maxAge; } /** diff --git a/src/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/src/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php index b00f58c4e8a27..83be44af67d2d 100644 --- a/src/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php +++ b/src/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php @@ -43,7 +43,21 @@ public function __construct(string $cmd = 'file -b --mime %s 2>/dev/null') */ public static function isSupported() { - return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg'); + static $supported = null; + + if (null !== $supported) { + return $supported; + } + + if ('\\' === DIRECTORY_SEPARATOR || !function_exists('passthru') || !function_exists('escapeshellarg')) { + return $supported = false; + } + + ob_start(); + passthru('command -v file', $exitStatus); + $binPath = trim(ob_get_clean()); + + return $supported = 0 === $exitStatus && '' !== $binPath; } /** diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index c733c5e1dd69e..7cc9d91ab40a5 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -21,6 +21,7 @@ class Response const HTTP_CONTINUE = 100; const HTTP_SWITCHING_PROTOCOLS = 101; const HTTP_PROCESSING = 102; // RFC2518 + const HTTP_EARLY_HINTS = 103; // RFC8297 const HTTP_OK = 200; const HTTP_CREATED = 201; const HTTP_ACCEPTED = 202; @@ -323,7 +324,7 @@ public function sendHeaders() } // headers - foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { + foreach ($this->headers->allPreserveCase() as $name => $values) { foreach ($values as $value) { header($name.': '.$value, false, $this->statusCode); } @@ -332,15 +333,6 @@ public function sendHeaders() // status header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); - // cookies - foreach ($this->headers->getCookies() as $cookie) { - if ($cookie->isRaw()) { - setrawcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); - } else { - setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); - } - } - return $this; } @@ -502,13 +494,19 @@ public function getCharset(): ?string } /** - * Returns true if the response is worth caching under any circumstance. + * Returns true if the response may safely be kept in a shared (surrogate) cache. * * Responses marked "private" with an explicit Cache-Control directive are * considered uncacheable. * * Responses with neither a freshness lifetime (Expires, max-age) nor cache - * validator (Last-Modified, ETag) are considered uncacheable. + * validator (Last-Modified, ETag) are considered uncacheable because there is + * no way to tell when or how to remove them from the cache. + * + * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation, + * for example "status codes that are defined as cacheable by default [...] + * can be reused by a cache with heuristic expiration unless otherwise indicated" + * (https://tools.ietf.org/html/rfc7231#section-6.1) * * @final */ diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index 6a1dd634ad7c4..12e32df36afd5 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -346,20 +346,20 @@ public function setOptions(array $options) } $validOptions = array_flip(array( - 'cache_limiter', 'cache_expire', 'cookie_domain', 'cookie_httponly', + 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', 'cookie_lifetime', 'cookie_path', 'cookie_secure', 'gc_divisor', 'gc_maxlifetime', 'gc_probability', 'lazy_write', 'name', 'referer_check', 'serialize_handler', 'use_strict_mode', 'use_cookies', 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', - 'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags', + 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags', 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags', )); foreach ($options as $key => $value) { if (isset($validOptions[$key])) { - ini_set('session.'.$key, $value); + ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value); } } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php b/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php index 264fafa097596..5abb5814080f2 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php @@ -159,13 +159,13 @@ public function testCookieIsCleared() public function testToString() { $cookie = new Cookie('foo', 'bar', $expire = strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true); - $this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie'); + $this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; Max-Age=0; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie'); $cookie = new Cookie('foo', 'bar with white spaces', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true); - $this->assertEquals('foo=bar%20with%20white%20spaces; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() encodes the value of the cookie according to RFC 3986 (white space = %20)'); + $this->assertEquals('foo=bar%20with%20white%20spaces; expires=Fri, 20-May-2011 15:25:52 GMT; Max-Age=0; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() encodes the value of the cookie according to RFC 3986 (white space = %20)'); $cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com'); - $this->assertEquals('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', $expire = time() - 31536001).'; max-age='.($expire - time()).'; path=/admin/; domain=.myfoodomain.com; httponly', (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL'); + $this->assertEquals('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', $expire = time() - 31536001).'; Max-Age=0; path=/admin/; domain=.myfoodomain.com; httponly', (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL'); $cookie = new Cookie('foo', 'bar', 0, '/', ''); $this->assertEquals('foo=bar; path=/; httponly', (string) $cookie); @@ -191,7 +191,7 @@ public function testGetMaxAge() $this->assertEquals($expire - time(), $cookie->getMaxAge()); $cookie = new Cookie('foo', 'bar', $expire = time() - 100); - $this->assertEquals($expire - time(), $cookie->getMaxAge()); + $this->assertEquals(0, $cookie->getMaxAge()); } public function testFromString() diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/common.inc b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/common.inc new file mode 100644 index 0000000000000..ba101d357852d --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/common.inc @@ -0,0 +1,39 @@ +headers->set('Date', 'Sat, 12 Nov 1955 20:04:00 GMT'); + +return $r; diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_max_age.expected b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_max_age.expected new file mode 100644 index 0000000000000..bdb9d023f8f3d --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_max_age.expected @@ -0,0 +1,11 @@ + +Warning: Expiry date cannot have a year greater than 9999 in %scookie_max_age.php on line 10 + +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: no-cache, private + [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT + [3] => Set-Cookie: foo=bar; expires=Sat, 01-Jan-10000 02:46:40 GMT; Max-Age=%d; path=/ +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_max_age.php b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_max_age.php new file mode 100644 index 0000000000000..8775a5cceeb88 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_max_age.php @@ -0,0 +1,10 @@ +headers->setCookie(new Cookie('foo', 'bar', 253402310800, '', null, false, false)); +$r->sendHeaders(); + +setcookie('foo2', 'bar', 253402310800, '/'); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.expected b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.expected new file mode 100644 index 0000000000000..0c097972e78e2 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.expected @@ -0,0 +1,10 @@ + +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: no-cache, private + [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT + [3] => Set-Cookie: ?*():@&+$/%#[]=?*():@&+$/%#[]; path=/ + [4] => Set-Cookie: ?*():@&+$/%#[]=?*():@&+$/%#[]; path=/ +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php new file mode 100644 index 0000000000000..2ca5b59f1a3e5 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php @@ -0,0 +1,12 @@ +headers->setCookie(new Cookie($str, $str, 0, '/', null, false, false, true)); +$r->sendHeaders(); + +setrawcookie($str, $str, 0, '/', null, false, false); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_lax.expected b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_lax.expected new file mode 100644 index 0000000000000..cbde2cbfe13f3 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_lax.expected @@ -0,0 +1,9 @@ + +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: no-cache, private + [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT + [3] => Set-Cookie: CookieSamesiteLaxTest=LaxValue; path=/; httponly; samesite=lax +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_lax.php b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_lax.php new file mode 100644 index 0000000000000..9a476f1d23fab --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_lax.php @@ -0,0 +1,8 @@ +headers->setCookie(new Cookie('CookieSamesiteLaxTest', 'LaxValue', 0, '/', null, false, true, false, Cookie::SAMESITE_LAX)); +$r->sendHeaders(); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_strict.expected b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_strict.expected new file mode 100644 index 0000000000000..adc491fd2bc51 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_strict.expected @@ -0,0 +1,9 @@ + +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: no-cache, private + [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT + [3] => Set-Cookie: CookieSamesiteStrictTest=StrictValue; path=/; httponly; samesite=strict +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_strict.php b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_strict.php new file mode 100644 index 0000000000000..3bcb41f8f059e --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_samesite_strict.php @@ -0,0 +1,8 @@ +headers->setCookie(new Cookie('CookieSamesiteStrictTest', 'StrictValue', 0, '/', null, false, true, false, Cookie::SAMESITE_STRICT)); +$r->sendHeaders(); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_urlencode.expected b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_urlencode.expected new file mode 100644 index 0000000000000..4e9c4c075f5ed --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_urlencode.expected @@ -0,0 +1,10 @@ + +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: no-cache, private + [2] => Date: Sat, 12 Nov 1955 20:04:00 GMT + [3] => Set-Cookie: %3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/ + [4] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/ +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_urlencode.php b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_urlencode.php new file mode 100644 index 0000000000000..05b9af30d58f2 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/cookie_urlencode.php @@ -0,0 +1,12 @@ +headers->setCookie(new Cookie($str, $str, 0, '', null, false, false)); +$r->sendHeaders(); + +setcookie($str, $str, 0, '/'); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/invalid_cookie_name.expected b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/invalid_cookie_name.expected new file mode 100644 index 0000000000000..2b560f0bd5689 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/invalid_cookie_name.expected @@ -0,0 +1,6 @@ +The cookie name "Hello + world" contains invalid characters. +Array +( + [0] => Content-Type: text/plain; charset=utf-8 +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/invalid_cookie_name.php b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/invalid_cookie_name.php new file mode 100644 index 0000000000000..3fe1571845628 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/invalid_cookie_name.php @@ -0,0 +1,11 @@ +headers->setCookie(new Cookie('Hello + world', 'hodor')); +} catch (\InvalidArgumentException $e) { + echo $e->getMessage(); +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php new file mode 100644 index 0000000000000..22f25e978e5a2 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; + +/** + * @requires PHP 7.0 + */ +class ResponseFunctionalTest extends TestCase +{ + private static $server; + + public static function setUpBeforeClass() + { + $spec = array( + 1 => array('file', '/dev/null', 'w'), + 2 => array('file', '/dev/null', 'w'), + ); + if (!self::$server = @proc_open('exec php -S localhost:8054', $spec, $pipes, __DIR__.'/Fixtures/response-functional')) { + self::markTestSkipped('PHP server unable to start.'); + } + sleep(1); + } + + public static function tearDownAfterClass() + { + if (self::$server) { + proc_terminate(self::$server); + proc_close(self::$server); + } + } + + /** + * @dataProvider provideCookie + */ + public function testCookie($fixture) + { + $result = file_get_contents(sprintf('http://localhost:8054/%s.php', $fixture)); + $this->assertStringMatchesFormatFile(__DIR__.sprintf('/Fixtures/response-functional/%s.expected', $fixture), $result); + } + + public function provideCookie() + { + foreach (glob(__DIR__.'/Fixtures/response-functional/*.php') as $file) { + yield array(pathinfo($file, PATHINFO_FILENAME)); + } + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php index ce8553590dcdb..7b5e720afdb61 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php @@ -116,7 +116,7 @@ public function testToStringIncludesCookieHeaders() $bag->clearCookie('foo'); - $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; httponly', $bag); + $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; httponly', $bag); } public function testClearCookieSecureNotHttpOnly() @@ -125,7 +125,7 @@ public function testClearCookieSecureNotHttpOnly() $bag->clearCookie('foo', '/', null, true, false); - $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; secure', $bag); + $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; secure', $bag); } public function testReplace() diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php index 8fb8b4222835b..382707b0c399e 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php @@ -182,6 +182,23 @@ public function testCookieOptions() $this->assertEquals($options, $gco); } + public function testSessionOptions() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM is not handled in this test case.'); + } + + $options = array( + 'url_rewriter.tags' => 'a=href', + 'cache_expire' => '200', + ); + + $this->getStorage($options); + + $this->assertSame('a=href', ini_get('url_rewriter.tags')); + $this->assertSame('200', ini_get('session.cache_expire')); + } + /** * @expectedException \InvalidArgumentException */ diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/ServiceValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/ServiceValueResolver.php index c55564c0467ef..7bc195f233114 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/ServiceValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/ServiceValueResolver.php @@ -39,9 +39,15 @@ public function supports(Request $request, ArgumentMetadata $argument) if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { $controller = $controller[0].'::'.$controller[1]; + } elseif (!\is_string($controller) || '' === $controller) { + return false; } - return \is_string($controller) && $this->container->has($controller) && $this->container->get($controller)->has($argument->getName()); + if ('\\' === $controller[0]) { + $controller = ltrim($controller, '\\'); + } + + return $this->container->has($controller) && $this->container->get($controller)->has($argument->getName()); } /** @@ -53,6 +59,10 @@ public function resolve(Request $request, ArgumentMetadata $argument) $controller = $controller[0].'::'.$controller[1]; } + if ('\\' === $controller[0]) { + $controller = ltrim($controller, '\\'); + } + yield $this->container->get($controller)->get($argument->getName()); } } diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php index 0a153dd943297..82061fd6ea0fc 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php @@ -69,7 +69,7 @@ public function onKernelResponse(FilterResponseEvent $event) $session->save(); } - if ($session instanceof Session ? !$session->isEmpty() || $session->getId() !== $this->sessionId : $wasStarted) { + if ($session instanceof Session ? !$session->isEmpty() || (null !== $this->sessionId && $session->getId() !== $this->sessionId) : $wasStarted) { $params = session_get_cookie_params(); $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'])); $this->sessionId = $session->getId(); diff --git a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php index f18e42c7d3693..3dfa4cd8ea79a 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php @@ -12,9 +12,11 @@ namespace Symfony\Component\HttpKernel\EventListener; use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\ExceptionHandler; use Symfony\Component\Debug\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; @@ -33,12 +35,14 @@ class ExceptionListener implements EventSubscriberInterface protected $controller; protected $logger; protected $debug; + private $charset; - public function __construct($controller, LoggerInterface $logger = null, $debug = false) + public function __construct($controller, LoggerInterface $logger = null, $debug = false, $charset = null) { $this->controller = $controller; $this->logger = $logger; $this->debug = $debug; + $this->charset = $charset; } public function onKernelException(GetResponseForExceptionEvent $event) @@ -117,8 +121,12 @@ protected function logException(\Exception $exception, $message) protected function duplicateRequest(\Exception $exception, Request $request) { $attributes = array( - '_controller' => $this->controller, - 'exception' => FlattenException::create($exception), + 'exception' => $exception = FlattenException::create($exception), + '_controller' => $this->controller ?: function () use ($exception) { + $handler = new ExceptionHandler($this->debug, $this->charset); + + return new Response($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders()); + }, 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, ); $request = $request->duplicate(null, null, $attributes); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php index 027b2b1761334..672cc893feb6d 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php @@ -72,7 +72,7 @@ public function update(Response $response) $response->setLastModified(null); } - if (!$response->isFresh()) { + if (!$response->isFresh() || !$response->isCacheable()) { $this->cacheable = false; } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index db80323c206d9..24cce648ef443 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -63,11 +63,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '4.0.8'; - const VERSION_ID = 40008; + const VERSION = '4.0.9'; + const VERSION_ID = 40009; const MAJOR_VERSION = 4; const MINOR_VERSION = 0; - const RELEASE_VERSION = 8; + const RELEASE_VERSION = 9; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '07/2018'; @@ -540,9 +540,11 @@ protected function initializeContainer() // Because concurrent requests might still be using them, // old container files are not removed immediately, // but on a next dump of the container. + static $legacyContainers = array(); $oldContainerDir = dirname($oldContainer->getFileName()); - foreach (glob(dirname($oldContainerDir).'/*.legacy') as $legacyContainer) { - if ($oldContainerDir.'.legacy' !== $legacyContainer && @unlink($legacyContainer)) { + $legacyContainers[$oldContainerDir.'.legacy'] = true; + foreach (glob(dirname($oldContainerDir).DIRECTORY_SEPARATOR.'*.legacy') as $legacyContainer) { + if (!isset($legacyContainers[$legacyContainer]) && @unlink($legacyContainer)) { (new Filesystem())->remove(substr($legacyContainer, 0, -7)); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/ClientTest.php b/src/Symfony/Component/HttpKernel/Tests/ClientTest.php index 051d5d47c0265..b774d8ec7ae1c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/ClientTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/ClientTest.php @@ -60,22 +60,17 @@ public function testFilterResponseConvertsCookies() $m = $r->getMethod('filterResponse'); $m->setAccessible(true); - $expected = array( - 'foo=bar; expires=Sun, 15-Feb-2009 20:00:00 GMT; max-age='.(strtotime('Sun, 15-Feb-2009 20:00:00 GMT') - time()).'; path=/foo; domain=http://example.com; secure; httponly', - 'foo1=bar1; expires=Sun, 15-Feb-2009 20:00:00 GMT; max-age='.(strtotime('Sun, 15-Feb-2009 20:00:00 GMT') - time()).'; path=/foo; domain=http://example.com; secure; httponly', - ); - $response = new Response(); - $response->headers->setCookie(new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $response->headers->setCookie($cookie1 = new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); $domResponse = $m->invoke($client, $response); - $this->assertEquals($expected[0], $domResponse->getHeader('Set-Cookie')); + $this->assertSame((string) $cookie1, $domResponse->getHeader('Set-Cookie')); $response = new Response(); - $response->headers->setCookie(new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); - $response->headers->setCookie(new Cookie('foo1', 'bar1', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $response->headers->setCookie($cookie1 = new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $response->headers->setCookie($cookie2 = new Cookie('foo1', 'bar1', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); $domResponse = $m->invoke($client, $response); - $this->assertEquals($expected[0], $domResponse->getHeader('Set-Cookie')); - $this->assertEquals($expected, $domResponse->getHeader('Set-Cookie', false)); + $this->assertSame((string) $cookie1, $domResponse->getHeader('Set-Cookie')); + $this->assertSame(array((string) $cookie1, (string) $cookie2), $domResponse->getHeader('Set-Cookie', false)); } public function testFilterResponseSupportsStreamedResponses() diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php index b05828f5bf6d2..7d34172ce3d8f 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php @@ -47,6 +47,25 @@ public function testExistingController() $this->assertYieldEquals(array(new DummyService()), $resolver->resolve($request, $argument)); } + public function testExistingControllerWithATrailingBackSlash() + { + $resolver = new ServiceValueResolver(new ServiceLocator(array( + 'App\\Controller\\Mine::method' => function () { + return new ServiceLocator(array( + 'dummy' => function () { + return new DummyService(); + }, + )); + }, + ))); + + $request = $this->requestWithAttributes(array('_controller' => '\\App\\Controller\\Mine::method')); + $argument = new ArgumentMetadata('dummy', DummyService::class, false, false, null); + + $this->assertTrue($resolver->supports($request, $argument)); + $this->assertYieldEquals(array(new DummyService()), $resolver->resolve($request, $argument)); + } + public function testControllerNameIsAnArray() { $resolver = new ServiceValueResolver(new ServiceLocator(array( diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php index 2e8007349a971..d809331cb0998 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php @@ -67,7 +67,7 @@ public function testCollectDefault() ob_start(); $collector->collect(new Request(), new Response()); - $output = ob_get_clean(); + $output = preg_replace("/\033\[[^m]*m/", '', ob_get_clean()); $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n123\n", $output); $this->assertSame(1, $collector->getDumpsCount()); @@ -111,7 +111,8 @@ public function testFlush() ob_start(); $collector->__destruct(); - $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n456\n", ob_get_clean()); + $output = preg_replace("/\033\[[^m]*m/", '', ob_get_clean()); + $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n456\n", $output); } public function testFlushNothingWhenDataDumperIsProvided() @@ -123,10 +124,11 @@ public function testFlushNothingWhenDataDumperIsProvided() ob_start(); $collector->dump($data); $line = __LINE__ - 1; + $output = preg_replace("/\033\[[^m]*m/", '', ob_get_clean()); if (\PHP_VERSION_ID >= 50400) { - $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n456\n", ob_get_clean()); + $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n456\n", $output); } else { - $this->assertSame("\"DumpDataCollectorTest.php on line {$line}:\"\n456\n", ob_get_clean()); + $this->assertSame("\"DumpDataCollectorTest.php on line {$line}:\"\n456\n", $output); } ob_start(); diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php index 3cb0b298bb07a..b607bf900ae91 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php @@ -151,6 +151,23 @@ public function testCSPHeaderIsRemoved() $this->assertFalse($response->headers->has('content-security-policy'), 'CSP header has been removed'); $this->assertFalse($dispatcher->hasListeners(KernelEvents::RESPONSE), 'CSP removal listener has been removed'); } + + public function testNullController() + { + $listener = new ExceptionListener(null); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel->expects($this->once())->method('handle')->will($this->returnCallback(function (Request $request) { + $controller = $request->attributes->get('_controller'); + + return $controller(); + })); + $request = Request::create('/'); + $event = new GetResponseForExceptionEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, new \Exception('foo')); + + $listener->onKernelException($event); + + $this->assertContains('Whoops, looks like something went wrong.', $event->getResponse()->getContent()); + } } class TestLogger extends Logger implements DebugLoggerInterface diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php index 0a2263d5a8e78..22a2b71239874 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; -use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -46,6 +45,9 @@ protected function setUp() { $this->listener = $this->getMockForAbstractClass('Symfony\Component\HttpKernel\EventListener\AbstractTestSessionListener'); $this->session = $this->getSession(); + $this->listener->expects($this->any()) + ->method('getSession') + ->will($this->returnValue($this->session)); } public function testShouldSaveMasterRequestSession() @@ -95,7 +97,7 @@ public function testEmptySessionWithNewSessionIdDoesSendCookie() $this->fixSessionId('456'); $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); - $request = Request::create('/', 'GET', array(), array(new Cookie('MOCKSESSID', '123'))); + $request = Request::create('/', 'GET', array(), array('MOCKSESSID' => '123')); $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); $this->listener->onKernelRequest($event); diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php index 5e4c322223eb3..6d67a177398c2 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php @@ -175,8 +175,26 @@ public function testEmbeddingPrivateResponseMakesMainResponsePrivate() $cacheStrategy->update($masterResponse); $this->assertTrue($masterResponse->headers->hasCacheControlDirective('private')); - // Not sure if we should pass "max-age: 60" in this case, as long as the response is private and - // that's the more conservative of both the master and embedded response...? + $this->assertFalse($masterResponse->headers->hasCacheControlDirective('public')); + } + + public function testEmbeddingPublicResponseDoesNotMakeMainResponsePublic() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setPrivate(); // this is the default, but let's be explicit + $masterResponse->setMaxAge(100); + + $embeddedResponse = new Response(); + $embeddedResponse->setPublic(); + $embeddedResponse->setSharedMaxAge(100); + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('private')); + $this->assertFalse($masterResponse->headers->hasCacheControlDirective('public')); } public function testResponseIsExiprableWhenEmbeddedResponseCombinesExpiryAndValidation() diff --git a/src/Symfony/Component/Lock/Store/FlockStore.php b/src/Symfony/Component/Lock/Store/FlockStore.php index bc03b859d00f3..64438fd461b2e 100644 --- a/src/Symfony/Component/Lock/Store/FlockStore.php +++ b/src/Symfony/Component/Lock/Store/FlockStore.php @@ -34,7 +34,7 @@ class FlockStore implements StoreInterface /** * @param string|null $lockPath the directory to store the lock, defaults to the system's temporary directory * - * @throws LockStorageException If the lock directory could not be created or is not writable + * @throws LockStorageException If the lock directory doesn’t exist or is not writable */ public function __construct(string $lockPath = null) { diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php index ae90a1e5e2fe1..f11569f162f66 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php @@ -144,7 +144,7 @@ public function getTypes($class, $property, array $context = array()) return; } - if (!in_array($prefix, $this->arrayMutatorPrefixes)) { + if (!\in_array($prefix, $this->arrayMutatorPrefixes)) { return $types; } diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index 74f9303cbc990..ca26c69c9c2e0 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -155,7 +155,7 @@ private function extractFromMutator(string $class, string $property): ?array } $type = $this->extractFromReflectionType($reflectionType); - if (in_array($prefix, $this->arrayMutatorPrefixes)) { + if (\in_array($prefix, $this->arrayMutatorPrefixes)) { $type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type); } @@ -178,7 +178,7 @@ private function extractFromAccessor(string $class, string $property): ?array return array($this->extractFromReflectionType($reflectionType)); } - if (in_array($prefix, array('is', 'can'))) { + if (\in_array($prefix, array('is', 'can'))) { return array(new Type(Type::BUILTIN_TYPE_BOOL)); } diff --git a/src/Symfony/Component/PropertyInfo/PropertyDescriptionExtractorInterface.php b/src/Symfony/Component/PropertyInfo/PropertyDescriptionExtractorInterface.php index a2e98d0febb29..3f400a8fa53ea 100644 --- a/src/Symfony/Component/PropertyInfo/PropertyDescriptionExtractorInterface.php +++ b/src/Symfony/Component/PropertyInfo/PropertyDescriptionExtractorInterface.php @@ -12,7 +12,7 @@ namespace Symfony\Component\PropertyInfo; /** - * Description extractor Interface. + * Guesses the property's human readable description. * * @author Kévin Dunglas */ diff --git a/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php b/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php index 5902d38621229..4baa7440f995f 100644 --- a/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php +++ b/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php @@ -95,7 +95,7 @@ public function isWritable($class, $property, array $context = array()) private function extract(iterable $extractors, string $method, array $arguments) { foreach ($extractors as $extractor) { - $value = call_user_func_array(array($extractor, $method), $arguments); + $value = \call_user_func_array(array($extractor, $method), $arguments); if (null !== $value) { return $value; } diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php index 2fe6fb596e42a..fd1d2b655ab29 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php @@ -119,10 +119,15 @@ public function load($class, $type = null) } } - if (0 === $collection->count() && $class->hasMethod('__invoke') && $annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { - $globals['path'] = ''; - $globals['name'] = ''; - $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); + if (0 === $collection->count() && $class->hasMethod('__invoke')) { + foreach ($this->reader->getClassAnnotations($class) as $annot) { + if ($annot instanceof $this->routeAnnotationClass) { + $globals['path'] = ''; + $globals['name'] = ''; + + $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); + } + } } return $collection; diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php index 70db1ccd9ad6a..32e401294ec81 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php @@ -191,9 +191,9 @@ public function testInvokableClassRouteLoad() ); $this->reader - ->expects($this->exactly(2)) - ->method('getClassAnnotation') - ->will($this->returnValue($this->getAnnotatedRoute($classRouteData))) + ->expects($this->exactly(1)) + ->method('getClassAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($classRouteData)))) ; $this->reader ->expects($this->once()) @@ -205,8 +205,49 @@ public function testInvokableClassRouteLoad() $route = $routeCollection->get($classRouteData['name']); $this->assertSame($classRouteData['path'], $route->getPath(), '->load preserves class route path'); - $this->assertEquals(array_merge($classRouteData['schemes'], $classRouteData['schemes']), $route->getSchemes(), '->load preserves class route schemes'); - $this->assertEquals(array_merge($classRouteData['methods'], $classRouteData['methods']), $route->getMethods(), '->load preserves class route methods'); + $this->assertEquals($classRouteData['schemes'], $route->getSchemes(), '->load preserves class route schemes'); + $this->assertEquals($classRouteData['methods'], $route->getMethods(), '->load preserves class route methods'); + } + + public function testInvokableClassMultipleRouteLoad() + { + $classRouteData1 = array( + 'name' => 'route1', + 'path' => '/1', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $classRouteData2 = array( + 'name' => 'route2', + 'path' => '/2', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $this->reader + ->expects($this->exactly(1)) + ->method('getClassAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($classRouteData1), $this->getAnnotatedRoute($classRouteData2)))) + ; + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass'); + $route = $routeCollection->get($classRouteData1['name']); + + $this->assertSame($classRouteData1['path'], $route->getPath(), '->load preserves class route path'); + $this->assertEquals($classRouteData1['schemes'], $route->getSchemes(), '->load preserves class route schemes'); + $this->assertEquals($classRouteData1['methods'], $route->getMethods(), '->load preserves class route methods'); + + $route = $routeCollection->get($classRouteData2['name']); + + $this->assertSame($classRouteData2['path'], $route->getPath(), '->load preserves class route path'); + $this->assertEquals($classRouteData2['schemes'], $route->getSchemes(), '->load preserves class route schemes'); + $this->assertEquals($classRouteData2['methods'], $route->getMethods(), '->load preserves class route methods'); } public function testInvokableClassWithMethodRouteLoad() diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php index 1e8ee394015e1..8a6668e0c2beb 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php @@ -29,7 +29,7 @@ protected function setUp() public function testLoad() { - $this->reader->expects($this->exactly(4))->method('getClassAnnotation'); + $this->reader->expects($this->exactly(3))->method('getClassAnnotation'); $this->reader ->expects($this->any()) @@ -37,6 +37,12 @@ public function testLoad() ->will($this->returnValue(array())) ; + $this->reader + ->expects($this->any()) + ->method('getClassAnnotations') + ->will($this->returnValue(array())) + ; + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses'); } @@ -45,7 +51,6 @@ public function testLoadIgnoresHiddenDirectories() $this->expectAnnotationsToBeReadFrom(array( 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass', - 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass', 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\FooClass', )); @@ -55,6 +60,12 @@ public function testLoadIgnoresHiddenDirectories() ->will($this->returnValue(array())) ; + $this->reader + ->expects($this->any()) + ->method('getClassAnnotations') + ->will($this->returnValue(array())) + ; + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses'); } diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/SimpleAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/SimpleAuthenticationProvider.php index e27ad368ab8c2..f5b1d05eda994 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/SimpleAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/SimpleAuthenticationProvider.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Security\Core\Authentication\Provider; -use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\User\UserChecker; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -50,20 +48,7 @@ public function authenticate(TokenInterface $token) $user = $authToken->getUser(); if (!$user instanceof UserInterface) { - try { - $user = $this->userProvider->loadUserByUsername($user); - - if (!$user instanceof UserInterface) { - throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); - } - } catch (UsernameNotFoundException $e) { - $e->setUsername($user); - throw $e; - } catch (\Exception $e) { - $e = new AuthenticationServiceException($e->getMessage(), 0, $e); - $e->setToken($token); - throw $e; - } + return $authToken; } $this->userChecker->checkPreAuth($user); diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/SimpleAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/SimpleAuthenticationProviderTest.php index acee33b856b42..3694d996feda6 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/SimpleAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/SimpleAuthenticationProviderTest.php @@ -12,10 +12,10 @@ namespace Symfony\Component\Security\Core\Tests\Authentication\Provider; use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Exception\DisabledException; use Symfony\Component\Security\Core\Authentication\Provider\SimpleAuthenticationProvider; +use Symfony\Component\Security\Core\Exception\DisabledException; use Symfony\Component\Security\Core\Exception\LockedException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\User\UserChecker; class SimpleAuthenticationProviderTest extends TestCase { @@ -73,52 +73,18 @@ public function testAuthenticateWhenPostChecksFails() $provider->authenticate($token); } - public function testAuthenticateFromString() + public function testAuthenticateSkipsUserChecksForNonUserInterfaceObjects() { - $user = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock(); - $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); $token->expects($this->any()) ->method('getUser') - ->will($this->returnValue('foo')); - + ->will($this->returnValue('string-user')); $authenticator = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\SimpleAuthenticatorInterface')->getMock(); $authenticator->expects($this->once()) ->method('authenticateToken') ->will($this->returnValue($token)); - $userProvider = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserProviderInterface')->getMock(); - $userProvider->expects($this->once()) - ->method('loadUserByUsername') - ->willReturn($this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock()); - $provider = $this->getProvider($authenticator, $userProvider); - - $this->assertSame($token, $provider->authenticate($token)); - } - - /** - * @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException - */ - public function testUsernameNotFound() - { - $user = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock(); - - $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); - $token->expects($this->any()) - ->method('getUser') - ->will($this->returnValue('foo')); - - $authenticator = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\SimpleAuthenticatorInterface')->getMock(); - $authenticator->expects($this->once()) - ->method('authenticateToken') - ->will($this->returnValue($token)); - - $userProvider = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserProviderInterface')->getMock(); - $userProvider->expects($this->once()) - ->method('loadUserByUsername') - ->willThrowException(new UsernameNotFoundException()); - - $this->getProvider($authenticator, $userProvider)->authenticate($token); + $this->assertSame($token, $this->getProvider($authenticator, null, new UserChecker())->authenticate($token)); } protected function getProvider($simpleAuthenticator = null, $userProvider = null, $userChecker = null, $key = 'test') diff --git a/src/Symfony/Component/Security/Core/composer.json b/src/Symfony/Component/Security/Core/composer.json index 76e91a98c34c7..5e382cca0b707 100644 --- a/src/Symfony/Component/Security/Core/composer.json +++ b/src/Symfony/Component/Security/Core/composer.json @@ -28,7 +28,7 @@ "psr/log": "~1.0" }, "suggest": { - "psr/container": "To instantiate the Security class", + "psr/container-implementation": "To instantiate the Security class", "symfony/event-dispatcher": "", "symfony/http-foundation": "", "symfony/validator": "For using the user password constraint", diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 4346754cbc55a..0af68b16a56a9 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -18,7 +18,6 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; @@ -97,11 +96,6 @@ public function authenticateUserAndHandleSuccess(UserInterface $user, Request $r */ public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $guardAuthenticator, string $providerKey): ?Response { - $token = $this->tokenStorage->getToken(); - if ($token instanceof PostAuthenticationGuardToken && $providerKey === $token->getProviderKey()) { - $this->tokenStorage->setToken(null); - } - $response = $guardAuthenticator->onAuthenticationFailure($request, $authenticationException); if ($response instanceof Response || null === $response) { // returning null is ok, it means they want the request to continue diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 9ca26a74c2343..62af83f23dabe 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Guard\Provider; use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Guard\AuthenticatorInterface; @@ -62,7 +63,7 @@ public function __construct($guardAuthenticators, UserProviderInterface $userPro */ public function authenticate(TokenInterface $token) { - if (!$this->supports($token)) { + if (!$token instanceof GuardTokenInterface) { throw new \InvalidArgumentException('GuardAuthenticationProvider only supports GuardTokenInterface.'); } @@ -86,19 +87,13 @@ public function authenticate(TokenInterface $token) throw new AuthenticationExpiredException(); } - // find the *one* GuardAuthenticator that this token originated from - foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { - // get a key that's unique to *this* guard authenticator - // this MUST be the same as GuardAuthenticationListener - $uniqueGuardKey = $this->providerKey.'_'.$key; + $guardAuthenticator = $this->findOriginatingAuthenticator($token); - if ($uniqueGuardKey == $token->getGuardProviderKey()) { - return $this->authenticateViaGuard($guardAuthenticator, $token); - } + if (null === $guardAuthenticator) { + throw new AuthenticationException(sprintf('Token with provider key "%s" did not originate from any of the guard authenticators of provider "%s".', $token->getGuardProviderKey(), $this->providerKey)); } - // no matching authenticator found - but there will be multiple GuardAuthenticationProvider - // instances that will be checked if you have multiple firewalls. + return $this->authenticateViaGuard($guardAuthenticator, $token); } private function authenticateViaGuard($guardAuthenticator, PreAuthenticationGuardToken $token) @@ -107,18 +102,11 @@ private function authenticateViaGuard($guardAuthenticator, PreAuthenticationGuar $user = $guardAuthenticator->getUser($token->getCredentials(), $this->userProvider); if (null === $user) { - throw new UsernameNotFoundException(sprintf( - 'Null returned from %s::getUser()', - get_class($guardAuthenticator) - )); + throw new UsernameNotFoundException(sprintf('Null returned from %s::getUser()', get_class($guardAuthenticator))); } if (!$user instanceof UserInterface) { - throw new \UnexpectedValueException(sprintf( - 'The %s::getUser() method must return a UserInterface. You returned %s.', - get_class($guardAuthenticator), - is_object($user) ? get_class($user) : gettype($user) - )); + throw new \UnexpectedValueException(sprintf('The %s::getUser() method must return a UserInterface. You returned %s.', get_class($guardAuthenticator), is_object($user) ? get_class($user) : gettype($user))); } $this->userChecker->checkPreAuth($user); @@ -130,18 +118,37 @@ private function authenticateViaGuard($guardAuthenticator, PreAuthenticationGuar // turn the UserInterface into a TokenInterface $authenticatedToken = $guardAuthenticator->createAuthenticatedToken($user, $this->providerKey); if (!$authenticatedToken instanceof TokenInterface) { - throw new \UnexpectedValueException(sprintf( - 'The %s::createAuthenticatedToken() method must return a TokenInterface. You returned %s.', - get_class($guardAuthenticator), - is_object($authenticatedToken) ? get_class($authenticatedToken) : gettype($authenticatedToken) - )); + throw new \UnexpectedValueException(sprintf('The %s::createAuthenticatedToken() method must return a TokenInterface. You returned %s.', get_class($guardAuthenticator), is_object($authenticatedToken) ? get_class($authenticatedToken) : gettype($authenticatedToken))); } return $authenticatedToken; } + private function findOriginatingAuthenticator(PreAuthenticationGuardToken $token) + { + // find the *one* GuardAuthenticator that this token originated from + foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { + // get a key that's unique to *this* guard authenticator + // this MUST be the same as GuardAuthenticationListener + $uniqueGuardKey = $this->providerKey.'_'.$key; + + if ($uniqueGuardKey === $token->getGuardProviderKey()) { + return $guardAuthenticator; + } + } + + // no matching authenticator found - but there will be multiple GuardAuthenticationProvider + // instances that will be checked if you have multiple firewalls. + + return null; + } + public function supports(TokenInterface $token) { + if ($token instanceof PreAuthenticationGuardToken) { + return null !== $this->findOriginatingAuthenticator($token); + } + return $token instanceof GuardTokenInterface; } } diff --git a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php index c67f38e9ef91c..5b03d4e13809b 100644 --- a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php @@ -82,7 +82,7 @@ public function testHandleAuthenticationFailure() /** * @dataProvider getTokenClearingTests */ - public function testHandleAuthenticationClearsToken($tokenClass, $tokenProviderKey, $actualProviderKey, $shouldTokenBeCleared) + public function testHandleAuthenticationClearsToken($tokenClass, $tokenProviderKey, $actualProviderKey) { $token = $this->getMockBuilder($tokenClass) ->disableOriginalConstructor() @@ -91,12 +91,7 @@ public function testHandleAuthenticationClearsToken($tokenClass, $tokenProviderK ->method('getProviderKey') ->will($this->returnValue($tokenProviderKey)); - // make the $token be the current token - $this->tokenStorage->expects($this->once()) - ->method('getToken') - ->will($this->returnValue($token)); - - $this->tokenStorage->expects($shouldTokenBeCleared ? $this->once() : $this->never()) + $this->tokenStorage->expects($this->never()) ->method('setToken') ->with(null); $authException = new AuthenticationException('Bad password!'); @@ -116,9 +111,9 @@ public function getTokenClearingTests() { $tests = array(); // correct token class and matching firewall => clear the token - $tests[] = array('Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken', 'the_firewall_key', 'the_firewall_key', true); - $tests[] = array('Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken', 'the_firewall_key', 'different_key', false); - $tests[] = array('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', 'the_firewall_key', 'the_firewall_key', false); + $tests[] = array('Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken', 'the_firewall_key', 'the_firewall_key'); + $tests[] = array('Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken', 'the_firewall_key', 'different_key'); + $tests[] = array('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', 'the_firewall_key', 'the_firewall_key'); return $tests; } diff --git a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php index 71b53f62f36b2..12de6e8c26dec 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Security\Guard\AuthenticatorInterface; use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider; use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; +use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; /** * @author Ryan Weaver @@ -136,6 +137,40 @@ public function testGuardWithNoLongerAuthenticatedTriggersLogout() $actualToken = $provider->authenticate($token); } + public function testSupportsChecksGuardAuthenticatorsTokenOrigin() + { + $authenticatorA = $this->getMockBuilder(AuthenticatorInterface::class)->getMock(); + $authenticatorB = $this->getMockBuilder(AuthenticatorInterface::class)->getMock(); + $authenticators = array($authenticatorA, $authenticatorB); + + $mockedUser = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock(); + $provider = new GuardAuthenticationProvider($authenticators, $this->userProvider, 'first_firewall', $this->userChecker); + + $token = new PreAuthenticationGuardToken($mockedUser, 'first_firewall_1'); + $supports = $provider->supports($token); + $this->assertTrue($supports); + + $token = new PreAuthenticationGuardToken($mockedUser, 'second_firewall_0'); + $supports = $provider->supports($token); + $this->assertFalse($supports); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationException + * @expectedExceptionMessageRegExp /second_firewall_0/ + */ + public function testAuthenticateFailsOnNonOriginatingToken() + { + $authenticatorA = $this->getMockBuilder(AuthenticatorInterface::class)->getMock(); + $authenticators = array($authenticatorA); + + $mockedUser = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock(); + $provider = new GuardAuthenticationProvider($authenticators, $this->userProvider, 'first_firewall', $this->userChecker); + + $token = new PreAuthenticationGuardToken($mockedUser, 'second_firewall_0'); + $provider->authenticate($token); + } + protected function setUp() { $this->userProvider = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserProviderInterface')->getMock(); diff --git a/src/Symfony/Component/Security/composer.json b/src/Symfony/Component/Security/composer.json index a7299f616d47b..fe1ebedebe7c7 100644 --- a/src/Symfony/Component/Security/composer.json +++ b/src/Symfony/Component/Security/composer.json @@ -39,7 +39,7 @@ "psr/log": "~1.0" }, "suggest": { - "psr/container": "To instantiate the Security class", + "psr/container-implementation": "To instantiate the Security class", "symfony/form": "", "symfony/validator": "For using the user password constraint", "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs", diff --git a/src/Symfony/Component/Templating/composer.json b/src/Symfony/Component/Templating/composer.json index a64a3ed9774b9..23111a86a781b 100644 --- a/src/Symfony/Component/Templating/composer.json +++ b/src/Symfony/Component/Templating/composer.json @@ -22,7 +22,7 @@ "psr/log": "~1.0" }, "suggest": { - "psr/log": "For using debug logging in loaders" + "psr/log-implementation": "For using debug logging in loaders" }, "autoload": { "psr-4": { "Symfony\\Component\\Templating\\": "" }, diff --git a/src/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd b/src/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd index 803eb602def50..dface628ce3e4 100644 --- a/src/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd +++ b/src/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd @@ -1,2223 +1,2223 @@ - - - - - - - - - - - - - - - Values for the attribute 'context-type'. - - - - - Indicates a database content. - - - - - Indicates the content of an element within an XML document. - - - - - Indicates the name of an element within an XML document. - - - - - Indicates the line number from the sourcefile (see context-type="sourcefile") where the <source> is found. - - - - - Indicates a the number of parameters contained within the <source>. - - - - - Indicates notes pertaining to the parameters in the <source>. - - - - - Indicates the content of a record within a database. - - - - - Indicates the name of a record within a database. - - - - - Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original <file> attribute in that this sourcefile is one of many that make up that file. - - - - - - - Values for the attribute 'count-type'. - - - - - Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts. - - - - - Indicates the count units are translation units existing already in the same document. - - - - - Indicates a total count. - - - - - - - Values for the attribute 'ctype' when used other elements than <ph> or <x>. - - - - - Indicates a run of bolded text. - - - - - Indicates a run of text in italics. - - - - - Indicates a run of underlined text. - - - - - Indicates a run of hyper-text. - - - - - - - Values for the attribute 'ctype' when used with <ph> or <x>. - - - - - Indicates a inline image. - - - - - Indicates a page break. - - - - - Indicates a line break. - - - - - - - - - - - - Values for the attribute 'datatype'. - - - - - Indicates Active Server Page data. - - - - - Indicates C source file data. - - - - - Indicates Channel Definition Format (CDF) data. - - - - - Indicates ColdFusion data. - - - - - Indicates C++ source file data. - - - - - Indicates C-Sharp data. - - - - - Indicates strings from C, ASM, and driver files data. - - - - - Indicates comma-separated values data. - - - - - Indicates database data. - - - - - Indicates portions of document that follows data and contains metadata. - - - - - Indicates portions of document that precedes data and contains metadata. - - - - - Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import). - - - - - Indicates standard user input screen data. - - - - - Indicates HyperText Markup Language (HTML) data - document instance. - - - - - Indicates content within an HTML document’s <body> element. - - - - - Indicates Windows INI file data. - - - - - Indicates Interleaf data. - - - - - Indicates Java source file data (extension '.java'). - - - - - Indicates Java property resource bundle data. - - - - - Indicates Java list resource bundle data. - - - - - Indicates JavaScript source file data. - - - - - Indicates JScript source file data. - - - - - Indicates information relating to formatting. - - - - - Indicates LISP source file data. - - - - - Indicates information relating to margin formats. - - - - - Indicates a file containing menu. - - - - - Indicates numerically identified string table. - - - - - Indicates Maker Interchange Format (MIF) data. - - - - - Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute. - - - - - Indicates GNU Machine Object data. - - - - - Indicates Message Librarian strings created by Novell's Message Librarian Tool. - - - - - Indicates information to be displayed at the bottom of each page of a document. - - - - - Indicates information to be displayed at the top of each page of a document. - - - - - Indicates a list of property values (e.g., settings within INI files or preferences dialog). - - - - - Indicates Pascal source file data. - - - - - Indicates Hypertext Preprocessor data. - - - - - Indicates plain text file (no formatting other than, possibly, wrapping). - - - - - Indicates GNU Portable Object file. - - - - - Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc. - - - - - Indicates Windows .NET binary resources. - - - - - Indicates Windows .NET Resources. - - - - - Indicates Rich Text Format (RTF) data. - - - - - Indicates Standard Generalized Markup Language (SGML) data - document instance. - - - - - Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD). - - - - - Indicates Scalable Vector Graphic (SVG) data. - - - - - Indicates VisualBasic Script source file. - - - - - Indicates warning message. - - - - - Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file). - - - - - Indicates Extensible HyperText Markup Language (XHTML) data - document instance. - - - - - Indicates Extensible Markup Language (XML) data - document instance. - - - - - Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD). - - - - - Indicates Extensible Stylesheet Language (XSL) data. - - - - - Indicates XUL elements. - - - - - - - Values for the attribute 'mtype'. - - - - - Indicates the marked text is an abbreviation. - - - - - ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept. - - - - - ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective'). - - - - - ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging'). - - - - - ISO-12620: A proper-name term, such as the name of an agency or other proper entity. - - - - - ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another. - - - - - ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language. - - - - - Indicates the marked text is a date and/or time. - - - - - ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign. - - - - - ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form. - - - - - ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula. - - - - - ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record. - - - - - ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy'). - - - - - ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body. - - - - - ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages. - - - - - ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like. - - - - - ISO-12620 2.1.17: A unit to track object. - - - - - Indicates the marked text is a name. - - - - - ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others. - - - - - ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system. - - - - - Indicates the marked text is a phrase. - - - - - ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase. - - - - - Indicates the marked text should not be translated. - - - - - ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet. - - - - - Indicates that the marked text represents a segment. - - - - - ISO-12620 2.1.18.2: A fixed, lexicalized phrase. - - - - - ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs'). - - - - - ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system. - - - - - ISO-12620 2.1.19: A fixed chunk of recurring text. - - - - - ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof. - - - - - ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry. - - - - - ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language. - - - - - Indicates the marked text is a term. - - - - - ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted. - - - - - ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system. - - - - - ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza'). - - - - - ISO-12620 2.1.9: One of the alternate forms of a term. - - - - - - - Values for the attribute 'restype'. - - - - - Indicates a Windows RC AUTO3STATE control. - - - - - Indicates a Windows RC AUTOCHECKBOX control. - - - - - Indicates a Windows RC AUTORADIOBUTTON control. - - - - - Indicates a Windows RC BEDIT control. - - - - - Indicates a bitmap, for example a BITMAP resource in Windows. - - - - - Indicates a button object, for example a BUTTON control Windows. - - - - - Indicates a caption, such as the caption of a dialog box. - - - - - Indicates the cell in a table, for example the content of the <td> element in HTML. - - - - - Indicates check box object, for example a CHECKBOX control in Windows. - - - - - Indicates a menu item with an associated checkbox. - - - - - Indicates a list box, but with a check-box for each item. - - - - - Indicates a color selection dialog. - - - - - Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows. - - - - - Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234). - - - - - Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403). - - - - - Indicates a UI base class element that cannot be represented by any other element. - - - - - Indicates a context menu. - - - - - Indicates a Windows RC CTEXT control. - - - - - Indicates a cursor, for example a CURSOR resource in Windows. - - - - - Indicates a date/time picker. - - - - - Indicates a Windows RC DEFPUSHBUTTON control. - - - - - Indicates a dialog box. - - - - - Indicates a Windows RC DLGINIT resource block. - - - - - Indicates an edit box object, for example an EDIT control in Windows. - - - - - Indicates a filename. - - - - - Indicates a file dialog. - - - - - Indicates a footnote. - - - - - Indicates a font name. - - - - - Indicates a footer. - - - - - Indicates a frame object. - - - - - Indicates a XUL grid element. - - - - - Indicates a groupbox object, for example a GROUPBOX control in Windows. - - - - - Indicates a header item. - - - - - Indicates a heading, such has the content of <h1>, <h2>, etc. in HTML. - - - - - Indicates a Windows RC HEDIT control. - - - - - Indicates a horizontal scrollbar. - - - - - Indicates an icon, for example an ICON resource in Windows. - - - - - Indicates a Windows RC IEDIT control. - - - - - Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF. - - - - - Indicates a label object. - - - - - Indicates a label that is also a HTML link (not necessarily a URL). - - - - - Indicates a list (a group of list-items, for example an <ol> or <ul> element in HTML). - - - - - Indicates a listbox object, for example an LISTBOX control in Windows. - - - - - Indicates an list item (an entry in a list). - - - - - Indicates a Windows RC LTEXT control. - - - - - Indicates a menu (a group of menu-items). - - - - - Indicates a toolbar containing one or more tope level menus. - - - - - Indicates a menu item (an entry in a menu). - - - - - Indicates a XUL menuseparator element. - - - - - Indicates a message, for example an entry in a MESSAGETABLE resource in Windows. - - - - - Indicates a calendar control. - - - - - Indicates an edit box beside a spin control. - - - - - Indicates a catch all for rectangular areas. - - - - - Indicates a standalone menu not necessarily associated with a menubar. - - - - - Indicates a pushbox object, for example a PUSHBOX control in Windows. - - - - - Indicates a Windows RC PUSHBUTTON control. - - - - - Indicates a radio button object. - - - - - Indicates a menuitem with associated radio button. - - - - - Indicates raw data resources for an application. - - - - - Indicates a row in a table. - - - - - Indicates a Windows RC RTEXT control. - - - - - Indicates a user navigable container used to show a portion of a document. - - - - - Indicates a generic divider object (e.g. menu group separator). - - - - - Windows accelerators, shortcuts in resource or property files. - - - - - Indicates a UI control to indicate process activity but not progress. - - - - - Indicates a splitter bar. - - - - - Indicates a Windows RC STATE3 control. - - - - - Indicates a window for providing feedback to the users, like 'read-only', etc. - - - - - Indicates a string, for example an entry in a STRINGTABLE resource in Windows. - - - - - Indicates a layers of controls with a tab to select layers. - - - - - Indicates a display and edits regular two-dimensional tables of cells. - - - - - Indicates a XUL textbox element. - - - - - Indicates a UI button that can be toggled to on or off state. - - - - - Indicates an array of controls, usually buttons. - - - - - Indicates a pop up tool tip text. - - - - - Indicates a bar with a pointer indicating a position within a certain range. - - - - - Indicates a control that displays a set of hierarchical data. - - - - - Indicates a URI (URN or URL). - - - - - Indicates a Windows RC USERBUTTON control. - - - - - Indicates a user-defined control like CONTROL control in Windows. - - - - - Indicates the text of a variable. - - - - - Indicates version information about a resource like VERSIONINFO in Windows. - - - - - Indicates a vertical scrollbar. - - - - - Indicates a graphical window. - - - - - - - Values for the attribute 'size-unit'. - - - - - Indicates a size in 8-bit bytes. - - - - - Indicates a size in Unicode characters. - - - - - Indicates a size in columns. Used for HTML text area. - - - - - Indicates a size in centimeters. - - - - - Indicates a size in dialog units, as defined in Windows resources. - - - - - Indicates a size in 'font-size' units (as defined in CSS). - - - - - Indicates a size in 'x-height' units (as defined in CSS). - - - - - Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster' - - - - - Indicates a size in inches. - - - - - Indicates a size in millimeters. - - - - - Indicates a size in percentage. - - - - - Indicates a size in pixels. - - - - - Indicates a size in point. - - - - - Indicates a size in rows. Used for HTML text area. - - - - - - - Values for the attribute 'state'. - - - - - Indicates the terminating state. - - - - - Indicates only non-textual information needs adaptation. - - - - - Indicates both text and non-textual information needs adaptation. - - - - - Indicates only non-textual information needs review. - - - - - Indicates both text and non-textual information needs review. - - - - - Indicates that only the text of the item needs to be reviewed. - - - - - Indicates that the item needs to be translated. - - - - - Indicates that the item is new. For example, translation units that were not in a previous version of the document. - - - - - Indicates that changes are reviewed and approved. - - - - - Indicates that the item has been translated. - - - - - - - Values for the attribute 'state-qualifier'. - - - - - Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously. - - - - - Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.). - - - - - Indicates a match based on matching IDs (in addition to matching text). - - - - - Indicates a translation derived from a glossary. - - - - - Indicates a translation derived from existing translation. - - - - - Indicates a translation derived from machine translation. - - - - - Indicates a translation derived from a translation repository. - - - - - Indicates a translation derived from a translation memory. - - - - - Indicates the translation is suggested by machine translation. - - - - - Indicates that the item has been rejected because of incorrect grammar. - - - - - Indicates that the item has been rejected because it is incorrect. - - - - - Indicates that the item has been rejected because it is too long or too short. - - - - - Indicates that the item has been rejected because of incorrect spelling. - - - - - Indicates the translation is suggested by translation memory. - - - - - - - Values for the attribute 'unit'. - - - - - Refers to words. - - - - - Refers to pages. - - - - - Refers to <trans-unit> elements. - - - - - Refers to <bin-unit> elements. - - - - - Refers to glyphs. - - - - - Refers to <trans-unit> and/or <bin-unit> elements. - - - - - Refers to the occurrences of instances defined by the count-type value. - - - - - Refers to characters. - - - - - Refers to lines. - - - - - Refers to sentences. - - - - - Refers to paragraphs. - - - - - Refers to segments. - - - - - Refers to placeables (inline elements). - - - - - - - Values for the attribute 'priority'. - - - - - Highest priority. - - - - - High priority. - - - - - High priority, but not as important as 2. - - - - - High priority, but not as important as 3. - - - - - Medium priority, but more important than 6. - - - - - Medium priority, but less important than 5. - - - - - Low priority, but more important than 8. - - - - - Low priority, but more important than 9. - - - - - Low priority. - - - - - Lowest priority. - - - - - - - - - This value indicates that all properties can be reformatted. This value must be used alone. - - - - - This value indicates that no properties should be reformatted. This value must be used alone. - - - - - - - - - - - - - This value indicates that all information in the coord attribute can be modified. - - - - - This value indicates that the x information in the coord attribute can be modified. - - - - - This value indicates that the y information in the coord attribute can be modified. - - - - - This value indicates that the cx information in the coord attribute can be modified. - - - - - This value indicates that the cy information in the coord attribute can be modified. - - - - - This value indicates that all the information in the font attribute can be modified. - - - - - This value indicates that the name information in the font attribute can be modified. - - - - - This value indicates that the size information in the font attribute can be modified. - - - - - This value indicates that the weight information in the font attribute can be modified. - - - - - This value indicates that the information in the css-style attribute can be modified. - - - - - This value indicates that the information in the style attribute can be modified. - - - - - This value indicates that the information in the exstyle attribute can be modified. - - - - - - - - - - - - - Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document. - - - - - Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed. - - - - - Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed. - - - - - - - - - Represents a translation proposal from a translation memory or other resource. - - - - - Represents a previous version of the target element. - - - - - Represents a rejected version of the target element. - - - - - Represents a translation to be used for reference purposes only, for example from a related product or a different language. - - - - - Represents a proposed translation that was used for the translation of the trans-unit, possibly modified. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Values for the attribute 'coord'. - - - - - - - - Version values: 1.0 and 1.1 are allowed for backward compatibility. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + Values for the attribute 'context-type'. + + + + + Indicates a database content. + + + + + Indicates the content of an element within an XML document. + + + + + Indicates the name of an element within an XML document. + + + + + Indicates the line number from the sourcefile (see context-type="sourcefile") where the <source> is found. + + + + + Indicates a the number of parameters contained within the <source>. + + + + + Indicates notes pertaining to the parameters in the <source>. + + + + + Indicates the content of a record within a database. + + + + + Indicates the name of a record within a database. + + + + + Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original <file> attribute in that this sourcefile is one of many that make up that file. + + + + + + + Values for the attribute 'count-type'. + + + + + Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts. + + + + + Indicates the count units are translation units existing already in the same document. + + + + + Indicates a total count. + + + + + + + Values for the attribute 'ctype' when used other elements than <ph> or <x>. + + + + + Indicates a run of bolded text. + + + + + Indicates a run of text in italics. + + + + + Indicates a run of underlined text. + + + + + Indicates a run of hyper-text. + + + + + + + Values for the attribute 'ctype' when used with <ph> or <x>. + + + + + Indicates a inline image. + + + + + Indicates a page break. + + + + + Indicates a line break. + + + + + + + + + + + + Values for the attribute 'datatype'. + + + + + Indicates Active Server Page data. + + + + + Indicates C source file data. + + + + + Indicates Channel Definition Format (CDF) data. + + + + + Indicates ColdFusion data. + + + + + Indicates C++ source file data. + + + + + Indicates C-Sharp data. + + + + + Indicates strings from C, ASM, and driver files data. + + + + + Indicates comma-separated values data. + + + + + Indicates database data. + + + + + Indicates portions of document that follows data and contains metadata. + + + + + Indicates portions of document that precedes data and contains metadata. + + + + + Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import). + + + + + Indicates standard user input screen data. + + + + + Indicates HyperText Markup Language (HTML) data - document instance. + + + + + Indicates content within an HTML document’s <body> element. + + + + + Indicates Windows INI file data. + + + + + Indicates Interleaf data. + + + + + Indicates Java source file data (extension '.java'). + + + + + Indicates Java property resource bundle data. + + + + + Indicates Java list resource bundle data. + + + + + Indicates JavaScript source file data. + + + + + Indicates JScript source file data. + + + + + Indicates information relating to formatting. + + + + + Indicates LISP source file data. + + + + + Indicates information relating to margin formats. + + + + + Indicates a file containing menu. + + + + + Indicates numerically identified string table. + + + + + Indicates Maker Interchange Format (MIF) data. + + + + + Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute. + + + + + Indicates GNU Machine Object data. + + + + + Indicates Message Librarian strings created by Novell's Message Librarian Tool. + + + + + Indicates information to be displayed at the bottom of each page of a document. + + + + + Indicates information to be displayed at the top of each page of a document. + + + + + Indicates a list of property values (e.g., settings within INI files or preferences dialog). + + + + + Indicates Pascal source file data. + + + + + Indicates Hypertext Preprocessor data. + + + + + Indicates plain text file (no formatting other than, possibly, wrapping). + + + + + Indicates GNU Portable Object file. + + + + + Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc. + + + + + Indicates Windows .NET binary resources. + + + + + Indicates Windows .NET Resources. + + + + + Indicates Rich Text Format (RTF) data. + + + + + Indicates Standard Generalized Markup Language (SGML) data - document instance. + + + + + Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD). + + + + + Indicates Scalable Vector Graphic (SVG) data. + + + + + Indicates VisualBasic Script source file. + + + + + Indicates warning message. + + + + + Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file). + + + + + Indicates Extensible HyperText Markup Language (XHTML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD). + + + + + Indicates Extensible Stylesheet Language (XSL) data. + + + + + Indicates XUL elements. + + + + + + + Values for the attribute 'mtype'. + + + + + Indicates the marked text is an abbreviation. + + + + + ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept. + + + + + ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective'). + + + + + ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging'). + + + + + ISO-12620: A proper-name term, such as the name of an agency or other proper entity. + + + + + ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another. + + + + + ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language. + + + + + Indicates the marked text is a date and/or time. + + + + + ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign. + + + + + ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form. + + + + + ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula. + + + + + ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record. + + + + + ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy'). + + + + + ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body. + + + + + ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages. + + + + + ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like. + + + + + ISO-12620 2.1.17: A unit to track object. + + + + + Indicates the marked text is a name. + + + + + ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others. + + + + + ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system. + + + + + Indicates the marked text is a phrase. + + + + + ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase. + + + + + Indicates the marked text should not be translated. + + + + + ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet. + + + + + Indicates that the marked text represents a segment. + + + + + ISO-12620 2.1.18.2: A fixed, lexicalized phrase. + + + + + ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs'). + + + + + ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system. + + + + + ISO-12620 2.1.19: A fixed chunk of recurring text. + + + + + ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof. + + + + + ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry. + + + + + ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language. + + + + + Indicates the marked text is a term. + + + + + ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted. + + + + + ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system. + + + + + ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza'). + + + + + ISO-12620 2.1.9: One of the alternate forms of a term. + + + + + + + Values for the attribute 'restype'. + + + + + Indicates a Windows RC AUTO3STATE control. + + + + + Indicates a Windows RC AUTOCHECKBOX control. + + + + + Indicates a Windows RC AUTORADIOBUTTON control. + + + + + Indicates a Windows RC BEDIT control. + + + + + Indicates a bitmap, for example a BITMAP resource in Windows. + + + + + Indicates a button object, for example a BUTTON control Windows. + + + + + Indicates a caption, such as the caption of a dialog box. + + + + + Indicates the cell in a table, for example the content of the <td> element in HTML. + + + + + Indicates check box object, for example a CHECKBOX control in Windows. + + + + + Indicates a menu item with an associated checkbox. + + + + + Indicates a list box, but with a check-box for each item. + + + + + Indicates a color selection dialog. + + + + + Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows. + + + + + Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234). + + + + + Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403). + + + + + Indicates a UI base class element that cannot be represented by any other element. + + + + + Indicates a context menu. + + + + + Indicates a Windows RC CTEXT control. + + + + + Indicates a cursor, for example a CURSOR resource in Windows. + + + + + Indicates a date/time picker. + + + + + Indicates a Windows RC DEFPUSHBUTTON control. + + + + + Indicates a dialog box. + + + + + Indicates a Windows RC DLGINIT resource block. + + + + + Indicates an edit box object, for example an EDIT control in Windows. + + + + + Indicates a filename. + + + + + Indicates a file dialog. + + + + + Indicates a footnote. + + + + + Indicates a font name. + + + + + Indicates a footer. + + + + + Indicates a frame object. + + + + + Indicates a XUL grid element. + + + + + Indicates a groupbox object, for example a GROUPBOX control in Windows. + + + + + Indicates a header item. + + + + + Indicates a heading, such has the content of <h1>, <h2>, etc. in HTML. + + + + + Indicates a Windows RC HEDIT control. + + + + + Indicates a horizontal scrollbar. + + + + + Indicates an icon, for example an ICON resource in Windows. + + + + + Indicates a Windows RC IEDIT control. + + + + + Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF. + + + + + Indicates a label object. + + + + + Indicates a label that is also a HTML link (not necessarily a URL). + + + + + Indicates a list (a group of list-items, for example an <ol> or <ul> element in HTML). + + + + + Indicates a listbox object, for example an LISTBOX control in Windows. + + + + + Indicates an list item (an entry in a list). + + + + + Indicates a Windows RC LTEXT control. + + + + + Indicates a menu (a group of menu-items). + + + + + Indicates a toolbar containing one or more tope level menus. + + + + + Indicates a menu item (an entry in a menu). + + + + + Indicates a XUL menuseparator element. + + + + + Indicates a message, for example an entry in a MESSAGETABLE resource in Windows. + + + + + Indicates a calendar control. + + + + + Indicates an edit box beside a spin control. + + + + + Indicates a catch all for rectangular areas. + + + + + Indicates a standalone menu not necessarily associated with a menubar. + + + + + Indicates a pushbox object, for example a PUSHBOX control in Windows. + + + + + Indicates a Windows RC PUSHBUTTON control. + + + + + Indicates a radio button object. + + + + + Indicates a menuitem with associated radio button. + + + + + Indicates raw data resources for an application. + + + + + Indicates a row in a table. + + + + + Indicates a Windows RC RTEXT control. + + + + + Indicates a user navigable container used to show a portion of a document. + + + + + Indicates a generic divider object (e.g. menu group separator). + + + + + Windows accelerators, shortcuts in resource or property files. + + + + + Indicates a UI control to indicate process activity but not progress. + + + + + Indicates a splitter bar. + + + + + Indicates a Windows RC STATE3 control. + + + + + Indicates a window for providing feedback to the users, like 'read-only', etc. + + + + + Indicates a string, for example an entry in a STRINGTABLE resource in Windows. + + + + + Indicates a layers of controls with a tab to select layers. + + + + + Indicates a display and edits regular two-dimensional tables of cells. + + + + + Indicates a XUL textbox element. + + + + + Indicates a UI button that can be toggled to on or off state. + + + + + Indicates an array of controls, usually buttons. + + + + + Indicates a pop up tool tip text. + + + + + Indicates a bar with a pointer indicating a position within a certain range. + + + + + Indicates a control that displays a set of hierarchical data. + + + + + Indicates a URI (URN or URL). + + + + + Indicates a Windows RC USERBUTTON control. + + + + + Indicates a user-defined control like CONTROL control in Windows. + + + + + Indicates the text of a variable. + + + + + Indicates version information about a resource like VERSIONINFO in Windows. + + + + + Indicates a vertical scrollbar. + + + + + Indicates a graphical window. + + + + + + + Values for the attribute 'size-unit'. + + + + + Indicates a size in 8-bit bytes. + + + + + Indicates a size in Unicode characters. + + + + + Indicates a size in columns. Used for HTML text area. + + + + + Indicates a size in centimeters. + + + + + Indicates a size in dialog units, as defined in Windows resources. + + + + + Indicates a size in 'font-size' units (as defined in CSS). + + + + + Indicates a size in 'x-height' units (as defined in CSS). + + + + + Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster' + + + + + Indicates a size in inches. + + + + + Indicates a size in millimeters. + + + + + Indicates a size in percentage. + + + + + Indicates a size in pixels. + + + + + Indicates a size in point. + + + + + Indicates a size in rows. Used for HTML text area. + + + + + + + Values for the attribute 'state'. + + + + + Indicates the terminating state. + + + + + Indicates only non-textual information needs adaptation. + + + + + Indicates both text and non-textual information needs adaptation. + + + + + Indicates only non-textual information needs review. + + + + + Indicates both text and non-textual information needs review. + + + + + Indicates that only the text of the item needs to be reviewed. + + + + + Indicates that the item needs to be translated. + + + + + Indicates that the item is new. For example, translation units that were not in a previous version of the document. + + + + + Indicates that changes are reviewed and approved. + + + + + Indicates that the item has been translated. + + + + + + + Values for the attribute 'state-qualifier'. + + + + + Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously. + + + + + Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.). + + + + + Indicates a match based on matching IDs (in addition to matching text). + + + + + Indicates a translation derived from a glossary. + + + + + Indicates a translation derived from existing translation. + + + + + Indicates a translation derived from machine translation. + + + + + Indicates a translation derived from a translation repository. + + + + + Indicates a translation derived from a translation memory. + + + + + Indicates the translation is suggested by machine translation. + + + + + Indicates that the item has been rejected because of incorrect grammar. + + + + + Indicates that the item has been rejected because it is incorrect. + + + + + Indicates that the item has been rejected because it is too long or too short. + + + + + Indicates that the item has been rejected because of incorrect spelling. + + + + + Indicates the translation is suggested by translation memory. + + + + + + + Values for the attribute 'unit'. + + + + + Refers to words. + + + + + Refers to pages. + + + + + Refers to <trans-unit> elements. + + + + + Refers to <bin-unit> elements. + + + + + Refers to glyphs. + + + + + Refers to <trans-unit> and/or <bin-unit> elements. + + + + + Refers to the occurrences of instances defined by the count-type value. + + + + + Refers to characters. + + + + + Refers to lines. + + + + + Refers to sentences. + + + + + Refers to paragraphs. + + + + + Refers to segments. + + + + + Refers to placeables (inline elements). + + + + + + + Values for the attribute 'priority'. + + + + + Highest priority. + + + + + High priority. + + + + + High priority, but not as important as 2. + + + + + High priority, but not as important as 3. + + + + + Medium priority, but more important than 6. + + + + + Medium priority, but less important than 5. + + + + + Low priority, but more important than 8. + + + + + Low priority, but more important than 9. + + + + + Low priority. + + + + + Lowest priority. + + + + + + + + + This value indicates that all properties can be reformatted. This value must be used alone. + + + + + This value indicates that no properties should be reformatted. This value must be used alone. + + + + + + + + + + + + + This value indicates that all information in the coord attribute can be modified. + + + + + This value indicates that the x information in the coord attribute can be modified. + + + + + This value indicates that the y information in the coord attribute can be modified. + + + + + This value indicates that the cx information in the coord attribute can be modified. + + + + + This value indicates that the cy information in the coord attribute can be modified. + + + + + This value indicates that all the information in the font attribute can be modified. + + + + + This value indicates that the name information in the font attribute can be modified. + + + + + This value indicates that the size information in the font attribute can be modified. + + + + + This value indicates that the weight information in the font attribute can be modified. + + + + + This value indicates that the information in the css-style attribute can be modified. + + + + + This value indicates that the information in the style attribute can be modified. + + + + + This value indicates that the information in the exstyle attribute can be modified. + + + + + + + + + + + + + Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document. + + + + + Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed. + + + + + Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed. + + + + + + + + + Represents a translation proposal from a translation memory or other resource. + + + + + Represents a previous version of the target element. + + + + + Represents a rejected version of the target element. + + + + + Represents a translation to be used for reference purposes only, for example from a related product or a different language. + + + + + Represents a proposed translation that was used for the translation of the trans-unit, possibly modified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Values for the attribute 'coord'. + + + + + + + + Version values: 1.0 and 1.1 are allowed for backward compatibility. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/Translation/composer.json b/src/Symfony/Component/Translation/composer.json index e8cfab2ce8220..6b609689b8bd8 100644 --- a/src/Symfony/Component/Translation/composer.json +++ b/src/Symfony/Component/Translation/composer.json @@ -35,7 +35,7 @@ "suggest": { "symfony/config": "", "symfony/yaml": "", - "psr/log": "To use logging capability in translator" + "psr/log-implementation": "To use logging capability in translator" }, "autoload": { "psr-4": { "Symfony\\Component\\Translation\\": "" }, diff --git a/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php index e36f0fcf543e8..fbb8c66669fc4 100644 --- a/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php +++ b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php @@ -88,6 +88,10 @@ public function getMetadataFor($value) return $this->loadedClasses[$class]; } + if (!class_exists($class) && !interface_exists($class, false)) { + throw new NoSuchMetadataException(sprintf('The class or interface "%s" does not exist.', $class)); + } + if (null !== $this->cache && false !== ($metadata = $this->cache->read($class))) { // Include constraints from the parent class $this->mergeConstraints($metadata); @@ -95,10 +99,6 @@ public function getMetadataFor($value) return $this->loadedClasses[$class] = $metadata; } - if (!class_exists($class) && !interface_exists($class)) { - throw new NoSuchMetadataException(sprintf('The class or interface "%s" does not exist.', $class)); - } - $metadata = new ClassMetadata($class); if (null !== $this->loader) { @@ -160,10 +160,6 @@ public function hasMetadataFor($value) $class = ltrim(is_object($value) ? get_class($value) : $value, '\\'); - if (class_exists($class) || interface_exists($class)) { - return true; - } - - return false; + return class_exists($class) || interface_exists($class, false); } } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php index b5d1a9dc84d4d..de6852271e17f 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php @@ -149,6 +149,21 @@ public function testReadMetadataFromCache() $this->assertEquals($metadata, $factory->getMetadataFor(self::PARENT_CLASS)); } + /** + * @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException + */ + public function testNonClassNameStringValues() + { + $testedValue = 'error@example.com'; + $loader = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Loader\LoaderInterface')->getMock(); + $cache = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Cache\CacheInterface')->getMock(); + $factory = new LazyLoadingMetadataFactory($loader, $cache); + $cache + ->expects($this->never()) + ->method('read'); + $factory->getMetadataFor($testedValue); + } + public function testMetadataCacheWithRuntimeConstraint() { $cache = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Cache\CacheInterface')->getMock(); diff --git a/src/Symfony/Component/VarDumper/Caster/SplCaster.php b/src/Symfony/Component/VarDumper/Caster/SplCaster.php index c9d25feeb945b..e8be74d60419e 100644 --- a/src/Symfony/Component/VarDumper/Caster/SplCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/SplCaster.php @@ -184,10 +184,11 @@ public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $s $storage = array(); unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 - foreach (clone $c as $obj) { + $clone = clone $c; + foreach ($clone as $obj) { $storage[spl_object_hash($obj)] = array( 'object' => $obj, - 'info' => $c->getInfo(), + 'info' => $clone->getInfo(), ); } diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php index 2faf54372f714..947b9c002eb52 100644 --- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -62,7 +62,7 @@ public function __construct($output = null, string $charset = null, int $flags = { parent::__construct($output, $charset, $flags); - if ('\\' === DIRECTORY_SEPARATOR && 'ON' !== @getenv('ConEmuANSI') && 'xterm' !== @getenv('TERM')) { + if ('\\' === DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) { // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI $this->setStyles(array( 'default' => '31', @@ -467,7 +467,7 @@ protected function style($style, $value, $attr = array()) protected function supportsColors() { if ($this->outputStream !== static::$defaultOutput) { - return @(is_resource($this->outputStream) && function_exists('posix_isatty') && posix_isatty($this->outputStream)); + return $this->hasColorSupport($this->outputStream); } if (null !== static::$defaultColors) { return static::$defaultColors; @@ -495,23 +495,10 @@ protected function supportsColors() } } - if ('\\' === DIRECTORY_SEPARATOR) { - static::$defaultColors = @( - function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support($this->outputStream) - || '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD - || false !== getenv('ANSICON') - || 'ON' === getenv('ConEmuANSI') - || 'xterm' === getenv('TERM') - ); - } elseif (function_exists('posix_isatty')) { - $h = stream_get_meta_data($this->outputStream) + array('wrapper_type' => null); - $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream; - static::$defaultColors = @posix_isatty($h); - } else { - static::$defaultColors = false; - } + $h = stream_get_meta_data($this->outputStream) + array('wrapper_type' => null); + $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream; - return static::$defaultColors; + return static::$defaultColors = $this->hasColorSupport($h); } /** @@ -537,4 +524,69 @@ protected function endValue(Cursor $cursor) $this->dumpLine($cursor->depth, true); } + + /** + * Returns true if the stream supports colorization. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @param mixed $stream A CLI output stream + * + * @return bool + */ + private function hasColorSupport($stream) + { + if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) { + return false; + } + + if (DIRECTORY_SEPARATOR === '\\') { + return (function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + if (function_exists('stream_isatty')) { + return @stream_isatty($stream); + } + + if (function_exists('posix_isatty')) { + return @posix_isatty($stream); + } + + $stat = @fstat($stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + /** + * Returns true if the Windows terminal supports true color. + * + * Note that this does not check an output stream, but relies on environment + * variables from known implementations, or a PHP and Windows version that + * supports true color. + * + * @return bool + */ + private function isWindowsTrueColor() + { + $result = 183 <= getenv('ANSICON_VER') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + + if (!$result && PHP_VERSION_ID >= 70200) { + $version = sprintf( + '%s.%s.%s', + PHP_WINDOWS_VERSION_MAJOR, + PHP_WINDOWS_VERSION_MINOR, + PHP_WINDOWS_VERSION_BUILD + ); + $result = $version >= '10.0.15063'; + } + + return $result; + } } diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index f0f1f83b6f560..28cf6699306cb 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -310,6 +310,9 @@ function xpathString(str) { return "concat(" + parts.join(",") + ", '')"; } + function xpathHasClass(className) { + return "contains(concat(' ', normalize-space(@class), ' '), ' " + className +" ')"; + } addEventListener(root, 'mouseover', function (e) { if ('' != refStyle.innerHTML) { refStyle.innerHTML = ''; @@ -516,7 +519,15 @@ function showCurrent(state) return; } - var xpathResult = doc.evaluate('//pre[@id="' + root.id + '"]//span[@class="sf-dump-str" or @class="sf-dump-key" or @class="sf-dump-public" or @class="sf-dump-protected" or @class="sf-dump-private"][contains(translate(child::text(), ' + xpathString(searchQuery.toUpperCase()) + ', ' + xpathString(searchQuery.toLowerCase()) + '), ' + xpathString(searchQuery.toLowerCase()) + ')]', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); + var classMatches = [ + "sf-dump-str", + "sf-dump-key", + "sf-dump-public", + "sf-dump-protected", + "sf-dump-private", + ].map(xpathHasClass).join(' or '); + + var xpathResult = doc.evaluate('.//span[' + classMatches + '][contains(translate(child::text(), ' + xpathString(searchQuery.toUpperCase()) + ', ' + xpathString(searchQuery.toLowerCase()) + '), ' + xpathString(searchQuery.toLowerCase()) + ')]', root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); while (node = xpathResult.iterateNext()) state.nodes.push(node); diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/SplCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/SplCasterTest.php index e2181d90b5b1d..48531562de677 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/SplCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/SplCasterTest.php @@ -144,4 +144,23 @@ public function provideCastSplDoublyLinkedList() array(\SplDoublyLinkedList::IT_MODE_LIFO | \SplDoublyLinkedList::IT_MODE_DELETE, 'IT_MODE_LIFO | IT_MODE_DELETE'), ); } + + public function testCastObjectStorageIsntModified() + { + $var = new \SplObjectStorage(); + $var->attach(new \stdClass()); + $var->rewind(); + $current = $var->current(); + + $this->assertDumpMatchesFormat('%A', $var); + $this->assertSame($current, $var->current()); + } + + public function testCastObjectStorageDumpsInfo() + { + $var = new \SplObjectStorage(); + $var->attach(new \stdClass(), new \DateTime()); + + $this->assertDumpMatchesFormat('%ADateTime%A', $var); + } } diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index abab86903b8d3..0c0433939cb38 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -269,6 +269,9 @@ public static function parseScalar(string $scalar, int $flags = 0, array $delimi if (null !== $delimiters) { $tmp = ltrim(substr($scalar, $i), ' '); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode($delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } if (!in_array($tmp[0], $delimiters)) { throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index 2cadfe36b8dce..7ba46bdffb496 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -748,4 +748,13 @@ public function testTagWithEmptyValueInMapping() $this->assertSame('bar', $value['foo']->getTag()); $this->assertSame('', $value['foo']->getValue()); } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + * @expectedExceptionMessage Unexpected end of line, expected one of ",}" at line 1 (near "{abc: 'def'"). + */ + public function testUnfinishedInlineMap() + { + Inline::parse("{abc: 'def'"); + } }