Merge branch '2.7'

* 2.7: (60 commits)
  [Translation][Profiler] fixed Collect empty Messages.
  [VarDumper] Towards PHP7 support
  Fix currently broken tests
  [Form][choice] added choice_translation_domain to avoid trans options.
  [Translation][Profiler]  added the number of times a translation has been used.
  [DoctrineBridge] Removed useless code
  [Debug] Updated CHANGELOG
  [Debug] Use symfony_debug_backtrace() in FatalErrorException when available
  [Debug] Add debug extension to the test suite
  [Debug] Add symfony_debug_backtrace() that works with fatal errors
  [Form] Updated CHANGELOG and UPGRADE files
  [HttpKernel] Embed the original exception as previous to bounced exceptions
  Added feedback about the current symfony version
  Deprecated precision option in favor of scale
  [Enhancement] netbeans - force interactive shell when limited detection
  Automatically start server:run if server:start failed
  Tweaked some console command styles
  [FrameworkBundle] fixes displaying of deprecation notices.
  make date formats and number formats configurable
  Revert "Added missing changelog entry"
  ...

Conflicts:
	CHANGELOG-2.3.md
	CHANGELOG-2.6.md
	src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
	src/Symfony/Bridge/Twig/composer.json
	src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml
	src/Symfony/Bundle/FrameworkBundle/composer.json
	src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php
	src/Symfony/Bundle/TwigBundle/Extension/ActionsExtension.php
	src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php
	src/Symfony/Component/Debug/CHANGELOG.md
	src/Symfony/Component/Debug/ErrorHandler.php
	src/Symfony/Component/Form/CHANGELOG.md
	src/Symfony/Component/VarDumper/Tests/CliDumperTest.php
	src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php
This commit is contained in:
Nicolas Grekas 2015-04-05 10:20:29 +02:00
commit 6a0b617c3d
182 changed files with 8320 additions and 1200 deletions

View File

@ -30,6 +30,7 @@ before_install:
- if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi; - if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;
- if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi; - if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;
- if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then (pecl install -f memcached-2.1.0 && echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini) || echo "Let's continue without memcache extension"; fi; - if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then (pecl install -f memcached-2.1.0 && echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini) || echo "Let's continue without memcache extension"; fi;
- if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then (cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo "extension = $(pwd)/modules/symfony_debug.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini); fi;
- if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then php -i; fi; - if [[ "$TRAVIS_PHP_VERSION" != *"nightly" ]]; then php -i; fi;
- sudo locale-gen fr_FR.UTF-8 && sudo update-locale - sudo locale-gen fr_FR.UTF-8 && sudo update-locale
# Set the COMPOSER_ROOT_VERSION to the right version according to the branch being built # Set the COMPOSER_ROOT_VERSION to the right version according to the branch being built

View File

@ -11,8 +11,8 @@ Symfony is the result of the work of many people who made the code better
- Jordi Boggiano (seldaek) - Jordi Boggiano (seldaek)
- Johannes S (johannes) - Johannes S (johannes)
- Kris Wallsmith (kriswallsmith) - Kris Wallsmith (kriswallsmith)
- Christophe Coevoet (stof)
- Nicolas Grekas (nicolas-grekas) - Nicolas Grekas (nicolas-grekas)
- Christophe Coevoet (stof)
- Jakub Zalas (jakubzalas) - Jakub Zalas (jakubzalas)
- Pascal Borreli (pborreli) - Pascal Borreli (pborreli)
- Hugo Hamon (hhamon) - Hugo Hamon (hhamon)
@ -21,9 +21,9 @@ Symfony is the result of the work of many people who made the code better
- Ryan Weaver (weaverryan) - Ryan Weaver (weaverryan)
- Lukas Kahwe Smith (lsmith) - Lukas Kahwe Smith (lsmith)
- Romain Neutron (romain) - Romain Neutron (romain)
- Christian Flothmann (xabbuh)
- Jeremy Mikola (jmikola) - Jeremy Mikola (jmikola)
- Jean-François Simon (jfsimon) - Jean-François Simon (jfsimon)
- Christian Flothmann (xabbuh)
- Benjamin Eberlei (beberlei) - Benjamin Eberlei (beberlei)
- Igor Wiedler (igorw) - Igor Wiedler (igorw)
- Martin Hasoň (hason) - Martin Hasoň (hason)
@ -37,16 +37,16 @@ Symfony is the result of the work of many people who made the code better
- stealth35 (stealth35) - stealth35 (stealth35)
- Alexander Mols (asm89) - Alexander Mols (asm89)
- Bulat Shakirzyanov (avalanche123) - Bulat Shakirzyanov (avalanche123)
- Abdellatif Ait boudad (aitboudad)
- Francis Besset (francisbesset) - Francis Besset (francisbesset)
- Saša Stamenković (umpirsky) - Saša Stamenković (umpirsky)
- Henrik Bjørnskov (henrikbjorn) - Henrik Bjørnskov (henrikbjorn)
- Miha Vrhovnik - Miha Vrhovnik
- Kévin Dunglas (dunglas)
- Sarah Khalil (saro0h)
- Konstantin Kudryashov (everzet) - Konstantin Kudryashov (everzet)
- Bilal Amarni (bamarni) - Bilal Amarni (bamarni)
- Kévin Dunglas (dunglas)
- Florin Patan (florinpatan) - Florin Patan (florinpatan)
- Abdellatif Ait Boudad (aitboudad)
- Sarah Khalil (saro0h)
- Eric Clemmons (ericclemmons) - Eric Clemmons (ericclemmons)
- Andrej Hudec (pulzarraider) - Andrej Hudec (pulzarraider)
- Deni - Deni
@ -61,13 +61,14 @@ Symfony is the result of the work of many people who made the code better
- Daniel Holmes (dholmes) - Daniel Holmes (dholmes)
- Bart van den Burg (burgov) - Bart van den Burg (burgov)
- Jordan Alliot (jalliot) - Jordan Alliot (jalliot)
- Kevin Bond (kbond)
- John Wards (johnwards) - John Wards (johnwards)
- Fran Moreno (franmomu) - Fran Moreno (franmomu)
- Antoine Hérault (herzult)
- Kevin Bond (kbond)
- Toni Uebernickel (havvg)
- Luis Cordova (cordoval) - Luis Cordova (cordoval)
- Antoine Hérault (herzult)
- Toni Uebernickel (havvg)
- Arnaud Le Blanc (arnaud-lb) - Arnaud Le Blanc (arnaud-lb)
- Gábor Egyed (1ed)
- Tim Nagel (merk) - Tim Nagel (merk)
- Brice BERNARD (brikou) - Brice BERNARD (brikou)
- marc.weistroff - marc.weistroff
@ -78,7 +79,6 @@ Symfony is the result of the work of many people who made the code better
- Colin Frei - Colin Frei
- Jérôme Tamarelle (gromnan) - Jérôme Tamarelle (gromnan)
- Adrien Brault (adrienbrault) - Adrien Brault (adrienbrault)
- Gábor Egyed (1ed)
- excelwebzone - excelwebzone
- Jacob Dreesen (jdreesen) - Jacob Dreesen (jdreesen)
- Fabien Pennequin (fabienpennequin) - Fabien Pennequin (fabienpennequin)
@ -93,26 +93,29 @@ Symfony is the result of the work of many people who made the code better
- Daniel Gomes (danielcsgomes) - Daniel Gomes (danielcsgomes)
- Hidenori Goto (hidenorigoto) - Hidenori Goto (hidenorigoto)
- David Buchmann (dbu) - David Buchmann (dbu)
- Guilherme Blanco (guilhermeblanco)
- Jérémy DERUSSÉ (jderusse) - Jérémy DERUSSÉ (jderusse)
- Pablo Godel (pgodel) - Pablo Godel (pgodel)
- Eric GELOEN (gelo) - Eric GELOEN (gelo)
- Jérémie Augustin (jaugustin) - Jérémie Augustin (jaugustin)
- Rafael Dohms (rdohms) - Rafael Dohms (rdohms)
- Guilherme Blanco (guilhermeblanco)
- Tigran Azatyan (tigranazatyan) - Tigran Azatyan (tigranazatyan)
- Javier Eguiluz (javier.eguiluz) - Javier Eguiluz (javier.eguiluz)
- Arnaud Kleinpeter (nanocom) - Arnaud Kleinpeter (nanocom)
- Richard Shank (iampersistent) - Richard Shank (iampersistent)
- Dariusz Ruminski
- Clemens Tolboom - Clemens Tolboom
- Helmer Aaviksoo - Helmer Aaviksoo
- Sebastiaan Stok (sstok) - Sebastiaan Stok (sstok)
- Hiromi Hishida (77web) - Hiromi Hishida (77web)
- Matthieu Ouellette-Vachon (maoueh) - Matthieu Ouellette-Vachon (maoueh)
- Michał Pipa (michal.pipa) - Michał Pipa (michal.pipa)
- Issei Murasawa (issei_m)
- Amal Raghav (kertz) - Amal Raghav (kertz)
- Jonathan Ingram (jonathaningram) - Jonathan Ingram (jonathaningram)
- Artur Kotyrba - Artur Kotyrba
- Rouven Weßling (realityking) - Rouven Weßling (realityking)
- Andréia Bohner (andreia)
- Dmitrii Chekaliuk (lazyhammer) - Dmitrii Chekaliuk (lazyhammer)
- Clément JOBEILI (dator) - Clément JOBEILI (dator)
- Dorian Villet (gnutix) - Dorian Villet (gnutix)
@ -123,14 +126,12 @@ Symfony is the result of the work of many people who made the code better
- Benjamin Dulau (dbenjamin) - Benjamin Dulau (dbenjamin)
- Matthias Pigulla (mpdude) - Matthias Pigulla (mpdude)
- Andreas Hucks (meandmymonkey) - Andreas Hucks (meandmymonkey)
- Andréia Bohner (andreia)
- Noel Guilbert (noel) - Noel Guilbert (noel)
- Joel Wurtz (brouznouf) - Joel Wurtz (brouznouf)
- Charles Sarrazin (csarrazi) - Charles Sarrazin (csarrazi)
- bronze1man - bronze1man
- sun (sun) - sun (sun)
- Larry Garfield (crell) - Larry Garfield (crell)
- Issei Murasawa (issei_m)
- Martin Schuhfuß (usefulthink) - Martin Schuhfuß (usefulthink)
- Thomas Rabaix (rande) - Thomas Rabaix (rande)
- Matthieu Bontemps (mbontemps) - Matthieu Bontemps (mbontemps)
@ -163,6 +164,7 @@ Symfony is the result of the work of many people who made the code better
- Xavier Montaña Carreras (xmontana) - Xavier Montaña Carreras (xmontana)
- Michele Orselli (orso) - Michele Orselli (orso)
- Chris Wilkinson (thewilkybarkid) - Chris Wilkinson (thewilkybarkid)
- Joshua Thijssen
- Xavier Perez - Xavier Perez
- Arjen Brouwer (arjenjb) - Arjen Brouwer (arjenjb)
- Katsuhiro OGAWA - Katsuhiro OGAWA
@ -181,16 +183,18 @@ Symfony is the result of the work of many people who made the code better
- Jeremy Livingston (jeremylivingston) - Jeremy Livingston (jeremylivingston)
- Nikita Konstantinov - Nikita Konstantinov
- Wodor Wodorski - Wodor Wodorski
- Matthieu Auger (matthieuauger)
- julien pauli (jpauli) - julien pauli (jpauli)
- Beau Simensen (simensen) - Beau Simensen (simensen)
- Robert Kiss (kepten) - Robert Kiss (kepten)
- John Kary (johnkary)
- Ruben Gonzalez (rubenrua) - Ruben Gonzalez (rubenrua)
- Kim Hemsø Rasmussen (kimhemsoe) - Kim Hemsø Rasmussen (kimhemsoe)
- Florian Lonqueu-Brochard (florianlb) - Florian Lonqueu-Brochard (florianlb)
- Tom Van Looy (tvlooy) - Tom Van Looy (tvlooy)
- Wouter Van Hecke - Wouter Van Hecke
- Joshua Thijssen
- Peter Kruithof (pkruithof) - Peter Kruithof (pkruithof)
- Vladimir Reznichenko (kalessil)
- Michael Holm (hollo) - Michael Holm (hollo)
- Warnar Boekkooi (boekkooi) - Warnar Boekkooi (boekkooi)
- Marc Weistroff (futurecat) - Marc Weistroff (futurecat)
@ -222,7 +226,6 @@ Symfony is the result of the work of many people who made the code better
- Marco Pivetta (ocramius) - Marco Pivetta (ocramius)
- Ricard Clau (ricardclau) - Ricard Clau (ricardclau)
- Erin Millard - Erin Millard
- John Kary (johnkary)
- Matthew Lewinski (lewinski) - Matthew Lewinski (lewinski)
- alquerci - alquerci
- Francesco Levorato - Francesco Levorato
@ -232,7 +235,6 @@ Symfony is the result of the work of many people who made the code better
- Inal DJAFAR (inalgnu) - Inal DJAFAR (inalgnu)
- Christian Gärtner (dagardner) - Christian Gärtner (dagardner)
- Felix Labrecque - Felix Labrecque
- Vladimir Reznichenko (kalessil)
- Yaroslav Kiliba - Yaroslav Kiliba
- Sébastien Lavoie (lavoiesl) - Sébastien Lavoie (lavoiesl)
- Terje Bråten - Terje Bråten
@ -283,7 +285,6 @@ Symfony is the result of the work of many people who made the code better
- Brian King - Brian King
- Michel Salib (michelsalib) - Michel Salib (michelsalib)
- geoffrey - geoffrey
- Matthieu Auger (matthieuauger)
- Lorenz Schori - Lorenz Schori
- Jeanmonod David (jeanmonod) - Jeanmonod David (jeanmonod)
- Jan Schumann - Jan Schumann
@ -309,7 +310,6 @@ Symfony is the result of the work of many people who made the code better
- Christian Schmidt - Christian Schmidt
- Marcin Sikoń (marphi) - Marcin Sikoń (marphi)
- franek (franek) - franek (franek)
- Dariusz Ruminski
- Adam Harvey - Adam Harvey
- Diego Saint Esteben (dii3g0) - Diego Saint Esteben (dii3g0)
- Alex Bakhturin - Alex Bakhturin
@ -336,6 +336,7 @@ Symfony is the result of the work of many people who made the code better
- mmoreram - mmoreram
- Markus Lanthaler (lanthaler) - Markus Lanthaler (lanthaler)
- Vicent Soria Durá (vicentgodella) - Vicent Soria Durá (vicentgodella)
- Anthony Ferrara
- Ioan Negulescu - Ioan Negulescu
- Jakub Škvára (jskvara) - Jakub Škvára (jskvara)
- Daniel Beyer - Daniel Beyer
@ -343,9 +344,11 @@ Symfony is the result of the work of many people who made the code better
- alexpods - alexpods
- Erik Trapman (eriktrapman) - Erik Trapman (eriktrapman)
- De Cock Xavier (xdecock) - De Cock Xavier (xdecock)
- Scott Arciszewski
- Norbert Orzechowicz (norzechowicz) - Norbert Orzechowicz (norzechowicz)
- Tobias Nyholm (tobias) - Tobias Nyholm (tobias)
- Matthijs van den Bos (matthijs) - Matthijs van den Bos (matthijs)
- Loick Piera (pyrech)
- Lenard Palko - Lenard Palko
- Nils Adermann (naderman) - Nils Adermann (naderman)
- Gábor Fási - Gábor Fási
@ -371,9 +374,11 @@ Symfony is the result of the work of many people who made the code better
- Zach Badgett (zachbadgett) - Zach Badgett (zachbadgett)
- Aurélien Fredouelle - Aurélien Fredouelle
- Pavel Campr (pcampr) - Pavel Campr (pcampr)
- Maxime Steinhausser (ogizanagi)
- Disquedur - Disquedur
- Geoffrey Tran (geoff) - Geoffrey Tran (geoff)
- Jan Behrens - Jan Behrens
- Mantas Var (mvar)
- Sebastian Krebs - Sebastian Krebs
- Christopher Davis (chrisguitarguy) - Christopher Davis (chrisguitarguy)
- Thomas Lallement (raziel057) - Thomas Lallement (raziel057)
@ -387,6 +392,7 @@ Symfony is the result of the work of many people who made the code better
- Max Rath (drak3) - Max Rath (drak3)
- Stéphane Escandell (sescandell) - Stéphane Escandell (sescandell)
- Sinan Eldem - Sinan Eldem
- Alexandre Dupuy (satchette)
- Nahuel Cuesta (ncuesta) - Nahuel Cuesta (ncuesta)
- Chris Boden (cboden) - Chris Boden (cboden)
- Asmir Mustafic (goetas) - Asmir Mustafic (goetas)
@ -484,7 +490,6 @@ Symfony is the result of the work of many people who made the code better
- Xavier Lacot (xavier) - Xavier Lacot (xavier)
- Olivier Maisonneuve (olineuve) - Olivier Maisonneuve (olineuve)
- Francis Turmel (fturmel) - Francis Turmel (fturmel)
- Loick Piera (pyrech)
- cgonzalez - cgonzalez
- Ben - Ben
- Jayson Xu (superjavason) - Jayson Xu (superjavason)
@ -506,6 +511,7 @@ Symfony is the result of the work of many people who made the code better
- Fabian Vogler (fabian) - Fabian Vogler (fabian)
- Korvin Szanto - Korvin Szanto
- Maksim Kotlyar (makasim) - Maksim Kotlyar (makasim)
- Ivan Kurnosov
- Neil Ferreira - Neil Ferreira
- Dmitry Parnas (parnas) - Dmitry Parnas (parnas)
- DQNEO - DQNEO
@ -516,6 +522,7 @@ Symfony is the result of the work of many people who made the code better
- David Romaní - David Romaní
- Patrick Allaert - Patrick Allaert
- Gustavo Falco (gfalco) - Gustavo Falco (gfalco)
- Matt Robinson (inanimatt)
- Aleksey Podskrebyshev - Aleksey Podskrebyshev
- David Marín Carreño (davefx) - David Marín Carreño (davefx)
- Jörn Lang (j.lang) - Jörn Lang (j.lang)
@ -580,6 +587,7 @@ Symfony is the result of the work of many people who made the code better
- Michael Tibben - Michael Tibben
- Sander Marechal - Sander Marechal
- Radosław Benkel - Radosław Benkel
- Marcos Sánchez
- ttomor - ttomor
- Mei Gwilym (meigwilym) - Mei Gwilym (meigwilym)
- Michael H. Arieli (excelwebzone) - Michael H. Arieli (excelwebzone)
@ -622,6 +630,7 @@ Symfony is the result of the work of many people who made the code better
- nacho - nacho
- Piotr Antosik (antek88) - Piotr Antosik (antek88)
- Artem Lopata - Artem Lopata
- Samuel ROZE (sroze)
- Marcos Quesada (marcos_quesada) - Marcos Quesada (marcos_quesada)
- Matthew Vickery (mattvick) - Matthew Vickery (mattvick)
- Dan Finnie - Dan Finnie
@ -675,6 +684,7 @@ Symfony is the result of the work of many people who made the code better
- Yannick - Yannick
- Eduardo García Sanz (coma) - Eduardo García Sanz (coma)
- Sebastian Grodzicki (sgrodzicki) - Sebastian Grodzicki (sgrodzicki)
- Michael Lee (zerustech)
- Roy Van Ginneken - Roy Van Ginneken
- David de Boer (ddeboer) - David de Boer (ddeboer)
- Gilles Doge (gido) - Gilles Doge (gido)
@ -721,6 +731,7 @@ Symfony is the result of the work of many people who made the code better
- Malaney J. Hill - Malaney J. Hill
- Christian Flach (cmfcmf) - Christian Flach (cmfcmf)
- Cédric Girard (enk_) - Cédric Girard (enk_)
- Lars Ambrosius Wallenborn (larsborn)
- Oriol Mangas Abellan (oriolman) - Oriol Mangas Abellan (oriolman)
- Sebastian Göttschkes (sgoettschkes) - Sebastian Göttschkes (sgoettschkes)
- Tatsuya Tsuruoka - Tatsuya Tsuruoka
@ -847,6 +858,7 @@ Symfony is the result of the work of many people who made the code better
- Gunnar Lium (gunnarlium) - Gunnar Lium (gunnarlium)
- Tiago Garcia (tiagojsag) - Tiago Garcia (tiagojsag)
- Artiom - Artiom
- Jakub Simon
- Bouke Haarsma - Bouke Haarsma
- Martin Eckhardt - Martin Eckhardt
- Denis Zunke - Denis Zunke
@ -876,7 +888,6 @@ Symfony is the result of the work of many people who made the code better
- Vasily Khayrulin (sirian) - Vasily Khayrulin (sirian)
- Stefan Koopmanschap (skoop) - Stefan Koopmanschap (skoop)
- Stefan Hüsges (tronsha) - Stefan Hüsges (tronsha)
- Ivan Kurnosov
- stloyd - stloyd
- Chris Tickner - Chris Tickner
- Andrew Coulton - Andrew Coulton
@ -905,7 +916,6 @@ Symfony is the result of the work of many people who made the code better
- Julius Beckmann - Julius Beckmann
- Romain Dorgueil - Romain Dorgueil
- Grayson Koonce (breerly) - Grayson Koonce (breerly)
- Matt Robinson (inanimatt)
- Karim Cassam Chenaï (ka) - Karim Cassam Chenaï (ka)
- Nicolas Bastien (nicolas_bastien) - Nicolas Bastien (nicolas_bastien)
- Andy Stanberry - Andy Stanberry
@ -992,6 +1002,7 @@ Symfony is the result of the work of many people who made the code better
- grifx - grifx
- Robert Campbell - Robert Campbell
- Matt Lehner - Matt Lehner
- Hidde Wieringa
- Hein Zaw Htet™ - Hein Zaw Htet™
- Ruben Kruiswijk - Ruben Kruiswijk
- Michael J - Michael J
@ -1048,9 +1059,7 @@ Symfony is the result of the work of many people who made the code better
- Muriel (metalmumu) - Muriel (metalmumu)
- Michaël Perrin (michael.perrin) - Michaël Perrin (michael.perrin)
- Michael Pohlers (mick_the_big) - Michael Pohlers (mick_the_big)
- Mantas Var (mvar)
- Cayetano Soriano Gallego (neoshadybeat) - Cayetano Soriano Gallego (neoshadybeat)
- Maxime Steinhausser (ogizanagi)
- Pablo Monterde Perez (plebs) - Pablo Monterde Perez (plebs)
- Jimmy Leger (redpanda) - Jimmy Leger (redpanda)
- Cyrille Jouineau (tuxosaurus) - Cyrille Jouineau (tuxosaurus)
@ -1128,6 +1137,7 @@ Symfony is the result of the work of many people who made the code better
- Brian Freytag - Brian Freytag
- Skorney - Skorney
- mieszko4 - mieszko4
- Neophy7e
- Arrilot - Arrilot
- Markus Staab - Markus Staab
- Pierre-Louis LAUNAY - Pierre-Louis LAUNAY
@ -1140,6 +1150,8 @@ Symfony is the result of the work of many people who made the code better
- Sema - Sema
- Thorsten Hallwas - Thorsten Hallwas
- Michael Squires - Michael Squires
- Norman Soetbeer
- Benjamin Long
- Matt Janssen - Matt Janssen
- Peter Gribanov - Peter Gribanov
- kwiateusz - kwiateusz
@ -1235,6 +1247,7 @@ Symfony is the result of the work of many people who made the code better
- Vincent (vincent1870) - Vincent (vincent1870)
- Eugene Babushkin (warl) - Eugene Babushkin (warl)
- Xavier Amado (xamado) - Xavier Amado (xamado)
- Jesper Søndergaard Pedersen (zerrvox)
- Florent Cailhol - Florent Cailhol
- szymek - szymek
- craigmarvelley - craigmarvelley

View File

@ -15,7 +15,7 @@ Router
`foo%bar%2` which would be compiled to `$foo % $bar % 2` in 2.6 `foo%bar%2` which would be compiled to `$foo % $bar % 2` in 2.6
but in 2.7 you would get an error if `bar` parameter but in 2.7 you would get an error if `bar` parameter
doesn't exist or unexpected result otherwise. doesn't exist or unexpected result otherwise.
Form Form
---- ----
@ -23,41 +23,446 @@ Form
AbstractType or AbstractExtensionType has been deprecated in favor of AbstractType or AbstractExtensionType has been deprecated in favor of
overriding the new "configureOptions" method. overriding the new "configureOptions" method.
The method "setDefaultOptions(OptionsResolverInterface $resolver)" will The method "setDefaultOptions(OptionsResolverInterface $resolver)" will
be renamed in Symfony 3.0 to "configureOptions(OptionsResolver $resolver)". be renamed in Symfony 3.0 to "configureOptions(OptionsResolver $resolver)".
Before: Before:
```php ```php
use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TaskType extends AbstractType class TaskType extends AbstractType
{
// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{ {
// ... $resolver->setDefaults(array(
public function setDefaultOptions(OptionsResolverInterface $resolver) 'data_class' => 'AppBundle\Entity\Task',
{ ));
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Task',
));
}
} }
}
``` ```
After: After:
```php ```php
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
class TaskType extends AbstractType class TaskType extends AbstractType
{
// ...
public function configureOptions(OptionsResolver $resolver)
{ {
// ... $resolver->setDefaults(array(
public function configureOptions(OptionsResolver $resolver) 'data_class' => 'AppBundle\Entity\Task',
{ ));
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Task',
));
}
} }
}
```
* The "choice_list" option of ChoiceType was deprecated. You should use
"choices_as_values" or "choice_loader" now.
Before:
```php
$form->add('status', 'choice', array(
'choice_list' => new ObjectChoiceList(array(
Status::getInstance(Status::ENABLED),
Status::getInstance(Status::DISABLED),
Status::getInstance(Status::IGNORED),
)),
));
```
After:
```php
$form->add('status', 'choice', array(
'choices' => array(
Status::getInstance(Status::ENABLED),
Status::getInstance(Status::DISABLED),
Status::getInstance(Status::IGNORED),
),
'choices_as_values' => true,
));
```
* You should flip the keys and values of the "choices" option in ChoiceType
and set the "choices_as_values" option to `true`. The default value of that
option will be switched to `true` in Symfony 3.0.
Before:
```php
$form->add('status', 'choice', array(
'choices' => array(
Status::ENABLED => 'Enabled',
Status::DISABLED => 'Disabled',
Status::IGNORED => 'Ignored',
)),
));
```
After:
```php
$form->add('status', 'choice', array(
'choices' => array(
'Enabled' => Status::ENABLED,
'Disabled' => Status::DISABLED,
'Ignored' => Status::IGNORED,
),
'choices_as_values' => true,
));
```
* `Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface` was
deprecated and will be removed in Symfony 3.0. You should use
`Symfony\Component\Form\ChoiceList\ChoiceListInterface` instead.
Before:
```php
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
public function doSomething(ChoiceListInterface $choiceList)
{
// ...
}
```
After:
```php
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
public function doSomething(ChoiceListInterface $choiceList)
{
// ...
}
```
* `Symfony\Component\Form\Extension\Core\ChoiceList\View\ChoiceView` was
deprecated and will be removed in Symfony 3.0. You should use
`Symfony\Component\Form\ChoiceList\View\ChoiceView` instead.
Note that the order of the arguments passed to the constructor was inverted.
Before:
```php
use Symfony\Component\Form\Extension\Core\ChoiceList\View\ChoiceView;
$view = new ChoiceView($data, 'value', 'Label');
```
After:
```php
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
$view = new ChoiceView('Label', 'value', $data);
```
* `Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList` was
deprecated and will be removed in Symfony 3.0. You should use
`Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory` instead.
Before:
```php
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
$choiceList = new ChoiceList(
array(Status::ENABLED, Status::DISABLED, Status::IGNORED),
array('Enabled', 'Disabled', 'Ignored'),
// Preferred choices
array(Status::ENABLED),
);
```
After:
```php
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
$factory = new DefaultChoiceListFactory();
$choices = array(Status::ENABLED, Status::DISABLED, Status::IGNORED);
$labels = array('Enabled', 'Disabled', 'Ignored');
$choiceList = $factory->createListFromChoices($choices);
$choiceListView = $factory->createView(
$choiceList,
// Preferred choices
array(Status::ENABLED),
// Labels
function ($choice, $key) use ($labels) {
return $labels[$key];
}
);
```
* `Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList` was
deprecated and will be removed in Symfony 3.0. You should use
`Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory::createListFromLoader()`
together with an implementation of
`Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface` instead.
Before:
```php
use Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList;
class MyLazyChoiceList extends LazyChoiceList
{
public function loadChoiceList()
{
// load $choiceList
return $choiceList;
}
}
$choiceList = new MyLazyChoiceList();
```
After:
```php
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
class MyChoiceLoader implements ChoiceLoaderInterface
{
// ...
}
$factory = new DefaultChoiceListFactory();
$choiceList = $factory->createListFromLoader(new MyChoiceLoader());
```
* `Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList` was
deprecated and will be removed in Symfony 3.0. You should use
`Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory` instead.
Before:
```php
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
$choiceList = new ObjectChoiceList(
array(Status::getInstance(Status::ENABLED), Status::getInstance(Status::DISABLED)),
// Label property
'name'
);
```
After:
```php
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
$factory = new DefaultChoiceListFactory();
$choiceList = $factory->createListFromChoices(array(
Status::getInstance(Status::ENABLED),
Status::getInstance(Status::DISABLED),
));
$choiceListView = $factory->createView(
$choiceList,
// Preferred choices
array(),
// Label property
'name'
);
```
* `Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList` was
deprecated and will be removed in Symfony 3.0. You should use
`Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory` instead.
Before:
```php
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
$choiceList = new SimpleChoiceList(array(
Status::ENABLED => 'Enabled',
Status::DISABLED => 'Disabled',
));
```
After:
```php
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
$factory = new DefaultChoiceListFactory();
$choices = array(Status::ENABLED, Status::DISABLED);
$labels = array('Enabled', 'Disabled');
$choiceList = $factory->createListFromChoices($choices);
$choiceListView = $factory->createView(
$choiceList,
// Preferred choices
array(),
// Label
function ($choice, $key) use ($labels) {
return $labels[$key];
}
);
```
* The "property" option of `DoctrineType` was deprecated. You should use the
new inherited option "choice_label" instead, which has the same effect.
Before:
```php
$form->add('tags', 'entity', array(
'class' => 'Acme\Entity\MyTag',
'property' => 'name',
))
```
After:
```php
$form->add('tags', 'entity', array(
'class' => 'Acme\Entity\MyTag',
'choice_label' => 'name',
))
```
* The "loader" option of `DoctrineType` was deprecated and will be removed in
Symfony 3.0. You should override the `getLoader()` method instead in a custom
type.
Before:
```php
$form->add('tags', 'entity', array(
'class' => 'Acme\Entity\MyTag',
'loader' => new MyEntityLoader(),
))
```
After:
class MyEntityType extends DoctrineType
{
// ...
public function getLoader()
{
return new MyEntityLoader();
}
}
* `Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList` was
deprecated and will be removed in Symfony 3.0. You should use
`Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader` instead.
Before:
```php
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
$choiceList = new EntityChoiceList($em, 'Acme\Entity\MyEntity');
```
After:
```php
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
$factory = new DefaultChoiceListFactory();
$choices = array(Status::ENABLED, Status::DISABLED);
$labels = array('Enabled', 'Disabled');
$choiceLoader = new DoctrineChoiceLoader($factory, $em, 'Acme\Entity\MyEntity');
$choiceList = $factory->createListFromLoader($choiceLoader);
```
* Passing a query builder closure to `ORMQueryBuilderLoader` was deprecated and
will not be supported anymore in Symfony 3.0. You should pass resolved query
builders only.
Consequently, the arguments `$manager` and `$class` of `ORMQueryBuilderLoader`
have been deprecated as well.
Note that the "query_builder" option of `DoctrineType` *does* support
closures, but the closure is now resolved in the type instead of in the
loader.
Before:
```
use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
$queryBuilder = function () {
// return QueryBuilder
};
$loader = new ORMQueryBuilderLoader($queryBuilder);
```
After:
```
use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
// create $queryBuilder
$loader = new ORMQueryBuilderLoader($queryBuilder);
```
* The classes `ChoiceToBooleanArrayTransformer`,
`ChoicesToBooleanArrayTransformer`, `FixRadioInputListener` and
`FixCheckboxInputListener` were deprecated and will be removed in Symfony 3.0.
Their functionality is covered by the new classes `RadioListMapper` and
`CheckboxListMapper`.
* The ability to translate Doctrine type entries by the translator component
is now disabled by default and to enable it you must explicitly set the option
"choice_translation_domain" to true
Before:
```
$form->add('products', 'entity', array(
'class' => 'AppBundle/Entity/Product',
));
```
After:
```
$form->add('products', 'entity', array(
'class' => 'AppBundle/Entity/Product',
'choice_translation_domain' => true,
));
```
* In the block `choice_widget_options` the `translation_domain` has been replaced
with the `choice_translation_domain` option.
Before:
```jinja
{{ choice.label|trans({}, translation_domain) }}
```
After:
```jinja
{{ choice_translation_domain is sameas(false) ? choice.label : choice.label|trans({}, choice_translation_domain) }}
``` ```
Serializer Serializer

View File

@ -110,6 +110,24 @@ UPGRADE FROM 2.x to 3.0
### Form ### Form
* The option "precision" was renamed to "scale".
Before:
```php
$builder->add('length', 'number', array(
'precision' => 3,
));
```
After:
```php
$builder->add('length', 'number', array(
'scale' => 3,
));
```
* The method `AbstractType::setDefaultOptions(OptionsResolverInterface $resolver)` and * The method `AbstractType::setDefaultOptions(OptionsResolverInterface $resolver)` and
`AbstractTypeExtension::setDefaultOptions(OptionsResolverInterface $resolver)` have been `AbstractTypeExtension::setDefaultOptions(OptionsResolverInterface $resolver)` have been
renamed. You should use `AbstractType::configureOptions(OptionsResolver $resolver)` and renamed. You should use `AbstractType::configureOptions(OptionsResolver $resolver)` and

View File

@ -1,6 +1,16 @@
CHANGELOG CHANGELOG
========= =========
2.7.0
-----
* added DoctrineChoiceLoader
* deprecated EntityChoiceList
* deprecated passing a query builder closure to ORMQueryBuilderLoader
* deprecated $manager and $em arguments of ORMQueryBuilderLoader
* added optional arguments $propertyAccessor and $choiceListFactory to DoctrineOrmExtension constructor
* deprecated "loader" and "property" options of DoctrineType
2.4.0 2.4.0
----- -----

View File

@ -0,0 +1,173 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
/**
* Loads choices using a Doctrine object manager.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class DoctrineChoiceLoader implements ChoiceLoaderInterface
{
/**
* @var ChoiceListFactoryInterface
*/
private $factory;
/**
* @var ObjectManager
*/
private $manager;
/**
* @var string
*/
private $class;
/**
* @var IdReader
*/
private $idReader;
/**
* @var null|EntityLoaderInterface
*/
private $objectLoader;
/**
* @var ChoiceListInterface
*/
private $choiceList;
/**
* Creates a new choice loader.
*
* Optionally, an implementation of {@link EntityLoaderInterface} can be
* passed which optimizes the object loading for one of the Doctrine
* mapper implementations.
*
* @param ChoiceListFactoryInterface $factory The factory for creating
* the loaded choice list
* @param ObjectManager $manager The object manager
* @param string $class The class name of the
* loaded objects
* @param IdReader $idReader The reader for the object
* IDs.
* @param null|EntityLoaderInterface $objectLoader The objects loader
*/
public function __construct(ChoiceListFactoryInterface $factory, ObjectManager $manager, $class, IdReader $idReader = null, EntityLoaderInterface $objectLoader = null)
{
$classMetadata = $manager->getClassMetadata($class);
$this->factory = $factory;
$this->manager = $manager;
$this->class = $classMetadata->getName();
$this->idReader = $idReader ?: new IdReader($manager, $classMetadata);
$this->objectLoader = $objectLoader;
}
/**
* {@inheritdoc}
*/
public function loadChoiceList($value = null)
{
if ($this->choiceList) {
return $this->choiceList;
}
$objects = $this->objectLoader
? $this->objectLoader->getEntities()
: $this->manager->getRepository($this->class)->findAll();
$this->choiceList = $this->factory->createListFromChoices($objects, $value);
return $this->choiceList;
}
/**
* {@inheritdoc}
*/
public function loadValuesForChoices(array $choices, $value = null)
{
// Performance optimization
if (empty($choices)) {
return array();
}
// Optimize performance for single-field identifiers. We already
// know that the IDs are used as values
// Attention: This optimization does not check choices for existence
if (!$this->choiceList && $this->idReader->isSingleId()) {
$values = array();
// Maintain order and indices of the given objects
foreach ($choices as $i => $object) {
if ($object instanceof $this->class) {
// Make sure to convert to the right format
$values[$i] = (string) $this->idReader->getIdValue($object);
}
}
return $values;
}
return $this->loadChoiceList($value)->getValuesForChoices($choices);
}
/**
* {@inheritdoc}
*/
public function loadChoicesForValues(array $values, $value = null)
{
// Performance optimization
// Also prevents the generation of "WHERE id IN ()" queries through the
// object loader. At least with MySQL and on the development machine
// this was tested on, no exception was thrown for such invalid
// statements, consequently no test fails when this code is removed.
// https://github.com/symfony/symfony/pull/8981#issuecomment-24230557
if (empty($values)) {
return array();
}
// Optimize performance in case we have an object loader and
// a single-field identifier
if (!$this->choiceList && $this->objectLoader && $this->idReader->isSingleId()) {
$unorderedObjects = $this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values);
$objectsById = array();
$objects = array();
// Maintain order and indices from the given $values
// An alternative approach to the following loop is to add the
// "INDEX BY" clause to the Doctrine query in the loader,
// but I'm not sure whether that's doable in a generic fashion.
foreach ($unorderedObjects as $object) {
$objectsById[$this->idReader->getIdValue($object)] = $object;
}
foreach ($values as $i => $id) {
if (isset($objectsById[$id])) {
$objects[$i] = $objectsById[$id];
}
}
return $objects;
}
return $this->loadChoiceList($value)->getChoicesForValues($values);
}
}

View File

@ -11,17 +11,20 @@
namespace Symfony\Bridge\Doctrine\Form\ChoiceList; namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\Exception\RuntimeException; use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\Form\Exception\StringCastException; use Symfony\Component\Form\Exception\StringCastException;
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList; use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
/** /**
* A choice list presenting a list of Doctrine entities as choices. * A choice list presenting a list of Doctrine entities as choices.
* *
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
* Use {@link DoctrineChoiceLoader} instead.
*/ */
class EntityChoiceList extends ObjectChoiceList class EntityChoiceList extends ObjectChoiceList
{ {
@ -126,6 +129,8 @@ class EntityChoiceList extends ObjectChoiceList
} }
parent::__construct($entities, $labelPath, $preferredEntities, $groupPath, null, $propertyAccessor); parent::__construct($entities, $labelPath, $preferredEntities, $groupPath, null, $propertyAccessor);
trigger_error('The '.__CLASS__.' class is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader instead.', E_USER_DEPRECATED);
} }
/** /**

View File

@ -0,0 +1,125 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\Exception\RuntimeException;
/**
* A utility for reading object IDs.
*
* @since 1.0
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal This class is meant for internal use only.
*/
class IdReader
{
/**
* @var ObjectManager
*/
private $om;
/**
* @var ClassMetadata
*/
private $classMetadata;
/**
* @var bool
*/
private $singleId;
/**
* @var bool
*/
private $intId;
/**
* @var string
*/
private $idField;
public function __construct(ObjectManager $om, ClassMetadata $classMetadata)
{
$ids = $classMetadata->getIdentifierFieldNames();
$idType = $classMetadata->getTypeOfField(current($ids));
$this->om = $om;
$this->classMetadata = $classMetadata;
$this->singleId = 1 === count($ids);
$this->intId = $this->singleId && in_array($idType, array('integer', 'smallint', 'bigint'));
$this->idField = current($ids);
}
/**
* Returns whether the class has a single-column ID.
*
* @return bool Returns `true` if the class has a single-column ID and
* `false` otherwise.
*/
public function isSingleId()
{
return $this->singleId;
}
/**
* Returns whether the class has a single-column integer ID.
*
* @return bool Returns `true` if the class has a single-column integer ID
* and `false` otherwise.
*/
public function isIntId()
{
return $this->intId;
}
/**
* Returns the ID value for an object.
*
* This method assumes that the object has a single-column ID.
*
* @param object $object The object.
*
* @return mixed The ID value.
*/
public function getIdValue($object)
{
if (!$object) {
return;
}
if (!$this->om->contains($object)) {
throw new RuntimeException(
'Entities passed to the choice field must be managed. Maybe '.
'persist them in the entity manager?'
);
}
$this->om->initializeObject($object);
return current($this->classMetadata->getIdentifierValues($object));
}
/**
* Returns the name of the ID field.
*
* This method assumes that the object has a single-column ID.
*
* @return string The name of the ID field.
*/
public function getIdField()
{
return $this->idField;
}
}

View File

@ -17,7 +17,10 @@ use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManager;
/** /**
* Getting Entities through the ORM QueryBuilder. * Loads entities using a {@link QueryBuilder} instance.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class ORMQueryBuilderLoader implements EntityLoaderInterface class ORMQueryBuilderLoader implements EntityLoaderInterface
{ {
@ -34,9 +37,14 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface
/** /**
* Construct an ORM Query Builder Loader. * Construct an ORM Query Builder Loader.
* *
* @param QueryBuilder|\Closure $queryBuilder * @param QueryBuilder|\Closure $queryBuilder The query builder or a closure
* @param EntityManager $manager * for creating the query builder.
* @param string $class * Passing a closure is
* deprecated and will not be
* supported anymore as of
* Symfony 3.0.
* @param EntityManager $manager Deprecated.
* @param string $class Deprecated.
* *
* @throws UnexpectedTypeException * @throws UnexpectedTypeException
*/ */
@ -49,10 +57,15 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface
} }
if ($queryBuilder instanceof \Closure) { if ($queryBuilder instanceof \Closure) {
trigger_error('Passing a QueryBuilder closure to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
if (!$manager instanceof EntityManager) { if (!$manager instanceof EntityManager) {
throw new UnexpectedTypeException($manager, 'Doctrine\ORM\EntityManager'); throw new UnexpectedTypeException($manager, 'Doctrine\ORM\EntityManager');
} }
trigger_error('Passing an EntityManager to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
trigger_error('Passing a class to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
$queryBuilder = $queryBuilder($manager->getRepository($class)); $queryBuilder = $queryBuilder($manager->getRepository($class));
if (!$queryBuilder instanceof QueryBuilder) { if (!$queryBuilder instanceof QueryBuilder) {

View File

@ -14,21 +14,38 @@ namespace Symfony\Bridge\Doctrine\Form;
use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractExtension; use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
class DoctrineOrmExtension extends AbstractExtension class DoctrineOrmExtension extends AbstractExtension
{ {
protected $registry; protected $registry;
public function __construct(ManagerRegistry $registry) /**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* @var ChoiceListFactoryInterface
*/
private $choiceListFactory;
public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
{ {
$this->registry = $registry; $this->registry = $registry;
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor));
} }
protected function loadTypes() protected function loadTypes()
{ {
return array( return array(
new EntityType($this->registry, PropertyAccess::createPropertyAccessor()), new EntityType($this->registry, $this->propertyAccessor, $this->choiceListFactory),
); );
} }

View File

@ -12,17 +12,23 @@
namespace Symfony\Bridge\Doctrine\Form\Type; namespace Symfony\Bridge\Doctrine\Form\Type;
use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Component\Form\Exception\RuntimeException;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\FormBuilderInterface; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener; use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader;
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer; use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
abstract class DoctrineType extends AbstractType abstract class DoctrineType extends AbstractType
@ -33,19 +39,63 @@ abstract class DoctrineType extends AbstractType
protected $registry; protected $registry;
/** /**
* @var array * @var ChoiceListFactoryInterface
*/ */
private $choiceListCache = array(); private $choiceListFactory;
/** /**
* @var PropertyAccessorInterface * @var IdReader[]
*/ */
private $propertyAccessor; private $idReaders = array();
public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null) /**
* @var DoctrineChoiceLoader[]
*/
private $choiceLoaders = array();
/**
* Creates the label for a choice.
*
* For backwards compatibility, objects are cast to strings by default.
*
* @param object $choice The object.
*
* @return string The string representation of the object.
*
* @internal This method is public to be usable as callback. It should not
* be used in user code.
*/
public static function createChoiceLabel($choice)
{
return (string) $choice;
}
/**
* Creates the field name for a choice.
*
* This method is used to generate field names if the underlying object has
* a single-column integer ID. In that case, the value of the field is
* the ID of the object. That ID is also used as field name.
*
* @param object $choice The object.
* @param int|string $key The choice key.
* @param string $value The choice value. Corresponds to the object's
* ID here.
*
* @return string The field name.
*
* @internal This method is public to be usable as callback. It should not
* be used in user code.
*/
public static function createChoiceName($choice, $key, $value)
{
return (string) $value;
}
public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
{ {
$this->registry = $registry; $this->registry = $registry;
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); $this->choiceListFactory = $choiceListFactory ?: new PropertyAccessDecorator(new DefaultChoiceListFactory(), $propertyAccessor);
} }
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
@ -60,85 +110,96 @@ abstract class DoctrineType extends AbstractType
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$choiceListCache = &$this->choiceListCache;
$registry = $this->registry; $registry = $this->registry;
$propertyAccessor = $this->propertyAccessor; $choiceListFactory = $this->choiceListFactory;
$idReaders = &$this->idReaders;
$choiceLoaders = &$this->choiceLoaders;
$loader = function (Options $options) { $choiceLoader = function (Options $options) use ($choiceListFactory, &$choiceLoaders) {
$queryBuilder = (null !== $options['query_builder']) // This closure and the "query_builder" options should be pushed to
? $options['query_builder'] // EntityType in Symfony 3.0 as they are specific to the ORM
: $options['em']->getRepository($options['class'])->createQueryBuilder('e');
return $this->getLoader($options['em'], $queryBuilder, $options['class']); // Unless the choices are given explicitly, load them on demand
}; if (null === $options['choices']) {
// We consider two query builders with an equal SQL string and
// equal parameters to be equal
$qbParts = $options['query_builder']
? array(
$options['query_builder']->getQuery()->getSQL(),
$options['query_builder']->getParameters()->toArray(),
)
: null;
$choiceList = function (Options $options) use (&$choiceListCache, $propertyAccessor) { $hash = CachingFactoryDecorator::generateHash(array(
// Support for closures
$propertyHash = is_object($options['property'])
? spl_object_hash($options['property'])
: $options['property'];
$choiceHashes = $options['choices'];
// Support for recursive arrays
if (is_array($choiceHashes)) {
// A second parameter ($key) is passed, so we cannot use
// spl_object_hash() directly (which strictly requires
// one parameter)
array_walk_recursive($choiceHashes, function (&$value) {
$value = spl_object_hash($value);
});
} elseif ($choiceHashes instanceof \Traversable) {
$hashes = array();
foreach ($choiceHashes as $value) {
$hashes[] = spl_object_hash($value);
}
$choiceHashes = $hashes;
}
$preferredChoiceHashes = $options['preferred_choices'];
if (is_array($preferredChoiceHashes)) {
array_walk_recursive($preferredChoiceHashes, function (&$value) {
$value = spl_object_hash($value);
});
}
// Support for custom loaders (with query builders)
$loaderHash = is_object($options['loader'])
? spl_object_hash($options['loader'])
: $options['loader'];
// Support for closures
$groupByHash = is_object($options['group_by'])
? spl_object_hash($options['group_by'])
: $options['group_by'];
$hash = hash('sha256', json_encode(array(
spl_object_hash($options['em']),
$options['class'],
$propertyHash,
$loaderHash,
$choiceHashes,
$preferredChoiceHashes,
$groupByHash,
)));
if (!isset($choiceListCache[$hash])) {
$choiceListCache[$hash] = new EntityChoiceList(
$options['em'], $options['em'],
$options['class'], $options['class'],
$options['property'], $qbParts,
$options['loader'], $options['loader'],
$options['choices'], ));
$options['preferred_choices'],
$options['group_by'], if (isset($choiceLoaders[$hash])) {
$propertyAccessor return $choiceLoaders[$hash];
}
if ($options['loader']) {
$entityLoader = $options['loader'];
} elseif (null !== $options['query_builder']) {
$entityLoader = $this->getLoader($options['em'], $options['query_builder'], $options['class']);
} else {
$queryBuilder = $options['em']->getRepository($options['class'])->createQueryBuilder('e');
$entityLoader = $this->getLoader($options['em'], $queryBuilder, $options['class']);
}
$choiceLoaders[$hash] = new DoctrineChoiceLoader(
$choiceListFactory,
$options['em'],
$options['class'],
$options['id_reader'],
$entityLoader
); );
return $choiceLoaders[$hash];
}
};
$choiceLabel = function (Options $options) {
// BC with the "property" option
if ($options['property']) {
return $options['property'];
} }
return $choiceListCache[$hash]; // BC: use __toString() by default
return array(__CLASS__, 'createChoiceLabel');
};
$choiceName = function (Options $options) {
/** @var IdReader $idReader */
$idReader = $options['id_reader'];
// If the object has a single-column, numeric ID, use that ID as
// field name. We can only use numeric IDs as names, as we cannot
// guarantee that a non-numeric ID contains a valid form name
if ($idReader->isIntId()) {
return array(__CLASS__, 'createChoiceName');
}
// Otherwise, an incrementing integer is used as name automatically
};
// The choices are always indexed by ID (see "choices" normalizer
// and DoctrineChoiceLoader), unless the ID is composite. Then they
// are indexed by an incrementing integer.
// Use the ID/incrementing integer as choice value.
$choiceValue = function (Options $options) {
/** @var IdReader $idReader */
$idReader = $options['id_reader'];
// If the entity has a single-column ID, use that ID as value
if ($idReader->isSingleId()) {
return array($idReader, 'getIdValue');
}
// Otherwise, an incrementing integer is used as value automatically
}; };
$emNormalizer = function (Options $options, $em) use ($registry) { $emNormalizer = function (Options $options, $em) use ($registry) {
@ -164,22 +225,86 @@ abstract class DoctrineType extends AbstractType
return $em; return $em;
}; };
// deprecation note
$propertyNormalizer = function (Options $options, $propertyName) {
if ($propertyName) {
trigger_error('The "property" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_label" instead.', E_USER_DEPRECATED);
}
return $propertyName;
};
// Invoke the query builder closure so that we can cache choice lists
// for equal query builders
$queryBuilderNormalizer = function (Options $options, $queryBuilder) {
if (is_callable($queryBuilder)) {
$queryBuilder = call_user_func($queryBuilder, $options['em']->getRepository($options['class']));
if (!$queryBuilder instanceof QueryBuilder) {
throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder');
}
}
return $queryBuilder;
};
// deprecation note
$loaderNormalizer = function (Options $options, $loader) {
if ($loader) {
trigger_error('The "loader" option is deprecated since version 2.7 and will be removed in 3.0. Override getLoader() instead.', E_USER_DEPRECATED);
}
return $loader;
};
// Set the "id_reader" option via the normalizer. This option is not
// supposed to be set by the user.
$idReaderNormalizer = function (Options $options) use (&$idReaders) {
$hash = CachingFactoryDecorator::generateHash(array(
$options['em'],
$options['class'],
));
// The ID reader is a utility that is needed to read the object IDs
// when generating the field values. The callback generating the
// field values has no access to the object manager or the class
// of the field, so we store that information in the reader.
// The reader is cached so that two choice lists for the same class
// (and hence with the same reader) can successfully be cached.
if (!isset($idReaders[$hash])) {
$classMetadata = $options['em']->getClassMetadata($options['class']);
$idReaders[$hash] = new IdReader($options['em'], $classMetadata);
}
return $idReaders[$hash];
};
$resolver->setDefaults(array( $resolver->setDefaults(array(
'em' => null, 'em' => null,
'property' => null, 'property' => null, // deprecated, use "choice_label"
'query_builder' => null, 'query_builder' => null,
'loader' => $loader, 'loader' => null, // deprecated, use "choice_loader"
'choices' => null, 'choices' => null,
'choice_list' => $choiceList, 'choices_as_values' => true,
'group_by' => null, 'choice_loader' => $choiceLoader,
'choice_label' => $choiceLabel,
'choice_name' => $choiceName,
'choice_value' => $choiceValue,
'id_reader' => null, // internal
'choice_translation_domain' => false,
)); ));
$resolver->setRequired(array('class')); $resolver->setRequired(array('class'));
$resolver->setNormalizer('em', $emNormalizer); $resolver->setNormalizer('em', $emNormalizer);
$resolver->setNormalizer('property', $propertyNormalizer);
$resolver->setNormalizer('query_builder', $queryBuilderNormalizer);
$resolver->setNormalizer('loader', $loaderNormalizer);
$resolver->setNormalizer('id_reader', $idReaderNormalizer);
$resolver->setAllowedTypes('em', array('null', 'string', 'Doctrine\Common\Persistence\ObjectManager')); $resolver->setAllowedTypes('em', array('null', 'string', 'Doctrine\Common\Persistence\ObjectManager'));
$resolver->setAllowedTypes('loader', array('null', 'Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface')); $resolver->setAllowedTypes('loader', array('null', 'Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface'));
$resolver->setAllowedTypes('query_builder', array('null', 'callable', 'Doctrine\ORM\QueryBuilder'));
} }
/** /**

View File

@ -17,71 +17,18 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
class EntityType extends DoctrineType class EntityType extends DoctrineType
{ {
/**
* @var ORMQueryBuilderLoader[]
*/
private $loaderCache = array();
/** /**
* Return the default loader object. * Return the default loader object.
* *
* @param ObjectManager $manager * @param ObjectManager $manager
* @param mixed $queryBuilder * @param QueryBuilder $queryBuilder
* @param string $class * @param string $class
* *
* @return ORMQueryBuilderLoader * @return ORMQueryBuilderLoader
*/ */
public function getLoader(ObjectManager $manager, $queryBuilder, $class) public function getLoader(ObjectManager $manager, $queryBuilder, $class)
{ {
if (!$queryBuilder instanceof QueryBuilder) { return new ORMQueryBuilderLoader($queryBuilder, $manager, $class);
return new ORMQueryBuilderLoader(
$queryBuilder,
$manager,
$class
);
}
$queryBuilderHash = $this->getQueryBuilderHash($queryBuilder);
$loaderHash = $this->getLoaderHash($manager, $queryBuilderHash, $class);
if (!isset($this->loaderCache[$loaderHash])) {
$this->loaderCache[$loaderHash] = new ORMQueryBuilderLoader(
$queryBuilder,
$manager,
$class
);
}
return $this->loaderCache[$loaderHash];
}
/**
* @param QueryBuilder $queryBuilder
*
* @return string
*/
private function getQueryBuilderHash(QueryBuilder $queryBuilder)
{
return hash('sha256', json_encode(array(
'sql' => $queryBuilder->getQuery()->getSQL(),
'parameters' => $queryBuilder->getParameters(),
)));
}
/**
* @param ObjectManager $manager
* @param string $queryBuilderHash
* @param string $class
*
* @return string
*/
private function getLoaderHash(ObjectManager $manager, $queryBuilderHash, $class)
{
return hash('sha256', json_encode(array(
'manager' => spl_object_hash($manager),
'queryBuilder' => $queryBuilderHash,
'class' => $class,
)));
} }
public function getName() public function getName()

View File

@ -16,6 +16,9 @@ use Symfony\Component\DependencyInjection\Container;
class ContainerAwareEventManagerTest extends \PHPUnit_Framework_TestCase class ContainerAwareEventManagerTest extends \PHPUnit_Framework_TestCase
{ {
private $container;
private $evm;
protected function setUp() protected function setUp()
{ {
$this->container = new Container(); $this->container = new Container();

View File

@ -19,6 +19,9 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
use Symfony\Component\Form\Extension\Core\View\ChoiceView; use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Doctrine\ORM\Tools\SchemaTool; use Doctrine\ORM\Tools\SchemaTool;
/**
* @group legacy
*/
class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
{ {
const SINGLE_INT_ID_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity'; const SINGLE_INT_ID_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity';
@ -36,6 +39,8 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
protected function setUp() protected function setUp()
{ {
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$this->em = DoctrineTestHelper::createTestEntityManager(); $this->em = DoctrineTestHelper::createTestEntityManager();
$schemaTool = new SchemaTool($this->em); $schemaTool = new SchemaTool($this->em);
@ -70,7 +75,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
* @expectedException \Symfony\Component\Form\Exception\StringCastException * @expectedException \Symfony\Component\Form\Exception\StringCastException
* @expectedMessage Entity "Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity" passed to the choice field must have a "__toString()" method defined (or you can also override the "property" option). * @expectedMessage Entity "Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity" passed to the choice field must have a "__toString()" method defined (or you can also override the "property" option).
*/ */
public function testEntitiesMustHaveAToStringMethod() public function testLegacyEntitiesMustHaveAToStringMethod()
{ {
$entity1 = new SingleIntIdNoToStringEntity(1, 'Foo'); $entity1 = new SingleIntIdNoToStringEntity(1, 'Foo');
$entity2 = new SingleIntIdNoToStringEntity(2, 'Bar'); $entity2 = new SingleIntIdNoToStringEntity(2, 'Bar');
@ -96,7 +101,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
/** /**
* @expectedException \Symfony\Component\Form\Exception\RuntimeException * @expectedException \Symfony\Component\Form\Exception\RuntimeException
*/ */
public function testChoicesMustBeManaged() public function testLegacyChoicesMustBeManaged()
{ {
$entity1 = new SingleIntIdEntity(1, 'Foo'); $entity1 = new SingleIntIdEntity(1, 'Foo');
$entity2 = new SingleIntIdEntity(2, 'Bar'); $entity2 = new SingleIntIdEntity(2, 'Bar');
@ -118,7 +123,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
$choiceList->getChoices(); $choiceList->getChoices();
} }
public function testInitExplicitChoices() public function testLegacyInitExplicitChoices()
{ {
$entity1 = new SingleIntIdEntity(1, 'Foo'); $entity1 = new SingleIntIdEntity(1, 'Foo');
$entity2 = new SingleIntIdEntity(2, 'Bar'); $entity2 = new SingleIntIdEntity(2, 'Bar');
@ -141,7 +146,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array(1 => $entity1, 2 => $entity2), $choiceList->getChoices()); $this->assertSame(array(1 => $entity1, 2 => $entity2), $choiceList->getChoices());
} }
public function testInitEmptyChoices() public function testLegacyInitEmptyChoices()
{ {
$entity1 = new SingleIntIdEntity(1, 'Foo'); $entity1 = new SingleIntIdEntity(1, 'Foo');
$entity2 = new SingleIntIdEntity(2, 'Bar'); $entity2 = new SingleIntIdEntity(2, 'Bar');
@ -161,7 +166,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array(), $choiceList->getChoices()); $this->assertSame(array(), $choiceList->getChoices());
} }
public function testInitNestedChoices() public function testLegacyInitNestedChoices()
{ {
$entity1 = new SingleIntIdEntity(1, 'Foo'); $entity1 = new SingleIntIdEntity(1, 'Foo');
$entity2 = new SingleIntIdEntity(2, 'Bar'); $entity2 = new SingleIntIdEntity(2, 'Bar');
@ -189,7 +194,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
), $choiceList->getRemainingViews()); ), $choiceList->getRemainingViews());
} }
public function testGroupByPropertyPath() public function testLegacyGroupByPropertyPath()
{ {
$item1 = new GroupableEntity(1, 'Foo', 'Group1'); $item1 = new GroupableEntity(1, 'Foo', 'Group1');
$item2 = new GroupableEntity(2, 'Bar', 'Group1'); $item2 = new GroupableEntity(2, 'Bar', 'Group1');
@ -224,7 +229,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
), $choiceList->getRemainingViews()); ), $choiceList->getRemainingViews());
} }
public function testGroupByInvalidPropertyPathReturnsFlatChoices() public function testLegacyGroupByInvalidPropertyPathReturnsFlatChoices()
{ {
$item1 = new GroupableEntity(1, 'Foo', 'Group1'); $item1 = new GroupableEntity(1, 'Foo', 'Group1');
$item2 = new GroupableEntity(2, 'Bar', 'Group1'); $item2 = new GroupableEntity(2, 'Bar', 'Group1');
@ -251,7 +256,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
), $choiceList->getChoices()); ), $choiceList->getChoices());
} }
public function testInitShorthandEntityName() public function testLegacyInitShorthandEntityName()
{ {
$item1 = new SingleIntIdEntity(1, 'Foo'); $item1 = new SingleIntIdEntity(1, 'Foo');
$item2 = new SingleIntIdEntity(2, 'Bar'); $item2 = new SingleIntIdEntity(2, 'Bar');
@ -267,13 +272,8 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(1, 2), $choiceList->getValuesForChoices(array($item1, $item2))); $this->assertEquals(array(1, 2), $choiceList->getValuesForChoices(array($item1, $item2)));
} }
/** public function testLegacyInitShorthandEntityName2()
* @group legacy
*/
public function testLegacyInitShorthandEntityName()
{ {
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$item1 = new SingleIntIdEntity(1, 'Foo'); $item1 = new SingleIntIdEntity(1, 'Foo');
$item2 = new SingleIntIdEntity(2, 'Bar'); $item2 = new SingleIntIdEntity(2, 'Bar');

View File

@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/ */
class LoadedEntityChoiceListCompositeIdTest extends AbstractEntityChoiceListCompositeIdTest class LoadedEntityChoiceListCompositeIdTest extends AbstractEntityChoiceListCompositeIdTest
{ {

View File

@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/ */
class LoadedEntityChoiceListSingleIntIdTest extends AbstractEntityChoiceListSingleIntIdTest class LoadedEntityChoiceListSingleIntIdTest extends AbstractEntityChoiceListSingleIntIdTest
{ {

View File

@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/ */
class LoadedEntityChoiceListSingleStringIdTest extends AbstractEntityChoiceListSingleStringIdTest class LoadedEntityChoiceListSingleStringIdTest extends AbstractEntityChoiceListSingleStringIdTest
{ {

View File

@ -13,10 +13,11 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/ */
class UnloadedEntityChoiceListCompositeIdTest extends AbstractEntityChoiceListCompositeIdTest class UnloadedEntityChoiceListCompositeIdTest extends AbstractEntityChoiceListCompositeIdTest
{ {
public function testGetIndicesForValuesIgnoresNonExistingValues() public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
{ {
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.'); $this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
} }

View File

@ -16,6 +16,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/ */
class UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest extends UnloadedEntityChoiceListCompositeIdTest class UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest extends UnloadedEntityChoiceListCompositeIdTest
{ {

View File

@ -13,17 +13,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/ */
class UnloadedEntityChoiceListSingleIntIdTest extends AbstractEntityChoiceListSingleIntIdTest class UnloadedEntityChoiceListSingleIntIdTest extends AbstractEntityChoiceListSingleIntIdTest
{ {
public function testGetIndicesForValuesIgnoresNonExistingValues()
{
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
}
/**
* @group legacy
*/
public function testLegacyGetIndicesForValuesIgnoresNonExistingValues() public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
{ {
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.'); $this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');

View File

@ -16,6 +16,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/ */
class UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest extends UnloadedEntityChoiceListSingleIntIdTest class UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest extends UnloadedEntityChoiceListSingleIntIdTest
{ {

View File

@ -13,10 +13,11 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/ */
class UnloadedEntityChoiceListSingleStringIdTest extends AbstractEntityChoiceListSingleStringIdTest class UnloadedEntityChoiceListSingleStringIdTest extends AbstractEntityChoiceListSingleStringIdTest
{ {
public function testGetIndicesForValuesIgnoresNonExistingValues() public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
{ {
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.'); $this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
} }

View File

@ -16,6 +16,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* @group legacy
*/ */
class UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest extends UnloadedEntityChoiceListSingleStringIdTest class UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest extends UnloadedEntityChoiceListSingleStringIdTest
{ {

View File

@ -11,21 +11,24 @@
namespace Symfony\Bridge\Doctrine\Tests\Form\Type; namespace Symfony\Bridge\Doctrine\Tests\Form\Type;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser; use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
use Symfony\Component\Form\FormBuilder; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity;
use Symfony\Component\Form\Forms; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity; use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension; use Symfony\Component\Form\Forms;
use Doctrine\ORM\Tools\SchemaTool; use Symfony\Component\Form\Test\TypeTestCase;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccess;
class EntityTypeTest extends TypeTestCase class EntityTypeTest extends TypeTestCase
@ -37,12 +40,12 @@ class EntityTypeTest extends TypeTestCase
const COMPOSITE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity'; const COMPOSITE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity';
/** /**
* @var \Doctrine\ORM\EntityManager * @var EntityManager
*/ */
private $em; private $em;
/** /**
* @var \PHPUnit_Framework_MockObject_MockObject * @var \PHPUnit_Framework_MockObject_MockObject|ManagerRegistry
*/ */
private $emRegistry; private $emRegistry;
@ -128,10 +131,10 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'required' => false, 'required' => false,
'property' => 'name', 'choice_label' => 'name',
)); ));
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); $this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
} }
public function testSetDataToUninitializedEntityWithNonRequiredToString() public function testSetDataToUninitializedEntityWithNonRequiredToString()
@ -147,7 +150,7 @@ class EntityTypeTest extends TypeTestCase
'required' => false, 'required' => false,
)); ));
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); $this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
} }
public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder() public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder()
@ -162,15 +165,15 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'required' => false, 'required' => false,
'property' => 'name', 'choice_label' => 'name',
'query_builder' => $qb, 'query_builder' => $qb,
)); ));
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); $this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
} }
/** /**
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/ */
public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure() public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure()
{ {
@ -249,7 +252,7 @@ class EntityTypeTest extends TypeTestCase
$field->submit(null); $field->submit(null);
$this->assertNull($field->getData()); $this->assertNull($field->getData());
$this->assertSame(array(), $field->getViewData()); $this->assertNull($field->getViewData());
} }
public function testSubmitSingleNonExpandedNull() public function testSubmitSingleNonExpandedNull()
@ -291,7 +294,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false, 'expanded' => false,
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit('2'); $field->submit('2');
@ -313,7 +316,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false, 'expanded' => false,
'em' => 'default', 'em' => 'default',
'class' => self::COMPOSITE_IDENT_CLASS, 'class' => self::COMPOSITE_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
// the collection key is used here // the collection key is used here
@ -337,7 +340,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false, 'expanded' => false,
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit(array('1', '3')); $field->submit(array('1', '3'));
@ -362,7 +365,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false, 'expanded' => false,
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
$existing = new ArrayCollection(array(0 => $entity2)); $existing = new ArrayCollection(array(0 => $entity2));
@ -393,7 +396,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false, 'expanded' => false,
'em' => 'default', 'em' => 'default',
'class' => self::COMPOSITE_IDENT_CLASS, 'class' => self::COMPOSITE_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
// because of the composite key collection keys are used // because of the composite key collection keys are used
@ -419,7 +422,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false, 'expanded' => false,
'em' => 'default', 'em' => 'default',
'class' => self::COMPOSITE_IDENT_CLASS, 'class' => self::COMPOSITE_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
$existing = new ArrayCollection(array(0 => $entity2)); $existing = new ArrayCollection(array(0 => $entity2));
@ -449,7 +452,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => true, 'expanded' => true,
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit('2'); $field->submit('2');
@ -475,7 +478,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => true, 'expanded' => true,
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit(array('1', '3')); $field->submit(array('1', '3'));
@ -505,12 +508,12 @@ class EntityTypeTest extends TypeTestCase
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
// not all persisted entities should be displayed // not all persisted entities should be displayed
'choices' => array($entity1, $entity2), 'choices' => array($entity1, $entity2),
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit('2'); $field->submit('2');
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); $this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
$this->assertTrue($field->isSynchronized()); $this->assertTrue($field->isSynchronized());
$this->assertSame($entity2, $field->getData()); $this->assertSame($entity2, $field->getData());
$this->assertSame('2', $field->getViewData()); $this->assertSame('2', $field->getViewData());
@ -529,7 +532,7 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default', 'em' => 'default',
'class' => self::ITEM_GROUP_CLASS, 'class' => self::ITEM_GROUP_CLASS,
'choices' => array($item1, $item2, $item3, $item4), 'choices' => array($item1, $item2, $item3, $item4),
'property' => 'name', 'choice_label' => 'name',
'group_by' => 'groupName', 'group_by' => 'groupName',
)); ));
@ -537,9 +540,14 @@ class EntityTypeTest extends TypeTestCase
$this->assertSame('2', $field->getViewData()); $this->assertSame('2', $field->getViewData());
$this->assertEquals(array( $this->assertEquals(array(
'Group1' => array(1 => new ChoiceView($item1, '1', 'Foo'), 2 => new ChoiceView($item2, '2', 'Bar')), 'Group1' => new ChoiceGroupView('Group1', array(
'Group2' => array(3 => new ChoiceView($item3, '3', 'Baz')), 1 => new ChoiceView('Foo', '1', $item1),
'4' => new ChoiceView($item4, '4', 'Boo!'), 2 => new ChoiceView('Bar', '2', $item2),
)),
'Group2' => new ChoiceGroupView('Group2', array(
3 => new ChoiceView('Baz', '3', $item3),
)),
4 => new ChoiceView('Boo!', '4', $item4),
), $field->createView()->vars['choices']); ), $field->createView()->vars['choices']);
} }
@ -555,11 +563,11 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'preferred_choices' => array($entity3, $entity2), 'preferred_choices' => array($entity3, $entity2),
'property' => 'name', 'choice_label' => 'name',
)); ));
$this->assertEquals(array(3 => new ChoiceView($entity3, '3', 'Baz'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['preferred_choices']); $this->assertEquals(array(3 => new ChoiceView('Baz', '3', $entity3), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['preferred_choices']);
$this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo')), $field->createView()->vars['choices']); $this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1)), $field->createView()->vars['choices']);
} }
public function testOverrideChoicesWithPreferredChoices() public function testOverrideChoicesWithPreferredChoices()
@ -575,11 +583,11 @@ class EntityTypeTest extends TypeTestCase
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'choices' => array($entity2, $entity3), 'choices' => array($entity2, $entity3),
'preferred_choices' => array($entity3), 'preferred_choices' => array($entity3),
'property' => 'name', 'choice_label' => 'name',
)); ));
$this->assertEquals(array(3 => new ChoiceView($entity3, '3', 'Baz')), $field->createView()->vars['preferred_choices']); $this->assertEquals(array(3 => new ChoiceView('Baz', '3', $entity3)), $field->createView()->vars['preferred_choices']);
$this->assertEquals(array(2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); $this->assertEquals(array(2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
} }
public function testDisallowChoicesThatAreNotIncludedChoicesSingleIdentifier() public function testDisallowChoicesThatAreNotIncludedChoicesSingleIdentifier()
@ -594,7 +602,7 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'choices' => array($entity1, $entity2), 'choices' => array($entity1, $entity2),
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit('3'); $field->submit('3');
@ -615,7 +623,7 @@ class EntityTypeTest extends TypeTestCase
'em' => 'default', 'em' => 'default',
'class' => self::COMPOSITE_IDENT_CLASS, 'class' => self::COMPOSITE_IDENT_CLASS,
'choices' => array($entity1, $entity2), 'choices' => array($entity1, $entity2),
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit('2'); $field->submit('2');
@ -639,7 +647,7 @@ class EntityTypeTest extends TypeTestCase
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'query_builder' => $repository->createQueryBuilder('e') 'query_builder' => $repository->createQueryBuilder('e')
->where('e.id IN (1, 2)'), ->where('e.id IN (1, 2)'),
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit('3'); $field->submit('3');
@ -663,7 +671,7 @@ class EntityTypeTest extends TypeTestCase
return $repository->createQueryBuilder('e') return $repository->createQueryBuilder('e')
->where('e.id IN (1, 2)'); ->where('e.id IN (1, 2)');
}, },
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit('3'); $field->submit('3');
@ -687,7 +695,7 @@ class EntityTypeTest extends TypeTestCase
return $repository->createQueryBuilder('e') return $repository->createQueryBuilder('e')
->where('e.id1 IN (10, 50)'); ->where('e.id1 IN (10, 50)');
}, },
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit('2'); $field->submit('2');
@ -707,7 +715,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false, 'expanded' => false,
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_STRING_IDENT_CLASS, 'class' => self::SINGLE_STRING_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
$field->submit('foo'); $field->submit('foo');
@ -728,7 +736,7 @@ class EntityTypeTest extends TypeTestCase
'expanded' => false, 'expanded' => false,
'em' => 'default', 'em' => 'default',
'class' => self::COMPOSITE_STRING_IDENT_CLASS, 'class' => self::COMPOSITE_STRING_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
// the collection key is used here // the collection key is used here
@ -752,7 +760,7 @@ class EntityTypeTest extends TypeTestCase
$this->factory->createNamed('name', 'entity', null, array( $this->factory->createNamed('name', 'entity', null, array(
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'required' => false, 'required' => false,
'property' => 'name', 'choice_label' => 'name',
)); ));
} }
@ -767,7 +775,7 @@ class EntityTypeTest extends TypeTestCase
$this->factory->createNamed('name', 'entity', null, array( $this->factory->createNamed('name', 'entity', null, array(
'em' => $this->em, 'em' => $this->em,
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'property' => 'name', 'choice_label' => 'name',
)); ));
} }
@ -779,8 +787,7 @@ class EntityTypeTest extends TypeTestCase
$this->persist(array($entity1, $entity2, $entity3)); $this->persist(array($entity1, $entity2, $entity3));
$repository = $this->em->getRepository(self::SINGLE_IDENT_CLASS); $repo = $this->em->getRepository(self::SINGLE_IDENT_CLASS);
$qb = $repository->createQueryBuilder('e')->where('e.id IN (1, 2)');
$entityType = new EntityType( $entityType = new EntityType(
$this->emRegistry, $this->emRegistry,
@ -799,19 +806,23 @@ class EntityTypeTest extends TypeTestCase
$formBuilder->add('property1', 'entity', array( $formBuilder->add('property1', 'entity', array(
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'query_builder' => $qb, 'query_builder' => $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'),
)); ));
$formBuilder->add('property2', 'entity', array( $formBuilder->add('property2', 'entity', array(
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'query_builder' => $qb, 'query_builder' => function (EntityRepository $repo) {
return $repo->createQueryBuilder('e')->where('e.id IN (1, 2)');
},
)); ));
$formBuilder->add('property3', 'entity', array( $formBuilder->add('property3', 'entity', array(
'em' => 'default', 'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS, 'class' => self::SINGLE_IDENT_CLASS,
'query_builder' => $qb, 'query_builder' => function (EntityRepository $repo) {
return $repo->createQueryBuilder('e')->where('e.id IN (1, 2)');
},
)); ));
$form = $formBuilder->getForm(); $form = $formBuilder->getForm();
@ -822,15 +833,59 @@ class EntityTypeTest extends TypeTestCase
'property3' => 2, 'property3' => 2,
)); ));
$reflectionClass = new \ReflectionObject($entityType); $choiceList1 = $form->get('property1')->getConfig()->getOption('choice_list');
$reflectionProperty = $reflectionClass->getProperty('loaderCache'); $choiceList2 = $form->get('property2')->getConfig()->getOption('choice_list');
$reflectionProperty->setAccessible(true); $choiceList3 = $form->get('property3')->getConfig()->getOption('choice_list');
$loaders = $reflectionProperty->getValue($entityType); $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $choiceList1);
$this->assertSame($choiceList1, $choiceList2);
$this->assertSame($choiceList1, $choiceList3);
}
$reflectionProperty->setAccessible(false); public function testCacheChoiceLists()
{
$entity1 = new SingleIntIdEntity(1, 'Foo');
$this->assertCount(1, $loaders); $this->persist(array($entity1));
$field1 = $this->factory->createNamed('name', 'entity', null, array(
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'required' => false,
'choice_label' => 'name',
));
$field2 = $this->factory->createNamed('name', 'entity', null, array(
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'required' => false,
'choice_label' => 'name',
));
$this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $field1->getConfig()->getOption('choice_list'));
$this->assertSame($field1->getConfig()->getOption('choice_list'), $field2->getConfig()->getOption('choice_list'));
}
/**
* @group legacy
*/
public function testLegacyPropertyOption()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$entity1 = new SingleIntIdEntity(1, 'Foo');
$entity2 = new SingleIntIdEntity(2, 'Bar');
$this->persist(array($entity1, $entity2));
$field = $this->factory->createNamed('name', 'entity', null, array(
'em' => 'default',
'class' => self::SINGLE_IDENT_CLASS,
'required' => false,
'property' => 'name',
));
$this->assertEquals(array(1 => new ChoiceView('Foo', '1', $entity1), 2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
} }
protected function createRegistryMock($name, $em) protected function createRegistryMock($name, $em)

View File

@ -31,13 +31,13 @@ class LintCommand extends Command
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function __construct($name = 'twig:lint') public function __construct($name = 'lint:twig')
{ {
parent::__construct($name); parent::__construct($name);
} }
/** /**
* Sets the twig environment * Sets the twig environment.
* *
* @param \Twig_Environment $twig * @param \Twig_Environment $twig
*/ */
@ -57,6 +57,7 @@ class LintCommand extends Command
protected function configure() protected function configure()
{ {
$this $this
->setAliases(array('twig:lint'))
->setDescription('Lints a template and outputs encountered errors') ->setDescription('Lints a template and outputs encountered errors')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
->addArgument('filename', InputArgument::IS_ARRAY) ->addArgument('filename', InputArgument::IS_ARRAY)
@ -83,6 +84,10 @@ EOF
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
if (false !== strpos($input->getFirstArgument(), ':l')) {
$output->writeln('<comment>The use of "twig:lint" command is deprecated since version 2.7 and will be removed in 3.0. Use the "lint:twig" instead.</comment>');
}
$twig = $this->getTwigEnvironment(); $twig = $this->getTwigEnvironment();
if (null === $twig) { if (null === $twig) {
@ -95,7 +100,7 @@ EOF
if (0 === count($filenames)) { if (0 === count($filenames)) {
if (0 !== ftell(STDIN)) { if (0 !== ftell(STDIN)) {
throw new \RuntimeException("Please provide a filename or pipe template content to STDIN."); throw new \RuntimeException('Please provide a filename or pipe template content to STDIN.');
} }
$template = ''; $template = '';
@ -206,14 +211,14 @@ EOF
$line = $exception->getTemplateLine(); $line = $exception->getTemplateLine();
if ($file) { if ($file) {
$output->writeln(sprintf("<error>KO</error> in %s (line %s)", $file, $line)); $output->writeln(sprintf('<error>KO</error> in %s (line %s)', $file, $line));
} else { } else {
$output->writeln(sprintf("<error>KO</error> (line %s)", $line)); $output->writeln(sprintf('<error>KO</error> (line %s)', $line));
} }
foreach ($this->getContext($template, $line) as $no => $code) { foreach ($this->getContext($template, $line) as $no => $code) {
$output->writeln(sprintf( $output->writeln(sprintf(
"%s %-6s %s", '%s %-6s %s',
$no == $line ? '<error>>></error>' : ' ', $no == $line ? '<error>>></error>' : ' ',
$no, $no,
$code $code

View File

@ -206,6 +206,9 @@ class CodeExtension extends \Twig_Extension
}, $text); }, $text);
} }
/**
* {@inheritdoc}
*/
public function getName() public function getName()
{ {
return 'code'; return 'code';

View File

@ -13,7 +13,7 @@ namespace Symfony\Bridge\Twig\Extension;
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
use Symfony\Bridge\Twig\Form\TwigRendererInterface; use Symfony\Bridge\Twig\Form\TwigRendererInterface;
use Symfony\Component\Form\Extension\Core\View\ChoiceView; use Symfony\Component\Form\ChoiceList\View\ChoiceView;
/** /**
* FormExtension extends Twig with form capabilities. * FormExtension extends Twig with form capabilities.

View File

@ -81,6 +81,9 @@ class HttpKernelExtension extends \Twig_Extension
return new ControllerReference($controller, $attributes, $query); return new ControllerReference($controller, $attributes, $query);
} }
/**
* {@inheritdoc}
*/
public function getName() public function getName()
{ {
return 'http_kernel'; return 'http_kernel';

View File

@ -89,9 +89,7 @@ class RoutingExtension extends \Twig_Extension
} }
/** /**
* Returns the name of the extension. * {@inheritdoc}
*
* @return string The extension name
*/ */
public function getName() public function getName()
{ {

View File

@ -52,9 +52,7 @@ class SecurityExtension extends \Twig_Extension
} }
/** /**
* Returns the name of the extension. * {@inheritdoc}
*
* @return string The extension name
*/ */
public function getName() public function getName()
{ {

View File

@ -99,9 +99,7 @@ class TranslationExtension extends \Twig_Extension
} }
/** /**
* Returns the name of the extension. * {@inheritdoc}
*
* @return string The extension name
*/ */
public function getName() public function getName()
{ {

View File

@ -56,9 +56,7 @@ class YamlExtension extends \Twig_Extension
} }
/** /**
* Returns the name of the extension. * {@inheritdoc}
*
* @return string The extension name
*/ */
public function getName() public function getName()
{ {

View File

@ -74,12 +74,13 @@
{%- block choice_widget_options -%} {%- block choice_widget_options -%}
{% for group_label, choice in options %} {% for group_label, choice in options %}
{%- if choice is iterable -%} {%- if choice is iterable -%}
<optgroup label="{{ group_label|trans({}, translation_domain) }}"> <optgroup label="{{ choice_translation_domain is sameas(false) ? group_label : group_label|trans({}, choice_translation_domain) }}">
{% set options = choice %} {% set options = choice %}
{{- block('choice_widget_options') -}} {{- block('choice_widget_options') -}}
</optgroup> </optgroup>
{%- else -%} {%- else -%}
<option value="{{ choice.value }}"{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice.label|trans({}, translation_domain) }}</option> {% set attr = choice.attr %}
<option value="{{ choice.value }}" {{ block('attributes') }}{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice_translation_domain is sameas(false) ? choice.label : choice.label|trans({}, choice_translation_domain) }}</option>
{%- endif -%} {%- endif -%}
{% endfor %} {% endfor %}
{%- endblock choice_widget_options -%} {%- endblock choice_widget_options -%}
@ -351,3 +352,16 @@
{%- endif -%} {%- endif -%}
{%- endfor -%} {%- endfor -%}
{%- endblock button_attributes -%} {%- endblock button_attributes -%}
{% block attributes -%}
{%- for attrname, attrvalue in attr -%}
{{- " " -}}
{%- if attrname in ['placeholder', 'title'] -%}
{{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}"
{%- elseif attrvalue is sameas(true) -%}
{{- attrname }}="{{ attrname }}"
{%- elseif attrvalue is not sameas(false) -%}
{{- attrname }}="{{ attrvalue }}"
{%- endif -%}
{%- endfor -%}
{%- endblock attributes -%}

View File

@ -80,7 +80,7 @@ class LintCommandTest extends \PHPUnit_Framework_TestCase
$application = new Application(); $application = new Application();
$application->add($command); $application->add($command);
$command = $application->find('twig:lint'); $command = $application->find('lint:twig');
return new CommandTester($command); return new CommandTester($command);
} }

View File

@ -17,8 +17,8 @@ use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader; use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Symfony\Component\Form\Tests\AbstractDivLayoutTest; use Symfony\Component\Form\Tests\AbstractDivLayoutTest;
class FormExtensionDivLayoutTest extends AbstractDivLayoutTest class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
@ -125,7 +125,7 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
*/ */
public function testIsChoiceSelected($expected, $choice, $value) public function testIsChoiceSelected($expected, $choice, $value)
{ {
$choice = new ChoiceView($choice, $choice, $choice.' label'); $choice = new ChoiceView($choice.' label', $choice, $choice);
$this->assertSame($expected, $this->extension->isSelectedChoice($choice, $value)); $this->assertSame($expected, $this->extension->isSelectedChoice($choice, $value));
} }

View File

@ -5,6 +5,7 @@ CHANGELOG
----- -----
* Added possibility to extract translation messages from a file or files besides extracting from a directory * Added possibility to extract translation messages from a file or files besides extracting from a directory
* Added `TranslationsCacheWarmer` to create catalogues at warmup
2.6.0 2.6.0
----- -----

View File

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* Generates the catalogues for translations.
*
* @author Xavier Leune <xavier.leune@gmail.com>
*/
class TranslationsCacheWarmer implements CacheWarmerInterface
{
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* {@inheritdoc}
*/
public function warmUp($cacheDir)
{
if ($this->translator instanceof WarmableInterface) {
$this->translator->warmUp($cacheDir);
}
}
/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}
}

View File

@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command; namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@ -75,6 +76,12 @@ EOF
$output->writeln('<error>This command needs the pcntl extension to run.</error>'); $output->writeln('<error>This command needs the pcntl extension to run.</error>');
$output->writeln('You can either install it or use the <info>server:run</info> command instead to run the built-in web server.'); $output->writeln('You can either install it or use the <info>server:run</info> command instead to run the built-in web server.');
if ($this->getHelper('question')->ask($input, $output, new ConfirmationQuestion('Do you want to start <info>server:run</info> immediately? [Yn] ', true))) {
$command = $this->getApplication()->find('server:run');
return $command->run($input, $output);
}
return 1; return 1;
} }

View File

@ -29,7 +29,8 @@ class YamlLintCommand extends Command
protected function configure() protected function configure()
{ {
$this $this
->setName('yaml:lint') ->setName('lint:yaml')
->setAliases(array('yaml:lint'))
->setDescription('Lints a file and outputs encountered errors') ->setDescription('Lints a file and outputs encountered errors')
->addArgument('filename', null, 'A file or a directory or STDIN') ->addArgument('filename', null, 'A file or a directory or STDIN')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
@ -61,6 +62,10 @@ EOF
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
if (false !== strpos($input->getFirstArgument(), ':l')) {
$output->writeln('<comment>The use of "yaml:lint" command is deprecated since version 2.7 and will be removed in 3.0. Use the "lint:yaml" instead.</comment>');
}
$filename = $input->getArgument('filename'); $filename = $input->getArgument('filename');
if (!$filename) { if (!$filename) {

View File

@ -318,7 +318,7 @@ abstract class Controller extends ContainerAware
} }
/** /**
* Gets a service by id. * Gets a container service by its id.
* *
* @param string $id The service id * @param string $id The service id
* *
@ -329,10 +329,22 @@ abstract class Controller extends ContainerAware
if ('request' === $id) { if ('request' === $id) {
trigger_error('The "request" service is deprecated and will be removed in 3.0. Add a typehint for Symfony\\Component\\HttpFoundation\\Request to your controller parameters to retrieve the request instead.', E_USER_DEPRECATED); trigger_error('The "request" service is deprecated and will be removed in 3.0. Add a typehint for Symfony\\Component\\HttpFoundation\\Request to your controller parameters to retrieve the request instead.', E_USER_DEPRECATED);
} }
return $this->container->get($id); return $this->container->get($id);
} }
/**
* Gets a container configuration parameter by its name.
*
* @param string $name The parameter name
*
* @return mixed
*/
protected function getParameter($name)
{
return $this->container->getParameter($name);
}
/** /**
* Checks the validity of a CSRF token * Checks the validity of a CSRF token
* *

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/** /**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com> * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
@ -38,6 +39,7 @@ class LoggingTranslatorPass implements CompilerPassInterface
$refClass = new \ReflectionClass($class); $refClass = new \ReflectionClass($class);
if ($refClass->implementsInterface('Symfony\Component\Translation\TranslatorInterface') && $refClass->implementsInterface('Symfony\Component\Translation\TranslatorBagInterface')) { if ($refClass->implementsInterface('Symfony\Component\Translation\TranslatorInterface') && $refClass->implementsInterface('Symfony\Component\Translation\TranslatorBagInterface')) {
$container->getDefinition('translator.logging')->setDecoratedService('translator'); $container->getDefinition('translator.logging')->setDecoratedService('translator');
$container->getDefinition('translation.warmer')->replaceArgument(0, new Reference('translator.logging.inner'));
} }
} }
} }

View File

@ -89,10 +89,7 @@ class Configuration implements ConfigurationInterface
->booleanNode('test')->end() ->booleanNode('test')->end()
->scalarNode('default_locale')->defaultValue('en')->end() ->scalarNode('default_locale')->defaultValue('en')->end()
->arrayNode('trusted_hosts') ->arrayNode('trusted_hosts')
->beforeNormalization() ->beforeNormalization()->ifString()->then(function ($v) { return array($v); })->end()
->ifTrue(function ($v) { return is_string($v); })
->then(function ($v) { return array($v); })
->end()
->prototype('scalar')->end() ->prototype('scalar')->end()
->end() ->end()
->end() ->end()
@ -338,7 +335,7 @@ class Configuration implements ConfigurationInterface
->addDefaultChildrenIfNoneSet() ->addDefaultChildrenIfNoneSet()
->prototype('scalar')->defaultValue('FrameworkBundle:Form')->end() ->prototype('scalar')->defaultValue('FrameworkBundle:Form')->end()
->validate() ->validate()
->ifTrue(function ($v) {return !in_array('FrameworkBundle:Form', $v); }) ->ifNotInArray(array('FrameworkBundle:Form'))
->then(function ($v) { ->then(function ($v) {
return array_merge(array('FrameworkBundle:Form'), $v); return array_merge(array('FrameworkBundle:Form'), $v);
}) })

View File

@ -705,12 +705,22 @@ class FrameworkExtension extends Extension
->in($dirs) ->in($dirs)
; ;
$locales = array();
foreach ($finder as $file) { foreach ($finder as $file) {
list($domain, $locale, $format) = explode('.', $file->getBasename(), 3); list($domain, $locale, $format) = explode('.', $file->getBasename(), 3);
$files[] = (string) $file; if (!isset($files[$locale])) {
$files[$locale] = array();
}
$files[$locale][] = (string) $file;
} }
$translator->replaceArgument(4, $files); $options = array_merge(
$translator->getArgument(3),
array('resource_files' => $files)
);
$translator->replaceArgument(3, $options);
} }
} }

View File

@ -14,7 +14,7 @@
<tag name="monolog.logger" channel="php" /> <tag name="monolog.logger" channel="php" />
<argument /><!-- Exception handler --> <argument /><!-- Exception handler -->
<argument type="service" id="logger" on-invalid="null" /> <argument type="service" id="logger" on-invalid="null" />
<argument /><!-- Log levels map for enabled error levels --> <argument>null</argument><!-- Log levels map for enabled error levels -->
<argument>null</argument> <argument>null</argument>
<argument>true</argument> <argument>true</argument>
<argument>null</argument><!-- %templating.helper.code.file_link_format% --> <argument>null</argument><!-- %templating.helper.code.file_link_format% -->

View File

@ -121,5 +121,10 @@
<service id="translation.extractor" class="Symfony\Component\Translation\Extractor\ChainExtractor"/> <service id="translation.extractor" class="Symfony\Component\Translation\Extractor\ChainExtractor"/>
<service id="translation.writer" class="Symfony\Component\Translation\Writer\TranslationWriter"/> <service id="translation.writer" class="Symfony\Component\Translation\Writer\TranslationWriter"/>
<service id="translation.warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer" public="false">
<argument type="service" id="translator" />
<tag name="kernel.cache_warmer" />
</service>
</services> </services>
</container> </container>

View File

@ -1,11 +1,13 @@
<?php $translatorHelper = $view['translator']; // outside of the loop for performance reasons! ?> <?php use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
$translatorHelper = $view['translator']; // outside of the loop for performance reasons! ?>
<?php $formHelper = $view['form']; ?> <?php $formHelper = $view['form']; ?>
<?php foreach ($choices as $index => $choice): ?> <?php foreach ($choices as $group_label => $choice): ?>
<?php if (is_array($choice)): ?> <?php if (is_array($choice) || $choice instanceof ChoiceGroupView): ?>
<optgroup label="<?php echo $view->escape($translatorHelper->trans($index, array(), $translation_domain)) ?>"> <optgroup label="<?php echo $view->escape(false !== $choice_translation_domain ? $translatorHelper->trans($group_label, array(), $choice_translation_domain) : $group_label) ?>">
<?php echo $formHelper->block($form, 'choice_widget_options', array('choices' => $choice)) ?> <?php echo $formHelper->block($form, 'choice_widget_options', array('choices' => $choice)) ?>
</optgroup> </optgroup>
<?php else: ?> <?php else: ?>
<option value="<?php echo $view->escape($choice->value) ?>"<?php if ($is_selected($choice->value, $value)): ?> selected="selected"<?php endif?>><?php echo $view->escape($translatorHelper->trans($choice->label, array(), $translation_domain)) ?></option> <option value="<?php echo $view->escape($choice->value) ?>" <?php echo $view['form']->block($form, 'attributes', array('attr' => $choice->attr)) ?><?php if ($is_selected($choice->value, $value)): ?> selected="selected"<?php endif?>><?php echo $view->escape(false !== $choice_translation_domain ? $translatorHelper->trans($choice->label, array(), $choice_translation_domain) : $choice->label) ?></option>
<?php endif ?> <?php endif ?>
<?php endforeach ?> <?php endforeach ?>

View File

@ -58,9 +58,7 @@ class ActionsHelper extends Helper
} }
/** /**
* Returns the canonical name of this helper. * {@inheritdoc}
*
* @return string The canonical name
*/ */
public function getName() public function getName()
{ {

View File

@ -199,9 +199,7 @@ class CodeHelper extends Helper
} }
/** /**
* Returns the canonical name of this helper. * {@inheritdoc}
*
* @return string The canonical name
*/ */
public function getName() public function getName()
{ {

View File

@ -83,9 +83,7 @@ class RequestHelper extends Helper
} }
/** /**
* Returns the canonical name of this helper. * {@inheritdoc}
*
* @return string The canonical name
*/ */
public function getName() public function getName()
{ {

View File

@ -50,9 +50,7 @@ class RouterHelper extends Helper
} }
/** /**
* Returns the canonical name of this helper. * {@inheritdoc}
*
* @return string The canonical name
*/ */
public function getName() public function getName()
{ {

View File

@ -86,9 +86,7 @@ class SessionHelper extends Helper
} }
/** /**
* Returns the canonical name of this helper. * {@inheritdoc}
*
* @return string The canonical name
*/ */
public function getName() public function getName()
{ {

View File

@ -50,9 +50,7 @@ class TranslatorHelper extends Helper
} }
/** /**
* Returns the canonical name of this helper. * {@inheritdoc}
*
* @return string The canonical name
*/ */
public function getName() public function getName()
{ {

View File

@ -35,7 +35,7 @@ class LoggingTranslatorPassTest extends \PHPUnit_Framework_TestCase
->method('getAlias') ->method('getAlias')
->will($this->returnValue('translation.default')); ->will($this->returnValue('translation.default'));
$container->expects($this->exactly(2)) $container->expects($this->exactly(3))
->method('getDefinition') ->method('getDefinition')
->will($this->returnValue($definition)); ->will($this->returnValue($definition));

View File

@ -234,9 +234,9 @@ abstract class FrameworkExtensionTest extends TestCase
$container = $this->createContainerFromFile('full'); $container = $this->createContainerFromFile('full');
$this->assertTrue($container->hasDefinition('translator.default'), '->registerTranslatorConfiguration() loads translation.xml'); $this->assertTrue($container->hasDefinition('translator.default'), '->registerTranslatorConfiguration() loads translation.xml');
$this->assertEquals('translator.default', (string) $container->getAlias('translator'), '->registerTranslatorConfiguration() redefines translator service from identity to real translator'); $this->assertEquals('translator.default', (string) $container->getAlias('translator'), '->registerTranslatorConfiguration() redefines translator service from identity to real translator');
$resources = $container->getDefinition('translator.default')->getArgument(4); $options = $container->getDefinition('translator.default')->getArgument(3);
$files = array_map(function ($resource) { return realpath($resource); }, $resources); $files = array_map(function ($resource) { return realpath($resource); }, $options['resource_files']['en']);
$ref = new \ReflectionClass('Symfony\Component\Validator\Validation'); $ref = new \ReflectionClass('Symfony\Component\Validator\Validation');
$this->assertContains( $this->assertContains(
strtr(dirname($ref->getFileName()).'/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR), strtr(dirname($ref->getFileName()).'/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR),

View File

@ -95,7 +95,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
public function testTransWithCachingWithInvalidLocale() public function testTransWithCachingWithInvalidLocale()
{ {
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), array(), 'loader', '\Symfony\Bundle\FrameworkBundle\Tests\Translation\TranslatorWithInvalidLocale'); $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'loader', '\Symfony\Bundle\FrameworkBundle\Tests\Translation\TranslatorWithInvalidLocale');
$translator->setLocale('invalid locale'); $translator->setLocale('invalid locale');
$this->setExpectedException('\InvalidArgumentException'); $this->setExpectedException('\InvalidArgumentException');
@ -106,23 +106,25 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
{ {
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
$resourceFiles = array( $resourceFiles = array(
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml', 'fr' => array(
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
),
); );
// prime the cache // prime the cache
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), $resourceFiles, 'yml'); $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'resource_files' => $resourceFiles), 'yml');
$translator->setLocale('fr'); $translator->setLocale('fr');
$this->assertEquals('répertoire', $translator->trans('folder')); $this->assertEquals('répertoire', $translator->trans('folder'));
// do it another time as the cache is primed now // do it another time as the cache is primed now
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), array(), 'yml'); $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'yml');
$translator->setLocale('fr'); $translator->setLocale('fr');
$this->assertEquals('répertoire', $translator->trans('folder')); $this->assertEquals('répertoire', $translator->trans('folder'));
// refresh cache when resources is changed in debug mode. // refresh cache when resources is changed in debug mode.
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'debug' => true), array(), 'yml'); $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'debug' => true), 'yml');
$translator->setLocale('fr'); $translator->setLocale('fr');
$this->assertEquals('folder', $translator->trans('folder')); $this->assertEquals('folder', $translator->trans('folder'));
@ -132,10 +134,12 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
{ {
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
$resourceFiles = array( $resourceFiles = array(
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml', 'fr' => array(
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
),
); );
$translator = $this->getTranslator($loader, array(), $resourceFiles, 'yml'); $translator = $this->getTranslator($loader, array('resource_files' => $resourceFiles), 'yml');
$translator->setLocale('fr'); $translator->setLocale('fr');
$this->assertEquals('répertoire', $translator->trans('folder')); $this->assertEquals('répertoire', $translator->trans('folder'));
@ -221,14 +225,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
return $container; return $container;
} }
public function getTranslator($loader, $options = array(), $resources = array(), $loaderFomat = 'loader', $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator') public function getTranslator($loader, $options = array(), $loaderFomat = 'loader', $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator')
{ {
$translator = new $translatorClass( $translator = new $translatorClass(
$this->getContainer($loader), $this->getContainer($loader),
new MessageSelector(), new MessageSelector(),
array($loaderFomat => array($loaderFomat)), array($loaderFomat => array($loaderFomat)),
$options, $options
$resources
); );
if ('loader' === $loaderFomat) { if ('loader' === $loaderFomat) {
@ -243,6 +246,22 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
return $translator; return $translator;
} }
public function testWarmup()
{
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
$resourceFiles = array(
'fr' => array(
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
),
);
// prime the cache
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'resource_files' => $resourceFiles), 'yml');
$this->assertFalse(file_exists($this->tmpDir.'/catalogue.fr.php'));
$translator->warmup($this->tmpDir);
$this->assertTrue(file_exists($this->tmpDir.'/catalogue.fr.php'));
}
} }
class TranslatorWithInvalidLocale extends Translator class TranslatorWithInvalidLocale extends Translator

View File

@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Translation; namespace Symfony\Bundle\FrameworkBundle\Translation;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Translation\Translator as BaseTranslator; use Symfony\Component\Translation\Translator as BaseTranslator;
use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
@ -20,17 +21,22 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class Translator extends BaseTranslator class Translator extends BaseTranslator implements WarmableInterface
{ {
protected $container; protected $container;
protected $loaderIds; protected $loaderIds;
protected $resourceFiles;
protected $options = array( protected $options = array(
'cache_dir' => null, 'cache_dir' => null,
'debug' => false, 'debug' => false,
'resource_files' => array(),
); );
/**
* @var array
*/
private $resourceLocales;
/** /**
* Constructor. * Constructor.
* *
@ -38,20 +44,19 @@ class Translator extends BaseTranslator
* *
* * cache_dir: The cache directory (or null to disable caching) * * cache_dir: The cache directory (or null to disable caching)
* * debug: Whether to enable debugging or not (false by default) * * debug: Whether to enable debugging or not (false by default)
* * resource_files: List of translation resources available grouped by locale.
* *
* @param ContainerInterface $container A ContainerInterface instance * @param ContainerInterface $container A ContainerInterface instance
* @param MessageSelector $selector The message selector for pluralization * @param MessageSelector $selector The message selector for pluralization
* @param array $loaderIds An array of loader Ids * @param array $loaderIds An array of loader Ids
* @param array $options An array of options * @param array $options An array of options
* @param array $resourceFiles An array of resource directories
* *
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array(), $resourceFiles = array()) public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array())
{ {
$this->container = $container; $this->container = $container;
$this->loaderIds = $loaderIds; $this->loaderIds = $loaderIds;
$this->resourceFiles = $resourceFiles;
// check option names // check option names
if ($diff = array_diff(array_keys($options), array_keys($this->options))) { if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
@ -59,6 +64,7 @@ class Translator extends BaseTranslator
} }
$this->options = array_merge($this->options, $options); $this->options = array_merge($this->options, $options);
$this->resourceLocales = array_keys($this->options['resource_files']);
if (null !== $this->options['cache_dir'] && $this->options['debug']) { if (null !== $this->options['cache_dir'] && $this->options['debug']) {
$this->loadResources(); $this->loadResources();
} }
@ -66,6 +72,16 @@ class Translator extends BaseTranslator
parent::__construct(null, $selector, $this->options['cache_dir'], $this->options['debug']); parent::__construct(null, $selector, $this->options['cache_dir'], $this->options['debug']);
} }
/**
* {@inheritdoc}
*/
public function warmUp($cacheDir)
{
foreach ($this->resourceLocales as $locale) {
$this->loadCatalogue($locale);
}
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -87,11 +103,13 @@ class Translator extends BaseTranslator
private function loadResources() private function loadResources()
{ {
foreach ($this->resourceFiles as $key => $file) { foreach ($this->options['resource_files'] as $locale => $files) {
// filename is domain.locale.format foreach ($files as $key => $file) {
list($domain, $locale, $format) = explode('.', basename($file), 3); // filename is domain.locale.format
$this->addResource($format, $file, $locale, $domain); list($domain, $locale, $format) = explode('.', basename($file), 3);
unset($this->resourceFiles[$key]); $this->addResource($format, $file, $locale, $domain);
unset($this->options['resource_files'][$locale][$key]);
}
} }
} }
} }

View File

@ -53,7 +53,7 @@
"symfony/finder": "For using the translation loader and cache warmer", "symfony/finder": "For using the translation loader and cache warmer",
"symfony/form": "For using forms", "symfony/form": "For using forms",
"symfony/validator": "For using validation", "symfony/validator": "For using validation",
"symfony/yaml": "For using the debug:config and yaml:lint commands", "symfony/yaml": "For using the debug:config and lint:yaml commands",
"doctrine/cache": "For using alternative cache drivers" "doctrine/cache": "For using alternative cache drivers"
}, },
"autoload": { "autoload": {

View File

@ -25,9 +25,6 @@ use Symfony\Component\Templating\Helper\Helper;
class LogoutUrlHelper extends Helper class LogoutUrlHelper extends Helper
{ {
private $generator; private $generator;
private $listeners = array();
private $router;
private $tokenStorage;
/** /**
* Constructor. * Constructor.
@ -79,9 +76,7 @@ class LogoutUrlHelper extends Helper
} }
/** /**
* Returns the canonical name of this helper. * {@inheritdoc}
*
* @return string The canonical name
*/ */
public function getName() public function getName()
{ {

View File

@ -43,9 +43,7 @@ class SecurityHelper extends Helper
} }
/** /**
* Returns the canonical name of this helper. * {@inheritdoc}
*
* @return string The canonical name
*/ */
public function getName() public function getName()
{ {

View File

@ -4,6 +4,7 @@ CHANGELOG
2.7.0 2.7.0
----- -----
* made it possible to configure the default formats for both the `date` and the `number_format` filter
* added support for the new Asset component (from Twig bridge) * added support for the new Asset component (from Twig bridge)
* deprecated the assets extension (use the one from the Twig bridge instead) * deprecated the assets extension (use the one from the Twig bridge instead)

View File

@ -59,7 +59,7 @@ class LintCommand extends BaseLintCommand implements ContainerAwareInterface
Or all template files in a bundle: Or all template files in a bundle:
<info>php %command.full_name% @AcmeDemoBundle</info> <info>php %command.full_name% @AcmeDemoBundle</info>
EOF EOF
) )
; ;

View File

@ -41,6 +41,7 @@ class Configuration implements ConfigurationInterface
$this->addFormThemesSection($rootNode); $this->addFormThemesSection($rootNode);
$this->addGlobalsSection($rootNode); $this->addGlobalsSection($rootNode);
$this->addTwigOptions($rootNode); $this->addTwigOptions($rootNode);
$this->addTwigFormatOptions($rootNode);
return $treeBuilder; return $treeBuilder;
} }
@ -160,4 +161,33 @@ class Configuration implements ConfigurationInterface
->end() ->end()
; ;
} }
private function addTwigFormatOptions(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('date')
->info('The default format options used by the date filter')
->addDefaultsIfNotSet()
->children()
->scalarNode('format')->defaultValue('F j, Y H:i')->end()
->scalarNode('interval_format')->defaultValue('%d days')->end()
->scalarNode('timezone')
->info('The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used')
->defaultNull()
->end()
->end()
->end()
->arrayNode('number_format')
->info('The default format options for the number_format filter')
->addDefaultsIfNotSet()
->children()
->integerNode('decimals')->defaultValue(0)->end()
->scalarNode('decimal_point')->defaultValue('.')->end()
->scalarNode('thousands_separator')->defaultValue(',')->end()
->end()
->end()
->end()
;
}
} }

View File

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Configurator;
/**
* Twig environment configurator.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class EnvironmentConfigurator
{
private $dateFormat;
private $intervalFormat;
private $timezone;
private $decimals;
private $decimalPoint;
private $thousandsSeparator;
public function __construct($dateFormat, $intervalFormat, $timezone, $decimals, $decimalPoint, $thousandsSeparator)
{
$this->dateFormat = $dateFormat;
$this->intervalFormat = $intervalFormat;
$this->timezone = $timezone;
$this->decimals = $decimals;
$this->decimalPoint = $decimalPoint;
$this->thousandsSeparator = $thousandsSeparator;
}
public function configure(\Twig_Environment $environment)
{
$environment->getExtension('core')->setDateFormat($this->dateFormat, $this->intervalFormat);
if (null !== $this->timezone) {
$environment->getExtension('core')->setTimezone($this->timezone);
}
$environment->getExtension('core')->setNumberFormat($this->decimals, $this->decimalPoint, $this->thousandsSeparator);
}
}

View File

@ -57,6 +57,14 @@ class TwigExtension extends Extension
$container->setParameter('twig.form.resources', $config['form_themes']); $container->setParameter('twig.form.resources', $config['form_themes']);
$envConfiguratorDefinition = $container->getDefinition('twig.configurator.environment');
$envConfiguratorDefinition->replaceArgument(0, $config['date']['format']);
$envConfiguratorDefinition->replaceArgument(1, $config['date']['interval_format']);
$envConfiguratorDefinition->replaceArgument(2, $config['date']['timezone']);
$envConfiguratorDefinition->replaceArgument(3, $config['number_format']['decimals']);
$envConfiguratorDefinition->replaceArgument(4, $config['number_format']['decimal_point']);
$envConfiguratorDefinition->replaceArgument(5, $config['number_format']['thousands_separator']);
$twigFilesystemLoaderDefinition = $container->getDefinition('twig.loader.filesystem'); $twigFilesystemLoaderDefinition = $container->getDefinition('twig.loader.filesystem');
// register user-configured paths // register user-configured paths

View File

@ -12,6 +12,7 @@
<argument>app</argument> <argument>app</argument>
<argument type="service" id="twig.app_variable" /> <argument type="service" id="twig.app_variable" />
</call> </call>
<configurator service="twig.configurator.environment" method="configure" />
</service> </service>
<service id="twig.app_variable" class="Symfony\Bridge\Twig\AppVariable" public="false"> <service id="twig.app_variable" class="Symfony\Bridge\Twig\AppVariable" public="false">
@ -135,5 +136,14 @@
<argument type="service" id="http_kernel" /> <argument type="service" id="http_kernel" />
<argument>%twig.exception_listener.controller%</argument> <argument>%twig.exception_listener.controller%</argument>
</service> </service>
<service id="twig.configurator.environment" class="Symfony\Bundle\TwigBundle\DependencyInjection\Configurator\EnvironmentConfigurator" public="false">
<argument /> <!-- date format, set in TwigExtension -->
<argument /> <!-- interval format, set in TwigExtension -->
<argument /> <!-- timezone, set in TwigExtension -->
<argument /> <!-- decimals, set in TwigExtension -->
<argument /> <!-- decimal point, set in TwigExtension -->
<argument /> <!-- thousands separator, set in TwigExtension -->
</service>
</services> </services>
</container> </container>

View File

@ -9,7 +9,18 @@
{% if collector.applicationname %} {% if collector.applicationname %}
{{ collector.applicationname }} {{ collector.applicationversion }} {{ collector.applicationname }} {{ collector.applicationversion }}
{% else %} {% else %}
{{ collector.symfonyversion }} {% if 'unknown' == collector.symfonyState -%}
<span class="sf-toolbar-status sf-toolbar-info-piece-additional" title="Unable to retrieve information about the Symfony version.">
{%- elseif 'eol' == collector.symfonyState -%}
<span class="sf-toolbar-status sf-toolbar-status-red" title="This Symfony version will no longer receive security fixes.">
{%- elseif 'eom' == collector.symfonyState -%}
<span class="sf-toolbar-status sf-toolbar-status-yellow" title="This Symfony version will only receive security fixes.">
{%- elseif 'dev' == collector.symfonyState -%}
<span class="sf-toolbar-status sf-toolbar-status-yellow" title="This Symfony version is still in the development phase.">
{%- else -%}
<span class="sf-toolbar-status sf-toolbar-status-green">
{%- endif -%}
{{ collector.symfonyversion }}</span>
{% endif %} {% endif %}
</span> </span>
</a> </a>

View File

@ -88,10 +88,13 @@
{% if collector.logs %} {% if collector.logs %}
<ul class="alt"> <ul class="alt">
{% for log in collector.logs if priority >= 0 and log.priority >= priority or priority < 0 and log.context.type|default(0) == priority %} {% for log in collector.logs %}
<li class="{{ cycle(['odd', 'even'], loop.index) }}{% if log.priority >= 400 %} error{% elseif log.priority >= 300 %} warning{% endif %}{% if log.context.scream is defined %} scream{% endif %}"> {% set is_deprecation = log.context.level is defined and log.context.type is defined and (constant('E_DEPRECATED') == log.context.type or constant('E_USER_DEPRECATED') == log.context.type) %}
{{ logger.display_message(loop.index, log) }} {% if priority == '-100' ? is_deprecation : log.priority >= priority %}
</li> <li class="{{ cycle(['odd', 'even'], loop.index) }}{% if log.context.scream is defined %} scream{% elseif log.priority >= 400 %} error{% elseif log.priority >= 300 %} warning{% endif %}">
{{ logger.display_message(loop.index, log, is_deprecation) }}
</li>
{% endif %}
{% else %} {% else %}
<li><em>No logs available for this priority.</em></li> <li><em>No logs available for this priority.</em></li>
{% endfor %} {% endfor %}
@ -104,15 +107,18 @@
{% endblock %} {% endblock %}
{% macro display_message(log_index, log) %} {% macro display_message(log_index, log, is_deprecation) %}
{% if log.context.level is defined and log.context.type is defined and (constant('E_DEPRECATED') == log.context.type or constant('E_USER_DEPRECATED') == log.context.type) %} {% if is_deprecation %}
DEPRECATION - {{ log.message }} {% set stack = log.context.stack|default([]) %}
{% set id = 'sf-call-stack-' ~ log_index %}
<a href="#" onclick="Sfjs.toggle('{{ id }}', document.getElementById('{{ id }}-on'), document.getElementById('{{ id }}-off')); return false;"> {% if stack %}
<img class="toggle" id="{{ id }}-off" alt="-" src="data:image/gif;base64,R0lGODlhEgASAMQSANft94TG57Hb8GS44ez1+mC24IvK6ePx+Wa44dXs92+942e54o3L6W2844/M6dnu+P/+/l614P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABIALAAAAAASABIAQAVCoCQBTBOd6Kk4gJhGBCTPxysJb44K0qD/ER/wlxjmisZkMqBEBW5NHrMZmVKvv9hMVsO+hE0EoNAstEYGxG9heIhCADs=" style="display:none"> <a href="#" onclick="Sfjs.toggle('{{ id }}', document.getElementById('{{ id }}-on'), document.getElementById('{{ id }}-off')); return false;">
<img class="toggle" id="{{ id }}-on" alt="+" src="data:image/gif;base64,R0lGODlhEgASAMQTANft99/v+Ga44bHb8ITG52S44dXs9+z1+uPx+YvK6WC24G+944/M6W28443L6dnu+Ge54v/+/l614P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABMALAAAAAASABIAQAVS4DQBTiOd6LkwgJgeUSzHSDoNaZ4PU6FLgYBA5/vFID/DbylRGiNIZu74I0h1hNsVxbNuUV4d9SsZM2EzWe1qThVzwWFOAFCQFa1RQq6DJB4iIQA7" style="display:inline"> <img class="toggle" id="{{ id }}-off" alt="-" src="data:image/gif;base64,R0lGODlhEgASAMQSANft94TG57Hb8GS44ez1+mC24IvK6ePx+Wa44dXs92+942e54o3L6W2844/M6dnu+P/+/l614P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABIALAAAAAASABIAQAVCoCQBTBOd6Kk4gJhGBCTPxysJb44K0qD/ER/wlxjmisZkMqBEBW5NHrMZmVKvv9hMVsO+hE0EoNAstEYGxG9heIhCADs=" style="display:none">
</a> <img class="toggle" id="{{ id }}-on" alt="+" src="data:image/gif;base64,R0lGODlhEgASAMQTANft99/v+Ga44bHb8ITG52S44dXs9+z1+uPx+YvK6WC24G+944/M6W28443L6dnu+Ge54v/+/l614P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABMALAAAAAASABIAQAVS4DQBTiOd6LkwgJgeUSzHSDoNaZ4PU6FLgYBA5/vFID/DbylRGiNIZu74I0h1hNsVxbNuUV4d9SsZM2EzWe1qThVzwWFOAFCQFa1RQq6DJB4iIQA7" style="display:inline">
{% for index, call in log.context.stack if index > 1 %} </a>
{% endif %}
{% for index, call in stack if index > 1 %}
{% if index == 2 %} {% if index == 2 %}
<ul class="sf-call-stack" id="{{ id }}" style="display: none"> <ul class="sf-call-stack" id="{{ id }}" style="display: none">
{% endif %} {% endif %}
@ -128,7 +134,7 @@
<li>Called from {{ call.file is defined and call.line is defined ? call.file|format_file(call.line, from) : from|raw }}</li> <li>Called from {{ call.file is defined and call.line is defined ? call.file|format_file(call.line, from) : from|raw }}</li>
{% if index == log.context.stack|length - 1 %} {% if index == stack|length - 1 %}
</ul> </ul>
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View File

@ -57,12 +57,23 @@
{% endblock %} {% endblock %}
{% block panelContent %} {% block panelContent %}
<h2>Called Translations</h2> <h2>Translation Stats</h2>
<ul> <table>
<li><strong>Defined messages: {{ collector.countdefines }}</strong></li> <tbody>
<li><strong>Fallback messages: {{ collector.countFallbacks }}</strong></li> <tr>
<li><strong>Missing messages: {{ collector.countMissings }}</strong></li> <th>Defined messages</th>
</ul> <td><pre>{{ collector.countdefines }}</pre></td>
</tr>
<tr>
<th scope="col" style="width: 30%">Fallback messages</th>
<td scope="col" style="width: 60%"><pre>{{ collector.countFallbacks }}</pre></td>
</tr>
<tr>
<th>Missing messages</th>
<td><pre>{{ collector.countMissings }}</pre></td>
</tr>
</tbody>
</table>
<table> <table>
<tr> <tr>
@ -77,7 +88,10 @@
<td><code>{{ translator.state(message) }}</code></td> <td><code>{{ translator.state(message) }}</code></td>
<td><code>{{ message.locale }}</code></td> <td><code>{{ message.locale }}</code></td>
<td><code>{{ message.domain }}</code></td> <td><code>{{ message.domain }}</code></td>
<td><code>{{ message.id }}</code></td> <td>
<code>{{ message.id }}</code>
{% if message.count > 1 %}<br><small style="color: gray;">(used {{ message.count }} times)</small>{% endif %}
</td>
<td><code>{{ message.translation }}</code></td> <td><code>{{ message.translation }}</code></td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -199,7 +199,7 @@
} }
{% if excluded_ajax_paths is defined %} {% if excluded_ajax_paths is defined %}
if (window.XMLHttpRequest && XMLHttpRequest.addEventListener) { if (window.XMLHttpRequest && XMLHttpRequest.prototype.addEventListener) {
var proxied = XMLHttpRequest.prototype.open; var proxied = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {

View File

@ -781,7 +781,7 @@ class Application
$input->setInteractive(false); $input->setInteractive(false);
} elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) { } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) {
$inputStream = $this->getHelperSet()->get('question')->getInputStream(); $inputStream = $this->getHelperSet()->get('question')->getInputStream();
if (!@posix_isatty($inputStream)) { if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
$input->setInteractive(false); $input->setInteractive(false);
} }
} }

View File

@ -97,7 +97,7 @@ class SymfonyStyle extends OutputStyle
*/ */
public function title($message) public function title($message)
{ {
$this->writeln(sprintf("\n<fg=blue>%s</fg=blue>\n<fg=blue>%s</fg=blue>\n", $message, str_repeat('=', strlen($message)))); $this->writeln(sprintf("\n<comment>%s</>\n<comment>%s</>\n", $message, str_repeat('=', strlen($message))));
} }
/** /**
@ -105,7 +105,7 @@ class SymfonyStyle extends OutputStyle
*/ */
public function section($message) public function section($message)
{ {
$this->writeln(sprintf("<fg=blue>%s</fg=blue>\n<fg=blue>%s</fg=blue>\n", $message, str_repeat('-', strlen($message)))); $this->writeln(sprintf("<comment>%s</>\n<comment>%s</>\n", $message, str_repeat('-', strlen($message))));
} }
/** /**
@ -119,7 +119,7 @@ class SymfonyStyle extends OutputStyle
$elements $elements
); );
$this->writeln(implode("\n\n", $elements)."\n"); $this->writeln(implode("\n", $elements)."\n");
} }
/** /**
@ -183,6 +183,8 @@ class SymfonyStyle extends OutputStyle
*/ */
public function table(array $headers, array $rows) public function table(array $headers, array $rows)
{ {
$headers = array_map(function ($value) { return sprintf('<info>%s</>', $value); }, $headers);
$table = new Table($this); $table = new Table($this);
$table->setHeaders($headers); $table->setHeaders($headers);
$table->setRows($rows); $table->setRows($rows);

View File

@ -61,7 +61,7 @@ class CommandTester
&& (null !== $application = $this->command->getApplication()) && (null !== $application = $this->command->getApplication())
&& $application->getDefinition()->hasArgument('command') && $application->getDefinition()->hasArgument('command')
) { ) {
$input['command'] = $this->command->getName(); $input = array_merge(array('command' => $this->command->getName()), $input);
} }
$this->input = new ArrayInput($input); $this->input = new ArrayInput($input);

View File

@ -6,6 +6,14 @@ CHANGELOG
* removed classes, methods and interfaces deprecated in 2.x * removed classes, methods and interfaces deprecated in 2.x
2.7.0
-----
* added deprecations checking for parent interfaces/classes to DebugClassLoader
* added ZTS support to symfony_debug extension
* added symfony_debug_backtrace() to symfony_debug extension
to track the backtrace of fatal errors
2.6.0 2.6.0
----- -----

View File

@ -66,9 +66,9 @@ class ErrorHandler
private $loggers = array( private $loggers = array(
E_DEPRECATED => array(null, LogLevel::INFO), E_DEPRECATED => array(null, LogLevel::INFO),
E_USER_DEPRECATED => array(null, LogLevel::INFO), E_USER_DEPRECATED => array(null, LogLevel::INFO),
E_NOTICE => array(null, LogLevel::NOTICE), E_NOTICE => array(null, LogLevel::WARNING),
E_USER_NOTICE => array(null, LogLevel::NOTICE), E_USER_NOTICE => array(null, LogLevel::WARNING),
E_STRICT => array(null, LogLevel::NOTICE), E_STRICT => array(null, LogLevel::WARNING),
E_WARNING => array(null, LogLevel::WARNING), E_WARNING => array(null, LogLevel::WARNING),
E_USER_WARNING => array(null, LogLevel::WARNING), E_USER_WARNING => array(null, LogLevel::WARNING),
E_COMPILE_WARNING => array(null, LogLevel::WARNING), E_COMPILE_WARNING => array(null, LogLevel::WARNING),
@ -223,7 +223,7 @@ class ErrorHandler
} }
/** /**
* Sets the error levels that are to be thrown. * Sets the PHP error levels that throw an exception when a PHP error occurs.
* *
* @param int $levels A bit field of E_* constants for thrown errors * @param int $levels A bit field of E_* constants for thrown errors
* @param bool $replace Replace or amend the previous value * @param bool $replace Replace or amend the previous value
@ -243,7 +243,7 @@ class ErrorHandler
} }
/** /**
* Sets the error levels that are logged or thrown with their local scope. * Sets the PHP error levels for which local variables are preserved.
* *
* @param int $levels A bit field of E_* constants for scoped errors * @param int $levels A bit field of E_* constants for scoped errors
* @param bool $replace Replace or amend the previous value * @param bool $replace Replace or amend the previous value
@ -262,7 +262,7 @@ class ErrorHandler
} }
/** /**
* Sets the error levels that are logged with their stack trace. * Sets the PHP error levels for which the stack trace is preserved.
* *
* @param int $levels A bit field of E_* constants for traced errors * @param int $levels A bit field of E_* constants for traced errors
* @param bool $replace Replace or amend the previous value * @param bool $replace Replace or amend the previous value
@ -336,55 +336,57 @@ class ErrorHandler
$throw = $this->thrownErrors & $type & $level; $throw = $this->thrownErrors & $type & $level;
$type &= $level | $this->screamedErrors; $type &= $level | $this->screamedErrors;
if ($type && ($log || $throw)) { if (!$type || (!$log && !$throw)) {
if ($throw) { return $type && $log;
if (($this->scopedErrors & $type) && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) { }
// Checking for class existence is a work around for https://bugs.php.net/42098
$throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
} else {
$throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
}
throw $throw; if ($throw) {
} if (($this->scopedErrors & $type) && class_exists(ContextErrorException::class)) {
// Checking for class existence is a work around for https://bugs.php.net/42098
// For duplicated errors, log the trace only once $throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
$e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
$trace = true;
if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
$trace = false;
} else { } else {
$this->loggedTraces[$e] = 1; $throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
} }
$e = compact('type', 'file', 'line', 'level'); throw $throw;
}
if ($type & $level) { // For duplicated errors, log the trace only once
if ($this->scopedErrors & $type) { $e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
$e['context'] = $context; $trace = true;
if ($trace) {
$e['stack'] = debug_backtrace(true); // Provide object if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
} $trace = false;
} elseif ($trace) { } else {
$e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $this->loggedTraces[$e] = 1;
}
$e = compact('type', 'file', 'line', 'level');
if ($type & $level) {
if ($this->scopedErrors & $type) {
$e['scope_vars'] = $context;
if ($trace) {
$e['stack'] = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
} }
} elseif ($trace) {
$e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
} }
}
if ($this->isRecursive) { if ($this->isRecursive) {
$log = 0; $log = 0;
} elseif (self::$stackedErrorLevels) { } elseif (self::$stackedErrorLevels) {
self::$stackedErrors[] = array($this->loggers[$type], $message, $e); self::$stackedErrors[] = array($this->loggers[$type], $message, $e);
} else { } else {
try { try {
$this->isRecursive = true; $this->isRecursive = true;
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e); $this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
$this->isRecursive = false; $this->isRecursive = false;
} catch (\Exception $e) { } catch (\Exception $e) {
$this->isRecursive = false; $this->isRecursive = false;
throw $e; throw $e;
}
} }
} }
@ -453,40 +455,44 @@ class ErrorHandler
public static function handleFatalError(array $error = null) public static function handleFatalError(array $error = null)
{ {
self::$reservedMemory = ''; self::$reservedMemory = '';
$handler = set_error_handler('var_dump', 0); $handler = set_error_handler('var_dump', 0);
$handler = is_array($handler) ? $handler[0] : null; $handler = is_array($handler) ? $handler[0] : null;
restore_error_handler(); restore_error_handler();
if ($handler instanceof self) {
if (null === $error) {
$error = error_get_last();
}
try { if (!$handler instanceof self) {
while (self::$stackedErrorLevels) { return;
static::unstackErrors(); }
}
} catch (\Exception $exception) {
// Handled below
}
if ($error && ($error['type'] & (E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR))) { if (null === $error) {
// Let's not throw anymore but keep logging $error = error_get_last();
$handler->throwAt(0, true); }
if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { try {
$exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false); while (self::$stackedErrorLevels) {
} else { static::unstackErrors();
$exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true);
}
} elseif (!isset($exception)) {
return;
} }
} catch (\Exception $exception) {
// Handled below
}
try { if ($error && ($error['type'] & (E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR))) {
$handler->handleException($exception, $error); // Let's not throw anymore but keep logging
} catch (FatalErrorException $e) { $handler->throwAt(0, true);
// Ignore this re-throw
if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
$exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false);
} else {
$exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true);
} }
} elseif (!isset($exception)) {
return;
}
try {
$handler->handleException($exception, $error);
} catch (FatalErrorException $e) {
// Ignore this re-throw
} }
} }

View File

@ -52,6 +52,11 @@ class FatalErrorException extends \ErrorException
unset($frame); unset($frame);
$trace = array_reverse($trace); $trace = array_reverse($trace);
} elseif (function_exists('symfony_debug_backtrace')) {
$trace = symfony_debug_backtrace();
if (0 < $traceOffset) {
array_splice($trace, 0, $traceOffset);
}
} else { } else {
$trace = array(); $trace = array();
} }

View File

@ -0,0 +1,133 @@
Symfony Debug Extension
=======================
This extension publishes several functions to help building powerful debugging tools.
symfony_zval_info()
-------------------
- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP,
- does work with references, preventing memory copying.
Its behavior is about the same as:
```php
<?php
function symfony_zval_info($key, $array, $options = 0)
{
// $options is currently not used, but could be in future version.
if (!array_key_exists($key, $array)) {
return null;
}
$info = array(
'type' => gettype($array[$key]),
'zval_hash' => /* hashed memory address of $array[$key] */,
'zval_refcount' => /* internal zval refcount of $array[$key] */,
'zval_isref' => /* is_ref status of $array[$key] */,
);
switch ($info['type']) {
case 'object':
$info += array(
'object_class' => get_class($array[$key]),
'object_refcount' => /* internal object refcount of $array[$key] */,
'object_hash' => spl_object_hash($array[$key]),
'object_handle' => /* internal object handle $array[$key] */,
);
break;
case 'resource':
$info += array(
'resource_handle' => (int) $array[$key],
'resource_type' => get_resource_type($array[$key]),
'resource_refcount' => /* internal resource refcount of $array[$key] */,
);
break;
case 'array':
$info += array(
'array_count' => count($array[$key]),
);
break;
case 'string':
$info += array(
'strlen' => strlen($array[$key]),
);
break;
}
return $info;
}
```
symfony_debug_backtrace()
-------------------------
This function works like debug_backtrace(), except that it can fetch the full backtrace in case of fatal errors:
```php
function foo() { fatal(); }
function bar() { foo(); }
function sd() { var_dump(symfony_debug_backtrace()); }
register_shutdown_function('sd');
bar();
/* Will output
Fatal error: Call to undefined function fatal() in foo.php on line 42
array(3) {
[0]=>
array(2) {
["function"]=>
string(2) "sd"
["args"]=>
array(0) {
}
}
[1]=>
array(4) {
["file"]=>
string(7) "foo.php"
["line"]=>
int(1)
["function"]=>
string(3) "foo"
["args"]=>
array(0) {
}
}
[2]=>
array(4) {
["file"]=>
string(102) "foo.php"
["line"]=>
int(2)
["function"]=>
string(3) "bar"
["args"]=>
array(0) {
}
}
}
*/
```
Usage
-----
The extension is compatible with ZTS mode, and should be supported by PHP5.3, 5.4, 5.5 and 5.6.
To enable the extension from source, run:
```
phpize
./configure
make
sudo make install
```

View File

@ -1,72 +0,0 @@
Symfony Debug Extension
=======================
This extension adds a ``symfony_zval_info($key, $array, $options = 0)`` function that:
- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP,
- does work with references, preventing memory copying.
Its behavior is about the same as:
.. code-block:: php
<?php
function symfony_zval_info($key, $array, $options = 0)
{
// $options is currently not used, but could be in future version.
if (!array_key_exists($key, $array)) {
return null;
}
$info = array(
'type' => gettype($array[$key]),
'zval_hash' => /* hashed memory address of $array[$key] */,
'zval_refcount' => /* internal zval refcount of $array[$key] */,
'zval_isref' => /* is_ref status of $array[$key] */,
);
switch ($info['type']) {
case 'object':
$info += array(
'object_class' => get_class($array[$key]),
'object_refcount' => /* internal object refcount of $array[$key] */,
'object_hash' => spl_object_hash($array[$key]),
'object_handle' => /* internal object handle $array[$key] */,
);
break;
case 'resource':
$info += array(
'resource_handle' => (int) $array[$key],
'resource_type' => get_resource_type($array[$key]),
'resource_refcount' => /* internal resource refcount of $array[$key] */,
);
break;
case 'array':
$info += array(
'array_count' => count($array[$key]),
);
break;
case 'string':
$info += array(
'strlen' => strlen($array[$key]),
);
break;
}
return $info;
}
To enable the extension from source, run:
.. code-block:: sh
phpize
./configure
make
sudo make install

View File

@ -13,7 +13,7 @@
extern zend_module_entry symfony_debug_module_entry; extern zend_module_entry symfony_debug_module_entry;
#define phpext_symfony_debug_ptr &symfony_debug_module_entry #define phpext_symfony_debug_ptr &symfony_debug_module_entry
#define PHP_SYMFONY_DEBUG_VERSION "1.0" #define PHP_SYMFONY_DEBUG_VERSION "2.7"
#ifdef PHP_WIN32 #ifdef PHP_WIN32
# define PHP_SYMFONY_DEBUG_API __declspec(dllexport) # define PHP_SYMFONY_DEBUG_API __declspec(dllexport)
@ -29,6 +29,8 @@ extern zend_module_entry symfony_debug_module_entry;
ZEND_BEGIN_MODULE_GLOBALS(symfony_debug) ZEND_BEGIN_MODULE_GLOBALS(symfony_debug)
intptr_t req_rand_init; intptr_t req_rand_init;
void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args);
zval *debug_bt;
ZEND_END_MODULE_GLOBALS(symfony_debug) ZEND_END_MODULE_GLOBALS(symfony_debug)
PHP_MINIT_FUNCTION(symfony_debug); PHP_MINIT_FUNCTION(symfony_debug);
@ -40,11 +42,14 @@ PHP_GINIT_FUNCTION(symfony_debug);
PHP_GSHUTDOWN_FUNCTION(symfony_debug); PHP_GSHUTDOWN_FUNCTION(symfony_debug);
PHP_FUNCTION(symfony_zval_info); PHP_FUNCTION(symfony_zval_info);
PHP_FUNCTION(symfony_debug_backtrace);
static char *_symfony_debug_memory_address_hash(void *); static char *_symfony_debug_memory_address_hash(void * TSRMLS_DC);
static const char *_symfony_debug_zval_type(zval *); static const char *_symfony_debug_zval_type(zval *);
static const char* _symfony_debug_get_resource_type(long); static const char* _symfony_debug_get_resource_type(long TSRMLS_DC);
static int _symfony_debug_get_resource_refcount(long); static int _symfony_debug_get_resource_refcount(long TSRMLS_DC);
void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args);
#ifdef ZTS #ifdef ZTS
#define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v) #define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v)

View File

@ -12,6 +12,9 @@
#endif #endif
#include "php.h" #include "php.h"
#ifdef ZTS
#include "TSRM.h"
#endif
#include "php_ini.h" #include "php_ini.h"
#include "ext/standard/info.h" #include "ext/standard/info.h"
#include "php_symfony_debug.h" #include "php_symfony_debug.h"
@ -19,6 +22,13 @@
#include "ext/standard/php_lcg.h" #include "ext/standard/php_lcg.h"
#include "ext/spl/php_spl.h" #include "ext/spl/php_spl.h"
#include "Zend/zend_gc.h" #include "Zend/zend_gc.h"
#include "Zend/zend_builtin_functions.h"
#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */
#include "ext/standard/php_array.h"
#include "Zend/zend_interfaces.h"
#include "SAPI.h"
#define IS_PHP_53 ZEND_EXTENSION_API_NO == 220090626
ZEND_DECLARE_MODULE_GLOBALS(symfony_debug) ZEND_DECLARE_MODULE_GLOBALS(symfony_debug)
@ -30,9 +40,28 @@ ZEND_END_ARG_INFO()
const zend_function_entry symfony_debug_functions[] = { const zend_function_entry symfony_debug_functions[] = {
PHP_FE(symfony_zval_info, symfony_zval_arginfo) PHP_FE(symfony_zval_info, symfony_zval_arginfo)
PHP_FE(symfony_debug_backtrace, NULL)
PHP_FE_END PHP_FE_END
}; };
PHP_FUNCTION(symfony_debug_backtrace)
{
if (zend_parse_parameters_none() == FAILURE) {
return;
}
#if IS_PHP_53
zend_fetch_debug_backtrace(return_value, 1, 0 TSRMLS_CC);
#else
zend_fetch_debug_backtrace(return_value, 1, 0, 0 TSRMLS_CC);
#endif
if (!SYMFONY_DEBUG_G(debug_bt)) {
return;
}
php_array_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_P(SYMFONY_DEBUG_G(debug_bt)), 0 TSRMLS_CC);
}
PHP_FUNCTION(symfony_zval_info) PHP_FUNCTION(symfony_zval_info)
{ {
zval *key = NULL, *arg = NULL; zval *key = NULL, *arg = NULL;
@ -40,7 +69,7 @@ PHP_FUNCTION(symfony_zval_info)
HashTable *array = NULL; HashTable *array = NULL;
long options = 0; long options = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zh|l", &key, &array, &options) == FAILURE) { if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zh|l", &key, &array, &options) == FAILURE) {
return; return;
} }
@ -62,13 +91,14 @@ PHP_FUNCTION(symfony_zval_info)
array_init(return_value); array_init(return_value);
add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1); add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1);
add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg), 16, 1); add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg TSRMLS_CC), 16, 0);
add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg)); add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg));
add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg)); add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg));
if (Z_TYPE_P(arg) == IS_OBJECT) { if (Z_TYPE_P(arg) == IS_OBJECT) {
static char hash[33] = {0}; char hash[33] = {0};
php_spl_object_hash(arg, (char *)hash);
php_spl_object_hash(arg, (char *)hash TSRMLS_CC);
add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1); add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1);
add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount); add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount);
add_assoc_string(return_value, "object_hash", hash, 1); add_assoc_string(return_value, "object_hash", hash, 1);
@ -77,17 +107,41 @@ PHP_FUNCTION(symfony_zval_info)
add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg))); add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg)));
} else if(Z_TYPE_P(arg) == IS_RESOURCE) { } else if(Z_TYPE_P(arg) == IS_RESOURCE) {
add_assoc_long(return_value, "resource_handle", Z_LVAL_P(arg)); add_assoc_long(return_value, "resource_handle", Z_LVAL_P(arg));
add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg)), 1); add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg) TSRMLS_CC), 1);
add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg))); add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg) TSRMLS_CC));
} else if (Z_TYPE_P(arg) == IS_STRING) { } else if (Z_TYPE_P(arg) == IS_STRING) {
add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg)); add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg));
} }
} }
static const char* _symfony_debug_get_resource_type(long rsid) void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args)
{
TSRMLS_FETCH();
zval *retval;
switch (type) {
case E_ERROR:
case E_PARSE:
case E_CORE_ERROR:
case E_CORE_WARNING:
case E_COMPILE_ERROR:
case E_COMPILE_WARNING:
ALLOC_INIT_ZVAL(retval);
#if IS_PHP_53
zend_fetch_debug_backtrace(retval, 1, 0 TSRMLS_CC);
#else
zend_fetch_debug_backtrace(retval, 1, 0, 0 TSRMLS_CC);
#endif
SYMFONY_DEBUG_G(debug_bt) = retval;
}
SYMFONY_DEBUG_G(old_error_cb)(type, error_filename, error_lineno, format, args);
}
static const char* _symfony_debug_get_resource_type(long rsid TSRMLS_DC)
{ {
const char *res_type; const char *res_type;
res_type = zend_rsrc_list_get_rsrc_type(rsid); res_type = zend_rsrc_list_get_rsrc_type(rsid TSRMLS_CC);
if (!res_type) { if (!res_type) {
return "Unknown"; return "Unknown";
@ -96,7 +150,7 @@ static const char* _symfony_debug_get_resource_type(long rsid)
return res_type; return res_type;
} }
static int _symfony_debug_get_resource_refcount(long rsid) static int _symfony_debug_get_resource_refcount(long rsid TSRMLS_DC)
{ {
zend_rsrc_list_entry *le; zend_rsrc_list_entry *le;
@ -107,21 +161,21 @@ static int _symfony_debug_get_resource_refcount(long rsid)
return 0; return 0;
} }
static char *_symfony_debug_memory_address_hash(void *address) static char *_symfony_debug_memory_address_hash(void *address TSRMLS_DC)
{ {
static char result[17] = {0}; char *result = NULL;
intptr_t address_rand; intptr_t address_rand;
if (!SYMFONY_DEBUG_G(req_rand_init)) { if (!SYMFONY_DEBUG_G(req_rand_init)) {
if (!BG(mt_rand_is_seeded)) { if (!BG(mt_rand_is_seeded)) {
php_mt_srand(GENERATE_SEED() TSRMLS_CC); php_mt_srand(GENERATE_SEED() TSRMLS_CC);
} }
SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand(); SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand(TSRMLS_C);
} }
address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init); address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init);
snprintf(result, 17, "%016zx", address_rand); spprintf(&result, 17, "%016zx", address_rand);
return result; return result;
} }
@ -187,7 +241,7 @@ ZEND_GET_MODULE(symfony_debug)
PHP_GINIT_FUNCTION(symfony_debug) PHP_GINIT_FUNCTION(symfony_debug)
{ {
symfony_debug_globals->req_rand_init = 0; memset(symfony_debug_globals, 0 , sizeof(*symfony_debug_globals));
} }
PHP_GSHUTDOWN_FUNCTION(symfony_debug) PHP_GSHUTDOWN_FUNCTION(symfony_debug)
@ -197,11 +251,16 @@ PHP_GSHUTDOWN_FUNCTION(symfony_debug)
PHP_MINIT_FUNCTION(symfony_debug) PHP_MINIT_FUNCTION(symfony_debug)
{ {
SYMFONY_DEBUG_G(old_error_cb) = zend_error_cb;
zend_error_cb = symfony_debug_error_cb;
return SUCCESS; return SUCCESS;
} }
PHP_MSHUTDOWN_FUNCTION(symfony_debug) PHP_MSHUTDOWN_FUNCTION(symfony_debug)
{ {
zend_error_cb = SYMFONY_DEBUG_G(old_error_cb);
return SUCCESS; return SUCCESS;
} }

View File

@ -3,7 +3,7 @@ Test symfony_zval_info API
--SKIPIF-- --SKIPIF--
<?php if (!extension_loaded("symfony_debug")) print "skip"; ?> <?php if (!extension_loaded("symfony_debug")) print "skip"; ?>
--FILE-- --FILE--
<?php <?php
$int = 42; $int = 42;
$float = 42.42; $float = 42.42;
@ -88,7 +88,7 @@ array(8) {
["object_hash"]=> ["object_hash"]=>
string(32) "%s" string(32) "%s"
["object_handle"]=> ["object_handle"]=>
int(1) int(%d)
} }
array(5) { array(5) {
["type"]=> ["type"]=>
@ -112,7 +112,7 @@ array(7) {
["zval_isref"]=> ["zval_isref"]=>
bool(false) bool(false)
["resource_handle"]=> ["resource_handle"]=>
int(4) int(%d)
["resource_type"]=> ["resource_type"]=>
string(6) "stream" string(6) "stream"
["resource_refcount"]=> ["resource_refcount"]=>

View File

@ -0,0 +1,64 @@
--TEST--
Test symfony_debug_backtrace in case of fatal error
--SKIPIF--
<?php if (!extension_loaded("symfony_debug")) print "skip"; ?>
--FILE--
<?php
function bar()
{
foo();
}
function foo()
{
notexist();
}
function bt()
{
print_r(symfony_debug_backtrace());
}
register_shutdown_function('bt');
bar();
?>
--EXPECTF--
Fatal error: Call to undefined function notexist() in %s on line %d
Array
(
[0] => Array
(
[function] => bt
[args] => Array
(
)
)
[1] => Array
(
[file] => %s
[line] => %d
[function] => foo
[args] => Array
(
)
)
[2] => Array
(
[file] => %s
[line] => %d
[function] => bar
[args] => Array
(
)
)
)

View File

@ -0,0 +1,47 @@
--TEST--
Test symfony_debug_backtrace in case of non fatal error
--SKIPIF--
<?php if (!extension_loaded("symfony_debug")) print "skip"; ?>
--FILE--
<?php
function bar()
{
bt();
}
function bt()
{
print_r(symfony_debug_backtrace());
}
bar();
?>
--EXPECTF--
Array
(
[0] => Array
(
[file] => %s
[line] => %d
[function] => bt
[args] => Array
(
)
)
[1] => Array
(
[file] => %s
[line] => %d
[function] => bar
[args] => Array
(
)
)
)

View File

@ -0,0 +1,85 @@
--TEST--
Test ErrorHandler in case of fatal error
--SKIPIF--
<?php if (!extension_loaded("symfony_debug")) print "skip"; ?>
--FILE--
<?php
namespace Psr\Log;
class LogLevel
{
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
}
namespace Symfony\Component\Debug;
$dir = __DIR__.'/../../../';
require $dir.'ErrorHandler.php';
require $dir.'Exception/FatalErrorException.php';
require $dir.'Exception/UndefinedFunctionException.php';
require $dir.'FatalErrorHandler/FatalErrorHandlerInterface.php';
require $dir.'FatalErrorHandler/ClassNotFoundFatalErrorHandler.php';
require $dir.'FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php';
require $dir.'FatalErrorHandler/UndefinedMethodFatalErrorHandler.php';
function bar()
{
foo();
}
function foo()
{
notexist();
}
$handler = ErrorHandler::register();
$handler->setExceptionHandler('print_r');
if (function_exists('xdebug_disable')) {
xdebug_disable();
}
bar();
?>
--EXPECTF--
Fatal error: Call to undefined function Symfony\Component\Debug\notexist() in %s on line %d
Symfony\Component\Debug\Exception\UndefinedFunctionException Object
(
[message:protected] => Attempted to call function "notexist" from namespace "Symfony\Component\Debug".
[string:Exception:private] =>
[code:protected] => 0
[file:protected] => -
[line:protected] => %d
[trace:Exception:private] => Array
(
[0] => Array
(
%A [function] => Symfony\Component\Debug\foo
%A [args] => Array
(
)
)
[1] => Array
(
%A [function] => Symfony\Component\Debug\bar
%A [args] => Array
(
)
)
%A
)
[previous:Exception:private] =>
[severity:protected] => 1
)

View File

@ -141,9 +141,9 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
$loggers = array( $loggers = array(
E_DEPRECATED => array(null, LogLevel::INFO), E_DEPRECATED => array(null, LogLevel::INFO),
E_USER_DEPRECATED => array(null, LogLevel::INFO), E_USER_DEPRECATED => array(null, LogLevel::INFO),
E_NOTICE => array($logger, LogLevel::NOTICE), E_NOTICE => array($logger, LogLevel::WARNING),
E_USER_NOTICE => array($logger, LogLevel::CRITICAL), E_USER_NOTICE => array($logger, LogLevel::CRITICAL),
E_STRICT => array(null, LogLevel::NOTICE), E_STRICT => array(null, LogLevel::WARNING),
E_WARNING => array(null, LogLevel::WARNING), E_WARNING => array(null, LogLevel::WARNING),
E_USER_WARNING => array(null, LogLevel::WARNING), E_USER_WARNING => array(null, LogLevel::WARNING),
E_COMPILE_WARNING => array(null, LogLevel::WARNING), E_COMPILE_WARNING => array(null, LogLevel::WARNING),

View File

@ -14,6 +14,9 @@
<testsuite name="Symfony Debug Component Test Suite"> <testsuite name="Symfony Debug Component Test Suite">
<directory>./Tests/</directory> <directory>./Tests/</directory>
</testsuite> </testsuite>
<testsuite name="Symfony Debug Extension Test Suite">
<directory suffix=".phpt">./Resources/ext/tests/</directory>
</testsuite>
</testsuites> </testsuites>
<filter> <filter>

View File

@ -14,8 +14,27 @@ CHANGELOG
2.7.0 2.7.0
----- -----
* deprecated the overwriting of `AbstractType::setDefaultOptions()` in favor of overwriting `AbstractType::configureOptions()`. * added option "choice_translation_domain" to ChoiceType.
* deprecated the overwriting of `AbstractTypeExtension::setDefaultOptions()` in favor of overwriting `AbstractTypeExtension::configureOptions()`. * deprecated option "precision" in favor of "scale"
* deprecated the overwriting of AbstractType::setDefaultOptions() in favor of overwriting AbstractType::configureOptions().
* deprecated the overwriting of AbstractTypeExtension::setDefaultOptions() in favor of overwriting AbstractTypeExtension::configureOptions().
* added new ChoiceList interface and implementations in the Symfony\Component\Form\ChoiceList namespace
* added new ChoiceView in the Symfony\Component\Form\ChoiceList\View namespace
* choice groups are now represented by ChoiceGroupView objects in the view
* deprecated the old ChoiceList interface and implementations
* deprecated the old ChoiceView class
* added CheckboxListMapper and RadioListMapper
* deprecated ChoiceToBooleanArrayTransformer and ChoicesToBooleanArrayTransformer
* deprecated FixCheckboxInputListener and FixRadioInputListener
* deprecated the "choice_list" option of ChoiceType
* added new options to ChoiceType:
* "choices_as_values"
* "choice_loader"
* "choice_label"
* "choice_name"
* "choice_value"
* "choice_attr"
* "group_by"
2.6.2 2.6.2
----- -----

View File

@ -0,0 +1,158 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
/**
* A list of choices with arbitrary data types.
*
* The user of this class is responsible for assigning string values to the
* choices. Both the choices and their values are passed to the constructor.
* Each choice must have a corresponding value (with the same array key) in
* the value array.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ArrayChoiceList implements ChoiceListInterface
{
/**
* The choices in the list.
*
* @var array
*/
protected $choices = array();
/**
* The values of the choices.
*
* @var string[]
*/
protected $values = array();
/**
* The callback for creating the value for a choice.
*
* @var callable
*/
protected $valueCallback;
/**
* Creates a list with the given choices and values.
*
* The given choice array must have the same array keys as the value array.
*
* @param array $choices The selectable choices
* @param callable|null $value The callable for creating the value for a
* choice. If `null` is passed, incrementing
* integers are used as values
*/
public function __construct(array $choices, $value = null)
{
if (null !== $value && !is_callable($value)) {
throw new UnexpectedTypeException($value, 'null or callable');
}
$this->choices = $choices;
$this->values = array();
$this->valueCallback = $value;
if (null === $value) {
$i = 0;
foreach ($this->choices as $key => $choice) {
$this->values[$key] = (string) $i++;
}
} else {
foreach ($choices as $key => $choice) {
$this->values[$key] = (string) call_user_func($value, $choice);
}
}
}
/**
* {@inheritdoc}
*/
public function getChoices()
{
return $this->choices;
}
/**
* {@inheritdoc}
*/
public function getValues()
{
return $this->values;
}
/**
* {@inheritdoc}
*/
public function getChoicesForValues(array $values)
{
$choices = array();
foreach ($values as $i => $givenValue) {
foreach ($this->values as $j => $value) {
if ($value !== (string) $givenValue) {
continue;
}
$choices[$i] = $this->choices[$j];
unset($values[$i]);
if (0 === count($values)) {
break 2;
}
}
}
return $choices;
}
/**
* {@inheritdoc}
*/
public function getValuesForChoices(array $choices)
{
$values = array();
// Use the value callback to compare choices by their values, if present
if ($this->valueCallback) {
$givenValues = array();
foreach ($choices as $i => $givenChoice) {
$givenValues[$i] = (string) call_user_func($this->valueCallback, $givenChoice);
}
return array_intersect($givenValues, $this->values);
}
// Otherwise compare choices by identity
foreach ($choices as $i => $givenChoice) {
foreach ($this->choices as $j => $choice) {
if ($choice !== $givenChoice) {
continue;
}
$values[$i] = $this->values[$j];
unset($choices[$i]);
if (0 === count($choices)) {
break 2;
}
}
}
return $values;
}
}

View File

@ -0,0 +1,146 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList;
use Symfony\Component\Form\Exception\InvalidArgumentException;
/**
* A list of choices that can be stored in the keys of a PHP array.
*
* PHP arrays accept only strings and integers as array keys. Other scalar types
* are cast to integers and strings according to the description of
* {@link toArrayKey()}. This implementation applies the same casting rules for
* the choices passed to the constructor and to {@link getValuesForChoices()}.
*
* By default, the choices are cast to strings and used as values. Optionally,
* you may pass custom values. The keys of the value array must match the keys
* of the choice array.
*
* Example:
*
* ```php
* $choices = array('' => 'Don\'t know', 0 => 'No', 1 => 'Yes');
* $choiceList = new ArrayKeyChoiceList(array_keys($choices));
*
* $values = $choiceList->getValues()
* // => array('', '0', '1')
*
* $selectedValues = $choiceList->getValuesForChoices(array(true));
* // => array('1')
* ```
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ArrayKeyChoiceList extends ArrayChoiceList
{
/**
* Whether the choices are used as values.
*
* @var bool
*/
private $useChoicesAsValues = false;
/**
* Casts the given choice to an array key.
*
* PHP arrays accept only strings and integers as array keys. Integer
* strings such as "42" are automatically cast to integers. The boolean
* values "true" and "false" are cast to the integers 1 and 0. Every other
* scalar value is cast to a string.
*
* @param mixed $choice The choice
*
* @return int|string The choice as PHP array key
*
* @throws InvalidArgumentException If the choice is not scalar
*/
public static function toArrayKey($choice)
{
if (!is_scalar($choice) && null !== $choice) {
throw new InvalidArgumentException(sprintf(
'The value of type "%s" cannot be converted to a valid array key.',
gettype($choice)
));
}
if (is_bool($choice) || (string) (int) $choice === (string) $choice) {
return (int) $choice;
}
return (string) $choice;
}
/**
* Creates a list with the given choices and values.
*
* The given choice array must have the same array keys as the value array.
* Each choice must be castable to an integer/string according to the
* casting rules described in {@link toArrayKey()}.
*
* If no values are given, the choices are cast to strings and used as
* values.
*
* @param array $choices The selectable choices
* @param callable $value The callable for creating the value for a
* choice. If `null` is passed, the choices are
* cast to strings and used as values
*
* @throws InvalidArgumentException If the keys of the choices don't match
* the keys of the values or if any of the
* choices is not scalar
*/
public function __construct(array $choices, $value = null)
{
$choices = array_map(array(__CLASS__, 'toArrayKey'), $choices);
if (null === $value) {
$value = function ($choice) {
return (string) $choice;
};
$this->useChoicesAsValues = true;
}
parent::__construct($choices, $value);
}
/**
* {@inheritdoc}
*/
public function getChoicesForValues(array $values)
{
if ($this->useChoicesAsValues) {
$values = array_map('strval', $values);
// If the values are identical to the choices, so we can just return
// them to improve performance a little bit
return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, $this->values));
}
return parent::getChoicesForValues($values);
}
/**
* {@inheritdoc}
*/
public function getValuesForChoices(array $choices)
{
$choices = array_map(array(__CLASS__, 'toArrayKey'), $choices);
if ($this->useChoicesAsValues) {
// If the choices are identical to the values, we can just return
// them to improve performance a little bit
return array_map('strval', array_intersect($choices, $this->choices));
}
return parent::getValuesForChoices($choices);
}
}

View File

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList;
/**
* A list of choices that can be selected in a choice field.
*
* A choice list assigns string values to each of a list of choices. These
* string values are displayed in the "value" attributes in HTML and submitted
* back to the server.
*
* The acceptable data types for the choices depend on the implementation.
* Values must always be strings and (within the list) free of duplicates.
*
* The choices returned by {@link getChoices()} and the values returned by
* {@link getValues()} must have the same array indices.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ChoiceListInterface
{
/**
* Returns all selectable choices.
*
* The keys of the choices correspond to the keys of the values returned by
* {@link getValues()}.
*
* @return array The selectable choices
*/
public function getChoices();
/**
* Returns the values for the choices.
*
* The keys of the values correspond to the keys of the choices returned by
* {@link getChoices()}.
*
* @return string[] The choice values
*/
public function getValues();
/**
* Returns the choices corresponding to the given values.
*
* The choices are returned with the same keys and in the same order as the
* corresponding values in the given array.
*
* @param string[] $values An array of choice values. Non-existing values in
* this array are ignored
*
* @return array An array of choices
*/
public function getChoicesForValues(array $values);
/**
* Returns the values corresponding to the given choices.
*
* The values are returned with the same keys and in the same order as the
* corresponding choices in the given array.
*
* @param array $choices An array of choices. Non-existing choices in this
* array are ignored
*
* @return string[] An array of choice values
*/
public function getValuesForChoices(array $choices);
}

View File

@ -0,0 +1,180 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\Factory;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
/**
* Caches the choice lists created by the decorated factory.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class CachingFactoryDecorator implements ChoiceListFactoryInterface
{
/**
* @var ChoiceListFactoryInterface
*/
private $decoratedFactory;
/**
* @var ChoiceListInterface[]
*/
private $lists = array();
/**
* @var ChoiceListView[]
*/
private $views = array();
/**
* Generates a SHA-256 hash for the given value.
*
* Optionally, a namespace string can be passed. Calling this method will
* the same values, but different namespaces, will return different hashes.
*
* @param mixed $value The value to hash
* @param string $namespace Optional. The namespace
*
* @return string The SHA-256 hash
*
* @internal Should not be used by user-land code.
*/
public static function generateHash($value, $namespace = '')
{
if (is_object($value)) {
$value = spl_object_hash($value);
} elseif (is_array($value)) {
array_walk_recursive($value, function (&$v) {
if (is_object($v)) {
$v = spl_object_hash($v);
}
});
}
return hash('sha256', $namespace.':'.json_encode($value));
}
/**
* Decorates the given factory.
*
* @param ChoiceListFactoryInterface $decoratedFactory The decorated factory
*/
public function __construct(ChoiceListFactoryInterface $decoratedFactory)
{
$this->decoratedFactory = $decoratedFactory;
}
/**
* Returns the decorated factory.
*
* @return ChoiceListFactoryInterface The decorated factory
*/
public function getDecoratedFactory()
{
return $this->decoratedFactory;
}
/**
* {@inheritdoc}
*/
public function createListFromChoices($choices, $value = null)
{
if ($choices instanceof \Traversable) {
$choices = iterator_to_array($choices);
}
// The value is not validated on purpose. The decorated factory may
// decide which values to accept and which not.
// We ignore the choice groups for caching. If two choice lists are
// requested with the same choices, but a different grouping, the same
// choice list is returned.
DefaultChoiceListFactory::flatten($choices, $flatChoices);
$hash = self::generateHash(array($flatChoices, $value), 'fromChoices');
if (!isset($this->lists[$hash])) {
$this->lists[$hash] = $this->decoratedFactory->createListFromChoices($choices, $value);
}
return $this->lists[$hash];
}
/**
* {@inheritdoc}
*
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
* removed in Symfony 3.0.
*/
public function createListFromFlippedChoices($choices, $value = null)
{
if ($choices instanceof \Traversable) {
$choices = iterator_to_array($choices);
}
// The value is not validated on purpose. The decorated factory may
// decide which values to accept and which not.
// We ignore the choice groups for caching. If two choice lists are
// requested with the same choices, but a different grouping, the same
// choice list is returned.
DefaultChoiceListFactory::flattenFlipped($choices, $flatChoices);
$hash = self::generateHash(array($flatChoices, $value), 'fromFlippedChoices');
if (!isset($this->lists[$hash])) {
$this->lists[$hash] = $this->decoratedFactory->createListFromFlippedChoices($choices, $value);
}
return $this->lists[$hash];
}
/**
* {@inheritdoc}
*/
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
{
$hash = self::generateHash(array($loader, $value), 'fromLoader');
if (!isset($this->lists[$hash])) {
$this->lists[$hash] = $this->decoratedFactory->createListFromLoader($loader, $value);
}
return $this->lists[$hash];
}
/**
* {@inheritdoc}
*/
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
{
// The input is not validated on purpose. This way, the decorated
// factory may decide which input to accept and which not.
$hash = self::generateHash(array($list, $preferredChoices, $label, $index, $groupBy, $attr));
if (!isset($this->views[$hash])) {
$this->views[$hash] = $this->decoratedFactory->createView(
$list,
$preferredChoices,
$label,
$index,
$groupBy,
$attr
);
}
return $this->views[$hash];
}
}

View File

@ -0,0 +1,124 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\Factory;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
/**
* Creates {@link ChoiceListInterface} instances.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ChoiceListFactoryInterface
{
/**
* Creates a choice list for the given choices.
*
* The choices should be passed in the values of the choices array.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as first and the array key as the second
* argument.
*
* @param array|\Traversable $choices The choices
* @param null|callable $value The callable generating the choice
* values
*
* @return ChoiceListInterface The choice list
*/
public function createListFromChoices($choices, $value = null);
/**
* Creates a choice list for the given choices.
*
* The choices should be passed in the keys of the choices array. Since the
* choices array will be flipped, the entries of the array must be strings
* or integers.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as first and the array key as the second
* argument.
*
* @param array|\Traversable $choices The choices
* @param null|callable $value The callable generating the choice
* values
*
* @return ChoiceListInterface The choice list
*
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
* removed in Symfony 3.0.
*/
public function createListFromFlippedChoices($choices, $value = null);
/**
* Creates a choice list that is loaded with the given loader.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as first and the array key as the second
* argument.
*
* @param ChoiceLoaderInterface $loader The choice loader
* @param null|callable $value The callable generating the choice
* values
*
* @return ChoiceListInterface The choice list
*/
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null);
/**
* Creates a view for the given choice list.
*
* Callables may be passed for all optional arguments. The callables receive
* the choice as first and the array key as the second argument.
*
* * The callable for the label and the name should return the generated
* label/choice name.
* * The callable for the preferred choices should return true or false,
* depending on whether the choice should be preferred or not.
* * The callable for the grouping should return the group name or null if
* a choice should not be grouped.
* * The callable for the attributes should return an array of HTML
* attributes that will be inserted in the tag of the choice.
*
* If no callable is passed, the labels will be generated from the choice
* keys. The view indices will be generated using an incrementing integer
* by default.
*
* The preferred choices can also be passed as array. Each choice that is
* contained in that array will be marked as preferred.
*
* The groups can be passed as a multi-dimensional array. In that case, a
* group will be created for each array entry containing a nested array.
* For all other entries, the choice for the corresponding key will be
* inserted at that position.
*
* The attributes can be passed as multi-dimensional array. The keys should
* match the keys of the choices. The values should be arrays of HTML
* attributes that should be added to the respective choice.
*
* @param ChoiceListInterface $list The choice list
* @param null|array|callable $preferredChoices The preferred choices
* @param null|callable $label The callable generating
* the choice labels
* @param null|callable $index The callable generating
* the view indices
* @param null|array|\Traversable|callable $groupBy The callable generating
* the group names
* @param null|array|callable $attr The callable generating
* the HTML attributes
*
* @return ChoiceListView The choice list view
*/
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null);
}

View File

@ -0,0 +1,343 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\Factory;
use Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList;
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface;
/**
* Default implementation of {@link ChoiceListFactoryInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class DefaultChoiceListFactory implements ChoiceListFactoryInterface
{
/**
* Flattens an array into the given output variable.
*
* @param array $array The array to flatten
* @param array $output The flattened output
*
* @internal Should not be used by user-land code
*/
public static function flatten(array $array, &$output)
{
if (null === $output) {
$output = array();
}
foreach ($array as $key => $value) {
if (is_array($value)) {
self::flatten($value, $output);
continue;
}
$output[$key] = $value;
}
}
/**
* Flattens and flips an array into the given output variable.
*
* During the flattening, the keys and values of the input array are
* flipped.
*
* @param array $array The array to flatten
* @param array $output The flattened output
*
* @internal Should not be used by user-land code
*/
public static function flattenFlipped(array $array, &$output)
{
if (null === $output) {
$output = array();
}
foreach ($array as $key => $value) {
if (is_array($value)) {
self::flattenFlipped($value, $output);
continue;
}
$output[$value] = $key;
}
}
/**
* {@inheritdoc}
*/
public function createListFromChoices($choices, $value = null)
{
if ($choices instanceof \Traversable) {
$choices = iterator_to_array($choices);
}
// If the choices are given as recursive array (i.e. with explicit
// choice groups), flatten the array. The grouping information is needed
// in the view only.
self::flatten($choices, $flatChoices);
return new ArrayChoiceList($flatChoices, $value);
}
/**
* {@inheritdoc}
*
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
* removed in Symfony 3.0.
*/
public function createListFromFlippedChoices($choices, $value = null)
{
if ($choices instanceof \Traversable) {
$choices = iterator_to_array($choices);
}
// If the choices are given as recursive array (i.e. with explicit
// choice groups), flatten the array. The grouping information is needed
// in the view only.
self::flattenFlipped($choices, $flatChoices);
// If no values are given, use the choices as values
// Since the choices are stored in the collection keys, i.e. they are
// strings or integers, we are guaranteed to be able to convert them
// to strings
if (null === $value) {
$value = function ($choice) {
return (string) $choice;
};
}
return new ArrayKeyChoiceList($flatChoices, $value);
}
/**
* {@inheritdoc}
*/
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
{
return new LazyChoiceList($loader, $value);
}
/**
* {@inheritdoc}
*/
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
{
// Backwards compatibility
if ($list instanceof LegacyChoiceListInterface && null === $preferredChoices
&& null === $label && null === $index && null === $groupBy && null === $attr) {
return new ChoiceListView($list->getRemainingViews(), $list->getPreferredViews());
}
$preferredViews = array();
$otherViews = array();
$choices = $list->getChoices();
$values = $list->getValues();
if (!is_callable($preferredChoices) && !empty($preferredChoices)) {
$preferredChoices = function ($choice) use ($preferredChoices) {
return false !== array_search($choice, $preferredChoices, true);
};
}
// The names are generated from an incrementing integer by default
if (null === $index) {
$index = 0;
}
// If $groupBy is not given, no grouping is done
if (empty($groupBy)) {
foreach ($choices as $key => $choice) {
self::addChoiceView(
$choice,
$key,
$label,
$values,
$index,
$attr,
$preferredChoices,
$preferredViews,
$otherViews
);
}
return new ChoiceListView($otherViews, $preferredViews);
}
// If $groupBy is a callable, choices are added to the group with the
// name returned by the callable. If the callable returns null, the
// choice is not added to any group
if (is_callable($groupBy)) {
foreach ($choices as $key => $choice) {
self::addChoiceViewGroupedBy(
$groupBy,
$choice,
$key,
$label,
$values,
$index,
$attr,
$preferredChoices,
$preferredViews,
$otherViews
);
}
} else {
// If $groupBy is passed as array, use that array as template for
// constructing the groups
self::addChoiceViewsGroupedBy(
$groupBy,
$label,
$choices,
$values,
$index,
$attr,
$preferredChoices,
$preferredViews,
$otherViews
);
}
// Remove any empty group view that may have been created by
// addChoiceViewGroupedBy()
foreach ($preferredViews as $key => $view) {
if ($view instanceof ChoiceGroupView && 0 === count($view->choices)) {
unset($preferredViews[$key]);
}
}
foreach ($otherViews as $key => $view) {
if ($view instanceof ChoiceGroupView && 0 === count($view->choices)) {
unset($otherViews[$key]);
}
}
return new ChoiceListView($otherViews, $preferredViews);
}
private static function addChoiceView($choice, $key, $label, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
{
$value = $values[$key];
$nextIndex = is_int($index) ? $index++ : call_user_func($index, $choice, $key, $value);
$view = new ChoiceView(
// If the labels are null, use the choice key by default
null === $label ? (string) $key : (string) call_user_func($label, $choice, $key, $value),
$value,
$choice,
// The attributes may be a callable or a mapping from choice indices
// to nested arrays
is_callable($attr) ? call_user_func($attr, $choice, $key, $value) : (isset($attr[$key]) ? $attr[$key] : array())
);
// $isPreferred may be null if no choices are preferred
if ($isPreferred && call_user_func($isPreferred, $choice, $key, $value)) {
$preferredViews[$nextIndex] = $view;
} else {
$otherViews[$nextIndex] = $view;
}
}
private static function addChoiceViewsGroupedBy($groupBy, $label, $choices, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
{
foreach ($groupBy as $key => $content) {
// Add the contents of groups to new ChoiceGroupView instances
if (is_array($content)) {
$preferredViewsForGroup = array();
$otherViewsForGroup = array();
self::addChoiceViewsGroupedBy(
$content,
$label,
$choices,
$values,
$index,
$attr,
$isPreferred,
$preferredViewsForGroup,
$otherViewsForGroup
);
if (count($preferredViewsForGroup) > 0) {
$preferredViews[$key] = new ChoiceGroupView($key, $preferredViewsForGroup);
}
if (count($otherViewsForGroup) > 0) {
$otherViews[$key] = new ChoiceGroupView($key, $otherViewsForGroup);
}
continue;
}
// Add ungrouped items directly
self::addChoiceView(
$choices[$key],
$key,
$label,
$values,
$index,
$attr,
$isPreferred,
$preferredViews,
$otherViews
);
}
}
private static function addChoiceViewGroupedBy($groupBy, $choice, $key, $label, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
{
$groupLabel = call_user_func($groupBy, $choice, $key, $values[$key]);
if (null === $groupLabel) {
// If the callable returns null, don't group the choice
self::addChoiceView(
$choice,
$key,
$label,
$values,
$index,
$attr,
$isPreferred,
$preferredViews,
$otherViews
);
return;
}
// Initialize the group views if necessary. Unnnecessarily built group
// views will be cleaned up at the end of createView()
if (!isset($preferredViews[$groupLabel])) {
$preferredViews[$groupLabel] = new ChoiceGroupView($groupLabel);
$otherViews[$groupLabel] = new ChoiceGroupView($groupLabel);
}
self::addChoiceView(
$choice,
$key,
$label,
$values,
$index,
$attr,
$isPreferred,
$preferredViews[$groupLabel]->choices,
$otherViews[$groupLabel]->choices
);
}
}

View File

@ -0,0 +1,230 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\Factory;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPath;
/**
* Adds property path support to a choice list factory.
*
* Pass the decorated factory to the constructor:
*
* ```php
* $decorator = new PropertyAccessDecorator($factory);
* ```
*
* You can now pass property paths for generating choice values, labels, view
* indices, HTML attributes and for determining the preferred choices and the
* choice groups:
*
* ```php
* // extract values from the $value property
* $list = $createListFromChoices($objects, 'value');
* ```
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class PropertyAccessDecorator implements ChoiceListFactoryInterface
{
/**
* @var ChoiceListFactoryInterface
*/
private $decoratedFactory;
/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* Decorates the given factory.
*
* @param ChoiceListFactoryInterface $decoratedFactory The decorated factory
* @param null|PropertyAccessorInterface $propertyAccessor The used property accessor
*/
public function __construct(ChoiceListFactoryInterface $decoratedFactory, PropertyAccessorInterface $propertyAccessor = null)
{
$this->decoratedFactory = $decoratedFactory;
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}
/**
* Returns the decorated factory.
*
* @return ChoiceListFactoryInterface The decorated factory
*/
public function getDecoratedFactory()
{
return $this->decoratedFactory;
}
/**
* {@inheritdoc}
*
* @param array|\Traversable $choices The choices
* @param null|callable|string|PropertyPath $value The callable or path for
* generating the choice values
*
* @return ChoiceListInterface The choice list
*/
public function createListFromChoices($choices, $value = null)
{
if (is_string($value)) {
$value = new PropertyPath($value);
}
if ($value instanceof PropertyPath) {
$accessor = $this->propertyAccessor;
$value = function ($choice) use ($accessor, $value) {
// The callable may be invoked with a non-object/array value
// when such values are passed to
// ChoiceListInterface::getValuesForChoices(). Handle this case
// so that the call to getValue() doesn't break.
if (is_object($choice) || is_array($choice)) {
return $accessor->getValue($choice, $value);
}
return;
};
}
return $this->decoratedFactory->createListFromChoices($choices, $value);
}
/**
* {@inheritdoc}
*
* @param array|\Traversable $choices The choices
* @param null|callable|string|PropertyPath $value The callable or path for
* generating the choice values
*
* @return ChoiceListInterface The choice list
*
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
* removed in Symfony 3.0.
*/
public function createListFromFlippedChoices($choices, $value = null)
{
// Property paths are not supported here, because array keys can never
// be objects
return $this->decoratedFactory->createListFromFlippedChoices($choices, $value);
}
/**
* {@inheritdoc}
*
* @param ChoiceLoaderInterface $loader The choice loader
* @param null|callable|string|PropertyPath $value The callable or path for
* generating the choice values
*
* @return ChoiceListInterface The choice list
*/
public function createListFromLoader(ChoiceLoaderInterface $loader, $value = null)
{
if (is_string($value)) {
$value = new PropertyPath($value);
}
if ($value instanceof PropertyPath) {
$accessor = $this->propertyAccessor;
$value = function ($choice) use ($accessor, $value) {
return $accessor->getValue($choice, $value);
};
}
return $this->decoratedFactory->createListFromLoader($loader, $value);
}
/**
* {@inheritdoc}
*
* @param ChoiceListInterface $list The choice list
* @param null|array|callable|string|PropertyPath $preferredChoices The preferred choices
* @param null|callable|string|PropertyPath $label The callable or path generating the choice labels
* @param null|callable|string|PropertyPath $index The callable or path generating the view indices
* @param null|array|\Traversable|callable|string|PropertyPath $groupBy The callable or path generating the group names
* @param null|array|callable|string|PropertyPath $attr The callable or path generating the HTML attributes
*
* @return ChoiceListView The choice list view
*/
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
{
$accessor = $this->propertyAccessor;
if (is_string($label)) {
$label = new PropertyPath($label);
}
if ($label instanceof PropertyPath) {
$label = function ($choice) use ($accessor, $label) {
return $accessor->getValue($choice, $label);
};
}
if (is_string($preferredChoices)) {
$preferredChoices = new PropertyPath($preferredChoices);
}
if ($preferredChoices instanceof PropertyPath) {
$preferredChoices = function ($choice) use ($accessor, $preferredChoices) {
try {
return $accessor->getValue($choice, $preferredChoices);
} catch (UnexpectedTypeException $e) {
// Assume not preferred if not readable
return false;
}
};
}
if (is_string($index)) {
$index = new PropertyPath($index);
}
if ($index instanceof PropertyPath) {
$index = function ($choice) use ($accessor, $index) {
return $accessor->getValue($choice, $index);
};
}
if (is_string($groupBy)) {
$groupBy = new PropertyPath($groupBy);
}
if ($groupBy instanceof PropertyPath) {
$groupBy = function ($choice) use ($accessor, $groupBy) {
try {
return $accessor->getValue($choice, $groupBy);
} catch (UnexpectedTypeException $e) {
// Don't group if path is not readable
}
};
}
if (is_string($attr)) {
$attr = new PropertyPath($attr);
}
if ($attr instanceof PropertyPath) {
$attr = function ($choice) use ($accessor, $attr) {
return $accessor->getValue($choice, $attr);
};
}
return $this->decoratedFactory->createView($list, $preferredChoices, $label, $index, $groupBy, $attr);
}
}

View File

@ -0,0 +1,123 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
/**
* A choice list that loads its choices lazily.
*
* The choices are fetched using a {@link ChoiceLoaderInterface} instance.
* If only {@link getChoicesForValues()} or {@link getValuesForChoices()} is
* called, the choice list is only loaded partially for improved performance.
*
* Once {@link getChoices()} or {@link getValues()} is called, the list is
* loaded fully.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LazyChoiceList implements ChoiceListInterface
{
/**
* The choice loader.
*
* @var ChoiceLoaderInterface
*/
private $loader;
/**
* The callable creating string values for each choice.
*
* If null, choices are simply cast to strings.
*
* @var null|callable
*/
private $value;
/**
* Whether to use the value callback to compare choices.
*
* @var bool
*/
private $compareByValue;
/**
* @var ChoiceListInterface|null
*/
private $loadedList;
/**
* Creates a lazily-loaded list using the given loader.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as first and the array key as the second
* argument.
*
* @param ChoiceLoaderInterface $loader The choice loader
* @param null|callable $value The callable generating the choice
* values
*/
public function __construct(ChoiceLoaderInterface $loader, $value = null, $compareByValue = false)
{
$this->loader = $loader;
$this->value = $value;
$this->compareByValue = $compareByValue;
}
/**
* {@inheritdoc}
*/
public function getChoices()
{
if (!$this->loadedList) {
$this->loadedList = $this->loader->loadChoiceList($this->value);
}
return $this->loadedList->getChoices();
}
/**
* {@inheritdoc}
*/
public function getValues()
{
if (!$this->loadedList) {
$this->loadedList = $this->loader->loadChoiceList($this->value);
}
return $this->loadedList->getValues();
}
/**
* {@inheritdoc}
*/
public function getChoicesForValues(array $values)
{
if (!$this->loadedList) {
return $this->loader->loadChoicesForValues($values, $this->value);
}
return $this->loadedList->getChoicesForValues($values);
}
/**
* {@inheritdoc}
*/
public function getValuesForChoices(array $choices)
{
if (!$this->loadedList) {
return $this->loader->loadValuesForChoices($choices, $this->value);
}
return $this->loadedList->getValuesForChoices($choices);
}
}

View File

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\Loader;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
/**
* Loads a choice list.
*
* The methods {@link loadChoicesForValues()} and {@link loadValuesForChoices()}
* can be used to load the list only partially in cases where a fully-loaded
* list is not necessary.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ChoiceLoaderInterface
{
/**
* Loads a list of choices.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as first and the array key as the second
* argument.
*
* @param null|callable $value The callable which generates the values
* from choices
*
* @return ChoiceListInterface The loaded choice list
*/
public function loadChoiceList($value = null);
/**
* Loads the choices corresponding to the given values.
*
* The choices are returned with the same keys and in the same order as the
* corresponding values in the given array.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as first and the array key as the second
* argument.
*
* @param string[] $values An array of choice values. Non-existing
* values in this array are ignored
* @param null|callable $value The callable generating the choice values
*
* @return array An array of choices
*/
public function loadChoicesForValues(array $values, $value = null);
/**
* Loads the values corresponding to the given choices.
*
* The values are returned with the same keys and in the same order as the
* corresponding choices in the given array.
*
* Optionally, a callable can be passed for generating the choice values.
* The callable receives the choice as first and the array key as the second
* argument.
*
* @param array $choices An array of choices. Non-existing choices in
* this array are ignored
* @param null|callable $value The callable generating the choice values
*
* @return string[] An array of choice values
*/
public function loadValuesForChoices(array $choices, $value = null);
}

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\View;
/**
* Represents a group of choices in templates.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ChoiceGroupView implements \IteratorAggregate
{
/**
* The label of the group
*
* @var string
*/
public $label;
/**
* The choice views in the group
*
* @var ChoiceGroupView[]|ChoiceView[]
*/
public $choices;
/**
* Creates a new choice group view.
*
* @param string $label The label of the group.
* @param ChoiceGroupView[]|ChoiceView[] $choices The choice views in the
* group.
*/
public function __construct($label, array $choices = array())
{
$this->label = $label;
$this->choices = $choices;
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
return new \ArrayIterator($this->choices);
}
}

View File

@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\View;
/**
* Represents a choice list in templates.
*
* A choice list contains choices and optionally preferred choices which are
* displayed in the very beginning of the list. Both choices and preferred
* choices may be grouped in {@link ChoiceGroupView} instances.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ChoiceListView
{
/**
* The choices.
*
* @var ChoiceGroupView[]|ChoiceView[]
*/
public $choices;
/**
* The preferred choices.
*
* @var ChoiceGroupView[]|ChoiceView[]
*/
public $preferredChoices;
/**
* Creates a new choice list view.
*
* @param ChoiceGroupView[]|ChoiceView[] $choices The choice views.
* @param ChoiceGroupView[]|ChoiceView[] $preferredChoices The preferred
* choice views.
*/
public function __construct(array $choices = array(), array $preferredChoices = array())
{
$this->choices = $choices;
$this->preferredChoices = $preferredChoices;
}
}

View File

@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList\View;
/**
* Represents a choice in templates.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ChoiceView
{
/**
* The label displayed to humans.
*
* @var string
*/
public $label;
/**
* The view representation of the choice.
*
* @var string
*/
public $value;
/**
* The original choice value.
*
* @var mixed
*/
public $data;
/**
* Additional attributes for the HTML tag.
*
* @var array
*/
public $attr;
/**
* Creates a new choice view.
*
* @param string $label The label displayed to humans
* @param string $value The view representation of the choice
* @param mixed $data The original choice
* @param array $attr Additional attributes for the HTML tag
*/
public function __construct($label, $value, $data, array $attr = array())
{
$this->label = $label;
$this->value = $value;
$this->data = $data;
$this->attr = $attr;
}
}

View File

@ -29,10 +29,13 @@ use Symfony\Component\Form\Extension\Core\View\ChoiceView;
* <code> * <code>
* $choices = array(true, false); * $choices = array(true, false);
* $labels = array('Agree', 'Disagree'); * $labels = array('Agree', 'Disagree');
* $choiceList = new ChoiceList($choices, $labels); * $choiceList = new ArrayChoiceList($choices, $labels);
* </code> * </code>
* *
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
* Use {@link \Symfony\Component\Form\ChoiceList\ArrayChoiceList} instead.
*/ */
class ChoiceList implements ChoiceListInterface class ChoiceList implements ChoiceListInterface
{ {
@ -89,6 +92,8 @@ class ChoiceList implements ChoiceListInterface
} }
$this->initialize($choices, $labels, $preferredChoices); $this->initialize($choices, $labels, $preferredChoices);
trigger_error('The '.__CLASS__.' class is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Form\ChoiceList\ArrayChoiceList instead.', E_USER_DEPRECATED);
} }
/** /**

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Form\Extension\Core\ChoiceList; namespace Symfony\Component\Form\Extension\Core\ChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface as BaseChoiceListInterface;
/** /**
* Contains choices that can be selected in a form field. * Contains choices that can be selected in a form field.
* *
@ -25,23 +27,12 @@ namespace Symfony\Component\Form\Extension\Core\ChoiceList;
* in the HTML "value" attribute. * in the HTML "value" attribute.
* *
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
* Use {@link BaseChoiceListInterface} instead.
*/ */
interface ChoiceListInterface interface ChoiceListInterface extends BaseChoiceListInterface
{ {
/**
* Returns the list of choices.
*
* @return array The choices with their indices as keys
*/
public function getChoices();
/**
* Returns the values for the choices.
*
* @return array The values with the corresponding choice indices as keys
*/
public function getValues();
/** /**
* Returns the choice views of the preferred choices as nested array with * Returns the choice views of the preferred choices as nested array with
* the choice groups as top-level keys. * the choice groups as top-level keys.
@ -92,37 +83,6 @@ interface ChoiceListInterface
*/ */
public function getRemainingViews(); public function getRemainingViews();
/**
* Returns the choices corresponding to the given values.
*
* The choices can have any data type.
*
* The choices must be returned with the same keys and in the same order
* as the corresponding values in the given array.
*
* @param array $values An array of choice values. Not existing values in
* this array are ignored
*
* @return array An array of choices with ascending, 0-based numeric keys
*/
public function getChoicesForValues(array $values);
/**
* Returns the values corresponding to the given choices.
*
* The values must be strings.
*
* The values must be returned with the same keys and in the same order
* as the corresponding choices in the given array.
*
* @param array $choices An array of choices. Not existing choices in this
* array are ignored
*
* @return array An array of choice values with ascending, 0-based numeric
* keys
*/
public function getValuesForChoices(array $choices);
/** /**
* Returns the indices corresponding to the given choices. * Returns the indices corresponding to the given choices.
* *

Some files were not shown because too many files have changed in this diff Show More