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:
commit
6a0b617c3d
|
@ -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" ]] && [ $(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 (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;
|
||||
- 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
|
||||
|
|
|
@ -11,8 +11,8 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Jordi Boggiano (seldaek)
|
||||
- Johannes S (johannes)
|
||||
- Kris Wallsmith (kriswallsmith)
|
||||
- Christophe Coevoet (stof)
|
||||
- Nicolas Grekas (nicolas-grekas)
|
||||
- Christophe Coevoet (stof)
|
||||
- Jakub Zalas (jakubzalas)
|
||||
- Pascal Borreli (pborreli)
|
||||
- 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)
|
||||
- Lukas Kahwe Smith (lsmith)
|
||||
- Romain Neutron (romain)
|
||||
- Christian Flothmann (xabbuh)
|
||||
- Jeremy Mikola (jmikola)
|
||||
- Jean-François Simon (jfsimon)
|
||||
- Christian Flothmann (xabbuh)
|
||||
- Benjamin Eberlei (beberlei)
|
||||
- Igor Wiedler (igorw)
|
||||
- Martin Hasoň (hason)
|
||||
|
@ -37,16 +37,16 @@ Symfony is the result of the work of many people who made the code better
|
|||
- stealth35 (stealth35)
|
||||
- Alexander Mols (asm89)
|
||||
- Bulat Shakirzyanov (avalanche123)
|
||||
- Abdellatif Ait boudad (aitboudad)
|
||||
- Francis Besset (francisbesset)
|
||||
- Saša Stamenković (umpirsky)
|
||||
- Henrik Bjørnskov (henrikbjorn)
|
||||
- Miha Vrhovnik
|
||||
- Kévin Dunglas (dunglas)
|
||||
- Sarah Khalil (saro0h)
|
||||
- Konstantin Kudryashov (everzet)
|
||||
- Bilal Amarni (bamarni)
|
||||
- Kévin Dunglas (dunglas)
|
||||
- Florin Patan (florinpatan)
|
||||
- Abdellatif Ait Boudad (aitboudad)
|
||||
- Sarah Khalil (saro0h)
|
||||
- Eric Clemmons (ericclemmons)
|
||||
- Andrej Hudec (pulzarraider)
|
||||
- Deni
|
||||
|
@ -61,13 +61,14 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Daniel Holmes (dholmes)
|
||||
- Bart van den Burg (burgov)
|
||||
- Jordan Alliot (jalliot)
|
||||
- Kevin Bond (kbond)
|
||||
- John Wards (johnwards)
|
||||
- Fran Moreno (franmomu)
|
||||
- Antoine Hérault (herzult)
|
||||
- Kevin Bond (kbond)
|
||||
- Toni Uebernickel (havvg)
|
||||
- Luis Cordova (cordoval)
|
||||
- Antoine Hérault (herzult)
|
||||
- Toni Uebernickel (havvg)
|
||||
- Arnaud Le Blanc (arnaud-lb)
|
||||
- Gábor Egyed (1ed)
|
||||
- Tim Nagel (merk)
|
||||
- Brice BERNARD (brikou)
|
||||
- marc.weistroff
|
||||
|
@ -78,7 +79,6 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Colin Frei
|
||||
- Jérôme Tamarelle (gromnan)
|
||||
- Adrien Brault (adrienbrault)
|
||||
- Gábor Egyed (1ed)
|
||||
- excelwebzone
|
||||
- Jacob Dreesen (jdreesen)
|
||||
- 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)
|
||||
- Hidenori Goto (hidenorigoto)
|
||||
- David Buchmann (dbu)
|
||||
- Guilherme Blanco (guilhermeblanco)
|
||||
- Jérémy DERUSSÉ (jderusse)
|
||||
- Pablo Godel (pgodel)
|
||||
- Eric GELOEN (gelo)
|
||||
- Jérémie Augustin (jaugustin)
|
||||
- Rafael Dohms (rdohms)
|
||||
- Guilherme Blanco (guilhermeblanco)
|
||||
- Tigran Azatyan (tigranazatyan)
|
||||
- Javier Eguiluz (javier.eguiluz)
|
||||
- Arnaud Kleinpeter (nanocom)
|
||||
- Richard Shank (iampersistent)
|
||||
- Dariusz Ruminski
|
||||
- Clemens Tolboom
|
||||
- Helmer Aaviksoo
|
||||
- Sebastiaan Stok (sstok)
|
||||
- Hiromi Hishida (77web)
|
||||
- Matthieu Ouellette-Vachon (maoueh)
|
||||
- Michał Pipa (michal.pipa)
|
||||
- Issei Murasawa (issei_m)
|
||||
- Amal Raghav (kertz)
|
||||
- Jonathan Ingram (jonathaningram)
|
||||
- Artur Kotyrba
|
||||
- Rouven Weßling (realityking)
|
||||
- Andréia Bohner (andreia)
|
||||
- Dmitrii Chekaliuk (lazyhammer)
|
||||
- Clément JOBEILI (dator)
|
||||
- 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)
|
||||
- Matthias Pigulla (mpdude)
|
||||
- Andreas Hucks (meandmymonkey)
|
||||
- Andréia Bohner (andreia)
|
||||
- Noel Guilbert (noel)
|
||||
- Joel Wurtz (brouznouf)
|
||||
- Charles Sarrazin (csarrazi)
|
||||
- bronze1man
|
||||
- sun (sun)
|
||||
- Larry Garfield (crell)
|
||||
- Issei Murasawa (issei_m)
|
||||
- Martin Schuhfuß (usefulthink)
|
||||
- Thomas Rabaix (rande)
|
||||
- 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)
|
||||
- Michele Orselli (orso)
|
||||
- Chris Wilkinson (thewilkybarkid)
|
||||
- Joshua Thijssen
|
||||
- Xavier Perez
|
||||
- Arjen Brouwer (arjenjb)
|
||||
- Katsuhiro OGAWA
|
||||
|
@ -181,16 +183,18 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Jeremy Livingston (jeremylivingston)
|
||||
- Nikita Konstantinov
|
||||
- Wodor Wodorski
|
||||
- Matthieu Auger (matthieuauger)
|
||||
- julien pauli (jpauli)
|
||||
- Beau Simensen (simensen)
|
||||
- Robert Kiss (kepten)
|
||||
- John Kary (johnkary)
|
||||
- Ruben Gonzalez (rubenrua)
|
||||
- Kim Hemsø Rasmussen (kimhemsoe)
|
||||
- Florian Lonqueu-Brochard (florianlb)
|
||||
- Tom Van Looy (tvlooy)
|
||||
- Wouter Van Hecke
|
||||
- Joshua Thijssen
|
||||
- Peter Kruithof (pkruithof)
|
||||
- Vladimir Reznichenko (kalessil)
|
||||
- Michael Holm (hollo)
|
||||
- Warnar Boekkooi (boekkooi)
|
||||
- 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)
|
||||
- Ricard Clau (ricardclau)
|
||||
- Erin Millard
|
||||
- John Kary (johnkary)
|
||||
- Matthew Lewinski (lewinski)
|
||||
- alquerci
|
||||
- Francesco Levorato
|
||||
|
@ -232,7 +235,6 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Inal DJAFAR (inalgnu)
|
||||
- Christian Gärtner (dagardner)
|
||||
- Felix Labrecque
|
||||
- Vladimir Reznichenko (kalessil)
|
||||
- Yaroslav Kiliba
|
||||
- Sébastien Lavoie (lavoiesl)
|
||||
- Terje Bråten
|
||||
|
@ -283,7 +285,6 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Brian King
|
||||
- Michel Salib (michelsalib)
|
||||
- geoffrey
|
||||
- Matthieu Auger (matthieuauger)
|
||||
- Lorenz Schori
|
||||
- Jeanmonod David (jeanmonod)
|
||||
- Jan Schumann
|
||||
|
@ -309,7 +310,6 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Christian Schmidt
|
||||
- Marcin Sikoń (marphi)
|
||||
- franek (franek)
|
||||
- Dariusz Ruminski
|
||||
- Adam Harvey
|
||||
- Diego Saint Esteben (dii3g0)
|
||||
- Alex Bakhturin
|
||||
|
@ -336,6 +336,7 @@ Symfony is the result of the work of many people who made the code better
|
|||
- mmoreram
|
||||
- Markus Lanthaler (lanthaler)
|
||||
- Vicent Soria Durá (vicentgodella)
|
||||
- Anthony Ferrara
|
||||
- Ioan Negulescu
|
||||
- Jakub Škvára (jskvara)
|
||||
- Daniel Beyer
|
||||
|
@ -343,9 +344,11 @@ Symfony is the result of the work of many people who made the code better
|
|||
- alexpods
|
||||
- Erik Trapman (eriktrapman)
|
||||
- De Cock Xavier (xdecock)
|
||||
- Scott Arciszewski
|
||||
- Norbert Orzechowicz (norzechowicz)
|
||||
- Tobias Nyholm (tobias)
|
||||
- Matthijs van den Bos (matthijs)
|
||||
- Loick Piera (pyrech)
|
||||
- Lenard Palko
|
||||
- Nils Adermann (naderman)
|
||||
- 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)
|
||||
- Aurélien Fredouelle
|
||||
- Pavel Campr (pcampr)
|
||||
- Maxime Steinhausser (ogizanagi)
|
||||
- Disquedur
|
||||
- Geoffrey Tran (geoff)
|
||||
- Jan Behrens
|
||||
- Mantas Var (mvar)
|
||||
- Sebastian Krebs
|
||||
- Christopher Davis (chrisguitarguy)
|
||||
- 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)
|
||||
- Stéphane Escandell (sescandell)
|
||||
- Sinan Eldem
|
||||
- Alexandre Dupuy (satchette)
|
||||
- Nahuel Cuesta (ncuesta)
|
||||
- Chris Boden (cboden)
|
||||
- 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)
|
||||
- Olivier Maisonneuve (olineuve)
|
||||
- Francis Turmel (fturmel)
|
||||
- Loick Piera (pyrech)
|
||||
- cgonzalez
|
||||
- Ben
|
||||
- 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)
|
||||
- Korvin Szanto
|
||||
- Maksim Kotlyar (makasim)
|
||||
- Ivan Kurnosov
|
||||
- Neil Ferreira
|
||||
- Dmitry Parnas (parnas)
|
||||
- DQNEO
|
||||
|
@ -516,6 +522,7 @@ Symfony is the result of the work of many people who made the code better
|
|||
- David Romaní
|
||||
- Patrick Allaert
|
||||
- Gustavo Falco (gfalco)
|
||||
- Matt Robinson (inanimatt)
|
||||
- Aleksey Podskrebyshev
|
||||
- David Marín Carreño (davefx)
|
||||
- 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
|
||||
- Sander Marechal
|
||||
- Radosław Benkel
|
||||
- Marcos Sánchez
|
||||
- ttomor
|
||||
- Mei Gwilym (meigwilym)
|
||||
- Michael H. Arieli (excelwebzone)
|
||||
|
@ -622,6 +630,7 @@ Symfony is the result of the work of many people who made the code better
|
|||
- nacho
|
||||
- Piotr Antosik (antek88)
|
||||
- Artem Lopata
|
||||
- Samuel ROZE (sroze)
|
||||
- Marcos Quesada (marcos_quesada)
|
||||
- Matthew Vickery (mattvick)
|
||||
- Dan Finnie
|
||||
|
@ -675,6 +684,7 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Yannick
|
||||
- Eduardo García Sanz (coma)
|
||||
- Sebastian Grodzicki (sgrodzicki)
|
||||
- Michael Lee (zerustech)
|
||||
- Roy Van Ginneken
|
||||
- David de Boer (ddeboer)
|
||||
- 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
|
||||
- Christian Flach (cmfcmf)
|
||||
- Cédric Girard (enk_)
|
||||
- Lars Ambrosius Wallenborn (larsborn)
|
||||
- Oriol Mangas Abellan (oriolman)
|
||||
- Sebastian Göttschkes (sgoettschkes)
|
||||
- Tatsuya Tsuruoka
|
||||
|
@ -847,6 +858,7 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Gunnar Lium (gunnarlium)
|
||||
- Tiago Garcia (tiagojsag)
|
||||
- Artiom
|
||||
- Jakub Simon
|
||||
- Bouke Haarsma
|
||||
- Martin Eckhardt
|
||||
- Denis Zunke
|
||||
|
@ -876,7 +888,6 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Vasily Khayrulin (sirian)
|
||||
- Stefan Koopmanschap (skoop)
|
||||
- Stefan Hüsges (tronsha)
|
||||
- Ivan Kurnosov
|
||||
- stloyd
|
||||
- Chris Tickner
|
||||
- Andrew Coulton
|
||||
|
@ -905,7 +916,6 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Julius Beckmann
|
||||
- Romain Dorgueil
|
||||
- Grayson Koonce (breerly)
|
||||
- Matt Robinson (inanimatt)
|
||||
- Karim Cassam Chenaï (ka)
|
||||
- Nicolas Bastien (nicolas_bastien)
|
||||
- Andy Stanberry
|
||||
|
@ -992,6 +1002,7 @@ Symfony is the result of the work of many people who made the code better
|
|||
- grifx
|
||||
- Robert Campbell
|
||||
- Matt Lehner
|
||||
- Hidde Wieringa
|
||||
- Hein Zaw Htet™
|
||||
- Ruben Kruiswijk
|
||||
- Michael J
|
||||
|
@ -1048,9 +1059,7 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Muriel (metalmumu)
|
||||
- Michaël Perrin (michael.perrin)
|
||||
- Michael Pohlers (mick_the_big)
|
||||
- Mantas Var (mvar)
|
||||
- Cayetano Soriano Gallego (neoshadybeat)
|
||||
- Maxime Steinhausser (ogizanagi)
|
||||
- Pablo Monterde Perez (plebs)
|
||||
- Jimmy Leger (redpanda)
|
||||
- Cyrille Jouineau (tuxosaurus)
|
||||
|
@ -1128,6 +1137,7 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Brian Freytag
|
||||
- Skorney
|
||||
- mieszko4
|
||||
- Neophy7e
|
||||
- Arrilot
|
||||
- Markus Staab
|
||||
- Pierre-Louis LAUNAY
|
||||
|
@ -1140,6 +1150,8 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Sema
|
||||
- Thorsten Hallwas
|
||||
- Michael Squires
|
||||
- Norman Soetbeer
|
||||
- Benjamin Long
|
||||
- Matt Janssen
|
||||
- Peter Gribanov
|
||||
- kwiateusz
|
||||
|
@ -1235,6 +1247,7 @@ Symfony is the result of the work of many people who made the code better
|
|||
- Vincent (vincent1870)
|
||||
- Eugene Babushkin (warl)
|
||||
- Xavier Amado (xamado)
|
||||
- Jesper Søndergaard Pedersen (zerrvox)
|
||||
- Florent Cailhol
|
||||
- szymek
|
||||
- craigmarvelley
|
||||
|
|
449
UPGRADE-2.7.md
449
UPGRADE-2.7.md
|
@ -15,7 +15,7 @@ Router
|
|||
`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
|
||||
doesn't exist or unexpected result otherwise.
|
||||
|
||||
|
||||
Form
|
||||
----
|
||||
|
||||
|
@ -23,41 +23,446 @@ Form
|
|||
AbstractType or AbstractExtensionType has been deprecated in favor of
|
||||
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)".
|
||||
|
||||
Before:
|
||||
|
||||
```php
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
|
||||
class TaskType extends AbstractType
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
|
||||
class TaskType extends AbstractType
|
||||
{
|
||||
// ...
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver)
|
||||
{
|
||||
// ...
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver)
|
||||
{
|
||||
$resolver->setDefaults(array(
|
||||
'data_class' => 'AppBundle\Entity\Task',
|
||||
));
|
||||
}
|
||||
$resolver->setDefaults(array(
|
||||
'data_class' => 'AppBundle\Entity\Task',
|
||||
));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```php
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TaskType extends AbstractType
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TaskType extends AbstractType
|
||||
{
|
||||
// ...
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
// ...
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults(array(
|
||||
'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
|
||||
|
|
|
@ -110,6 +110,24 @@ UPGRADE FROM 2.x to 3.0
|
|||
|
||||
### 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
|
||||
`AbstractTypeExtension::setDefaultOptions(OptionsResolverInterface $resolver)` have been
|
||||
renamed. You should use `AbstractType::configureOptions(OptionsResolver $resolver)` and
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
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
|
||||
-----
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -11,17 +11,20 @@
|
|||
|
||||
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\StringCastException;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* A choice list presenting a list of Doctrine entities as choices.
|
||||
*
|
||||
* @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
|
||||
{
|
||||
|
@ -126,6 +129,8 @@ class EntityChoiceList extends ObjectChoiceList
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -17,7 +17,10 @@ use Doctrine\DBAL\Connection;
|
|||
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
|
||||
{
|
||||
|
@ -34,9 +37,14 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface
|
|||
/**
|
||||
* Construct an ORM Query Builder Loader.
|
||||
*
|
||||
* @param QueryBuilder|\Closure $queryBuilder
|
||||
* @param EntityManager $manager
|
||||
* @param string $class
|
||||
* @param QueryBuilder|\Closure $queryBuilder The query builder or a closure
|
||||
* for creating the query builder.
|
||||
* 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
|
||||
*/
|
||||
|
@ -49,10 +57,15 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface
|
|||
}
|
||||
|
||||
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) {
|
||||
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));
|
||||
|
||||
if (!$queryBuilder instanceof QueryBuilder) {
|
||||
|
|
|
@ -14,21 +14,38 @@ namespace Symfony\Bridge\Doctrine\Form;
|
|||
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
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\PropertyAccessorInterface;
|
||||
|
||||
class DoctrineOrmExtension extends AbstractExtension
|
||||
{
|
||||
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->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
|
||||
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor));
|
||||
}
|
||||
|
||||
protected function loadTypes()
|
||||
{
|
||||
return array(
|
||||
new EntityType($this->registry, PropertyAccess::createPropertyAccessor()),
|
||||
new EntityType($this->registry, $this->propertyAccessor, $this->choiceListFactory),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,17 +12,23 @@
|
|||
namespace Symfony\Bridge\Doctrine\Form\Type;
|
||||
|
||||
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\Form\Exception\RuntimeException;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader;
|
||||
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\EventListener\MergeDoctrineCollectionListener;
|
||||
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\OptionsResolver;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
abstract class DoctrineType extends AbstractType
|
||||
|
@ -33,19 +39,63 @@ abstract class DoctrineType extends AbstractType
|
|||
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->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
|
||||
$this->choiceListFactory = $choiceListFactory ?: new PropertyAccessDecorator(new DefaultChoiceListFactory(), $propertyAccessor);
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
|
@ -60,85 +110,96 @@ abstract class DoctrineType extends AbstractType
|
|||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$choiceListCache = &$this->choiceListCache;
|
||||
$registry = $this->registry;
|
||||
$propertyAccessor = $this->propertyAccessor;
|
||||
$choiceListFactory = $this->choiceListFactory;
|
||||
$idReaders = &$this->idReaders;
|
||||
$choiceLoaders = &$this->choiceLoaders;
|
||||
|
||||
$loader = function (Options $options) {
|
||||
$queryBuilder = (null !== $options['query_builder'])
|
||||
? $options['query_builder']
|
||||
: $options['em']->getRepository($options['class'])->createQueryBuilder('e');
|
||||
$choiceLoader = function (Options $options) use ($choiceListFactory, &$choiceLoaders) {
|
||||
// This closure and the "query_builder" options should be pushed to
|
||||
// EntityType in Symfony 3.0 as they are specific to the ORM
|
||||
|
||||
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) {
|
||||
// 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(
|
||||
$hash = CachingFactoryDecorator::generateHash(array(
|
||||
$options['em'],
|
||||
$options['class'],
|
||||
$options['property'],
|
||||
$qbParts,
|
||||
$options['loader'],
|
||||
$options['choices'],
|
||||
$options['preferred_choices'],
|
||||
$options['group_by'],
|
||||
$propertyAccessor
|
||||
));
|
||||
|
||||
if (isset($choiceLoaders[$hash])) {
|
||||
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) {
|
||||
|
@ -164,22 +225,86 @@ abstract class DoctrineType extends AbstractType
|
|||
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(
|
||||
'em' => null,
|
||||
'property' => null,
|
||||
'property' => null, // deprecated, use "choice_label"
|
||||
'query_builder' => null,
|
||||
'loader' => $loader,
|
||||
'loader' => null, // deprecated, use "choice_loader"
|
||||
'choices' => null,
|
||||
'choice_list' => $choiceList,
|
||||
'group_by' => null,
|
||||
'choices_as_values' => true,
|
||||
'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->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('loader', array('null', 'Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface'));
|
||||
$resolver->setAllowedTypes('query_builder', array('null', 'callable', 'Doctrine\ORM\QueryBuilder'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,71 +17,18 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
|
|||
|
||||
class EntityType extends DoctrineType
|
||||
{
|
||||
/**
|
||||
* @var ORMQueryBuilderLoader[]
|
||||
*/
|
||||
private $loaderCache = array();
|
||||
|
||||
/**
|
||||
* Return the default loader object.
|
||||
*
|
||||
* @param ObjectManager $manager
|
||||
* @param mixed $queryBuilder
|
||||
* @param QueryBuilder $queryBuilder
|
||||
* @param string $class
|
||||
*
|
||||
* @return ORMQueryBuilderLoader
|
||||
*/
|
||||
public function getLoader(ObjectManager $manager, $queryBuilder, $class)
|
||||
{
|
||||
if (!$queryBuilder instanceof QueryBuilder) {
|
||||
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,
|
||||
)));
|
||||
return new ORMQueryBuilderLoader($queryBuilder, $manager, $class);
|
||||
}
|
||||
|
||||
public function getName()
|
||||
|
|
|
@ -16,6 +16,9 @@ use Symfony\Component\DependencyInjection\Container;
|
|||
|
||||
class ContainerAwareEventManagerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $container;
|
||||
private $evm;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->container = new Container();
|
||||
|
|
|
@ -19,6 +19,9 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
|
|||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
const SINGLE_INT_ID_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity';
|
||||
|
@ -36,6 +39,8 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
|
||||
|
||||
$this->em = DoctrineTestHelper::createTestEntityManager();
|
||||
|
||||
$schemaTool = new SchemaTool($this->em);
|
||||
|
@ -70,7 +75,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
|
|||
* @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).
|
||||
*/
|
||||
public function testEntitiesMustHaveAToStringMethod()
|
||||
public function testLegacyEntitiesMustHaveAToStringMethod()
|
||||
{
|
||||
$entity1 = new SingleIntIdNoToStringEntity(1, 'Foo');
|
||||
$entity2 = new SingleIntIdNoToStringEntity(2, 'Bar');
|
||||
|
@ -96,7 +101,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
|
|||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\RuntimeException
|
||||
*/
|
||||
public function testChoicesMustBeManaged()
|
||||
public function testLegacyChoicesMustBeManaged()
|
||||
{
|
||||
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
||||
$entity2 = new SingleIntIdEntity(2, 'Bar');
|
||||
|
@ -118,7 +123,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
|
|||
$choiceList->getChoices();
|
||||
}
|
||||
|
||||
public function testInitExplicitChoices()
|
||||
public function testLegacyInitExplicitChoices()
|
||||
{
|
||||
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
||||
$entity2 = new SingleIntIdEntity(2, 'Bar');
|
||||
|
@ -141,7 +146,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
|
|||
$this->assertSame(array(1 => $entity1, 2 => $entity2), $choiceList->getChoices());
|
||||
}
|
||||
|
||||
public function testInitEmptyChoices()
|
||||
public function testLegacyInitEmptyChoices()
|
||||
{
|
||||
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
||||
$entity2 = new SingleIntIdEntity(2, 'Bar');
|
||||
|
@ -161,7 +166,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
|
|||
$this->assertSame(array(), $choiceList->getChoices());
|
||||
}
|
||||
|
||||
public function testInitNestedChoices()
|
||||
public function testLegacyInitNestedChoices()
|
||||
{
|
||||
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
||||
$entity2 = new SingleIntIdEntity(2, 'Bar');
|
||||
|
@ -189,7 +194,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
|
|||
), $choiceList->getRemainingViews());
|
||||
}
|
||||
|
||||
public function testGroupByPropertyPath()
|
||||
public function testLegacyGroupByPropertyPath()
|
||||
{
|
||||
$item1 = new GroupableEntity(1, 'Foo', 'Group1');
|
||||
$item2 = new GroupableEntity(2, 'Bar', 'Group1');
|
||||
|
@ -224,7 +229,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
|
|||
), $choiceList->getRemainingViews());
|
||||
}
|
||||
|
||||
public function testGroupByInvalidPropertyPathReturnsFlatChoices()
|
||||
public function testLegacyGroupByInvalidPropertyPathReturnsFlatChoices()
|
||||
{
|
||||
$item1 = new GroupableEntity(1, 'Foo', 'Group1');
|
||||
$item2 = new GroupableEntity(2, 'Bar', 'Group1');
|
||||
|
@ -251,7 +256,7 @@ class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase
|
|||
), $choiceList->getChoices());
|
||||
}
|
||||
|
||||
public function testInitShorthandEntityName()
|
||||
public function testLegacyInitShorthandEntityName()
|
||||
{
|
||||
$item1 = new SingleIntIdEntity(1, 'Foo');
|
||||
$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)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
public function testLegacyInitShorthandEntityName()
|
||||
public function testLegacyInitShorthandEntityName2()
|
||||
{
|
||||
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
|
||||
|
||||
$item1 = new SingleIntIdEntity(1, 'Foo');
|
||||
$item2 = new SingleIntIdEntity(2, 'Bar');
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
|
|||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @group legacy
|
||||
*/
|
||||
class LoadedEntityChoiceListCompositeIdTest extends AbstractEntityChoiceListCompositeIdTest
|
||||
{
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
|
|||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @group legacy
|
||||
*/
|
||||
class LoadedEntityChoiceListSingleIntIdTest extends AbstractEntityChoiceListSingleIntIdTest
|
||||
{
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
|
|||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @group legacy
|
||||
*/
|
||||
class LoadedEntityChoiceListSingleStringIdTest extends AbstractEntityChoiceListSingleStringIdTest
|
||||
{
|
||||
|
|
|
@ -13,10 +13,11 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
|
|||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @group legacy
|
||||
*/
|
||||
class UnloadedEntityChoiceListCompositeIdTest extends AbstractEntityChoiceListCompositeIdTest
|
||||
{
|
||||
public function testGetIndicesForValuesIgnoresNonExistingValues()
|
||||
public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
|
||||
{
|
||||
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
|
|||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @group legacy
|
||||
*/
|
||||
class UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest extends UnloadedEntityChoiceListCompositeIdTest
|
||||
{
|
||||
|
|
|
@ -13,17 +13,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
|
|||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @group legacy
|
||||
*/
|
||||
class UnloadedEntityChoiceListSingleIntIdTest extends AbstractEntityChoiceListSingleIntIdTest
|
||||
{
|
||||
public function testGetIndicesForValuesIgnoresNonExistingValues()
|
||||
{
|
||||
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
|
||||
{
|
||||
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
|
||||
|
|
|
@ -16,6 +16,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
|
|||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @group legacy
|
||||
*/
|
||||
class UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest extends UnloadedEntityChoiceListSingleIntIdTest
|
||||
{
|
||||
|
|
|
@ -13,10 +13,11 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList;
|
|||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @group legacy
|
||||
*/
|
||||
class UnloadedEntityChoiceListSingleStringIdTest extends AbstractEntityChoiceListSingleStringIdTest
|
||||
{
|
||||
public function testGetIndicesForValuesIgnoresNonExistingValues()
|
||||
public function testLegacyGetIndicesForValuesIgnoresNonExistingValues()
|
||||
{
|
||||
$this->markTestSkipped('Non-existing values are not detected for unloaded choice lists.');
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
|
|||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @group legacy
|
||||
*/
|
||||
class UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest extends UnloadedEntityChoiceListSingleStringIdTest
|
||||
{
|
||||
|
|
|
@ -11,21 +11,24 @@
|
|||
|
||||
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\Type\EntityType;
|
||||
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
use Symfony\Component\Form\Forms;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity;
|
||||
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Form\Forms;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
|
||||
class EntityTypeTest extends TypeTestCase
|
||||
|
@ -37,12 +40,12 @@ class EntityTypeTest extends TypeTestCase
|
|||
const COMPOSITE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity';
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject|ManagerRegistry
|
||||
*/
|
||||
private $emRegistry;
|
||||
|
||||
|
@ -128,10 +131,10 @@ class EntityTypeTest extends TypeTestCase
|
|||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'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()
|
||||
|
@ -147,7 +150,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'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()
|
||||
|
@ -162,15 +165,15 @@ class EntityTypeTest extends TypeTestCase
|
|||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'required' => false,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
'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()
|
||||
{
|
||||
|
@ -249,7 +252,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
$field->submit(null);
|
||||
|
||||
$this->assertNull($field->getData());
|
||||
$this->assertSame(array(), $field->getViewData());
|
||||
$this->assertNull($field->getViewData());
|
||||
}
|
||||
|
||||
public function testSubmitSingleNonExpandedNull()
|
||||
|
@ -291,7 +294,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'expanded' => false,
|
||||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$field->submit('2');
|
||||
|
@ -313,7 +316,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'expanded' => false,
|
||||
'em' => 'default',
|
||||
'class' => self::COMPOSITE_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
// the collection key is used here
|
||||
|
@ -337,7 +340,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'expanded' => false,
|
||||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$field->submit(array('1', '3'));
|
||||
|
@ -362,7 +365,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'expanded' => false,
|
||||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$existing = new ArrayCollection(array(0 => $entity2));
|
||||
|
@ -393,7 +396,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'expanded' => false,
|
||||
'em' => 'default',
|
||||
'class' => self::COMPOSITE_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
// because of the composite key collection keys are used
|
||||
|
@ -419,7 +422,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'expanded' => false,
|
||||
'em' => 'default',
|
||||
'class' => self::COMPOSITE_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$existing = new ArrayCollection(array(0 => $entity2));
|
||||
|
@ -449,7 +452,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'expanded' => true,
|
||||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$field->submit('2');
|
||||
|
@ -475,7 +478,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'expanded' => true,
|
||||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$field->submit(array('1', '3'));
|
||||
|
@ -505,12 +508,12 @@ class EntityTypeTest extends TypeTestCase
|
|||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
// not all persisted entities should be displayed
|
||||
'choices' => array($entity1, $entity2),
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$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->assertSame($entity2, $field->getData());
|
||||
$this->assertSame('2', $field->getViewData());
|
||||
|
@ -529,7 +532,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'em' => 'default',
|
||||
'class' => self::ITEM_GROUP_CLASS,
|
||||
'choices' => array($item1, $item2, $item3, $item4),
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
'group_by' => 'groupName',
|
||||
));
|
||||
|
||||
|
@ -537,9 +540,14 @@ class EntityTypeTest extends TypeTestCase
|
|||
|
||||
$this->assertSame('2', $field->getViewData());
|
||||
$this->assertEquals(array(
|
||||
'Group1' => array(1 => new ChoiceView($item1, '1', 'Foo'), 2 => new ChoiceView($item2, '2', 'Bar')),
|
||||
'Group2' => array(3 => new ChoiceView($item3, '3', 'Baz')),
|
||||
'4' => new ChoiceView($item4, '4', 'Boo!'),
|
||||
'Group1' => new ChoiceGroupView('Group1', array(
|
||||
1 => new ChoiceView('Foo', '1', $item1),
|
||||
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']);
|
||||
}
|
||||
|
||||
|
@ -555,11 +563,11 @@ class EntityTypeTest extends TypeTestCase
|
|||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'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(1 => new ChoiceView($entity1, '1', 'Foo')), $field->createView()->vars['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('Foo', '1', $entity1)), $field->createView()->vars['choices']);
|
||||
}
|
||||
|
||||
public function testOverrideChoicesWithPreferredChoices()
|
||||
|
@ -575,11 +583,11 @@ class EntityTypeTest extends TypeTestCase
|
|||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'choices' => array($entity2, $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(2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']);
|
||||
$this->assertEquals(array(3 => new ChoiceView('Baz', '3', $entity3)), $field->createView()->vars['preferred_choices']);
|
||||
$this->assertEquals(array(2 => new ChoiceView('Bar', '2', $entity2)), $field->createView()->vars['choices']);
|
||||
}
|
||||
|
||||
public function testDisallowChoicesThatAreNotIncludedChoicesSingleIdentifier()
|
||||
|
@ -594,7 +602,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'choices' => array($entity1, $entity2),
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$field->submit('3');
|
||||
|
@ -615,7 +623,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'em' => 'default',
|
||||
'class' => self::COMPOSITE_IDENT_CLASS,
|
||||
'choices' => array($entity1, $entity2),
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$field->submit('2');
|
||||
|
@ -639,7 +647,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'query_builder' => $repository->createQueryBuilder('e')
|
||||
->where('e.id IN (1, 2)'),
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$field->submit('3');
|
||||
|
@ -663,7 +671,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
return $repository->createQueryBuilder('e')
|
||||
->where('e.id IN (1, 2)');
|
||||
},
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$field->submit('3');
|
||||
|
@ -687,7 +695,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
return $repository->createQueryBuilder('e')
|
||||
->where('e.id1 IN (10, 50)');
|
||||
},
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$field->submit('2');
|
||||
|
@ -707,7 +715,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'expanded' => false,
|
||||
'em' => 'default',
|
||||
'class' => self::SINGLE_STRING_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
$field->submit('foo');
|
||||
|
@ -728,7 +736,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
'expanded' => false,
|
||||
'em' => 'default',
|
||||
'class' => self::COMPOSITE_STRING_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
|
||||
// the collection key is used here
|
||||
|
@ -752,7 +760,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
$this->factory->createNamed('name', 'entity', null, array(
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'required' => false,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -767,7 +775,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
$this->factory->createNamed('name', 'entity', null, array(
|
||||
'em' => $this->em,
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'property' => 'name',
|
||||
'choice_label' => 'name',
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -779,8 +787,7 @@ class EntityTypeTest extends TypeTestCase
|
|||
|
||||
$this->persist(array($entity1, $entity2, $entity3));
|
||||
|
||||
$repository = $this->em->getRepository(self::SINGLE_IDENT_CLASS);
|
||||
$qb = $repository->createQueryBuilder('e')->where('e.id IN (1, 2)');
|
||||
$repo = $this->em->getRepository(self::SINGLE_IDENT_CLASS);
|
||||
|
||||
$entityType = new EntityType(
|
||||
$this->emRegistry,
|
||||
|
@ -799,19 +806,23 @@ class EntityTypeTest extends TypeTestCase
|
|||
$formBuilder->add('property1', 'entity', array(
|
||||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'query_builder' => $qb,
|
||||
'query_builder' => $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'),
|
||||
));
|
||||
|
||||
$formBuilder->add('property2', 'entity', array(
|
||||
'em' => 'default',
|
||||
'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(
|
||||
'em' => 'default',
|
||||
'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();
|
||||
|
@ -822,15 +833,59 @@ class EntityTypeTest extends TypeTestCase
|
|||
'property3' => 2,
|
||||
));
|
||||
|
||||
$reflectionClass = new \ReflectionObject($entityType);
|
||||
$reflectionProperty = $reflectionClass->getProperty('loaderCache');
|
||||
$reflectionProperty->setAccessible(true);
|
||||
$choiceList1 = $form->get('property1')->getConfig()->getOption('choice_list');
|
||||
$choiceList2 = $form->get('property2')->getConfig()->getOption('choice_list');
|
||||
$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)
|
||||
|
|
|
@ -31,13 +31,13 @@ class LintCommand extends Command
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($name = 'twig:lint')
|
||||
public function __construct($name = 'lint:twig')
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the twig environment
|
||||
* Sets the twig environment.
|
||||
*
|
||||
* @param \Twig_Environment $twig
|
||||
*/
|
||||
|
@ -57,6 +57,7 @@ class LintCommand extends Command
|
|||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setAliases(array('twig:lint'))
|
||||
->setDescription('Lints a template and outputs encountered errors')
|
||||
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
|
||||
->addArgument('filename', InputArgument::IS_ARRAY)
|
||||
|
@ -83,6 +84,10 @@ EOF
|
|||
|
||||
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();
|
||||
|
||||
if (null === $twig) {
|
||||
|
@ -95,7 +100,7 @@ EOF
|
|||
|
||||
if (0 === count($filenames)) {
|
||||
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 = '';
|
||||
|
@ -206,14 +211,14 @@ EOF
|
|||
$line = $exception->getTemplateLine();
|
||||
|
||||
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 {
|
||||
$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) {
|
||||
$output->writeln(sprintf(
|
||||
"%s %-6s %s",
|
||||
'%s %-6s %s',
|
||||
$no == $line ? '<error>>></error>' : ' ',
|
||||
$no,
|
||||
$code
|
||||
|
|
|
@ -206,6 +206,9 @@ class CodeExtension extends \Twig_Extension
|
|||
}, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'code';
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Symfony\Bridge\Twig\Extension;
|
|||
|
||||
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
|
||||
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.
|
||||
|
|
|
@ -81,6 +81,9 @@ class HttpKernelExtension extends \Twig_Extension
|
|||
return new ControllerReference($controller, $attributes, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'http_kernel';
|
||||
|
|
|
@ -89,9 +89,7 @@ class RoutingExtension extends \Twig_Extension
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the extension.
|
||||
*
|
||||
* @return string The extension name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -52,9 +52,7 @@ class SecurityExtension extends \Twig_Extension
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the extension.
|
||||
*
|
||||
* @return string The extension name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -99,9 +99,7 @@ class TranslationExtension extends \Twig_Extension
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the extension.
|
||||
*
|
||||
* @return string The extension name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -56,9 +56,7 @@ class YamlExtension extends \Twig_Extension
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the extension.
|
||||
*
|
||||
* @return string The extension name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -74,12 +74,13 @@
|
|||
{%- block choice_widget_options -%}
|
||||
{% for group_label, choice in options %}
|
||||
{%- 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 %}
|
||||
{{- block('choice_widget_options') -}}
|
||||
</optgroup>
|
||||
{%- 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 -%}
|
||||
{% endfor %}
|
||||
{%- endblock choice_widget_options -%}
|
||||
|
@ -351,3 +352,16 @@
|
|||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- 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 -%}
|
||||
|
|
|
@ -80,7 +80,7 @@ class LintCommandTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
$application = new Application();
|
||||
$application->add($command);
|
||||
$command = $application->find('twig:lint');
|
||||
$command = $application->find('lint:twig');
|
||||
|
||||
return new CommandTester($command);
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ use Symfony\Bridge\Twig\Form\TwigRendererEngine;
|
|||
use Symfony\Bridge\Twig\Extension\TranslationExtension;
|
||||
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator;
|
||||
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
use Symfony\Component\Form\Tests\AbstractDivLayoutTest;
|
||||
|
||||
class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
|
||||
|
@ -125,7 +125,7 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
|
|||
*/
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ CHANGELOG
|
|||
-----
|
||||
|
||||
* 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
|
||||
-----
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
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('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;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ class YamlLintCommand extends Command
|
|||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('yaml:lint')
|
||||
->setName('lint:yaml')
|
||||
->setAliases(array('yaml:lint'))
|
||||
->setDescription('Lints a file and outputs encountered errors')
|
||||
->addArgument('filename', null, 'A file or a directory or STDIN')
|
||||
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
|
||||
|
@ -61,6 +62,10 @@ EOF
|
|||
|
||||
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');
|
||||
|
||||
if (!$filename) {
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
@ -329,10 +329,22 @@ abstract class Controller extends ContainerAware
|
|||
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);
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
*
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
|
|||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||
|
@ -38,6 +39,7 @@ class LoggingTranslatorPass implements CompilerPassInterface
|
|||
$refClass = new \ReflectionClass($class);
|
||||
if ($refClass->implementsInterface('Symfony\Component\Translation\TranslatorInterface') && $refClass->implementsInterface('Symfony\Component\Translation\TranslatorBagInterface')) {
|
||||
$container->getDefinition('translator.logging')->setDecoratedService('translator');
|
||||
$container->getDefinition('translation.warmer')->replaceArgument(0, new Reference('translator.logging.inner'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,10 +89,7 @@ class Configuration implements ConfigurationInterface
|
|||
->booleanNode('test')->end()
|
||||
->scalarNode('default_locale')->defaultValue('en')->end()
|
||||
->arrayNode('trusted_hosts')
|
||||
->beforeNormalization()
|
||||
->ifTrue(function ($v) { return is_string($v); })
|
||||
->then(function ($v) { return array($v); })
|
||||
->end()
|
||||
->beforeNormalization()->ifString()->then(function ($v) { return array($v); })->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
|
@ -338,7 +335,7 @@ class Configuration implements ConfigurationInterface
|
|||
->addDefaultChildrenIfNoneSet()
|
||||
->prototype('scalar')->defaultValue('FrameworkBundle:Form')->end()
|
||||
->validate()
|
||||
->ifTrue(function ($v) {return !in_array('FrameworkBundle:Form', $v); })
|
||||
->ifNotInArray(array('FrameworkBundle:Form'))
|
||||
->then(function ($v) {
|
||||
return array_merge(array('FrameworkBundle:Form'), $v);
|
||||
})
|
||||
|
|
|
@ -705,12 +705,22 @@ class FrameworkExtension extends Extension
|
|||
->in($dirs)
|
||||
;
|
||||
|
||||
$locales = array();
|
||||
foreach ($finder as $file) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<tag name="monolog.logger" channel="php" />
|
||||
<argument /><!-- Exception handler -->
|
||||
<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>true</argument>
|
||||
<argument>null</argument><!-- %templating.helper.code.file_link_format% -->
|
||||
|
|
|
@ -121,5 +121,10 @@
|
|||
<service id="translation.extractor" class="Symfony\Component\Translation\Extractor\ChainExtractor"/>
|
||||
|
||||
<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>
|
||||
</container>
|
||||
|
|
|
@ -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 foreach ($choices as $index => $choice): ?>
|
||||
<?php if (is_array($choice)): ?>
|
||||
<optgroup label="<?php echo $view->escape($translatorHelper->trans($index, array(), $translation_domain)) ?>">
|
||||
<?php foreach ($choices as $group_label => $choice): ?>
|
||||
<?php if (is_array($choice) || $choice instanceof ChoiceGroupView): ?>
|
||||
<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)) ?>
|
||||
</optgroup>
|
||||
<?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 endforeach ?>
|
||||
|
|
|
@ -58,9 +58,7 @@ class ActionsHelper extends Helper
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical name of this helper.
|
||||
*
|
||||
* @return string The canonical name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -199,9 +199,7 @@ class CodeHelper extends Helper
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical name of this helper.
|
||||
*
|
||||
* @return string The canonical name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -83,9 +83,7 @@ class RequestHelper extends Helper
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical name of this helper.
|
||||
*
|
||||
* @return string The canonical name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -50,9 +50,7 @@ class RouterHelper extends Helper
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical name of this helper.
|
||||
*
|
||||
* @return string The canonical name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -86,9 +86,7 @@ class SessionHelper extends Helper
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical name of this helper.
|
||||
*
|
||||
* @return string The canonical name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -50,9 +50,7 @@ class TranslatorHelper extends Helper
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical name of this helper.
|
||||
*
|
||||
* @return string The canonical name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@ class LoggingTranslatorPassTest extends \PHPUnit_Framework_TestCase
|
|||
->method('getAlias')
|
||||
->will($this->returnValue('translation.default'));
|
||||
|
||||
$container->expects($this->exactly(2))
|
||||
$container->expects($this->exactly(3))
|
||||
->method('getDefinition')
|
||||
->will($this->returnValue($definition));
|
||||
|
||||
|
|
|
@ -234,9 +234,9 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||
$container = $this->createContainerFromFile('full');
|
||||
$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');
|
||||
$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');
|
||||
$this->assertContains(
|
||||
strtr(dirname($ref->getFileName()).'/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR),
|
||||
|
|
|
@ -95,7 +95,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
|||
public function testTransWithCachingWithInvalidLocale()
|
||||
{
|
||||
$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');
|
||||
|
||||
$this->setExpectedException('\InvalidArgumentException');
|
||||
|
@ -106,23 +106,25 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
|
||||
$resourceFiles = array(
|
||||
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
|
||||
'fr' => array(
|
||||
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
|
||||
),
|
||||
);
|
||||
|
||||
// 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');
|
||||
|
||||
$this->assertEquals('répertoire', $translator->trans('folder'));
|
||||
|
||||
// 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');
|
||||
|
||||
$this->assertEquals('répertoire', $translator->trans('folder'));
|
||||
|
||||
// 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');
|
||||
|
||||
$this->assertEquals('folder', $translator->trans('folder'));
|
||||
|
@ -132,10 +134,12 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
|
||||
$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');
|
||||
|
||||
$this->assertEquals('répertoire', $translator->trans('folder'));
|
||||
|
@ -221,14 +225,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
|||
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(
|
||||
$this->getContainer($loader),
|
||||
new MessageSelector(),
|
||||
array($loaderFomat => array($loaderFomat)),
|
||||
$options,
|
||||
$resources
|
||||
$options
|
||||
);
|
||||
|
||||
if ('loader' === $loaderFomat) {
|
||||
|
@ -243,6 +246,22 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
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
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Translation;
|
||||
|
||||
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
|
||||
use Symfony\Component\Translation\Translator as BaseTranslator;
|
||||
use Symfony\Component\Translation\MessageSelector;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
@ -20,17 +21,22 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Translator extends BaseTranslator
|
||||
class Translator extends BaseTranslator implements WarmableInterface
|
||||
{
|
||||
protected $container;
|
||||
protected $loaderIds;
|
||||
protected $resourceFiles;
|
||||
|
||||
protected $options = array(
|
||||
'cache_dir' => null,
|
||||
'debug' => false,
|
||||
'resource_files' => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $resourceLocales;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
|
@ -38,20 +44,19 @@ class Translator extends BaseTranslator
|
|||
*
|
||||
* * cache_dir: The cache directory (or null to disable caching)
|
||||
* * 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 MessageSelector $selector The message selector for pluralization
|
||||
* @param array $loaderIds An array of loader Ids
|
||||
* @param array $options An array of options
|
||||
* @param array $resourceFiles An array of resource directories
|
||||
* @param ContainerInterface $container A ContainerInterface instance
|
||||
* @param MessageSelector $selector The message selector for pluralization
|
||||
* @param array $loaderIds An array of loader Ids
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @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->loaderIds = $loaderIds;
|
||||
$this->resourceFiles = $resourceFiles;
|
||||
|
||||
// check option names
|
||||
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->resourceLocales = array_keys($this->options['resource_files']);
|
||||
if (null !== $this->options['cache_dir'] && $this->options['debug']) {
|
||||
$this->loadResources();
|
||||
}
|
||||
|
@ -66,6 +72,16 @@ class Translator extends BaseTranslator
|
|||
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}
|
||||
*/
|
||||
|
@ -87,11 +103,13 @@ class Translator extends BaseTranslator
|
|||
|
||||
private function loadResources()
|
||||
{
|
||||
foreach ($this->resourceFiles as $key => $file) {
|
||||
// filename is domain.locale.format
|
||||
list($domain, $locale, $format) = explode('.', basename($file), 3);
|
||||
$this->addResource($format, $file, $locale, $domain);
|
||||
unset($this->resourceFiles[$key]);
|
||||
foreach ($this->options['resource_files'] as $locale => $files) {
|
||||
foreach ($files as $key => $file) {
|
||||
// filename is domain.locale.format
|
||||
list($domain, $locale, $format) = explode('.', basename($file), 3);
|
||||
$this->addResource($format, $file, $locale, $domain);
|
||||
unset($this->options['resource_files'][$locale][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
"symfony/finder": "For using the translation loader and cache warmer",
|
||||
"symfony/form": "For using forms",
|
||||
"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"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -25,9 +25,6 @@ use Symfony\Component\Templating\Helper\Helper;
|
|||
class LogoutUrlHelper extends Helper
|
||||
{
|
||||
private $generator;
|
||||
private $listeners = array();
|
||||
private $router;
|
||||
private $tokenStorage;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -79,9 +76,7 @@ class LogoutUrlHelper extends Helper
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical name of this helper.
|
||||
*
|
||||
* @return string The canonical name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -43,9 +43,7 @@ class SecurityHelper extends Helper
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical name of this helper.
|
||||
*
|
||||
* @return string The canonical name
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ CHANGELOG
|
|||
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)
|
||||
* deprecated the assets extension (use the one from the Twig bridge instead)
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ class LintCommand extends BaseLintCommand implements ContainerAwareInterface
|
|||
Or all template files in a bundle:
|
||||
|
||||
<info>php %command.full_name% @AcmeDemoBundle</info>
|
||||
|
||||
|
||||
EOF
|
||||
)
|
||||
;
|
||||
|
|
|
@ -41,6 +41,7 @@ class Configuration implements ConfigurationInterface
|
|||
$this->addFormThemesSection($rootNode);
|
||||
$this->addGlobalsSection($rootNode);
|
||||
$this->addTwigOptions($rootNode);
|
||||
$this->addTwigFormatOptions($rootNode);
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
|
@ -160,4 +161,33 @@ class Configuration implements ConfigurationInterface
|
|||
->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()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -57,6 +57,14 @@ class TwigExtension extends Extension
|
|||
|
||||
$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');
|
||||
|
||||
// register user-configured paths
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<argument>app</argument>
|
||||
<argument type="service" id="twig.app_variable" />
|
||||
</call>
|
||||
<configurator service="twig.configurator.environment" method="configure" />
|
||||
</service>
|
||||
|
||||
<service id="twig.app_variable" class="Symfony\Bridge\Twig\AppVariable" public="false">
|
||||
|
@ -135,5 +136,14 @@
|
|||
<argument type="service" id="http_kernel" />
|
||||
<argument>%twig.exception_listener.controller%</argument>
|
||||
</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>
|
||||
</container>
|
||||
|
|
|
@ -9,7 +9,18 @@
|
|||
{% if collector.applicationname %}
|
||||
{{ collector.applicationname }} {{ collector.applicationversion }}
|
||||
{% 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 %}
|
||||
</span>
|
||||
</a>
|
||||
|
|
|
@ -88,10 +88,13 @@
|
|||
|
||||
{% if collector.logs %}
|
||||
<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 %}
|
||||
<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 %}">
|
||||
{{ logger.display_message(loop.index, log) }}
|
||||
</li>
|
||||
{% for log in collector.logs %}
|
||||
{% 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) %}
|
||||
{% if priority == '-100' ? is_deprecation : log.priority >= priority %}
|
||||
<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 %}
|
||||
<li><em>No logs available for this priority.</em></li>
|
||||
{% endfor %}
|
||||
|
@ -104,15 +107,18 @@
|
|||
{% endblock %}
|
||||
|
||||
|
||||
{% macro display_message(log_index, log) %}
|
||||
{% 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) %}
|
||||
DEPRECATION - {{ log.message }}
|
||||
{% set id = 'sf-call-stack-' ~ log_index %}
|
||||
<a href="#" onclick="Sfjs.toggle('{{ id }}', document.getElementById('{{ id }}-on'), document.getElementById('{{ id }}-off')); return false;">
|
||||
<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">
|
||||
<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">
|
||||
</a>
|
||||
{% for index, call in log.context.stack if index > 1 %}
|
||||
{% macro display_message(log_index, log, is_deprecation) %}
|
||||
{% if is_deprecation %}
|
||||
{% set stack = log.context.stack|default([]) %}
|
||||
|
||||
{% if stack %}
|
||||
<a href="#" onclick="Sfjs.toggle('{{ id }}', document.getElementById('{{ id }}-on'), document.getElementById('{{ id }}-off')); return false;">
|
||||
<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">
|
||||
<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">
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% for index, call in stack if index > 1 %}
|
||||
{% if index == 2 %}
|
||||
<ul class="sf-call-stack" id="{{ id }}" style="display: none">
|
||||
{% 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>
|
||||
|
||||
{% if index == log.context.stack|length - 1 %}
|
||||
{% if index == stack|length - 1 %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -57,12 +57,23 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block panelContent %}
|
||||
<h2>Called Translations</h2>
|
||||
<ul>
|
||||
<li><strong>Defined messages: {{ collector.countdefines }}</strong></li>
|
||||
<li><strong>Fallback messages: {{ collector.countFallbacks }}</strong></li>
|
||||
<li><strong>Missing messages: {{ collector.countMissings }}</strong></li>
|
||||
</ul>
|
||||
<h2>Translation Stats</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Defined messages</th>
|
||||
<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>
|
||||
<tr>
|
||||
|
@ -77,7 +88,10 @@
|
|||
<td><code>{{ translator.state(message) }}</code></td>
|
||||
<td><code>{{ message.locale }}</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>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -199,7 +199,7 @@
|
|||
}
|
||||
|
||||
{% if excluded_ajax_paths is defined %}
|
||||
if (window.XMLHttpRequest && XMLHttpRequest.addEventListener) {
|
||||
if (window.XMLHttpRequest && XMLHttpRequest.prototype.addEventListener) {
|
||||
var proxied = XMLHttpRequest.prototype.open;
|
||||
|
||||
XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
|
||||
|
|
|
@ -781,7 +781,7 @@ class Application
|
|||
$input->setInteractive(false);
|
||||
} elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) {
|
||||
$inputStream = $this->getHelperSet()->get('question')->getInputStream();
|
||||
if (!@posix_isatty($inputStream)) {
|
||||
if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
|
||||
$input->setInteractive(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ class SymfonyStyle extends OutputStyle
|
|||
*/
|
||||
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)
|
||||
{
|
||||
$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
|
||||
);
|
||||
|
||||
$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)
|
||||
{
|
||||
$headers = array_map(function ($value) { return sprintf('<info>%s</>', $value); }, $headers);
|
||||
|
||||
$table = new Table($this);
|
||||
$table->setHeaders($headers);
|
||||
$table->setRows($rows);
|
||||
|
|
|
@ -61,7 +61,7 @@ class CommandTester
|
|||
&& (null !== $application = $this->command->getApplication())
|
||||
&& $application->getDefinition()->hasArgument('command')
|
||||
) {
|
||||
$input['command'] = $this->command->getName();
|
||||
$input = array_merge(array('command' => $this->command->getName()), $input);
|
||||
}
|
||||
|
||||
$this->input = new ArrayInput($input);
|
||||
|
|
|
@ -6,6 +6,14 @@ CHANGELOG
|
|||
|
||||
* 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
|
||||
-----
|
||||
|
||||
|
|
|
@ -66,9 +66,9 @@ class ErrorHandler
|
|||
private $loggers = array(
|
||||
E_DEPRECATED => array(null, LogLevel::INFO),
|
||||
E_USER_DEPRECATED => array(null, LogLevel::INFO),
|
||||
E_NOTICE => array(null, LogLevel::NOTICE),
|
||||
E_USER_NOTICE => array(null, LogLevel::NOTICE),
|
||||
E_STRICT => array(null, LogLevel::NOTICE),
|
||||
E_NOTICE => array(null, LogLevel::WARNING),
|
||||
E_USER_NOTICE => array(null, LogLevel::WARNING),
|
||||
E_STRICT => array(null, LogLevel::WARNING),
|
||||
E_WARNING => array(null, LogLevel::WARNING),
|
||||
E_USER_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 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 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 bool $replace Replace or amend the previous value
|
||||
|
@ -336,55 +336,57 @@ class ErrorHandler
|
|||
$throw = $this->thrownErrors & $type & $level;
|
||||
$type &= $level | $this->screamedErrors;
|
||||
|
||||
if ($type && ($log || $throw)) {
|
||||
if ($throw) {
|
||||
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);
|
||||
}
|
||||
if (!$type || (!$log && !$throw)) {
|
||||
return $type && $log;
|
||||
}
|
||||
|
||||
throw $throw;
|
||||
}
|
||||
|
||||
// For duplicated errors, log the trace only once
|
||||
$e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
|
||||
$trace = true;
|
||||
|
||||
if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
|
||||
$trace = false;
|
||||
if ($throw) {
|
||||
if (($this->scopedErrors & $type) && class_exists(ContextErrorException::class)) {
|
||||
// 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 {
|
||||
$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) {
|
||||
if ($this->scopedErrors & $type) {
|
||||
$e['context'] = $context;
|
||||
if ($trace) {
|
||||
$e['stack'] = debug_backtrace(true); // Provide object
|
||||
}
|
||||
} elseif ($trace) {
|
||||
$e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
// For duplicated errors, log the trace only once
|
||||
$e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
|
||||
$trace = true;
|
||||
|
||||
if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
|
||||
$trace = false;
|
||||
} else {
|
||||
$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) {
|
||||
$log = 0;
|
||||
} elseif (self::$stackedErrorLevels) {
|
||||
self::$stackedErrors[] = array($this->loggers[$type], $message, $e);
|
||||
} else {
|
||||
try {
|
||||
$this->isRecursive = true;
|
||||
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
|
||||
$this->isRecursive = false;
|
||||
} catch (\Exception $e) {
|
||||
$this->isRecursive = false;
|
||||
if ($this->isRecursive) {
|
||||
$log = 0;
|
||||
} elseif (self::$stackedErrorLevels) {
|
||||
self::$stackedErrors[] = array($this->loggers[$type], $message, $e);
|
||||
} else {
|
||||
try {
|
||||
$this->isRecursive = true;
|
||||
$this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
|
||||
$this->isRecursive = false;
|
||||
} catch (\Exception $e) {
|
||||
$this->isRecursive = false;
|
||||
|
||||
throw $e;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,40 +455,44 @@ class ErrorHandler
|
|||
public static function handleFatalError(array $error = null)
|
||||
{
|
||||
self::$reservedMemory = '';
|
||||
|
||||
$handler = set_error_handler('var_dump', 0);
|
||||
$handler = is_array($handler) ? $handler[0] : null;
|
||||
restore_error_handler();
|
||||
if ($handler instanceof self) {
|
||||
if (null === $error) {
|
||||
$error = error_get_last();
|
||||
}
|
||||
|
||||
try {
|
||||
while (self::$stackedErrorLevels) {
|
||||
static::unstackErrors();
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
// Handled below
|
||||
}
|
||||
if (!$handler instanceof self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($error && ($error['type'] & (E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR))) {
|
||||
// Let's not throw anymore but keep logging
|
||||
$handler->throwAt(0, true);
|
||||
if (null === $error) {
|
||||
$error = error_get_last();
|
||||
}
|
||||
|
||||
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 {
|
||||
while (self::$stackedErrorLevels) {
|
||||
static::unstackErrors();
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
// Handled below
|
||||
}
|
||||
|
||||
try {
|
||||
$handler->handleException($exception, $error);
|
||||
} catch (FatalErrorException $e) {
|
||||
// Ignore this re-throw
|
||||
if ($error && ($error['type'] & (E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR))) {
|
||||
// Let's not throw anymore but keep logging
|
||||
$handler->throwAt(0, true);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,11 @@ class FatalErrorException extends \ErrorException
|
|||
|
||||
unset($frame);
|
||||
$trace = array_reverse($trace);
|
||||
} elseif (function_exists('symfony_debug_backtrace')) {
|
||||
$trace = symfony_debug_backtrace();
|
||||
if (0 < $traceOffset) {
|
||||
array_splice($trace, 0, $traceOffset);
|
||||
}
|
||||
} else {
|
||||
$trace = array();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
```
|
|
@ -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
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
extern zend_module_entry 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
|
||||
# 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)
|
||||
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)
|
||||
|
||||
PHP_MINIT_FUNCTION(symfony_debug);
|
||||
|
@ -40,11 +42,14 @@ PHP_GINIT_FUNCTION(symfony_debug);
|
|||
PHP_GSHUTDOWN_FUNCTION(symfony_debug);
|
||||
|
||||
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_get_resource_type(long);
|
||||
static int _symfony_debug_get_resource_refcount(long);
|
||||
static const char* _symfony_debug_get_resource_type(long TSRMLS_DC);
|
||||
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
|
||||
#define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v)
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
#endif
|
||||
|
||||
#include "php.h"
|
||||
#ifdef ZTS
|
||||
#include "TSRM.h"
|
||||
#endif
|
||||
#include "php_ini.h"
|
||||
#include "ext/standard/info.h"
|
||||
#include "php_symfony_debug.h"
|
||||
|
@ -19,6 +22,13 @@
|
|||
#include "ext/standard/php_lcg.h"
|
||||
#include "ext/spl/php_spl.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)
|
||||
|
||||
|
@ -30,9 +40,28 @@ ZEND_END_ARG_INFO()
|
|||
|
||||
const zend_function_entry symfony_debug_functions[] = {
|
||||
PHP_FE(symfony_zval_info, symfony_zval_arginfo)
|
||||
PHP_FE(symfony_debug_backtrace, NULL)
|
||||
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)
|
||||
{
|
||||
zval *key = NULL, *arg = NULL;
|
||||
|
@ -40,7 +69,7 @@ PHP_FUNCTION(symfony_zval_info)
|
|||
HashTable *array = NULL;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -62,13 +91,14 @@ PHP_FUNCTION(symfony_zval_info)
|
|||
array_init(return_value);
|
||||
|
||||
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_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg));
|
||||
|
||||
if (Z_TYPE_P(arg) == IS_OBJECT) {
|
||||
static char hash[33] = {0};
|
||||
php_spl_object_hash(arg, (char *)hash);
|
||||
char hash[33] = {0};
|
||||
|
||||
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_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);
|
||||
|
@ -77,17 +107,41 @@ PHP_FUNCTION(symfony_zval_info)
|
|||
add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg)));
|
||||
} else if(Z_TYPE_P(arg) == IS_RESOURCE) {
|
||||
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_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg)));
|
||||
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) TSRMLS_CC));
|
||||
} else if (Z_TYPE_P(arg) == IS_STRING) {
|
||||
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;
|
||||
res_type = zend_rsrc_list_get_rsrc_type(rsid);
|
||||
res_type = zend_rsrc_list_get_rsrc_type(rsid TSRMLS_CC);
|
||||
|
||||
if (!res_type) {
|
||||
return "Unknown";
|
||||
|
@ -96,7 +150,7 @@ static const char* _symfony_debug_get_resource_type(long rsid)
|
|||
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;
|
||||
|
||||
|
@ -107,21 +161,21 @@ static int _symfony_debug_get_resource_refcount(long rsid)
|
|||
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;
|
||||
|
||||
if (!SYMFONY_DEBUG_G(req_rand_init)) {
|
||||
if (!BG(mt_rand_is_seeded)) {
|
||||
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);
|
||||
|
||||
snprintf(result, 17, "%016zx", address_rand);
|
||||
spprintf(&result, 17, "%016zx", address_rand);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -187,7 +241,7 @@ ZEND_GET_MODULE(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)
|
||||
|
@ -197,11 +251,16 @@ PHP_GSHUTDOWN_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;
|
||||
}
|
||||
|
||||
PHP_MSHUTDOWN_FUNCTION(symfony_debug)
|
||||
{
|
||||
zend_error_cb = SYMFONY_DEBUG_G(old_error_cb);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ Test symfony_zval_info API
|
|||
--SKIPIF--
|
||||
<?php if (!extension_loaded("symfony_debug")) print "skip"; ?>
|
||||
--FILE--
|
||||
<?php
|
||||
<?php
|
||||
|
||||
$int = 42;
|
||||
$float = 42.42;
|
||||
|
@ -88,7 +88,7 @@ array(8) {
|
|||
["object_hash"]=>
|
||||
string(32) "%s"
|
||||
["object_handle"]=>
|
||||
int(1)
|
||||
int(%d)
|
||||
}
|
||||
array(5) {
|
||||
["type"]=>
|
||||
|
@ -112,7 +112,7 @@ array(7) {
|
|||
["zval_isref"]=>
|
||||
bool(false)
|
||||
["resource_handle"]=>
|
||||
int(4)
|
||||
int(%d)
|
||||
["resource_type"]=>
|
||||
string(6) "stream"
|
||||
["resource_refcount"]=>
|
||||
|
|
|
@ -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
|
||||
(
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
)
|
|
@ -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
|
||||
(
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
)
|
|
@ -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
|
||||
)
|
|
@ -141,9 +141,9 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
|
|||
$loggers = array(
|
||||
E_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_STRICT => array(null, LogLevel::NOTICE),
|
||||
E_STRICT => array(null, LogLevel::WARNING),
|
||||
E_WARNING => array(null, LogLevel::WARNING),
|
||||
E_USER_WARNING => array(null, LogLevel::WARNING),
|
||||
E_COMPILE_WARNING => array(null, LogLevel::WARNING),
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
<testsuite name="Symfony Debug Component Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Symfony Debug Extension Test Suite">
|
||||
<directory suffix=".phpt">./Resources/ext/tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
|
|
|
@ -14,8 +14,27 @@ CHANGELOG
|
|||
2.7.0
|
||||
-----
|
||||
|
||||
* 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 option "choice_translation_domain" to ChoiceType.
|
||||
* 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
|
||||
-----
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -29,10 +29,13 @@ use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
|||
* <code>
|
||||
* $choices = array(true, false);
|
||||
* $labels = array('Agree', 'Disagree');
|
||||
* $choiceList = new ChoiceList($choices, $labels);
|
||||
* $choiceList = new ArrayChoiceList($choices, $labels);
|
||||
* </code>
|
||||
*
|
||||
* @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
|
||||
{
|
||||
|
@ -89,6 +92,8 @@ class ChoiceList implements ChoiceListInterface
|
|||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
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.
|
||||
*
|
||||
|
@ -25,23 +27,12 @@ namespace Symfony\Component\Form\Extension\Core\ChoiceList;
|
|||
* in the HTML "value" attribute.
|
||||
*
|
||||
* @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
|
||||
* the choice groups as top-level keys.
|
||||
|
@ -92,37 +83,6 @@ interface ChoiceListInterface
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue