diff --git a/.appveyor.yml b/.appveyor.yml index 1155c1e754e5e..552c26ce55d86 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -63,6 +63,7 @@ test_script: - SET X=0 - cd c:\php && copy /Y php.ini-min php.ini - cd c:\projects\symfony + - IF %APPVEYOR_REPO_BRANCH% neq master (rm -Rf src\Symfony\Bridge\PhpUnit) - php phpunit src\Symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! - cd c:\php && 7z x php-5.5.9-nts-Win32-VC11-x86.zip -y >nul && copy /Y php.ini-min php.ini - cd c:\projects\symfony diff --git a/.travis.yml b/.travis.yml index b05db83f768e4..824e29048305f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,6 +48,8 @@ before_install: # Enable Sury ppa sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 6B05F25D762E3157 sudo add-apt-repository -y ppa:ondrej/php + sudo rm /etc/apt/sources.list.d/google-chrome.list + sudo rm /etc/apt/sources.list.d/mongodb-3.4.list sudo apt update - | @@ -222,6 +224,12 @@ install: SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*') fi + - | + # Skip the phpunit-bridge on not-master branches when $deps is empty + if [[ ! $deps && $TRAVIS_BRANCH != master ]]; then + COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -not -wholename '*/Bridge/PhpUnit/*' -printf '%h\n') + fi + - | # Install symfony/flex if [[ $deps = low ]]; then @@ -269,6 +277,7 @@ install: echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && ([ -e composer.lock ] && ${COMPOSER_UP/update/install} || $COMPOSER_UP --prefer-lowest --prefer-stable) && $PHPUNIT_X'" echo "$COMPONENTS" | xargs -n1 -I{} tar --append -f ~/php-ext/composer-lowest.lock.tar {}/composer.lock elif [[ $PHP = hhvm* ]]; then + rm src/Symfony/Bridge/PhpUnit -Rf $PHPUNIT --exclude-group no-hhvm,benchmark,intl-data else echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}" diff --git a/CHANGELOG-3.4.md b/CHANGELOG-3.4.md index ba31a942c3a35..cc532847e6f8c 100644 --- a/CHANGELOG-3.4.md +++ b/CHANGELOG-3.4.md @@ -7,6 +7,32 @@ in 3.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.4.0...v3.4.1 +* 3.4.25 (2019-04-16) + + * bug #29944 [DI] Overriding services autowired by name under _defaults bind not working (przemyslaw-bogusz, renanbr) + * bug #31076 [HttpKernel] Fixed LoggerDataCollector crashing on empty file (althaus) + * bug #31071 property normalizer should also pass format and context to isAllowedAttribute (dbu) + * bug #31059 Show more accurate message in profiler when missing stopwatch (linaori) + * bug #30423 [Security] Rework firewall's access denied rule (dimabory) + * bug #31012 [Process] Fix missing $extraDirs when open_basedir returns (arsonik) + * bug #30907 [Serializer] Respect ignored attributes in cache key of normalizer (dbu) + * bug #30085 Fix TestRunner compatibility to PhpUnit 8 (alexander-schranz) + * bug #30977 [serializer] prevent mixup in normalizer of the object to populate (dbu) + * bug #30976 [Debug] Fixed error handling when an error is already handled when another error is already handled (5) (lyrixx) + * bug #30979 Fix the configurability of CoreExtension deps in standalone usage (stof) + * bug #30918 [Cache] fix using ProxyAdapter inside TagAwareAdapter (dmaicher) + * bug #30961 [Form] fix translating file validation error message (xabbuh) + * bug #30951 Handle case where no translations were found (greg0ire) + * bug #29800 [Validator] Only traverse arrays that are cascaded into (corphi) + * bug #30921 [Translator] Warm up the translations cache in dev (tgalopin) + * bug #30922 [TwigBridge] fix horizontal spacing of inlined Bootstrap forms (xabbuh) + * bug #30895 [Form] turn failed file uploads into form errors (xabbuh) + * bug #30919 [Translator] Fix wrong dump for PO files (deguif) + * bug #30889 [DependencyInjection] Fix a wrong error when using a factory (Simperfit) + * bug #30879 [Form] Php doc fixes and cs + optimizations (Jules Pietri) + * bug #30883 [Console] Fix stty not reset when aborting in QuestionHelper::autocomplete() (Simperfit) + * bug #30878 [Console] Fix inconsistent result for choice questions in non-interactive mode (chalasr) + * 3.4.24 (2019-04-02) * bug #30660 [Bridge][Twig] DebugCommand - fix escaping and filter (SpacePossum) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index efb1b16d81ab9..146b9822ffdce 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -25,66 +25,66 @@ Symfony is the result of the work of many people who made the code better - Hugo Hamon (hhamon) - Abdellatif Ait boudad (aitboudad) - Romain Neutron (romain) + - Samuel ROZE (sroze) - Pascal Borreli (pborreli) - Wouter De Jong (wouterj) - - Samuel ROZE (sroze) - Joseph Bielawski (stloyd) - Karma Dordrak (drak) - Lukas Kahwe Smith (lsmith) - Martin Hasoň (hason) - Jeremy Mikola (jmikola) + - Yonel Ceruto (yonelceruto) - Jean-François Simon (jfsimon) + - Jules Pietri (heah) - Benjamin Eberlei (beberlei) - Igor Wiedler (igorw) - - Yonel Ceruto (yonelceruto) - - Jules Pietri (heah) - Eriksen Costa (eriksencosta) - Guilhem Niot (energetick) - Sarah Khalil (saro0h) + - Hamza Amrouche (simperfit) - Jonathan Wage (jwage) - Tobias Nyholm (tobias) - - Hamza Amrouche (simperfit) + - Lynn van der Berg (kjarli) - Diego Saint Esteben (dosten) - - Iltar van der Berg (kjarli) - Alexandre Salomé (alexandresalome) - William Durand (couac) - ornicar - - Francis Besset (francisbesset) + - Jérémy DERUSSÉ (jderusse) - Dany Maillard (maidmaid) + - Francis Besset (francisbesset) - stealth35 ‏ (stealth35) - Alexander Mols (asm89) - - Bulat Shakirzyanov (avalanche123) - - Jérémy DERUSSÉ (jderusse) - Matthias Pigulla (mpdude) + - Bulat Shakirzyanov (avalanche123) + - Alexander M. Turek (derrabus) - Saša Stamenković (umpirsky) - Peter Rehm (rpet) - Kevin Bond (kbond) - Pierre du Plessis (pierredup) - Henrik Bjørnskov (henrikbjorn) - - Alexander M. Turek (derrabus) - Miha Vrhovnik - Diego Saint Esteben (dii3g0) - Konstantin Kudryashov (everzet) + - Gábor Egyed (1ed) - Bilal Amarni (bamarni) + - Titouan Galopin (tgalopin) - Mathieu Piot (mpiot) + - David Maicher (dmaicher) - Florin Patan (florinpatan) - - Gábor Egyed (1ed) + - Grégoire Paris (greg0ire) - Gabriel Ostrolucký (gadelat) - - Titouan Galopin (tgalopin) + - Valentin Udaltsov (vudaltsov) - Vladimir Reznichenko (kalessil) - Jáchym Toušek (enumag) - - David Maicher (dmaicher) + - Konstantin Myakshin (koc) - Michel Weimerskirch (mweimerskirch) - Andrej Hudec (pulzarraider) - - Konstantin Myakshin (koc) - Eric Clemmons (ericclemmons) - Charles Sarrazin (csarrazi) - Christian Raue - Issei Murasawa (issei_m) - - Valentin Udaltsov (vudaltsov) - Arnout Boks (aboks) - Deni - - Grégoire Paris (greg0ire) - Henrik Westphal (snc) - Dariusz Górecki (canni) - Douglas Greenshields (shieldo) @@ -99,7 +99,9 @@ Symfony is the result of the work of many people who made the code better - Jordan Alliot (jalliot) - Jérôme Tamarelle (gromnan) - John Wards (johnwards) + - Thomas Calvet (fancyweb) - Fran Moreno (franmomu) + - David Buchmann (dbu) - Antoine Hérault (herzult) - Paráda József (paradajozsef) - Arnaud Le Blanc (arnaud-lb) @@ -111,66 +113,66 @@ Symfony is the result of the work of many people who made the code better - Chris Wilkinson (thewilkybarkid) - marc.weistroff - Tomáš Votruba (tomas_votruba) - - David Buchmann (dbu) - lenar - Alexander Schwenn (xelaris) - Włodzimierz Gajda (gajdaw) - - Thomas Calvet (fancyweb) - Jérôme Vasseur (jvasseur) - Peter Kokot (maastermedia) - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) - Colin Frei + - Javier Spagnoletti (phansys) - Adrien Brault (adrienbrault) - Joshua Thijssen - Daniel Wehner (dawehner) - excelwebzone - Gordon Franke (gimler) - Sebastiaan Stok (sstok) - - Javier Spagnoletti (phansys) - Fabien Pennequin (fabienpennequin) + - Théo FIDRY (theofidry) - Eric GELOEN (gelo) + - Joel Wurtz (brouznouf) - Lars Strojny (lstrojny) - Tugdual Saunier (tucksaun) - - Théo FIDRY (theofidry) - Robert Schönthal (digitalkaoz) - Florian Lonqueu-Brochard (florianlb) + - Oskar Stark (oskarstark) - Stefano Sala (stefano.sala) - Evgeniy (ewgraf) - Alex Pott - Vincent AUBERT (vincent) - Juti Noppornpitak (shiroyuki) - Anthony MARTIN (xurudragon) - - Oskar Stark (oskarstark) - Tigran Azatyan (tigranazatyan) - Sebastian Hörl (blogsh) - Daniel Gomes (danielcsgomes) - - Joel Wurtz (brouznouf) - Gabriel Caruso - Hidenori Goto (hidenorigoto) - Arnaud Kleinpeter (nanocom) - Jannik Zschiesche (apfelbox) - Guilherme Blanco (guilhermeblanco) - Teoh Han Hui (teohhanhui) + - SpacePossum - Pablo Godel (pgodel) - Jérémie Augustin (jaugustin) + - Oleg Voronkovich - Andréia Bohner (andreia) - Philipp Wahala (hifi) - Julien Falque (julienfalque) - Rafael Dohms (rdohms) - jwdeitch - Mikael Pajunen - - Oleg Voronkovich + - Niels Keurentjes (curry684) - Vyacheslav Pavlov - Richard van Laak (rvanlaak) - Richard Shank (iampersistent) - Thomas Rabaix (rande) - Rouven Weßling (realityking) + - François-Xavier de Guillebon (de-gui_f) - Clemens Tolboom - Helmer Aaviksoo - Alessandro Chitolina (alekitto) - Hiromi Hishida (77web) - - Niels Keurentjes (curry684) - Matthieu Ouellette-Vachon (maoueh) - Michał Pipa (michal.pipa) - Dawid Nowak @@ -179,7 +181,6 @@ Symfony is the result of the work of many people who made the code better - Artur Kotyrba - Tyson Andre - GDIBass - - SpacePossum - jeremyFreeAgent (Jérémy Romey) (jeremyfreeagent) - James Halsall (jaitsu) - Matthieu Napoli (mnapoli) @@ -198,8 +199,10 @@ Symfony is the result of the work of many people who made the code better - Dennis Benkert (denderello) - DQNEO - Samuel NELA (snela) + - Gregor Harlan (gharlan) + - Gary PEGEOT (gary-p) + - Ruben Gonzalez (rubenrua) - Benjamin Dulau (dbenjamin) - - François-Xavier de Guillebon (de-gui_f) - Mathieu Lemoine (lemoinem) - Christian Schmidt - Andreas Hucks (meandmymonkey) @@ -221,9 +224,10 @@ Symfony is the result of the work of many people who made the code better - fivestar - Dominique Bongiraud - Jeremy Livingston (jeremylivingston) + - Vincent Touzet (vincenttouzet) + - Jan Schädlich (jschaedl) - Michael Lee (zerustech) - Matthieu Auger (matthieuauger) - - Gregor Harlan (gharlan) - Leszek Prabucki (l3l0) - Fabien Bourigault (fbourigault) - François Zaninotto (fzaninotto) @@ -233,10 +237,10 @@ Symfony is the result of the work of many people who made the code better - Andreas Schempp (aschempp) - Justin Hileman (bobthecow) - Blanchon Vincent (blanchonvincent) + - Alexander Schranz (alexander-schranz) - Michele Orselli (orso) - Sven Paulus (subsven) - Maxime Veber (nek-) - - Gary PEGEOT (gary-p) - Rui Marinho (ruimarinho) - Massimiliano Arione (garak) - Eugene Wissner @@ -250,7 +254,6 @@ Symfony is the result of the work of many people who made the code better - Loïc Faugeron - Hidde Wieringa (hiddewie) - Marco Pivetta (ocramius) - - Jan Schädlich (jschaedl) - Rob Frawley 2nd (robfrawley) - julien pauli (jpauli) - Lorenz Schori @@ -265,24 +268,24 @@ Symfony is the result of the work of many people who made the code better - Elnur Abdurrakhimov (elnur) - Manuel Reinhard (sprain) - Danny Berger (dpb587) - - Ruben Gonzalez (rubenrua) - Adam Prager (padam87) - Benoît Burnichon (bburnichon) - Roman Marintšenko (inori) - Xavier Montaña Carreras (xmontana) + - Rémon van de Kamp (rpkamp) - Mickaël Andrieu (mickaelandrieu) - Xavier Perez - Arjen Brouwer (arjenjb) - Katsuhiro OGAWA - Patrick McDougle (patrick-mcdougle) - Alif Rachmawadi + - Anton Chernikov (anton_ch1989) - Kristen Gilden (kgilden) - Pierre-Yves LEBECQ (pylebecq) - Jordan Samouh (jordansamouh) - Baptiste Lafontaine (magnetik) - Jakub Kucharovic (jkucharovic) - Edi Modrić (emodric) - - Alexander Schranz (alexander-schranz) - Uwe Jäger (uwej711) - Eugene Leonovich (rybakit) - Filippo Tessarotto @@ -311,18 +314,20 @@ Symfony is the result of the work of many people who made the code better - Beau Simensen (simensen) - Michael Hirschler (mvhirsch) - Robert Kiss (kepten) + - Zan Baldwin (zanderbaldwin) - Roumen Damianoff (roumen) - Antonio J. García Lagar (ajgarlag) - Kim Hemsø Rasmussen (kimhemsoe) + - Przemysław Bogusz (przemyslaw-bogusz) - Pascal Luna (skalpa) - Wouter Van Hecke - Jérôme Parmentier (lctrs) - Peter Kruithof (pkruithof) - Michael Holm (hollo) - - Remon van de Kamp (rpkamp) - Mathieu Lechat - Marc Weistroff (futurecat) - Christian Schmidt + - Patrick Landolt (scube) - MatTheCat - Chad Sikorra (chadsikorra) - Chris Smith (cs278) @@ -342,6 +347,7 @@ Symfony is the result of the work of many people who made the code better - Jerzy Zawadzki (jzawadzki) - Wouter J - Ismael Ambrosi (iambrosi) + - Emmanuel BORGES (eborges78) - François Pluchino (francoispluchino) - Aurelijus Valeiša (aurelijus) - Jan Decavele (jandc) @@ -360,18 +366,18 @@ Symfony is the result of the work of many people who made the code better - janschoenherr - Emanuele Gaspari (inmarelibero) - Dariusz Rumiński - - Vincent Touzet (vincenttouzet) - Berny Cantos (xphere81) - Thierry Thuon (lepiaf) - Ricard Clau (ricardclau) - Mark Challoner (markchalloner) - Gennady Telegin (gtelegin) + - renanbr - Erin Millard - Artur Melo (restless) - Matthew Lewinski (lewinski) - Magnus Nordlander (magnusnordlander) - - Zan Baldwin (zanderbaldwin) - Thomas Royer (cydonia7) + - Nicolas LEFEVRE (nicoweb) - alquerci - Mateusz Sip (mateusz_sip) - Andre Rømcke (andrerom) @@ -402,12 +408,13 @@ Symfony is the result of the work of many people who made the code better - ShinDarth - Stéphane PY (steph_py) - Philipp Kräutli (pkraeutli) - - Anton Chernikov (anton_ch1989) - Grzegorz (Greg) Zdanowski (kiler129) - Iker Ibarguren (ikerib) - Kirill chEbba Chebunin (chebba) - Greg Thornton (xdissent) + - Quynh Xuan Nguyen (xuanquynh) - Martin Hujer (martinhujer) + - Philipp Cordes - Costin Bereveanu (schniper) - Loïc Chardonnet (gnusat) - Marek Kalnik (marekkalnik) @@ -426,7 +433,6 @@ Symfony is the result of the work of many people who made the code better - Romain Gautier (mykiwi) - Joe Lencioni - Daniel Tschinder - - Emmanuel BORGES (eborges78) - vladimir.reznichenko - Kai - Lee Rowlands @@ -460,7 +466,6 @@ Symfony is the result of the work of many people who made the code better - Webnet team (webnet) - Jan Schumann - Niklas Fiekas - - renanbr - Markus Bachmann (baachi) - lancergr - Mihai Stancu @@ -468,7 +473,6 @@ Symfony is the result of the work of many people who made the code better - Olivier Dolbeau (odolbeau) - Jan Rosier (rosier) - Alessandro Lai (jean85) - - Nicolas LEFEVRE (nicoweb) - Arturs Vonda - Josip Kruslin - Asmir Mustafic (goetas) @@ -477,9 +481,12 @@ Symfony is the result of the work of many people who made the code better - EdgarPE - Florian Pfitzer (marmelatze) - Asier Illarramendi (doup) + - Martijn Cuppens - Vlad Gregurco (vgregurco) + - Maciej Malarz (malarzm) - Boris Vujicic (boris.vujicic) - Chris Sedlmayr (catchamonkey) + - Dmytro Borysovskyi (dmytr0) - Kamil Kokot (pamil) - Seb Koelen - Christoph Mewes (xrstf) @@ -491,7 +498,7 @@ Symfony is the result of the work of many people who made the code better - Jonas Flodén (flojon) - Gonzalo Vilaseca (gonzalovilaseca) - Marcin Sikoń (marphi) - - Przemysław Bogusz (przemyslaw-bogusz) + - Denis Brumann (dbrumann) - Dominik Zogg (dominik.zogg) - Marek Pietrzak - Luc Vieillescazes (iamluc) @@ -506,6 +513,7 @@ Symfony is the result of the work of many people who made the code better - Rhodri Pugh (rodnaph) - Sam Fleming (sam_fleming) - Alex Bakhturin + - Pol Dellaiera (drupol) - insekticid - Alexander Obuhovich (aik099) - boombatower @@ -534,7 +542,6 @@ Symfony is the result of the work of many people who made the code better - ondrowan - Barry vd. Heuvel (barryvdh) - Craig Duncan (duncan3dc) - - Patrick Landolt (scube) - Sébastien Alfaiate (seb33300) - Evan S Kaufman (evanskaufman) - mcben @@ -559,10 +566,10 @@ Symfony is the result of the work of many people who made the code better - Almog Baku (almogbaku) - Scott Arciszewski - Xavier HAUSHERR - - Philipp Cordes - Norbert Orzechowicz (norzechowicz) - Denis Charrier (brucewouaigne) - Matthijs van den Bos (matthijs) + - Jaik Dean (jaikdean) - Lenard Palko - Nils Adermann (naderman) - Gábor Fási @@ -604,6 +611,7 @@ Symfony is the result of the work of many people who made the code better - Aurélien Fredouelle - Pavel Campr (pcampr) - Johnny Robeson (johnny) + - Marko Kaznovac (kaznovac) - Disquedur - Michiel Boeckaert (milio) - Geoffrey Tran (geoff) @@ -634,6 +642,7 @@ Symfony is the result of the work of many people who made the code better - Sinan Eldem - Alexandre Dupuy (satchette) - Malte Blättermann + - Desjardins Jérôme (jewome62) - Kévin THERAGE (kevin_therage) - Simeon Kolev (simeon_kolev9) - Nahuel Cuesta (ncuesta) @@ -667,11 +676,11 @@ Symfony is the result of the work of many people who made the code better - DerManoMann - Rostyslav Kinash - Dennis Fridrich (dfridrich) - - Maciej Malarz (malarzm) - Daisuke Ohata - Vincent Simonin - Alex Bogomazov (alebo) - maxime.steinhausser + - dFayet - adev - Stefan Warman - Arkadius Stefanski (arkadius) @@ -686,7 +695,6 @@ Symfony is the result of the work of many people who made the code better - Miquel Rodríguez Telep (mrtorrent) - Sergey Kolodyazhnyy (skolodyazhnyy) - umpirski - - Denis Brumann (dbrumann) - Quentin de Longraye (quentinus95) - Chris Heng (gigablah) - Shaun Simmons (simshaun) @@ -736,6 +744,7 @@ Symfony is the result of the work of many people who made the code better - VJ - RJ Garcia - Delf Tonder (leberknecht) + - Raulnet - Mark Sonnabaum - Massimiliano Braglia (massimilianobraglia) - Richard Quadling @@ -746,6 +755,8 @@ Symfony is the result of the work of many people who made the code better - Michael Piecko - yclian - Aleksey Prilipko + - Tomas Norkūnas (norkunas) + - Andrew Berry - twifty - Indra Gunawan (guind) - Peter Ward @@ -783,6 +794,7 @@ Symfony is the result of the work of many people who made the code better - Andrew Tchircoff (andrewtch) - michaelwilliams - 1emming + - Matthias Althaus - Leevi Graham (leevigraham) - Nykopol (nykopol) - Tri Pham (phamuyentri) @@ -813,6 +825,7 @@ Symfony is the result of the work of many people who made the code better - Denis Zunke (donalberto) - Ahmadou Waly Ndiaye (waly) - Ahmed TAILOULOUTE (ahmedtai) + - Jonathan Johnson (jrjohnson) - Olivier Maisonneuve (olineuve) - Pedro Miguel Maymone de Resende (pedroresende) - Masterklavi @@ -824,7 +837,6 @@ Symfony is the result of the work of many people who made the code better - Jayson Xu (superjavason) - Christopher Hertel (chertel) - Hubert Lenoir (hubert_lenoir) - - Jaik Dean (jaikdean) - fago - Harm van Tilborg - Jan Prieser @@ -842,7 +854,6 @@ Symfony is the result of the work of many people who made the code better - Reen Lokum - Andreas Möller (localheinz) - Martin Parsiegla (spea) - - Nguyen Xuan Quynh (xuanquynh) - Quentin Schuler - Pierre Vanliefland (pvanliefland) - Roy Klutman (royklutman) @@ -854,6 +865,7 @@ Symfony is the result of the work of many people who made the code better - Abhoryo - Fabian Vogler (fabian) - Korvin Szanto + - soyuka - Stéphan Kochen - Arjan Keeman - Alaattin Kahramanlar (alaattin) @@ -938,7 +950,6 @@ Symfony is the result of the work of many people who made the code better - Stéphane Delprat - Brian Freytag (brianfreytag) - Samuele Lilli (doncallisto) - - Marko Kaznovac (kaznovac) - Brunet Laurent (lbrunet) - Florent Viel (luxifer) - Mikhail Yurasov (mym) @@ -949,11 +960,13 @@ Symfony is the result of the work of many people who made the code better - Benoît Merlet (trompette) - Koen Kuipers - datibbaw + - Rokas Mikalkėnas (rokasm) - Erik Saunier (snickers) - Rootie - Kyle - Daniel Alejandro Castro Arellano (lexcast) - sensio + - Chris Tanaskoski - Thomas Jarrand - Sebastien Morel (plopix) - Patrick Kaufmann @@ -961,6 +974,7 @@ Symfony is the result of the work of many people who made the code better - Reece Fowell (reecefowell) - Mátyás Somfai (smatyas) - stefan.r + - Guillaume Gammelin - Valérian Galliat - d-ph - Renan Taranto (renan-taranto) @@ -971,6 +985,7 @@ Symfony is the result of the work of many people who made the code better - Christian Jul Jensen - Alexandre GESLIN (alexandregeslin) - The Whole Life to Learn + - Mikkel Paulson - ergiegonzaga - Farhad Safarov - Alexis Lefebvre @@ -978,6 +993,7 @@ Symfony is the result of the work of many people who made the code better - Sam Malone - Phan Thanh Ha (haphan) - Chris Jones (leek) + - neghmurken - xaav - Mahmoud Mostafa (mahmoud) - Ahmed Abdou @@ -988,6 +1004,7 @@ Symfony is the result of the work of many people who made the code better - Sander Marechal - Franz Wilding (killerpoke) - ProgMiner + - Jonas Elfering - Oleg Golovakhin (doc_tr) - Joost van Driel - Icode4Food (icode4food) @@ -1096,6 +1113,7 @@ Symfony is the result of the work of many people who made the code better - Christian - Denis Golubovskiy (bukashk0zzz) - Sergii Smertin (nfx) + - Mikkel Paulson - Michał Strzelecki - Soner Sayakci - hugofonseca (fonsecas72) @@ -1200,6 +1218,7 @@ Symfony is the result of the work of many people who made the code better - MightyBranch - Kacper Gunia (cakper) - Peter Thompson (petert82) + - error56 - Felicitus - Krzysztof Przybyszewski - alexpozzi @@ -1237,6 +1256,7 @@ Symfony is the result of the work of many people who made the code better - stoccc - Tomasz Szymczyk (karion) - Alex Vasilchenko + - sez-open - Xavier Coureau - ConneXNL - Aharon Perkel @@ -1273,7 +1293,6 @@ Symfony is the result of the work of many people who made the code better - Saem Ghani - Clément LEFEBVRE - Conrad Kleinespel - - Matthias Althaus - Zacharias Luiten - Sebastian Utz - Adrien Gallou (agallou) @@ -1299,6 +1318,7 @@ Symfony is the result of the work of many people who made the code better - Andy Raines - Anthony Ferrara - Geoffrey Pécro (gpekz) + - Jérémy DECOOL (jdecool) - Klaas Cuvelier (kcuvelier) - Flavien Knuchel (knuch) - Mathieu TUDISCO (mathieutu) @@ -1330,7 +1350,6 @@ Symfony is the result of the work of many people who made the code better - Philip Frank - Lance McNearney - Giorgio Premi - - Andrew Berry - ncou - Ian Carroll - caponica @@ -1361,7 +1380,6 @@ Symfony is the result of the work of many people who made the code better - Daniel González Zaballos (dem3trio) - Emmanuel Vella (emmanuel.vella) - Guillaume BRETOU (guiguiboy) - - Jonathan Johnson (jrjohnson) - Dāvis Zālītis (k0d3r1s) - Carsten Nielsen (phreaknerd) - Roger Guasch (rogerguasch) @@ -1417,11 +1435,13 @@ Symfony is the result of the work of many people who made the code better - Mike Meier - Tim Jabs - Sebastian Ionescu + - Pablo Ogando Ferreira - Thomas Ploch - Simon Neidhold - Valentin VALCIU - Jeremiah VALERIE - Julien Menth + - Yannick Snobbert - Kevin Dew - James Cowgill - 1ma (jautenim) @@ -1436,6 +1456,7 @@ Symfony is the result of the work of many people who made the code better - Lance Chen - Ciaran McNulty (ciaranmcnulty) - Andrew (drew) + - Giso Stallenberg (gisostallenberg) - kor3k kor3k (kor3k) - Stelian Mocanita (stelian) - Justin (wackymole) @@ -1495,7 +1516,6 @@ Symfony is the result of the work of many people who made the code better - Martynas Sudintas (martiis) - ryunosuke - victoria - - Dmytro Borysovskyi (dmytr0) - Francisco Facioni (fran6co) - Iwan van Staveren (istaveren) - Povilas S. (povilas) @@ -1579,6 +1599,7 @@ Symfony is the result of the work of many people who made the code better - loru88 - Romain Dorgueil - Christopher Parotat + - Dennis Haarbrink - me_shaon - 蝦米 - Grayson Koonce (breerly) @@ -1632,6 +1653,7 @@ Symfony is the result of the work of many people who made the code better - Jan van Thoor (janvt) - Johannes Müller (johmue) - Jordi Llonch (jordillonch) + - Nicholas Ruunu (nicholasruunu) - Cédric Dugat (ph3nol) - Philip Dahlstrøm (phidah) - Milos Colakovic (project2481) @@ -1713,6 +1735,7 @@ Symfony is the result of the work of many people who made the code better - Pedro Magalhães (pmmaga) - Rares Vlaseanu (raresvla) - tante kinast (tante) + - Ahmed Hannachi (tiecoders) - Vincent LEFORT (vlefort) - Darryl Hein (xmmedia) - Sadicov Vladimir (xtech) @@ -1734,6 +1757,7 @@ Symfony is the result of the work of many people who made the code better - Oleg Andreyev - neFAST - Pierre Rineau + - Florian Morello - Maxim Lovchikov - adenkejawen - Florent SEVESTRE (aniki-taicho) @@ -1764,7 +1788,6 @@ Symfony is the result of the work of many people who made the code better - timaschew - Jochen Mandl - Marin Nicolae - - soyuka - Alessandro Loffredo - Ian Phillips - Marco Lipparini @@ -1786,6 +1809,7 @@ Symfony is the result of the work of many people who made the code better - insidestyles - Maerlyn - Even André Fiskvik + - Александр Ли - Arjan Keeman - Erik van Wingerden - Valouleloup @@ -1846,6 +1870,7 @@ Symfony is the result of the work of many people who made the code better - Michael Pohlers (mick_the_big) - mlpo (mlpo) - Marek Šimeček (mssimi) + - Dmitriy Tkachenko (neka) - Cayetano Soriano Gallego (neoshadybeat) - Olivier Laviale (olvlvl) - Ondrej Machulda (ondram) @@ -2114,7 +2139,6 @@ Symfony is the result of the work of many people who made the code better - Ala Eddine Khefifi (nayzo) - emilienbouard (neime) - Nicholas Byfleet (nickbyfleet) - - Tomas Norkūnas (norkunas) - Marco Petersen (ocrampete16) - ollie harridge (ollietb) - Dimitri Gritsajuk (ottaviano) @@ -2131,7 +2155,6 @@ Symfony is the result of the work of many people who made the code better - Ralf Kuehnel (ralfkuehnel) - Brayden Williams (redstar504) - Rich Sage (richsage) - - Rokas Mikalkėnas (rokasm) - Bart Ruysseveldt (ruyss) - scourgen hung (scourgen) - Sebastian Busch (sebu) diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php index d4b5ea26d8cd8..95dcb1e5541fc 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV5.php @@ -23,6 +23,24 @@ class CommandForV5 extends \PHPUnit_TextUI_Command */ protected function createRunner() { - return new TestRunnerForV5($this->arguments['loader']); + $listener = new SymfonyTestsListenerForV5(); + + $this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : array(); + + $registeredLocally = false; + + foreach ($this->arguments['listeners'] as $registeredListener) { + if ($registeredListener instanceof SymfonyTestsListenerForV5) { + $registeredListener->globalListenerDisabled(); + $registeredLocally = true; + break; + } + } + + if (!$registeredLocally) { + $this->arguments['listeners'][] = $listener; + } + + return parent::createRunner(); } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php index fc717ef415cb3..f8f75bb09a93b 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV6.php @@ -13,7 +13,7 @@ use PHPUnit\TextUI\Command as BaseCommand; use PHPUnit\TextUI\TestRunner as BaseRunner; -use Symfony\Bridge\PhpUnit\TextUI\TestRunner; +use Symfony\Bridge\PhpUnit\SymfonyTestsListener; /** * {@inheritdoc} @@ -27,6 +27,24 @@ class CommandForV6 extends BaseCommand */ protected function createRunner(): BaseRunner { - return new TestRunner($this->arguments['loader']); + $listener = new SymfonyTestsListener(); + + $this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : []; + + $registeredLocally = false; + + foreach ($this->arguments['listeners'] as $registeredListener) { + if ($registeredListener instanceof SymfonyTestsListener) { + $registeredListener->globalListenerDisabled(); + $registeredLocally = true; + break; + } + } + + if (!$registeredLocally) { + $this->arguments['listeners'][] = $listener; + } + + return parent::createRunner(); } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV5.php b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV5.php deleted file mode 100644 index 7897861cf52f7..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV5.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\Legacy; - -/** - * {@inheritdoc} - * - * @internal - */ -class TestRunnerForV5 extends \PHPUnit_TextUI_TestRunner -{ - /** - * {@inheritdoc} - */ - protected function handleConfiguration(array &$arguments) - { - $listener = new SymfonyTestsListenerForV5(); - - $result = parent::handleConfiguration($arguments); - - $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); - - $registeredLocally = false; - - foreach ($arguments['listeners'] as $registeredListener) { - if ($registeredListener instanceof SymfonyTestsListenerForV5) { - $registeredListener->globalListenerDisabled(); - $registeredLocally = true; - break; - } - } - - if (!$registeredLocally) { - $arguments['listeners'][] = $listener; - } - - return $result; - } -} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV6.php deleted file mode 100644 index 6da7c65448532..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV6.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * 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 deleted file mode 100644 index a175fb65d7f5a..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunnerForV7.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * 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/TestRunner.php b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php deleted file mode 100644 index cda59209790d5..0000000000000 --- a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PhpUnit\TextUI; - -if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { - 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 { - class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunnerForV7', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner'); -} - -if (false) { - class TestRunner - { - } -} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig index e1164cdfbce1b..708e149bce82b 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig @@ -108,10 +108,10 @@ {% block form_row -%}
- {{- form_label(form) -}} - {{- form_widget(form) -}} - {{- form_errors(form) -}} -
+ {{- form_label(form) }} {# -#} + {{ form_widget(form) }} {# -#} + {{ form_errors(form) }} {# -#} + {# -#} {%- endblock form_row %} {% block button_row -%} diff --git a/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php b/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php index 572685a6c4b35..71180156ba2b1 100644 --- a/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php +++ b/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php @@ -30,9 +30,14 @@ public function testCompile($source, $expected) { $env = new Environment($this->getMockBuilder('Twig\Loader\LoaderInterface')->getMock(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]); $env->addTokenParser(new FormThemeTokenParser()); - $stream = $env->tokenize(new Source($source, '')); + $source = new Source($source, ''); + $stream = $env->tokenize($source); $parser = new Parser($env); + if (method_exists($expected, 'setSourceContext')) { + $expected->setSourceContext($source); + } + $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index 7dd51bbed6a6a..5088554ff1947 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -93,7 +93,7 @@ The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. + The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php index d53bb45461f78..c8f8013f3f8a0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php @@ -126,14 +126,12 @@ public function testEncodePasswordOutput() public function testEncodePasswordEmptySaltOutput() { - $this->passwordEncoderCommandTester->execute( - [ - 'command' => 'security:encode-password', - 'password' => 'p@ssw0rd', - 'user-class' => 'Symfony\Component\Security\Core\User\User', - '--empty-salt' => true, - ] - ); + $this->passwordEncoderCommandTester->execute([ + 'command' => 'security:encode-password', + 'password' => 'p@ssw0rd', + 'user-class' => 'Symfony\Component\Security\Core\User\User', + '--empty-salt' => true, + ]); $this->assertContains('Password encoding succeeded', $this->passwordEncoderCommandTester->getDisplay()); $this->assertContains(' Encoded password p@ssw0rd', $this->passwordEncoderCommandTester->getDisplay()); diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/images/chevron-right.svg b/src/Symfony/Bundle/TwigBundle/Resources/views/images/chevron-right.svg index 6f07d1b5b0bbc..6837aff18bd20 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/images/chevron-right.svg +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/images/chevron-right.svg @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/images/icon-plus-square.svg b/src/Symfony/Bundle/TwigBundle/Resources/views/images/icon-plus-square.svg index 8fbf7c4608d90..2f5c3b3583076 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/images/icon-plus-square.svg +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/images/icon-plus-square.svg @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-ghost.svg b/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-ghost.svg index 08234346851b0..58266bcbfaf38 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-ghost.svg +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-ghost.svg @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-logo.svg b/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-logo.svg index 785341864d00b..f10824ae96f6a 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-logo.svg +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/images/symfony-logo.svg @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig index e9a4a51b403f6..dd95b511b40ea 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig @@ -95,7 +95,11 @@

Execution timeline

- {% if collector.events is empty %} + {% if not collector.isStopwatchInstalled() %} +
+

The Stopwatch component is not installed. If you want to see timing events, run: composer require symfony/stopwatch.

+
+ {% elseif collector.events is empty %}

No timing events have been recorded. Are you sure that debugging is enabled in the kernel?

diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/ajax.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/ajax.svg index bd878c3c6c21f..4019e3249bb22 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/ajax.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/ajax.svg @@ -1,6 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg index 5b36ae37e0158..798198928a6fb 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/cache.svg @@ -1,3 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/close.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/close.svg index 4eef3ee624ed2..6038d73f9ab5b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/close.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/close.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg index a407719e8c761..ba51407d12d32 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/config.svg @@ -1,3 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/event.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/event.svg index 898117ef94558..76eaa32453453 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/event.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/event.svg @@ -1,11 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg index aafe2e874b30c..0e4df2b23a23c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/exception.svg @@ -1,15 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg index 84fa75b6c3bf5..e1307960bd568 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/form.svg @@ -1,6 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/forward.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/forward.svg index 8b92d448fefd6..28a960a5bf835 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/forward.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/forward.svg @@ -1,4 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg index 5c46bbb2e4957..ae8c5aae4447c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/logger.svg @@ -1,7 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg index 104ba57aeac79..deb047fc4a0ba 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/memory.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg index 3c863393bc1ab..afccc7f629bb5 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/menu.svg @@ -1,3 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/no.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/no.svg index c0bb768d40433..5ffc020f4efe8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/no.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/no.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg index dcdd15c288bd8..8c329d052f2c1 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/redirect.svg @@ -1,10 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/request.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/request.svg index 68b092c6b8809..67d6c643fced5 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/request.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/request.svg @@ -1,16 +1 @@ - - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/router.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/router.svg index 3b7f94355b37d..e16c617ebe18c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/router.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/router.svg @@ -1,6 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/search.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/search.svg index 7c8724aab54c1..cae0a67f9120b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/search.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/search.svg @@ -1,7 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg index 30b1c2e9aad51..c3beff6c8ddfd 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg @@ -1,12 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg index 8ae615d38bc84..d49851d440be4 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/time.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/translation.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/translation.svg index b7aa1249304b2..735bb92c78b70 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/translation.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/translation.svg @@ -1,13 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg index bfb4e6dea1e09..8c6ad014e4783 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/twig.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/validator.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/validator.svg index 0b60184f9def5..6a81d92daa786 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/validator.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/validator.svg @@ -1 +1 @@ - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/yes.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/yes.svg index da650231d520d..dbbff93d007d7 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/yes.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/yes.svg @@ -1,5 +1 @@ - - - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig index 9c76e0ad8f33d..8953316de06f2 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -5,8 +5,6 @@ background-color: #222; border-top-left-radius: 4px; bottom: 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; box-sizing: border-box; display: none; height: 36px; @@ -36,8 +34,6 @@ } .sf-toolbarreset * { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; box-sizing: content-box; vertical-align: baseline; letter-spacing: normal; @@ -367,21 +363,8 @@ div.sf-toolbar .sf-toolbar-block a:hover { text-align: right; } .sf-ajax-request-loading { - -webkit-animation: sf-blink .5s ease-in-out infinite; - -o-animation: sf-blink .5s ease-in-out infinite; - -moz-animation: sf-blink .5s ease-in-out infinite; animation: sf-blink .5s ease-in-out infinite; } -@-webkit-keyframes sf-blink { - 0% { background: #222; } - 50% { background: #444; } - 100% { background: #222; } -} -@-moz-keyframes sf-blink { - 0% { background: #222; } - 50% { background: #444; } - 100% { background: #222; } -} @keyframes sf-blink { 0% { background: #222; } 50% { background: #444; } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig index 2dc3eaff9635d..f13edfcfebf52 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig @@ -114,7 +114,7 @@ var sfwdt = document.getElementById('sfwdt{{ token }}'); sfwdt.innerHTML = '\
\ -
\ +
\ An error occurred while loading the web debug toolbar. Open the web profiler.\
\ '; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php index 040d4003f5c54..c3d691d4d3dbf 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Resources/IconTest.php @@ -20,7 +20,7 @@ class IconTest extends TestCase */ public function testIconFileContents($iconFilePath) { - $this->assertRegExp('~.*~s', file_get_contents($iconFilePath), sprintf('The SVG metadata of the %s icon is different than expected (use the same as the other icons).', $iconFilePath)); + $this->assertRegExp('~.*~s', file_get_contents($iconFilePath), sprintf('The SVG metadata of the %s icon is different than expected (use the same as the other icons).', $iconFilePath)); } public function provideIconFilePaths() diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index c1efe11a26bb1..241a5e350b414 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^5.5.9|>=7.0.8", - "symfony/http-kernel": "~3.3|~4.0", + "symfony/http-kernel": "~3.4.25|^4.2.6", "symfony/polyfill-php70": "~1.0", "symfony/routing": "~2.8|~3.0|~4.0", "symfony/twig-bridge": "~2.8|~3.0|~4.0", diff --git a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php index ab6d118589c11..a21a9481a7ac6 100644 --- a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php @@ -621,6 +621,9 @@ public function testRestart() $this->assertEquals([], $client->getCookieJar()->all(), '->restart() clears the cookies'); } + /** + * @runInSeparateProcess + */ public function testInsulatedRequests() { $client = new TestClient(); diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index 9060523609159..0b7918287e90a 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -45,12 +45,15 @@ public function __construct(CacheItemPoolInterface $pool, $namespace = '', $defa function ($key, $innerItem) use ($defaultLifetime, $poolHash) { $item = new CacheItem(); $item->key = $key; - $item->value = $innerItem->get(); - $item->isHit = $innerItem->isHit(); $item->defaultLifetime = $defaultLifetime; - $item->innerItem = $innerItem; $item->poolHash = $poolHash; - $innerItem->set(null); + + if (null !== $innerItem) { + $item->value = $innerItem->get(); + $item->isHit = $innerItem->isHit(); + $item->innerItem = $innerItem; + $innerItem->set(null); + } return $item; }, @@ -156,7 +159,18 @@ private function doSave(CacheItemInterface $item, $method) if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) { $expiry = time() + $item["\0*\0defaultLifetime"]; } - $innerItem = $item["\0*\0poolHash"] === $this->poolHash ? $item["\0*\0innerItem"] : $this->pool->getItem($this->namespace.$item["\0*\0key"]); + + if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) { + $innerItem = $item["\0*\0innerItem"]; + } elseif ($this->pool instanceof AdapterInterface) { + // this is an optimization specific for AdapterInterface implementations + // so we can save a round-trip to the backend by just creating a new item + $f = $this->createCacheItem; + $innerItem = $f($this->namespace.$item["\0*\0key"], null); + } else { + $innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]); + } + $innerItem->set($item["\0*\0value"]); $innerItem->expiresAt(null !== $expiry ? \DateTime::createFromFormat('U', $expiry) : null); diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index d453e271aef4c..362aceed0eb18 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -48,7 +48,6 @@ function ($key, $value, CacheItem $protoItem) { $item->value = $value; $item->defaultLifetime = $protoItem->defaultLifetime; $item->expiry = $protoItem->expiry; - $item->innerItem = $protoItem->innerItem; $item->poolHash = $protoItem->poolHash; return $item; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php new file mode 100644 index 0000000000000..b11c1f2870545 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/TagAwareAndProxyAdapterIntegrationTest.php @@ -0,0 +1,38 @@ +getItem('foo'); + $item->tag(['tag1', 'tag2']); + $item->set('bar'); + $cache->save($item); + + $this->assertSame('bar', $cache->getItem('foo')->get()); + } + + public function dataProvider() + { + return [ + [new ArrayAdapter()], + // also testing with a non-AdapterInterface implementation + // because the ProxyAdapter behaves slightly different for those + [new ExternalAdapter()], + ]; + } +} diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index 9c200fe897ea9..677aa764d4a0f 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -49,7 +49,13 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu if (!$input->isInteractive()) { $default = $question->getDefault(); - if (null !== $default && $question instanceof ChoiceQuestion) { + if (null === $default) { + return $default; + } + + if ($validator = $question->getValidator()) { + return \call_user_func($question->getValidator(), $default); + } elseif ($question instanceof ChoiceQuestion) { $choices = $question->getChoices(); if (!$question->isMultiselect()) { @@ -254,6 +260,7 @@ private function autocomplete(OutputInterface $output, Question $question, $inpu // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false. if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) { + shell_exec(sprintf('stty %s', $sttyMode)); throw new RuntimeException('Aborted.'); } elseif ("\177" === $c) { // Backspace Character if (0 === $numMatches && 0 !== $i) { diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index ae9d130f502ab..200568f0701d7 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -41,6 +41,21 @@ class ApplicationTest extends TestCase { protected static $fixturesPath; + private $colSize; + + protected function setUp() + { + $this->colSize = getenv('COLUMNS'); + } + + protected function tearDown() + { + putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS'); + putenv('SHELL_VERBOSITY'); + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + } + public static function setUpBeforeClass() { self::$fixturesPath = realpath(__DIR__.'/Fixtures/'); @@ -383,6 +398,7 @@ public function testFindWithCommandLoader() */ public function testFindWithAmbiguousAbbreviations($abbreviation, $expectedExceptionMessage) { + putenv('COLUMNS=120'); if (method_exists($this, 'expectException')) { $this->expectException('Symfony\Component\Console\Exception\CommandNotFoundException'); $this->expectExceptionMessage($expectedExceptionMessage); @@ -468,6 +484,7 @@ public function provideInvalidCommandNamesSingle() public function testFindAlternativeExceptionMessageMultiple() { + putenv('COLUMNS=120'); $application = new Application(); $application->add(new \FooCommand()); $application->add(new \Foo1Command()); @@ -1689,13 +1706,6 @@ public function testErrorIsRethrownIfNotHandledByConsoleErrorEventWithCatchingEn $this->assertSame($e->getMessage(), 'Class \'UnknownClass\' not found'); } } - - protected function tearDown() - { - putenv('SHELL_VERBOSITY'); - unset($_ENV['SHELL_VERBOSITY']); - unset($_SERVER['SHELL_VERBOSITY']); - } } class CustomApplication extends Application diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index e352311ccad09..a0be9b8a6d94d 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -21,6 +21,19 @@ */ class ProgressBarTest extends TestCase { + private $colSize; + + protected function setUp() + { + $this->colSize = getenv('COLUMNS'); + putenv('COLUMNS=120'); + } + + protected function tearDown() + { + putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS'); + } + public function testMultipleStart() { $bar = new ProgressBar($output = $this->getOutputStream()); diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 46cfe8184278d..56ba1c6891322 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -137,6 +137,9 @@ public function testAskChoiceNonInteractive() $question->setMultiselect(true); $this->assertNull($questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question)); + $question = new ChoiceQuestion('Who are your favorite superheros?', ['a' => 'Batman', 'b' => 'Superman'], 'a'); + $this->assertSame('a', $questionHelper->ask($this->createStreamableInputInterfaceMock('', false), $this->createOutputInterface(), $question), 'ChoiceQuestion validator returns the key if it\'s a string'); + try { $question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, ''); $question->setMultiselect(true); diff --git a/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php b/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php index e6e061f4416b6..88d00c8a9926b 100644 --- a/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php +++ b/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php @@ -26,9 +26,11 @@ class SymfonyStyleTest extends TestCase protected $command; /** @var CommandTester */ protected $tester; + private $colSize; protected function setUp() { + $this->colSize = getenv('COLUMNS'); putenv('COLUMNS=121'); $this->command = new Command('sfstyle'); $this->tester = new CommandTester($this->command); @@ -36,7 +38,7 @@ protected function setUp() protected function tearDown() { - putenv('COLUMNS'); + putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS'); $this->command = null; $this->tester = null; } diff --git a/src/Symfony/Component/Console/Tests/TerminalTest.php b/src/Symfony/Component/Console/Tests/TerminalTest.php index 91af1d0ab459d..93b8c44a78158 100644 --- a/src/Symfony/Component/Console/Tests/TerminalTest.php +++ b/src/Symfony/Component/Console/Tests/TerminalTest.php @@ -16,6 +16,21 @@ class TerminalTest extends TestCase { + private $colSize; + private $lineSize; + + protected function setUp() + { + $this->colSize = getenv('COLUMNS'); + $this->lineSize = getenv('LINES'); + } + + protected function tearDown() + { + putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS'); + putenv($this->lineSize ? 'LINES' : 'LINES='.$this->lineSize); + } + public function test() { putenv('COLUMNS=100'); diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index 2f3d56601271c..b79e83ea2c9bf 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -372,7 +372,7 @@ private function formatPath($path, $line) $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 : ''); + return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); } if (\is_string($fmt)) { @@ -388,7 +388,11 @@ private function formatPath($path, $line) $link = strtr($fmt[0], ['%f' => $path, '%l' => $line]); } else { - $link = $fmt->format($path, $line); + try { + $link = $fmt->format($path, $line); + } catch (\Exception $e) { + return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); + } } return sprintf('in %s%s', $this->escapeHtml($link), $file, 0 < $line ? ' line '.$line : ''); @@ -435,6 +439,6 @@ private function escapeHtml($str) private function getSymfonyGhostAsSvg() { - return ''; + return ''; } } diff --git a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php index c1dea75bbd90e..15de37c7b7808 100644 --- a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -525,7 +525,7 @@ public function testHandleFatalError() */ public function testHandleErrorException() { - $exception = new \Error("Class 'Foo' not found"); + $exception = new \Error("Class 'IReallyReallyDoNotExistAnywhereInTheRepositoryISwear' not found"); $handler = new ErrorHandler(); $handler->setExceptionHandler(function () use (&$args) { @@ -535,7 +535,7 @@ public function testHandleErrorException() $handler->handleException($exception); $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]); - $this->assertStringStartsWith("Attempted to load class \"Foo\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage()); + $this->assertStringStartsWith("Attempted to load class \"IReallyReallyDoNotExistAnywhereInTheRepositoryISwear\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage()); } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 252b304f118f5..c8e7a0f575f4e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -181,7 +181,15 @@ private function autowireCalls(\ReflectionClass $reflectionClass, array $methodC if ($method instanceof \ReflectionFunctionAbstract) { $reflectionMethod = $method; } else { - $reflectionMethod = $this->getReflectionMethod(new Definition($reflectionClass->name), $method); + $definition = new Definition($reflectionClass->name); + try { + $reflectionMethod = $this->getReflectionMethod($definition, $method); + } catch (RuntimeException $e) { + if ($definition->getFactory()) { + continue; + } + throw $e; + } } $arguments = $this->autowireMethod($reflectionMethod, $arguments); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php index 20b262b6d461c..f3c4b593cccc2 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php @@ -34,6 +34,8 @@ class ResolveBindingsPass extends AbstractRecursivePass */ public function process(ContainerBuilder $container) { + $this->usedBindings = $container->getRemovedBindingIds(); + try { parent::process($container); @@ -115,7 +117,14 @@ protected function processValue($value, $isRoot = false) if ($method instanceof \ReflectionFunctionAbstract) { $reflectionMethod = $method; } else { - $reflectionMethod = $this->getReflectionMethod($value, $method); + try { + $reflectionMethod = $this->getReflectionMethod($value, $method); + } catch (RuntimeException $e) { + if ($value->getFactory()) { + continue; + } + throw $e; + } } foreach ($reflectionMethod->getParameters() as $key => $parameter) { diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 0c1966a42e255..72307464a7b5e 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -123,6 +123,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface private $removedIds = []; + private $removedBindingIds = []; + private static $internalTypes = [ 'int' => true, 'float' => true, @@ -1504,6 +1506,35 @@ public function normalizeId($id) return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || isset($this->removedIds[$id]) ? $id : parent::normalizeId($id); } + /** + * Gets removed binding ids. + * + * @return array + * + * @internal + */ + public function getRemovedBindingIds() + { + return $this->removedBindingIds; + } + + /** + * Adds a removed binding id. + * + * @param int $id + * + * @internal + */ + public function addRemovedBindingIds($id) + { + if ($this->hasDefinition($id)) { + foreach ($this->getDefinition($id)->getBindings() as $key => $binding) { + list(, $bindingId) = $binding->getValues(); + $this->removedBindingIds[(int) $bindingId] = true; + } + } + } + /** * Returns the Service Conditionals. * diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php index 12054cad0c021..21b1669d09eca 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ServiceConfigurator.php @@ -59,6 +59,8 @@ public function __destruct() { parent::__destruct(); + $this->container->addRemovedBindingIds($this->id); + if (!$this->definition instanceof ChildDefinition) { $this->container->setDefinition($this->id, $this->definition->setInstanceofConditionals($this->instanceof)); } else { diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index a428c6e35f6c4..0dd1a3d8bded4 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -91,6 +91,8 @@ public function registerClasses(Definition $prototype, $namespace, $resource, $e */ protected function setDefinition($id, Definition $definition) { + $this->container->addRemovedBindingIds($id); + if ($this->isLoadingInstanceof) { if (!$definition instanceof ChildDefinition) { throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, \get_class($definition))); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 6bd49fa5c6f82..31fa665ae7a85 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -21,6 +21,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic; use Symfony\Component\DependencyInjection\TypedReference; @@ -605,6 +606,22 @@ public function testSetterInjection() ); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @exceptedExceptionMessage Invalid service "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy": method "setLogger()" does not exist. + */ + public function testWithNonExistingSetterAndAutowiring() + { + $container = new ContainerBuilder(); + + $definition = $container->register(CaseSensitiveClass::class, CaseSensitiveClass::class)->setAutowired(true); + $definition->addMethodCall('setLogger'); + + (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); + (new AutowirePass())->process($container); + } + public function testExplicitMethodInjection() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php index 5118b416f93fa..7bbecf6207f46 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php @@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy; @@ -111,4 +112,24 @@ public function testScalarSetter() $this->assertEquals([['setDefaultLocale', ['fr']]], $definition->getMethodCalls()); } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @exceptedExceptionMessage Invalid service "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy": method "setLogger()" does not exist. + */ + public function testWithNonExistingSetterAndBinding() + { + $container = new ContainerBuilder(); + + $bindings = [ + '$c' => (new Definition('logger'))->setFactory('logger'), + ]; + + $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); + $definition->addMethodCall('setLogger'); + $definition->setBindings($bindings); + + $pass = new ResolveBindingsPass(); + $pass->process($container); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php index 83f2da12a70ab..1aaca2f1560c9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php @@ -13,8 +13,11 @@ class Bar implements BarInterface { + public $quz; + public function __construct($quz = null, \NonExistent $nonExistent = null, BarInterface $decorated = null, array $foo = []) { + $this->quz = $quz; } public static function create(\NonExistent $nonExistent = null, $factory = null) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/defaults_bindings.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/defaults_bindings.xml new file mode 100644 index 0000000000000..d943dfad2d7f7 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/defaults_bindings.xml @@ -0,0 +1,15 @@ + + + + + value + + value + + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/defaults_bindings2.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/defaults_bindings2.xml new file mode 100644 index 0000000000000..db41330f854eb --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/defaults_bindings2.xml @@ -0,0 +1,10 @@ + + + + + overridden + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/defaults_bindings.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/defaults_bindings.yml new file mode 100644 index 0000000000000..ca5e5048de122 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/defaults_bindings.yml @@ -0,0 +1,11 @@ +services: + _defaults: + bind: + $quz: value + $foo: [value] + + bar: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\Bar + + foo: + class: stdClass diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/defaults_bindings2.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/defaults_bindings2.yml new file mode 100644 index 0000000000000..01fc5af62458c --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/defaults_bindings2.yml @@ -0,0 +1,7 @@ +services: + _defaults: + bind: + $quz: overridden + + bar: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\Bar diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index d89887acca9ea..03376a641dcc0 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\GlobResource; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\Loader\IniFileLoader; @@ -820,4 +821,20 @@ public function testTsantosContainer() $dump = $dumper->dump(); $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_tsantos.php', $dumper->dump()); } + + /** + * The pass may throw an exception, which will cause the test to fail. + */ + public function testOverriddenDefaultsBindings() + { + $container = new ContainerBuilder(); + + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('defaults_bindings.xml'); + $loader->load('defaults_bindings2.xml'); + + (new ResolveBindingsPass())->process($container); + + $this->assertSame('overridden', $container->get('bar')->quz); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 7bcf0ec057584..05521bf7846f7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\GlobResource; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; @@ -732,4 +733,20 @@ public function testBindings() '$factory' => 'factory', ], array_map(function ($v) { return $v->getValues()[0]; }, $definition->getBindings())); } + + /** + * The pass may throw an exception, which will cause the test to fail. + */ + public function testOverriddenDefaultsBindings() + { + $container = new ContainerBuilder(); + + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('defaults_bindings.yml'); + $loader->load('defaults_bindings2.yml'); + + (new ResolveBindingsPass())->process($container); + + $this->assertSame('overridden', $container->get('bar')->quz); + } } diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index aac35bc234195..c0eac6da12b59 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -339,6 +339,10 @@ public function testInWithNonDirectoryGlob() public function testInWithGlobBrace() { + if (!\defined('GLOB_BRACE')) { + $this->markTestSkipped('Glob brace is not supported on this system.'); + } + $finder = $this->buildFinder(); $finder->in([__DIR__.'/Fixtures/{A,copy/A}/B/C'])->getIterator(); diff --git a/src/Symfony/Component/Form/AbstractExtension.php b/src/Symfony/Component/Form/AbstractExtension.php index f4ac2e5ce2b0f..d5ab6bd48b883 100644 --- a/src/Symfony/Component/Form/AbstractExtension.php +++ b/src/Symfony/Component/Form/AbstractExtension.php @@ -36,7 +36,7 @@ abstract class AbstractExtension implements FormExtensionInterface /** * The type guesser provided by this extension. * - * @var FormTypeGuesserInterface + * @var FormTypeGuesserInterface|null */ private $typeGuesser; @@ -136,7 +136,7 @@ protected function loadTypeExtensions() /** * Registers the type guesser. * - * @return FormTypeGuesserInterface|null A type guesser + * @return FormTypeGuesserInterface|null */ protected function loadTypeGuesser() { diff --git a/src/Symfony/Component/Form/AbstractRendererEngine.php b/src/Symfony/Component/Form/AbstractRendererEngine.php index 649b600ddcf23..00bc254daefd3 100644 --- a/src/Symfony/Component/Form/AbstractRendererEngine.php +++ b/src/Symfony/Component/Form/AbstractRendererEngine.php @@ -133,7 +133,7 @@ abstract protected function loadResourceForBlockName($cacheKey, FormView $view, * resource * @param FormView $view The form view for finding the applying * themes - * @param array $blockNameHierarchy The block hierarchy, with the most + * @param string[] $blockNameHierarchy The block hierarchy, with the most * specific block name at the end * @param int $hierarchyLevel The level in the block hierarchy that * should be loaded diff --git a/src/Symfony/Component/Form/Button.php b/src/Symfony/Component/Form/Button.php index 8ae4946c565e0..ed1106b467a21 100644 --- a/src/Symfony/Component/Form/Button.php +++ b/src/Symfony/Component/Form/Button.php @@ -22,7 +22,7 @@ class Button implements \IteratorAggregate, FormInterface { /** - * @var FormInterface|null + * @var FormInterface */ private $parent; @@ -111,6 +111,8 @@ public function setParent(FormInterface $parent = null) } $this->parent = $parent; + + return $this; } /** @@ -199,11 +201,13 @@ public function getErrors($deep = false, $flatten = true) * This method should not be invoked. * * @param mixed $modelData + * + * @return $this */ public function setData($modelData) { - // called during initialization of the form tree - // noop + // no-op, called during initialization of the form tree + return $this; } /** @@ -211,6 +215,7 @@ public function setData($modelData) */ public function getData() { + return null; } /** @@ -218,6 +223,7 @@ public function getData() */ public function getNormData() { + return null; } /** @@ -225,6 +231,7 @@ public function getNormData() */ public function getViewData() { + return null; } /** @@ -240,7 +247,7 @@ public function getExtraData() /** * Returns the button's configuration. * - * @return FormConfigInterface The configuration + * @return FormConfigInterface The configuration instance */ public function getConfig() { @@ -272,6 +279,7 @@ public function getName() */ public function getPropertyPath() { + return null; } /** @@ -309,11 +317,11 @@ public function isRequired() */ public function isDisabled() { - if (null === $this->parent || !$this->parent->isDisabled()) { - return $this->config->getDisabled(); + if ($this->parent && $this->parent->isDisabled()) { + return true; } - return true; + return $this->config->getDisabled(); } /** @@ -341,6 +349,7 @@ public function isSynchronized() */ public function getTransformationFailure() { + return null; } /** @@ -368,7 +377,7 @@ public function handleRequest($request = null) /** * Submits data to the button. * - * @param string|null $submittedData The data + * @param string|null $submittedData Not used * @param bool $clearMissing Not used * * @return $this diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index 903e842b9e8b8..598749eeac27d 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -22,9 +22,6 @@ */ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface { - /** - * @var bool - */ protected $locked = false; /** @@ -53,8 +50,6 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface private $options; /** - * Creates a new button builder. - * * @param string $name The name of the button * @param array $options The button's options * @@ -524,6 +519,7 @@ public function getFormConfig() */ public function getEventDispatcher() { + return null; } /** @@ -539,6 +535,7 @@ public function getName() */ public function getPropertyPath() { + return null; } /** @@ -606,6 +603,7 @@ public function getModelTransformers() */ public function getDataMapper() { + return null; } /** @@ -643,6 +641,7 @@ public function getErrorBubbling() */ public function getEmptyData() { + return null; } /** @@ -685,6 +684,7 @@ public function getAttribute($name, $default = null) */ public function getData() { + return null; } /** @@ -692,6 +692,7 @@ public function getData() */ public function getDataClass() { + return null; } /** @@ -709,6 +710,7 @@ public function getDataLocked() */ public function getFormFactory() { + throw new BadMethodCallException('Buttons do not support adding children.'); } /** @@ -716,6 +718,7 @@ public function getFormFactory() */ public function getAction() { + return null; } /** @@ -723,6 +726,7 @@ public function getAction() */ public function getMethod() { + return null; } /** @@ -730,6 +734,7 @@ public function getMethod() */ public function getRequestHandler() { + return null; } /** diff --git a/src/Symfony/Component/Form/CallbackTransformer.php b/src/Symfony/Component/Form/CallbackTransformer.php index 8155e4dca8ed1..6db5bfb183351 100644 --- a/src/Symfony/Component/Form/CallbackTransformer.php +++ b/src/Symfony/Component/Form/CallbackTransformer.php @@ -11,9 +11,6 @@ namespace Symfony\Component\Form; -use Symfony\Component\Form\Exception\TransformationFailedException; -use Symfony\Component\Form\Exception\UnexpectedTypeException; - class CallbackTransformer implements DataTransformerInterface { private $transform; @@ -30,14 +27,7 @@ public function __construct(callable $transform, callable $reverseTransform) } /** - * Transforms a value from the original representation to a transformed representation. - * - * @param mixed $data The value in the original representation - * - * @return mixed The value in the transformed representation - * - * @throws UnexpectedTypeException when the argument is not of the expected type - * @throws TransformationFailedException when the transformation fails + * {@inheritdoc} */ public function transform($data) { @@ -45,15 +35,7 @@ public function transform($data) } /** - * Transforms a value from the transformed representation to its original - * representation. - * - * @param mixed $data The value in the transformed representation - * - * @return mixed The value in the original representation - * - * @throws UnexpectedTypeException when the argument is not of the expected type - * @throws TransformationFailedException when the transformation fails + * {@inheritdoc} */ public function reverseTransform($data) { diff --git a/src/Symfony/Component/Form/DataMapperInterface.php b/src/Symfony/Component/Form/DataMapperInterface.php index bb262e7b8e6b8..dee8f784910ea 100644 --- a/src/Symfony/Component/Form/DataMapperInterface.php +++ b/src/Symfony/Component/Form/DataMapperInterface.php @@ -17,22 +17,46 @@ interface DataMapperInterface { /** - * Maps properties of some data to a list of forms. + * Maps the view data of a compound form to its children. * - * @param mixed $data Structured data - * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances + * The method is responsible for calling {@link FormInterface::setData()} + * on the children of compound forms, defining their underlying model data. + * + * @param mixed $viewData View data of the compound form being initialized + * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapDataToForms($data, $forms); + public function mapDataToForms($viewData, $forms); /** - * Maps the data of a list of forms into the properties of some data. + * Maps the model data of a list of children forms into the view data of their parent. + * + * This is the internal cascade call of FormInterface::submit for compound forms, since they + * cannot be bound to any input nor the request as scalar, but their children may: + * + * $compoundForm->submit($arrayOfChildrenViewData) + * // inside: + * $childForm->submit($childViewData); + * // for each entry, do the same and/or reverse transform + * $this->dataMapper->mapFormsToData($compoundForm, $compoundInitialViewData) + * // then reverse transform + * + * When a simple form is submitted the following is happening: + * + * $simpleForm->submit($submittedViewData) + * // inside: + * $this->viewData = $submittedViewData + * // then reverse transform + * + * The model data can be an array or an object, so this second argument is always passed + * by reference. * - * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances - * @param mixed $data Structured data + * @param FormInterface[]|\Traversable $forms A list of {@link FormInterface} instances + * @param mixed $viewData The compound form's view data that get mapped + * its children model data * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapFormsToData($forms, &$data); + public function mapFormsToData($forms, &$viewData); } diff --git a/src/Symfony/Component/Form/DataTransformerInterface.php b/src/Symfony/Component/Form/DataTransformerInterface.php index deb073c8128fe..e5ac5992944e5 100644 --- a/src/Symfony/Component/Form/DataTransformerInterface.php +++ b/src/Symfony/Component/Form/DataTransformerInterface.php @@ -23,23 +23,35 @@ interface DataTransformerInterface /** * Transforms a value from the original representation to a transformed representation. * - * This method is called on two occasions inside a form field: + * This method is called when the form field is initialized with its default data, on + * two occasions for two types of transformers: * - * 1. When the form field is initialized with the data attached from the datasource (object or array). - * 2. When data from a request is submitted using {@link Form::submit()} to transform the new input data - * back into the renderable format. For example if you have a date field and submit '2009-10-10' - * you might accept this value because its easily parsed, but the transformer still writes back - * "2009/10/10" onto the form field (for further displaying or other purposes). + * 1. Model transformers which normalize the model data. + * This is mainly useful when the same form type (the same configuration) + * has to handle different kind of underlying data, e.g The DateType can + * deal with strings or \DateTime objects as input. + * + * 2. View transformers which adapt the normalized data to the view format. + * a/ When the form is simple, the value returned by convention is used + * directly in the view and thus can only be a string or an array. In + * this case the data class should be null. + * + * b/ When the form is compound the returned value should be an array or + * an object to be mapped to the children. Each property of the compound + * data will be used as model data by each child and will be transformed + * too. In this case data class should be the class of the object, or null + * when it is an array. + * + * All transformers are called in a configured order from model data to view value. + * At the end of this chain the view data will be validated against the data class + * setting. * * This method must be able to deal with empty values. Usually this will * be NULL, but depending on your implementation other empty values are * possible as well (such as empty strings). The reasoning behind this is - * that value transformers must be chainable. If the transform() method - * of the first value transformer outputs NULL, the second value transformer - * must be able to process that value. - * - * By convention, transform() should return an empty string if NULL is - * passed. + * that data transformers must be chainable. If the transform() method + * of the first data transformer outputs NULL, the second must be able to + * process that value. * * @param mixed $value The value in the original representation * @@ -54,7 +66,10 @@ public function transform($value); * representation. * * This method is called when {@link Form::submit()} is called to transform the requests tainted data - * into an acceptable format for your data processing/model layer. + * into an acceptable format. + * + * The same transformers are called in the reverse order so the responsibility is to + * return one of the types that would be expected as input of transform(). * * This method must be able to deal with empty values. Usually this will * be an empty string, but depending on your implementation other empty diff --git a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php index 4eb980bf9cdff..0db802501c7d0 100644 --- a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php +++ b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php @@ -69,7 +69,7 @@ protected function loadTypes() new Type\TimeType(), new Type\TimezoneType(), new Type\UrlType(), - new Type\FileType(), + new Type\FileType($this->translator), new Type\ButtonType(), new Type\SubmitType(), new Type\ResetType(), diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php index 2b60f4f31e1e5..b05dcc018dc36 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php @@ -43,8 +43,6 @@ public function configureOptions(OptionsResolver $resolver) { parent::configureOptions($resolver); - $resolver->setDefaults([ - 'auto_initialize' => false, - ]); + $resolver->setDefault('auto_initialize', false); } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 28320ca38942e..2ad0859e880fa 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -328,8 +328,8 @@ public function configureOptions(OptionsResolver $resolver) 'placeholder' => $placeholderDefault, 'error_bubbling' => false, 'compound' => $compound, - // The view data is always a string, even if the "data" option - // is manually set to an object. + // The view data is always a string or an array of strings, + // even if the "data" option is manually set to an object. // See https://github.com/symfony/symfony/pull/5582 'data_class' => null, 'choice_translation_domain' => true, diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php index 181ce74993d64..2c41b1c6a35e6 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateIntervalType.php @@ -205,38 +205,36 @@ public function configureOptions(OptionsResolver $resolver) })); }; - $resolver->setDefaults( - [ - 'with_years' => true, - 'with_months' => true, - 'with_days' => true, - 'with_weeks' => false, - 'with_hours' => false, - 'with_minutes' => false, - 'with_seconds' => false, - 'with_invert' => false, - 'years' => range(0, 100), - 'months' => range(0, 12), - 'weeks' => range(0, 52), - 'days' => range(0, 31), - 'hours' => range(0, 24), - 'minutes' => range(0, 60), - 'seconds' => range(0, 60), - 'widget' => 'choice', - 'input' => 'dateinterval', - 'placeholder' => $placeholderDefault, - 'by_reference' => true, - 'error_bubbling' => false, - // If initialized with a \DateInterval object, FormType initializes - // this option to "\DateInterval". Since the internal, normalized - // representation is not \DateInterval, but an array, we need to unset - // this option. - 'data_class' => null, - 'compound' => $compound, - 'empty_data' => $emptyData, - 'labels' => [], - ] - ); + $resolver->setDefaults([ + 'with_years' => true, + 'with_months' => true, + 'with_days' => true, + 'with_weeks' => false, + 'with_hours' => false, + 'with_minutes' => false, + 'with_seconds' => false, + 'with_invert' => false, + 'years' => range(0, 100), + 'months' => range(0, 12), + 'weeks' => range(0, 52), + 'days' => range(0, 31), + 'hours' => range(0, 24), + 'minutes' => range(0, 60), + 'seconds' => range(0, 60), + 'widget' => 'choice', + 'input' => 'dateinterval', + 'placeholder' => $placeholderDefault, + 'by_reference' => true, + 'error_bubbling' => false, + // If initialized with a \DateInterval object, FormType initializes + // this option to "\DateInterval". Since the internal, normalized + // representation is not \DateInterval, but an array, we need to unset + // this option. + 'data_class' => null, + 'compound' => $compound, + 'empty_data' => $emptyData, + 'labels' => [], + ]); $resolver->setNormalizer('placeholder', $placeholderNormalizer); $resolver->setNormalizer('labels', $labelsNormalizer); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 59c72889d6683..f8afce2ee5a4d 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -13,15 +13,33 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Translation\TranslatorInterface; class FileType extends AbstractType { + const KIB_BYTES = 1024; + const MIB_BYTES = 1048576; + + private static $suffixes = [ + 1 => 'bytes', + self::KIB_BYTES => 'KiB', + self::MIB_BYTES => 'MiB', + ]; + + private $translator; + + public function __construct(TranslatorInterface $translator = null) + { + $this->translator = $translator; + } + /** * {@inheritdoc} */ @@ -43,6 +61,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) foreach ($files as $file) { if ($requestHandler->isFileUpload($file)) { $data[] = $file; + + if (method_exists($requestHandler, 'getUploadFileError') && null !== $errorCode = $requestHandler->getUploadFileError($file)) { + $form->addError($this->getFileUploadError($errorCode)); + } } } @@ -54,6 +76,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) } $event->setData($data); + } elseif ($requestHandler->isFileUpload($event->getData()) && method_exists($requestHandler, 'getUploadFileError') && null !== $errorCode = $requestHandler->getUploadFileError($event->getData())) { + $form->addError($this->getFileUploadError($errorCode)); } elseif (!$requestHandler->isFileUpload($event->getData())) { $event->setData(null); } @@ -116,4 +140,109 @@ public function getBlockPrefix() { return 'file'; } + + private function getFileUploadError($errorCode) + { + $messageParameters = []; + + if (UPLOAD_ERR_INI_SIZE === $errorCode) { + list($limitAsString, $suffix) = $this->factorizeSizes(0, self::getMaxFilesize()); + $messageTemplate = 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.'; + $messageParameters = [ + '{{ limit }}' => $limitAsString, + '{{ suffix }}' => $suffix, + ]; + } elseif (UPLOAD_ERR_FORM_SIZE === $errorCode) { + $messageTemplate = 'The file is too large.'; + } else { + $messageTemplate = 'The file could not be uploaded.'; + } + + if (null !== $this->translator) { + $message = $this->translator->trans($messageTemplate, $messageParameters); + } else { + $message = strtr($messageTemplate, $messageParameters); + } + + return new FormError($message, $messageTemplate, $messageParameters); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini. + * + * This method should be kept in sync with Symfony\Component\HttpFoundation\File\UploadedFile::getMaxFilesize(). + * + * @return int The maximum size of an uploaded file in bytes + */ + private static function getMaxFilesize() + { + $iniMax = strtolower(ini_get('upload_max_filesize')); + + if ('' === $iniMax) { + return PHP_INT_MAX; + } + + $max = ltrim($iniMax, '+'); + if (0 === strpos($max, '0x')) { + $max = \intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($iniMax, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Converts the limit to the smallest possible number + * (i.e. try "MB", then "kB", then "bytes"). + * + * This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::factorizeSizes(). + */ + private function factorizeSizes($size, $limit) + { + $coef = self::MIB_BYTES; + $coefFactor = self::KIB_BYTES; + + $limitAsString = (string) ($limit / $coef); + + // Restrict the limit to 2 decimals (without rounding! we + // need the precise value) + while (self::moreDecimalsThan($limitAsString, 2)) { + $coef /= $coefFactor; + $limitAsString = (string) ($limit / $coef); + } + + // Convert size to the same measure, but round to 2 decimals + $sizeAsString = (string) round($size / $coef, 2); + + // If the size and limit produce the same string output + // (due to rounding), reduce the coefficient + while ($sizeAsString === $limitAsString) { + $coef /= $coefFactor; + $limitAsString = (string) ($limit / $coef); + $sizeAsString = (string) round($size / $coef, 2); + } + + return [$limitAsString, self::$suffixes[$coef]]; + } + + /** + * This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::moreDecimalsThan(). + */ + private static function moreDecimalsThan($double, $numberOfDecimals) + { + return \strlen((string) $double) > \strlen(round($double, $numberOfDecimals)); + } } diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php index 75ee65443f450..cf255792fee83 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -17,6 +17,7 @@ use Symfony\Component\Form\RequestHandlerInterface; use Symfony\Component\Form\Util\ServerParams; use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; /** @@ -115,4 +116,16 @@ public function isFileUpload($data) { return $data instanceof File; } + + /** + * @return int|null + */ + public function getUploadFileError($data) + { + if (!$data instanceof UploadedFile || $data->isValid()) { + return null; + } + + return $data->getError(); + } } diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index cfa7eb5b6a6d8..5f417d6d90901 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -21,6 +21,7 @@ use Symfony\Component\Form\Util\InheritDataAwareIterator; use Symfony\Component\Form\Util\OrderedHashMap; use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\PropertyAccess\PropertyPathInterface; /** * Form represents a form. @@ -63,79 +64,57 @@ class Form implements \IteratorAggregate, FormInterface { /** - * The form's configuration. - * * @var FormConfigInterface */ private $config; /** - * The parent of this form. - * - * @var FormInterface + * @var FormInterface|null */ private $parent; /** - * The children of this form. - * - * @var FormInterface[] A map of FormInterface instances + * @var FormInterface[]|OrderedHashMap A map of FormInterface instances */ private $children; /** - * The errors of this form. - * * @var FormError[] An array of FormError instances */ private $errors = []; /** - * Whether this form was submitted. - * * @var bool */ private $submitted = false; /** - * The button that was used to submit the form. - * - * @var Button + * @var ClickableInterface|null The button that was used to submit the form */ private $clickedButton; /** - * The form data in model format. - * * @var mixed */ private $modelData; /** - * The form data in normalized format. - * * @var mixed */ private $normData; /** - * The form data in view format. - * * @var mixed */ private $viewData; /** - * The submitted values that don't belong to any children. - * - * @var array + * @var array The submitted values that don't belong to any children */ private $extraData = []; /** - * Returns the transformation failure generated during submission, if any. - * - * @var TransformationFailedException|null + * @var TransformationFailedException|null The transformation failure generated during submission, if any */ private $transformationFailure; @@ -161,8 +140,21 @@ class Form implements \IteratorAggregate, FormInterface private $lockSetData = false; /** - * Creates a new form based on the given configuration. - * + * @var string|int|null + */ + private $name; + + /** + * @var bool Whether the form inherits its underlying data from its parent + */ + private $inheritData; + + /** + * @var PropertyPathInterface|null + */ + private $propertyPath; + + /** * @throws LogicException if a data mapper is not provided for a compound form */ public function __construct(FormConfigInterface $config) @@ -176,12 +168,13 @@ public function __construct(FormConfigInterface $config) // If the form inherits the data from its parent, it is not necessary // to call setData() with the default data. - if ($config->getInheritData()) { + if ($this->inheritData = $config->getInheritData()) { $this->defaultDataSet = true; } $this->config = $config; $this->children = new OrderedHashMap(); + $this->name = $config->getName(); } public function __clone() @@ -206,7 +199,7 @@ public function getConfig() */ public function getName() { - return $this->config->getName(); + return $this->name; } /** @@ -214,11 +207,11 @@ public function getName() */ public function getPropertyPath() { - if (null !== $this->config->getPropertyPath()) { - return $this->config->getPropertyPath(); + if ($this->propertyPath || $this->propertyPath = $this->config->getPropertyPath()) { + return $this->propertyPath; } - if (null === $this->getName() || '' === $this->getName()) { + if (null === $this->name || '' === $this->name) { return null; } @@ -229,10 +222,12 @@ public function getPropertyPath() } if ($parent && null === $parent->getConfig()->getDataClass()) { - return new PropertyPath('['.$this->getName().']'); + $this->propertyPath = new PropertyPath('['.$this->name.']'); + } else { + $this->propertyPath = new PropertyPath($this->name); } - return new PropertyPath($this->getName()); + return $this->propertyPath; } /** @@ -268,7 +263,7 @@ public function setParent(FormInterface $parent = null) throw new AlreadySubmittedException('You cannot set the parent of a submitted form'); } - if (null !== $parent && '' === $this->config->getName()) { + if (null !== $parent && '' === $this->name) { throw new LogicException('A form with an empty name cannot have a parent form.'); } @@ -315,7 +310,7 @@ public function setData($modelData) // If the form inherits its parent's data, disallow data setting to // prevent merge conflicts - if ($this->config->getInheritData()) { + if ($this->inheritData) { throw new RuntimeException('You cannot change the data of a form inheriting its parent data.'); } @@ -335,7 +330,7 @@ public function setData($modelData) $this->lockSetData = true; $dispatcher = $this->config->getEventDispatcher(); - // Hook to change content of the data + // Hook to change content of the model data before transformation and mapping children if ($dispatcher->hasListeners(FormEvents::PRE_SET_DATA)) { $event = new FormEvent($this, $modelData); $dispatcher->dispatch(FormEvents::PRE_SET_DATA, $event); @@ -348,6 +343,7 @@ public function setData($modelData) } // Synchronize representations - must not change the content! + // Transformation exceptions are not caught on initialization $normData = $this->modelToNorm($modelData); $viewData = $this->normToView($normData); @@ -370,13 +366,10 @@ public function setData($modelData) $this->defaultDataSet = true; $this->lockSetData = false; - // It is not necessary to invoke this method if the form doesn't have children, - // even if the form is compound. + // Compound forms don't need to invoke this method if they don't have children if (\count($this->children) > 0) { - // Update child forms from the data - $iterator = new InheritDataAwareIterator($this->children); - $iterator = new \RecursiveIteratorIterator($iterator); - $this->config->getDataMapper()->mapDataToForms($viewData, $iterator); + // Update child forms from the data (unless their config data is locked) + $this->config->getDataMapper()->mapDataToForms($viewData, new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children))); } if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) { @@ -392,7 +385,7 @@ public function setData($modelData) */ public function getData() { - if ($this->config->getInheritData()) { + if ($this->inheritData) { if (!$this->parent) { throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); } @@ -416,7 +409,7 @@ public function getData() */ public function getNormData() { - if ($this->config->getInheritData()) { + if ($this->inheritData) { if (!$this->parent) { throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); } @@ -440,7 +433,7 @@ public function getNormData() */ public function getViewData() { - if ($this->config->getInheritData()) { + if ($this->inheritData) { if (!$this->parent) { throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); } @@ -505,8 +498,8 @@ public function submit($submittedData, $clearMissing = true) throw new AlreadySubmittedException('A form can only be submitted once'); } - // Initialize errors in the very beginning so that we don't lose any - // errors added during listeners + // Initialize errors in the very beginning so we're sure + // they are collectable during submission only $this->errors = []; // Obviously, a disabled form should not change its data upon submission. @@ -605,18 +598,16 @@ public function submit($submittedData, $clearMissing = true) // changes in the grandchildren (i.e. children of the form that inherits // its parent's data) into account. // (see InheritDataAwareIterator below) - if (!$this->config->getInheritData()) { - // If the form is compound, the default data in view format - // is reused. The data of the children is merged into this - // default data using the data mapper. - // If the form is not compound, the submitted data is also the data in view format. + if (!$this->inheritData) { + // If the form is compound, the view data is merged with the data + // of the children using the data mapper. + // If the form is not compound, the view data is assigned to the submitted data. $viewData = $this->config->getCompound() ? $this->viewData : $submittedData; if (FormUtil::isEmpty($viewData)) { $emptyData = $this->config->getEmptyData(); if ($emptyData instanceof \Closure) { - /* @var \Closure $emptyData */ $emptyData = $emptyData($this, $viewData); } @@ -631,9 +622,10 @@ public function submit($submittedData, $clearMissing = true) // descendants that inherit this form's data. // These descendants will not be submitted normally (see the check // for $this->config->getInheritData() above) - $childrenIterator = new InheritDataAwareIterator($this->children); - $childrenIterator = new \RecursiveIteratorIterator($childrenIterator); - $this->config->getDataMapper()->mapFormsToData($childrenIterator, $viewData); + $this->config->getDataMapper()->mapFormsToData( + new \RecursiveIteratorIterator(new InheritDataAwareIterator($this->children)), + $viewData + ); } // Normalize data to unified representation @@ -658,7 +650,7 @@ public function submit($submittedData, $clearMissing = true) // the erroneous data is accessible on the form. // Forms that inherit data never set any data, because the getters // forward to the parent form's getters anyway. - if (null === $viewData && !$this->config->getInheritData()) { + if (null === $viewData && !$this->inheritData) { $viewData = $submittedData; } } @@ -757,8 +749,7 @@ public function isValid() /** * Returns the button that was used to submit the form. * - * @return Button|null The clicked button or NULL if the form was not - * submitted + * @return ClickableInterface|null */ public function getClickedButton() { @@ -826,29 +817,6 @@ public function add($child, $type = null, array $options = []) throw new LogicException('You cannot add children to a simple form. Maybe you should set the option "compound" to true?'); } - // Obtain the view data - $viewData = null; - - // If setData() is currently being called, there is no need to call - // mapDataToForms() here, as mapDataToForms() is called at the end - // of setData() anyway. Not doing this check leads to an endless - // recursion when initializing the form lazily and an event listener - // (such as ResizeFormListener) adds fields depending on the data: - // - // * setData() is called, the form is not initialized yet - // * add() is called by the listener (setData() is not complete, so - // the form is still not initialized) - // * getViewData() is called - // * setData() is called since the form is not initialized yet - // * ... endless recursion ... - // - // Also skip data mapping if setData() has not been called yet. - // setData() will be called upon form initialization and data mapping - // will take place by then. - if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { - $viewData = $this->getViewData(); - } - if (!$child instanceof FormInterface) { if (!\is_string($child) && !\is_int($child)) { throw new UnexpectedTypeException($child, 'string, integer or Symfony\Component\Form\FormInterface'); @@ -878,10 +846,28 @@ public function add($child, $type = null, array $options = []) $child->setParent($this); - if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { - $iterator = new InheritDataAwareIterator(new \ArrayIterator([$child->getName() => $child])); - $iterator = new \RecursiveIteratorIterator($iterator); - $this->config->getDataMapper()->mapDataToForms($viewData, $iterator); + // If setData() is currently being called, there is no need to call + // mapDataToForms() here, as mapDataToForms() is called at the end + // of setData() anyway. Not doing this check leads to an endless + // recursion when initializing the form lazily and an event listener + // (such as ResizeFormListener) adds fields depending on the data: + // + // * setData() is called, the form is not initialized yet + // * add() is called by the listener (setData() is not complete, so + // the form is still not initialized) + // * getViewData() is called + // * setData() is called since the form is not initialized yet + // * ... endless recursion ... + // + // Also skip data mapping if setData() has not been called yet. + // setData() will be called upon form initialization and data mapping + // will take place by then. + if (!$this->lockSetData && $this->defaultDataSet && !$this->inheritData) { + $viewData = $this->getViewData(); + $this->config->getDataMapper()->mapDataToForms( + $viewData, + new \RecursiveIteratorIterator(new InheritDataAwareIterator(new \ArrayIterator([$child->getName() => $child]))) + ); } return $this; @@ -1030,13 +1016,13 @@ public function createView(FormView $parent = null) } /** - * Normalizes the value if a model transformer is set. + * Normalizes the underlying data if a model transformer is set. * * @param mixed $value The value to transform * * @return mixed * - * @throws TransformationFailedException If the value cannot be transformed to "normalized" format + * @throws TransformationFailedException If the underlying data cannot be transformed to "normalized" format */ private function modelToNorm($value) { @@ -1045,7 +1031,7 @@ private function modelToNorm($value) $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception); + throw new TransformationFailedException('Unable to transform data for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception); } return $value; @@ -1082,7 +1068,7 @@ private function normToModel($value) * * @return mixed * - * @throws TransformationFailedException If the value cannot be transformed to "view" format + * @throws TransformationFailedException If the normalized value cannot be transformed to "view" format */ private function normToView($value) { @@ -1091,12 +1077,12 @@ private function normToView($value) // Only do this for simple forms, as the resulting value in // compound forms is passed to the data mapper and thus should // not be converted to a string before. - if (!$this->config->getViewTransformers() && !$this->config->getCompound()) { + if (!($transformers = $this->config->getViewTransformers()) && !$this->config->getCompound()) { return null === $value || is_scalar($value) ? (string) $value : $value; } try { - foreach ($this->config->getViewTransformers() as $transformer) { + foreach ($transformers as $transformer) { $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { @@ -1113,13 +1099,11 @@ private function normToView($value) * * @return mixed * - * @throws TransformationFailedException If the value cannot be transformed to "normalized" format + * @throws TransformationFailedException If the submitted value cannot be transformed to "normalized" format */ private function viewToNorm($value) { - $transformers = $this->config->getViewTransformers(); - - if (!$transformers) { + if (!$transformers = $this->config->getViewTransformers()) { return '' === $value ? null : $value; } diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php index 00affbf6d8c3d..13b1ea3b36420 100644 --- a/src/Symfony/Component/Form/FormBuilder.php +++ b/src/Symfony/Component/Form/FormBuilder.php @@ -38,8 +38,6 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB private $unresolvedChildren = []; /** - * Creates a new form builder. - * * @param string $name * @param string|null $dataClass * @param EventDispatcherInterface $dispatcher @@ -81,10 +79,7 @@ public function add($child, $type = null, array $options = []) // Add to "children" to maintain order $this->children[$child] = null; - $this->unresolvedChildren[$child] = [ - 'type' => $type, - 'options' => $options, - ]; + $this->unresolvedChildren[$child] = [$type, $options]; return $this; } @@ -152,15 +147,7 @@ public function has($name) throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - if (isset($this->unresolvedChildren[$name])) { - return true; - } - - if (isset($this->children[$name])) { - return true; - } - - return false; + return isset($this->unresolvedChildren[$name]) || isset($this->children[$name]); } /** @@ -232,7 +219,7 @@ public function getForm() /** * {@inheritdoc} * - * @return FormBuilderInterface[] + * @return FormBuilderInterface[]|\Traversable */ public function getIterator() { @@ -252,12 +239,11 @@ public function getIterator() */ private function resolveChild($name) { - $info = $this->unresolvedChildren[$name]; - $child = $this->create($name, $info['type'], $info['options']); - $this->children[$name] = $child; + list($type, $options) = $this->unresolvedChildren[$name]; + unset($this->unresolvedChildren[$name]); - return $child; + return $this->children[$name] = $this->create($name, $type, $options); } /** @@ -266,7 +252,7 @@ private function resolveChild($name) private function resolveChildren() { foreach ($this->unresolvedChildren as $name => $info) { - $this->children[$name] = $this->create($name, $info['type'], $info['options']); + $this->children[$name] = $this->create($name, $info[0], $info[1]); } $this->unresolvedChildren = []; diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index fc864fc617173..fea1d70cd8ee4 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -47,44 +47,19 @@ class FormConfigBuilder implements FormConfigBuilderInterface 'PATCH', ]; - /** - * @var bool - */ protected $locked = false; - /** - * @var EventDispatcherInterface - */ private $dispatcher; - - /** - * @var string - */ private $name; /** - * @var PropertyPathInterface + * @var PropertyPathInterface|string|null */ private $propertyPath; - /** - * @var bool - */ private $mapped = true; - - /** - * @var bool - */ private $byReference = true; - - /** - * @var bool - */ private $inheritData = false; - - /** - * @var bool - */ private $compound = false; /** @@ -92,34 +67,16 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $type; - /** - * @var array - */ private $viewTransformers = []; - - /** - * @var array - */ private $modelTransformers = []; /** - * @var DataMapperInterface + * @var DataMapperInterface|null */ private $dataMapper; - /** - * @var bool - */ private $required = true; - - /** - * @var bool - */ private $disabled = false; - - /** - * @var bool - */ private $errorBubbling = false; /** @@ -127,9 +84,6 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $emptyData; - /** - * @var array - */ private $attributes = []; /** @@ -142,39 +96,26 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $dataClass; - /** - * @var bool - */ - private $dataLocked; + private $dataLocked = false; /** - * @var FormFactoryInterface + * @var FormFactoryInterface|null */ private $formFactory; /** - * @var string + * @var string|null */ private $action; - /** - * @var string - */ private $method = 'POST'; /** - * @var RequestHandlerInterface + * @var RequestHandlerInterface|null */ private $requestHandler; - /** - * @var bool - */ private $autoInitialize = false; - - /** - * @var array - */ private $options; /** @@ -616,7 +557,7 @@ public function setErrorBubbling($errorBubbling) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->errorBubbling = null === $errorBubbling ? null : (bool) $errorBubbling; + $this->errorBubbling = (bool) $errorBubbling; return $this; } @@ -662,7 +603,7 @@ public function setMapped($mapped) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->mapped = $mapped; + $this->mapped = (bool) $mapped; return $this; } @@ -676,7 +617,7 @@ public function setByReference($byReference) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->byReference = $byReference; + $this->byReference = (bool) $byReference; return $this; } @@ -690,7 +631,7 @@ public function setInheritData($inheritData) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->inheritData = $inheritData; + $this->inheritData = (bool) $inheritData; return $this; } @@ -704,7 +645,7 @@ public function setCompound($compound) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->compound = $compound; + $this->compound = (bool) $compound; return $this; } @@ -746,7 +687,7 @@ public function setDataLocked($locked) throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - $this->dataLocked = $locked; + $this->dataLocked = (bool) $locked; return $this; } @@ -774,7 +715,7 @@ public function setAction($action) throw new BadMethodCallException('The config builder cannot be modified anymore.'); } - $this->action = $action; + $this->action = (string) $action; return $this; } @@ -790,7 +731,7 @@ public function setMethod($method) $upperCaseMethod = strtoupper($method); - if (!\in_array($upperCaseMethod, self::$allowedMethods)) { + if (!\in_array($upperCaseMethod, self::$allowedMethods, true)) { throw new InvalidArgumentException(sprintf('The form method is "%s", but should be one of "%s".', $method, implode('", "', self::$allowedMethods))); } @@ -846,7 +787,7 @@ public function getFormConfig() /** * Validates whether the given variable is a valid form name. * - * @param string|int $name The tested form name + * @param string|int|null $name The tested form name * * @throws UnexpectedTypeException if the name is not a string or an integer * @throws InvalidArgumentException if the name contains invalid characters @@ -872,7 +813,7 @@ public static function validateName($name) * * contains only letters, digits, numbers, underscores ("_"), * hyphens ("-") and colons (":") * - * @param string $name The tested form name + * @param string|null $name The tested form name * * @return bool Whether the name is valid */ diff --git a/src/Symfony/Component/Form/FormConfigBuilderInterface.php b/src/Symfony/Component/Form/FormConfigBuilderInterface.php index f422840a82c45..d516e41056ecc 100644 --- a/src/Symfony/Component/Form/FormConfigBuilderInterface.php +++ b/src/Symfony/Component/Form/FormConfigBuilderInterface.php @@ -108,7 +108,7 @@ public function setAttributes(array $attributes); public function setDataMapper(DataMapperInterface $dataMapper = null); /** - * Set whether the form is disabled. + * Sets whether the form is disabled. * * @param bool $disabled Whether the form is disabled * @@ -166,8 +166,7 @@ public function setMapped($mapped); /** * Sets whether the form's data should be modified by reference. * - * @param bool $byReference Whether the data should be - * modified by reference + * @param bool $byReference Whether the data should be modified by reference * * @return $this The configuration object */ @@ -194,7 +193,7 @@ public function setInheritData($inheritData); public function setCompound($compound); /** - * Set the types. + * Sets the resolved type. * * @return $this The configuration object */ @@ -203,7 +202,7 @@ public function setType(ResolvedFormTypeInterface $type); /** * Sets the initial data of the form. * - * @param mixed $data The data of the form in application format + * @param mixed $data The data of the form in model format * * @return $this The configuration object */ @@ -214,9 +213,12 @@ public function setData($data); * * A form with locked data is restricted to the data passed in * this configuration. The data can only be modified then by - * submitting the form. + * submitting the form or using PRE_SET_DATA event. * - * @param bool $locked Whether to lock the default data + * It means data passed to a factory method or mapped from the + * parent will be ignored. + * + * @param bool $locked Whether to lock the default configured data * * @return $this The configuration object */ diff --git a/src/Symfony/Component/Form/FormConfigInterface.php b/src/Symfony/Component/Form/FormConfigInterface.php index ce9171f3d496b..7dbda33033b55 100644 --- a/src/Symfony/Component/Form/FormConfigInterface.php +++ b/src/Symfony/Component/Form/FormConfigInterface.php @@ -70,15 +70,17 @@ public function getInheritData(); * This property is independent of whether the form actually has * children. A form can be compound and have no children at all, like * for example an empty collection form. + * The contrary is not possible, a form which is not compound + * cannot have any children. * * @return bool Whether the form is compound */ public function getCompound(); /** - * Returns the form types used to construct the form. + * Returns the resolved form type used to construct the form. * - * @return ResolvedFormTypeInterface The form's type + * @return ResolvedFormTypeInterface The form's resolved type */ public function getType(); @@ -97,7 +99,7 @@ public function getViewTransformers(); public function getModelTransformers(); /** - * Returns the data mapper of the form. + * Returns the data mapper of the compound form or null for a simple form. * * @return DataMapperInterface|null The data mapper */ @@ -125,9 +127,15 @@ public function getDisabled(); public function getErrorBubbling(); /** - * Returns the data that should be returned when the form is empty. + * Used when the view data is empty on submission. * - * @return mixed The data returned if the form is empty + * When the form is compound it will also be used to map the + * children data. + * + * The empty data must match the view format as it will passed to the first view transformer's + * "reverseTransform" method. + * + * @return mixed The data used when the submitted form is initially empty */ public function getEmptyData(); @@ -165,7 +173,7 @@ public function getAttribute($name, $default = null); public function getData(); /** - * Returns the class of the form data or null if the data is scalar or an array. + * Returns the class of the view data or null if the data is scalar or an array. * * @return string|null The data class or null */ diff --git a/src/Symfony/Component/Form/FormError.php b/src/Symfony/Component/Form/FormError.php index 7717c19019a3c..98a1e29a83a82 100644 --- a/src/Symfony/Component/Form/FormError.php +++ b/src/Symfony/Component/Form/FormError.php @@ -49,7 +49,7 @@ class FormError implements \Serializable */ public function __construct($message, $messageTemplate = null, array $messageParameters = [], $messagePluralization = null, $cause = null) { - $this->message = $message; + $this->message = (string) $message; $this->messageTemplate = $messageTemplate ?: $message; $this->messageParameters = $messageParameters; $this->messagePluralization = $messagePluralization; diff --git a/src/Symfony/Component/Form/FormErrorIterator.php b/src/Symfony/Component/Form/FormErrorIterator.php index 2cc53c0923f94..db1d311a30d7a 100644 --- a/src/Symfony/Component/Form/FormErrorIterator.php +++ b/src/Symfony/Component/Form/FormErrorIterator.php @@ -39,10 +39,9 @@ class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \Array private $errors; /** - * Creates a new iterator. - * - * @param FormInterface $form The erroneous form - * @param FormError[]|FormErrorIterator[] $errors The form errors + * @param FormInterface $form The erroneous form + * @param FormError[]|self[] $errors An array of form errors and instances + * of FormErrorIterator * * @throws InvalidArgumentException If the errors are invalid */ @@ -71,7 +70,7 @@ public function __toString() if ($error instanceof FormError) { $string .= 'ERROR: '.$error->getMessage()."\n"; } else { - /* @var $error FormErrorIterator */ + /** @var self $error */ $string .= $error->form->getName().":\n"; $string .= self::indent((string) $error); } @@ -93,8 +92,7 @@ public function getForm() /** * Returns the current element of the iterator. * - * @return FormError|FormErrorIterator an error or an iterator containing - * nested errors + * @return FormError|self An error or an iterator containing nested errors */ public function current() { diff --git a/src/Symfony/Component/Form/FormEvent.php b/src/Symfony/Component/Form/FormEvent.php index c688a19566b76..3b6d484e75803 100644 --- a/src/Symfony/Component/Form/FormEvent.php +++ b/src/Symfony/Component/Form/FormEvent.php @@ -21,6 +21,10 @@ class FormEvent extends Event private $form; protected $data; + /** + * @param FormInterface $form The associated form + * @param mixed $data The data + */ public function __construct(FormInterface $form, $data) { $this->form = $form; diff --git a/src/Symfony/Component/Form/FormEvents.php b/src/Symfony/Component/Form/FormEvents.php index b795f95dcfafc..c4c613f567f19 100644 --- a/src/Symfony/Component/Form/FormEvents.php +++ b/src/Symfony/Component/Form/FormEvents.php @@ -34,21 +34,35 @@ final class FormEvents const PRE_SUBMIT = 'form.pre_bind'; /** - * The SUBMIT event is dispatched just before the Form::submit() method - * transforms back the normalized data to the model and view data. + * The SUBMIT event is dispatched after the Form::submit() method + * has changed the view data by the request data, or submitted and mapped + * the children if the form is compound, and after reverse transformation + * to normalized representation. * - * It can be used to change data from the normalized representation of the data. + * It's also dispatched just before the Form::submit() method transforms back + * the normalized data to the model and view data. + * + * So at this stage children of compound forms are submitted and synchronized, unless + * their transformation failed, but a parent would still be at the PRE_SUBMIT level. + * + * Since the current form is not synchronized yet, it is still possible to add and + * remove fields. * * @Event("Symfony\Component\Form\FormEvent") */ const SUBMIT = 'form.bind'; /** - * The FormEvents::POST_SUBMIT event is dispatched after the Form::submit() - * once the model and view data have been denormalized. + * The FormEvents::POST_SUBMIT event is dispatched at the very end of the Form::submit(). + * + * It this stage the model and view data may have been denormalized. Otherwise the form + * is desynchronized because transformation failed during submission. * * It can be used to fetch data after denormalization. * + * The event attaches the current view data. To know whether this is the renormalized data + * or the invalid request data, call Form::isSynchronized() first. + * * @Event("Symfony\Component\Form\FormEvent") */ const POST_SUBMIT = 'form.post_bind'; @@ -58,7 +72,7 @@ final class FormEvents * * It can be used to: * - Modify the data given during pre-population; - * - Modify a form depending on the pre-populated data (adding or removing fields dynamically). + * - Keep synchronized the form depending on the data (adding or removing fields dynamically). * * @Event("Symfony\Component\Form\FormEvent") */ @@ -67,7 +81,8 @@ final class FormEvents /** * The FormEvents::POST_SET_DATA event is dispatched at the end of the Form::setData() method. * - * This event is mostly here for reading data after having pre-populated the form. + * This event can be used to modify the form depending on the final state of the underlying data + * accessible in every representation: model, normalized and view. * * @Event("Symfony\Component\Form\FormEvent") */ diff --git a/src/Symfony/Component/Form/FormFactoryBuilder.php b/src/Symfony/Component/Form/FormFactoryBuilder.php index bccb86f40791d..759affa3c02f1 100644 --- a/src/Symfony/Component/Form/FormFactoryBuilder.php +++ b/src/Symfony/Component/Form/FormFactoryBuilder.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Form; +use Symfony\Component\Form\Extension\Core\CoreExtension; + /** * The default implementation of FormFactoryBuilderInterface. * @@ -18,6 +20,8 @@ */ class FormFactoryBuilder implements FormFactoryBuilderInterface { + private $forceCoreExtension; + /** * @var ResolvedFormTypeFactoryInterface */ @@ -43,6 +47,14 @@ class FormFactoryBuilder implements FormFactoryBuilderInterface */ private $typeGuessers = []; + /** + * @param bool $forceCoreExtension + */ + public function __construct($forceCoreExtension = false) + { + $this->forceCoreExtension = $forceCoreExtension; + } + /** * {@inheritdoc} */ @@ -144,6 +156,21 @@ public function getFormFactory() { $extensions = $this->extensions; + if ($this->forceCoreExtension) { + $hasCoreExtension = false; + + foreach ($extensions as $extension) { + if ($extension instanceof CoreExtension) { + $hasCoreExtension = true; + break; + } + } + + if (!$hasCoreExtension) { + array_unshift($extensions, new CoreExtension()); + } + } + if (\count($this->types) > 0 || \count($this->typeExtensions) > 0 || \count($this->typeGuessers) > 0) { if (\count($this->typeGuessers) > 1) { $typeGuesser = new FormTypeGuesserChain($this->typeGuessers); diff --git a/src/Symfony/Component/Form/FormInterface.php b/src/Symfony/Component/Form/FormInterface.php index 2add7938443aa..022b60aa1368d 100644 --- a/src/Symfony/Component/Form/FormInterface.php +++ b/src/Symfony/Component/Form/FormInterface.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Form; -use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\PropertyAccess\PropertyPathInterface; /** * A form group bundling multiple forms in a hierarchical structure. @@ -23,7 +23,9 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable /** * Sets the parent form. * - * @return self + * @param FormInterface|null $parent The parent form or null if it's the root + * + * @return $this * * @throws Exception\AlreadySubmittedException if the form has already been submitted * @throws Exception\LogicException when trying to set a parent for a form with @@ -45,7 +47,7 @@ public function getParent(); * @param string|null $type The child's type, if a name was passed * @param array $options The child's options, if a name was passed * - * @return self + * @return $this * * @throws Exception\AlreadySubmittedException if the form has already been submitted * @throws Exception\LogicException when trying to add a child to a non-compound form @@ -104,44 +106,70 @@ public function all(); public function getErrors($deep = false, $flatten = true); /** - * Updates the form with default data. + * Updates the form with default model data. * * @param mixed $modelData The data formatted as expected for the underlying object * * @return $this * - * @throws Exception\AlreadySubmittedException if the form has already been submitted - * @throws Exception\LogicException If listeners try to call setData in a cycle. Or if - * the view data does not match the expected type - * according to {@link FormConfigInterface::getDataClass}. + * @throws Exception\AlreadySubmittedException If the form has already been submitted + * @throws Exception\LogicException If the view data does not match the expected type + * according to {@link FormConfigInterface::getDataClass}. + * @throws Exception\RuntimeException If listeners try to call setData in a cycle or if + * the form inherits data from its parent + * @throws Exception\TransformationFailedException If the synchronization failed. */ public function setData($modelData); /** - * Returns the data in the format needed for the underlying object. + * Returns the model data in the format needed for the underlying object. * - * @return mixed + * @return mixed When the field is not submitted, the default data is returned. + * When the field is submitted, the default data has been bound + * to the submitted view data. + * + * @throws Exception\RuntimeException If the form inherits data but has no parent */ public function getData(); /** - * Returns the normalized data of the field. + * Returns the normalized data of the field, used as internal bridge + * between model data and view data. * * @return mixed When the field is not submitted, the default data is returned. - * When the field is submitted, the normalized submitted data is - * returned if the field is valid, null otherwise. + * When the field is submitted, the normalized submitted data + * is returned if the field is synchronized with the view data, + * null otherwise. + * + * @throws Exception\RuntimeException If the form inherits data but has no parent */ public function getNormData(); /** - * Returns the data transformed by the value transformer. + * Returns the view data of the field. + * + * It may be defined by {@link FormConfigInterface::getDataClass}. + * + * There are two cases: + * + * - When the form is compound the view data is mapped to the children. + * Each child will use its mapped data as model data. + * It can be an array, an object or null. + * + * - When the form is simple its view data is used to be bound + * to the submitted data. + * It can be a string or an array. + * + * In both cases the view data is the actual altered data on submission. * * @return mixed + * + * @throws Exception\RuntimeException If the form inherits data but has no parent */ public function getViewData(); /** - * Returns the extra data. + * Returns the extra submitted data. * * @return array The submitted data which do not belong to a child */ @@ -150,7 +178,7 @@ public function getExtraData(); /** * Returns the form's configuration. * - * @return FormConfigInterface The configuration + * @return FormConfigInterface The configuration instance */ public function getConfig(); @@ -164,6 +192,8 @@ public function isSubmitted(); /** * Returns the name by which the form is identified in forms. * + * Only root forms are allowed to have an empty name. + * * @return string The name of the form */ public function getName(); @@ -171,7 +201,7 @@ public function getName(); /** * Returns the property path that the form is mapped to. * - * @return \Symfony\Component\PropertyAccess\PropertyPathInterface|null The property path + * @return PropertyPathInterface|null The property path instance */ public function getPropertyPath(); @@ -230,14 +260,16 @@ public function isEmpty(); * If the data is not synchronized, you can get the transformation failure * by calling {@link getTransformationFailure()}. * + * If the form is not submitted, this method always returns true. + * * @return bool */ public function isSynchronized(); /** - * Returns the data transformation failure, if any. + * Returns the data transformation failure, if any, during submission. * - * @return TransformationFailedException|null The transformation failure + * @return Exception\TransformationFailedException|null The transformation failure or null */ public function getTransformationFailure(); @@ -247,6 +279,8 @@ public function getTransformationFailure(); * Should be called on the root form after constructing the tree. * * @return $this + * + * @throws Exception\RuntimeException If the form is not the root */ public function initialize(); @@ -265,11 +299,13 @@ public function initialize(); public function handleRequest($request = null); /** - * Submits data to the form, transforms and validates it. + * Submits data to the form. * - * @param mixed $submittedData The submitted data - * @param bool $clearMissing Whether to set fields to NULL when they - * are missing in the submitted data + * @param string|array|null $submittedData The submitted data + * @param bool $clearMissing Whether to set fields to NULL + * when they are missing in the + * submitted data. This argument + * is only used in compound form * * @return $this * @@ -280,7 +316,7 @@ public function submit($submittedData, $clearMissing = true); /** * Returns the root of the form tree. * - * @return self The root of the tree + * @return self The root of the tree, may be the instance itself */ public function getRoot(); @@ -292,8 +328,6 @@ public function getRoot(); public function isRoot(); /** - * Creates a view. - * * @return FormView The view */ public function createView(FormView $parent = null); diff --git a/src/Symfony/Component/Form/FormRegistry.php b/src/Symfony/Component/Form/FormRegistry.php index 1446976191c6d..cbb1d7a4174c7 100644 --- a/src/Symfony/Component/Form/FormRegistry.php +++ b/src/Symfony/Component/Form/FormRegistry.php @@ -26,7 +26,7 @@ class FormRegistry implements FormRegistryInterface /** * Extensions. * - * @var FormExtensionInterface[] An array of FormExtensionInterface + * @var FormExtensionInterface[] */ private $extensions = []; diff --git a/src/Symfony/Component/Form/FormRendererEngineInterface.php b/src/Symfony/Component/Form/FormRendererEngineInterface.php index 9ae5233f46273..2f0b868dd0098 100644 --- a/src/Symfony/Component/Form/FormRendererEngineInterface.php +++ b/src/Symfony/Component/Form/FormRendererEngineInterface.php @@ -74,7 +74,7 @@ public function getResourceForBlockName(FormView $view, $blockName); * First the themes attached directly to * the view with {@link setTheme()} are * considered, then the ones of its parent etc. - * @param array $blockNameHierarchy The block name hierarchy, with the root block + * @param string[] $blockNameHierarchy The block name hierarchy, with the root block * at the beginning * @param int $hierarchyLevel The level in the hierarchy at which to start * looking. Level 0 indicates the root block, i.e. @@ -112,7 +112,7 @@ public function getResourceForBlockNameHierarchy(FormView $view, array $blockNam * First the themes attached directly to * the view with {@link setTheme()} are * considered, then the ones of its parent etc. - * @param array $blockNameHierarchy The block name hierarchy, with the root block + * @param string[] $blockNameHierarchy The block name hierarchy, with the root block * at the beginning * @param int $hierarchyLevel The level in the hierarchy at which to start * looking. Level 0 indicates the root block, i.e. diff --git a/src/Symfony/Component/Form/FormTypeGuesserChain.php b/src/Symfony/Component/Form/FormTypeGuesserChain.php index f38ba8e5d431a..5ce8794248801 100644 --- a/src/Symfony/Component/Form/FormTypeGuesserChain.php +++ b/src/Symfony/Component/Form/FormTypeGuesserChain.php @@ -19,7 +19,7 @@ class FormTypeGuesserChain implements FormTypeGuesserInterface private $guessers = []; /** - * @param FormTypeGuesserInterface[] $guessers Guessers as instances of FormTypeGuesserInterface + * @param FormTypeGuesserInterface[] $guessers * * @throws UnexpectedTypeException if any guesser does not implement FormTypeGuesserInterface */ diff --git a/src/Symfony/Component/Form/FormTypeGuesserInterface.php b/src/Symfony/Component/Form/FormTypeGuesserInterface.php index 6521ea47ca767..3be9a0c9f8570 100644 --- a/src/Symfony/Component/Form/FormTypeGuesserInterface.php +++ b/src/Symfony/Component/Form/FormTypeGuesserInterface.php @@ -49,8 +49,8 @@ public function guessMaxLength($class, $property); /** * Returns a guess about the field's pattern. * - * - When you have a min value, you guess a min length of this min (LOW_CONFIDENCE) , lines below - * - If this value is a float type, this is wrong so you guess null with MEDIUM_CONFIDENCE to override the previous guess. + * - When you have a min value, you guess a min length of this min (LOW_CONFIDENCE) + * - Then line below, if this value is a float type, this is wrong so you guess null with MEDIUM_CONFIDENCE to override the previous guess. * Example: * You want a float greater than 5, 4.512313 is not valid but length(4.512314) > length(5) * diff --git a/src/Symfony/Component/Form/Forms.php b/src/Symfony/Component/Form/Forms.php index 7c61d640e21ca..b8e2d27fec3e8 100644 --- a/src/Symfony/Component/Form/Forms.php +++ b/src/Symfony/Component/Form/Forms.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Form; -use Symfony\Component\Form\Extension\Core\CoreExtension; - /** * Entry point of the Form component. * @@ -105,10 +103,7 @@ public static function createFormFactory() */ public static function createFormFactoryBuilder() { - $builder = new FormFactoryBuilder(); - $builder->addExtension(new CoreExtension()); - - return $builder; + return new FormFactoryBuilder(true); } /** diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index 246ea92ccee50..5997fba67df15 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -41,6 +41,8 @@ public function __construct(ServerParams $params = null) /** * {@inheritdoc} + * + * @throws Exception\UnexpectedTypeException If the $request is not null */ public function handleRequest(FormInterface $form, $request = null) { @@ -133,6 +135,30 @@ public function isFileUpload($data) return \is_array($data) && isset($data['error']) && \is_int($data['error']); } + /** + * @return int|null + */ + public function getUploadFileError($data) + { + if (!\is_array($data)) { + return null; + } + + if (!isset($data['error'])) { + return null; + } + + if (!\is_int($data['error'])) { + return null; + } + + if (UPLOAD_ERR_OK === $data['error']) { + return null; + } + + return $data['error']; + } + /** * Returns the method used to submit the request to the server. * diff --git a/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php index 16d4045e6d580..b470769344bb2 100644 --- a/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php @@ -360,6 +360,28 @@ public function testInvalidFilesAreRejected() $this->assertFalse($this->requestHandler->isFileUpload($this->getInvalidFile())); } + /** + * @dataProvider uploadFileErrorCodes + */ + public function testFailedFileUploadIsTurnedIntoFormError($errorCode, $expectedErrorCode) + { + $this->assertSame($expectedErrorCode, $this->requestHandler->getUploadFileError($this->getFailedUploadedFile($errorCode))); + } + + public function uploadFileErrorCodes() + { + return [ + 'no error' => [UPLOAD_ERR_OK, null], + 'upload_max_filesize ini directive' => [UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_INI_SIZE], + 'MAX_FILE_SIZE from form' => [UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_FORM_SIZE], + 'partially uploaded' => [UPLOAD_ERR_PARTIAL, UPLOAD_ERR_PARTIAL], + 'no file upload' => [UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_FILE], + 'missing temporary directory' => [UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_NO_TMP_DIR], + 'write failure' => [UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_CANT_WRITE], + 'stopped by extension' => [UPLOAD_ERR_EXTENSION, UPLOAD_ERR_EXTENSION], + ]; + } + abstract protected function setRequestData($method, $data, $files = []); abstract protected function getRequestHandler(); @@ -368,6 +390,8 @@ abstract protected function getUploadedFile($suffix = ''); abstract protected function getInvalidFile(); + abstract protected function getFailedUploadedFile($errorCode); + protected function createForm($name, $method = null, $compound = false) { $config = $this->createBuilder($name, $compound); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index c520ab1a0de74..5a9884e2951b0 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -144,12 +144,10 @@ function ($object) { return $object->value; } public function testCreateFromChoicesGrouped() { - $list = $this->factory->createListFromChoices( - [ - 'Group 1' => ['A' => $this->obj1, 'B' => $this->obj2], - 'Group 2' => ['C' => $this->obj3, 'D' => $this->obj4], - ] - ); + $list = $this->factory->createListFromChoices([ + 'Group 1' => ['A' => $this->obj1, 'B' => $this->obj2], + 'Group 2' => ['C' => $this->obj3, 'D' => $this->obj4], + ]); $this->assertObjectListWithGeneratedValues($list); } diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index ba4f26ecc9d8c..edc3aeda14b2c 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -450,6 +450,31 @@ public function testSetDataMapsViewDataToChildren() $form->setData('foo'); } + public function testSetDataDoesNotMapViewDataToChildrenWithLockedSetData() + { + $mapper = new PropertyPathMapper(); + $viewData = [ + 'firstName' => 'Fabien', + 'lastName' => 'Pot', + ]; + $form = $this->getBuilder() + ->setCompound(true) + ->setDataMapper($mapper) + ->addViewTransformer(new FixedDataTransformer([ + '' => '', + 'foo' => $viewData, + ])) + ->getForm(); + + $form->add($child1 = $this->getBuilder('firstName')->getForm()); + $form->add($child2 = $this->getBuilder('lastName')->setData('Potencier')->setDataLocked(true)->getForm()); + + $form->setData('foo'); + + $this->assertSame('Fabien', $form->get('firstName')->getData()); + $this->assertSame('Potencier', $form->get('lastName')->getData()); + } + public function testSubmitSupportsDynamicAdditionAndRemovalOfChildren() { $form = $this->form; diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateIntervalTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateIntervalTypeTest.php index 6e19b9fc3d22d..4add8b13bfbf2 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateIntervalTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateIntervalTypeTest.php @@ -442,7 +442,7 @@ public function testSubmitNullUsesDateEmptyData($widget, $emptyData, $expectedDa public function provideEmptyData() { - $expectedData = \DateInterval::createFromDateString('6 years and 4 months'); + $expectedData = new \DateInterval('P6Y4M'); return [ 'Simple field' => ['single_text', 'P6Y4M0D', $expectedData], diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php index ea012c451e885..c566786c8cf17 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php @@ -11,16 +11,26 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; +use Symfony\Component\Form\Extension\Core\CoreExtension; use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; use Symfony\Component\Form\NativeRequestHandler; use Symfony\Component\Form\RequestHandlerInterface; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\Translation\TranslatorInterface; class FileTypeTest extends BaseTypeTest { const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\FileType'; + protected function getExtensions() + { + $translator = $this->getMockBuilder(TranslatorInterface::class)->getMock(); + $translator->expects($this->any())->method('trans')->willReturnArgument(0); + + return array_merge(parent::getExtensions(), [new CoreExtension(null, null, $translator)]); + } + // https://github.com/symfony/symfony/pull/5028 public function testSetData() { @@ -184,6 +194,128 @@ public function requestHandlerProvider() ]; } + /** + * @dataProvider uploadFileErrorCodes + */ + public function testFailedFileUploadIsTurnedIntoFormErrorUsingHttpFoundationRequestHandler($errorCode, $expectedErrorMessage) + { + $form = $this->factory + ->createBuilder(static::TESTED_TYPE) + ->setRequestHandler(new HttpFoundationRequestHandler()) + ->getForm(); + $form->submit(new UploadedFile(__DIR__.'/../../../Fixtures/foo', 'foo', null, null, $errorCode, true)); + + if (UPLOAD_ERR_OK === $errorCode) { + $this->assertTrue($form->isValid()); + } else { + $this->assertFalse($form->isValid()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[0]->getMessage()); + } + } + + /** + * @dataProvider uploadFileErrorCodes + */ + public function testFailedFileUploadIsTurnedIntoFormErrorUsingNativeRequestHandler($errorCode, $expectedErrorMessage) + { + $form = $this->factory + ->createBuilder(static::TESTED_TYPE) + ->setRequestHandler(new NativeRequestHandler()) + ->getForm(); + $form->submit([ + 'name' => 'foo.txt', + 'type' => 'text/plain', + 'tmp_name' => 'owfdskjasdfsa', + 'error' => $errorCode, + 'size' => 100, + ]); + + if (UPLOAD_ERR_OK === $errorCode) { + $this->assertTrue($form->isValid()); + } else { + $this->assertFalse($form->isValid()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[0]->getMessage()); + } + } + + /** + * @dataProvider uploadFileErrorCodes + */ + public function testMultipleSubmittedFailedFileUploadsAreTurnedIntoFormErrorUsingHttpFoundationRequestHandler($errorCode, $expectedErrorMessage) + { + $form = $this->factory + ->createBuilder(static::TESTED_TYPE, null, [ + 'multiple' => true, + ]) + ->setRequestHandler(new HttpFoundationRequestHandler()) + ->getForm(); + $form->submit([ + new UploadedFile(__DIR__.'/../../../Fixtures/foo', 'foo', null, null, $errorCode, true), + new UploadedFile(__DIR__.'/../../../Fixtures/foo', 'bar', null, null, $errorCode, true), + ]); + + if (UPLOAD_ERR_OK === $errorCode) { + $this->assertTrue($form->isValid()); + } else { + $this->assertFalse($form->isValid()); + $this->assertCount(2, $form->getErrors()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[0]->getMessage()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[1]->getMessage()); + } + } + + /** + * @dataProvider uploadFileErrorCodes + */ + public function testMultipleSubmittedFailedFileUploadsAreTurnedIntoFormErrorUsingNativeRequestHandler($errorCode, $expectedErrorMessage) + { + $form = $this->factory + ->createBuilder(static::TESTED_TYPE, null, [ + 'multiple' => true, + ]) + ->setRequestHandler(new NativeRequestHandler()) + ->getForm(); + $form->submit([ + [ + 'name' => 'foo.txt', + 'type' => 'text/plain', + 'tmp_name' => 'owfdskjasdfsa', + 'error' => $errorCode, + 'size' => 100, + ], + [ + 'name' => 'bar.txt', + 'type' => 'text/plain', + 'tmp_name' => 'owfdskjasdfsa', + 'error' => $errorCode, + 'size' => 100, + ], + ]); + + if (UPLOAD_ERR_OK === $errorCode) { + $this->assertTrue($form->isValid()); + } else { + $this->assertFalse($form->isValid()); + $this->assertCount(2, $form->getErrors()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[0]->getMessage()); + $this->assertSame($expectedErrorMessage, $form->getErrors()[1]->getMessage()); + } + } + + public function uploadFileErrorCodes() + { + return [ + 'no error' => [UPLOAD_ERR_OK, null], + 'upload_max_filesize ini directive' => [UPLOAD_ERR_INI_SIZE, 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.'], + 'MAX_FILE_SIZE from form' => [UPLOAD_ERR_FORM_SIZE, 'The file is too large.'], + 'partially uploaded' => [UPLOAD_ERR_PARTIAL, 'The file could not be uploaded.'], + 'no file upload' => [UPLOAD_ERR_NO_FILE, 'The file could not be uploaded.'], + 'missing temporary directory' => [UPLOAD_ERR_NO_TMP_DIR, 'The file could not be uploaded.'], + 'write failure' => [UPLOAD_ERR_CANT_WRITE, 'The file could not be uploaded.'], + 'stopped by extension' => [UPLOAD_ERR_EXTENSION, 'The file could not be uploaded.'], + ]; + } + private function createUploadedFile(RequestHandlerInterface $requestHandler, $path, $originalName) { if ($requestHandler instanceof HttpFoundationRequestHandler) { diff --git a/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php index 2b134511830e7..0e5389568e5ce 100644 --- a/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/HttpFoundation/HttpFoundationRequestHandlerTest.php @@ -56,4 +56,9 @@ protected function getInvalidFile() { return 'file:///etc/passwd'; } + + protected function getFailedUploadedFile($errorCode) + { + return new UploadedFile(__DIR__.'/../../Fixtures/foo', 'foo', null, null, $errorCode, true); + } } diff --git a/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php index 25a4716650755..36638a124f072 100644 --- a/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/NativeRequestHandlerTest.php @@ -275,4 +275,15 @@ protected function getInvalidFile() 'size' => '100', ]; } + + protected function getFailedUploadedFile($errorCode) + { + return [ + 'name' => 'upload.txt', + 'type' => 'text/plain', + 'tmp_name' => 'owfdskjasdfsa', + 'error' => $errorCode, + 'size' => 100, + ]; + } } diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 214ff3beab241..32c21c8432b7a 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -54,6 +54,26 @@ public function getIterator() class SimpleFormTest extends AbstractFormTest { + /** + * @dataProvider provideFormNames + */ + public function testGetPropertyPath($name, $propertyPath) + { + $config = new FormConfigBuilder($name, null, $this->dispatcher); + $form = new Form($config); + + $this->assertEquals($propertyPath, $form->getPropertyPath()); + } + + public function provideFormNames() + { + yield [null, null]; + yield ['', null]; + yield ['0', new PropertyPath('0')]; + yield [0, new PropertyPath('0')]; + yield ['name', new PropertyPath('name')]; + } + public function testDataIsInitializedToConfiguredValue() { $model = new FixedDataTransformer([ @@ -76,7 +96,7 @@ public function testDataIsInitializedToConfiguredValue() /** * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException - * @expectedExceptionMessage Unable to transform value for property path "name": No mapping for value "arg" + * @expectedExceptionMessage Unable to transform data for property path "name": No mapping for value "arg" */ public function testDataTransformationFailure() { diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php index 1d95b8b566a5b..a8dda9f671d17 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php @@ -126,9 +126,13 @@ private function getContainerDeprecationLogs() return []; } + if ('' === $logContent = trim(file_get_contents($file))) { + return []; + } + $bootTime = filemtime($file); $logs = []; - foreach (unserialize(file_get_contents($file)) as $log) { + foreach (unserialize($logContent) as $log) { $log['context'] = ['exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])]; $log['timestamp'] = $bootTime; $log['priority'] = 100; diff --git a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php index 99149ab0be569..f48db705686b6 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php @@ -47,6 +47,7 @@ public function collect(Request $request, Response $response, \Exception $except 'token' => $response->headers->get('X-Debug-Token'), 'start_time' => $startTime * 1000, 'events' => [], + 'stopwatch_installed' => \class_exists(Stopwatch::class, false), ]; } @@ -139,6 +140,14 @@ public function getStartTime() return $this->data['start_time']; } + /** + * @return bool whether or not the stopwatch component is installed + */ + public function isStopwatchInstalled() + { + return $this->data['stopwatch_installed']; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index fed9e7e3febe7..df9df09c0bc32 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -19,6 +19,7 @@ use Symfony\Component\Debug\ExceptionHandler; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -40,13 +41,13 @@ class DebugHandlersListener implements EventSubscriberInterface private $hasTerminatedWithException; /** - * @param callable|null $exceptionHandler A handler that will be called on Exception - * @param LoggerInterface|null $logger A PSR-3 logger - * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants - * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value - * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged - * @param string|array $fileLinkFormat The format for links to source files - * @param bool $scope Enables/disables scoping mode + * @param callable|null $exceptionHandler A handler that will be called on Exception + * @param LoggerInterface|null $logger A PSR-3 logger + * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged + * @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files + * @param bool $scope Enables/disables scoping mode */ public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, $throwAt = E_ALL, $scream = true, $fileLinkFormat = null, $scope = true) { diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index f283f0c7bdb60..ac0ecf301744a 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -67,11 +67,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '3.4.24'; - const VERSION_ID = 30424; + const VERSION = '3.4.25'; + const VERSION_ID = 30425; const MAJOR_VERSION = 3; const MINOR_VERSION = 4; - const RELEASE_VERSION = 24; + const RELEASE_VERSION = 25; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '11/2020'; diff --git a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php index caac7fd6ebb99..8fdc00506c860 100644 --- a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php +++ b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php @@ -51,21 +51,7 @@