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 -%}
\
- \
+ \
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 @@