diff --git a/.travis.yml b/.travis.yml index 3f3c89ab12c87..a674eac1e8cc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,10 +36,10 @@ before_install: - echo "memory_limit = -1" >> $INI_FILE - echo "session.gc_probability = 0" >> $INI_FILE - if [ "$deps" != "skip" ]; then composer self-update; fi; - - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then phpenv config-rm xdebug.ini; fi; + - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini; fi; - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then echo "extension = mongo.so" >> $INI_FILE; fi; - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then echo "extension = memcache.so" >> $INI_FILE; fi; - - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then (echo yes | pecl install -f apcu-4.0.7 && echo "apc.enable_cli = 1" >> $INI_FILE) || echo "Let's continue without apcu extension"; fi; + - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then (echo yes | pecl install -f apcu-4.0.8 && echo "apc.enable_cli = 1" >> $INI_FILE) || echo "Let's continue without apcu extension"; fi; - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then pecl install -f memcached-2.1.0 || echo "Let's continue without memcached extension"; fi; - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]] && [ "$deps" = "no" ]; then (cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo "extension = $(pwd)/modules/symfony_debug.so" >> $INI_FILE); fi; - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo "extension = ldap.so" >> $INI_FILE; fi; diff --git a/CHANGELOG-2.3.md b/CHANGELOG-2.3.md index 9c9d593aa08c7..4974aa08b3a2c 100644 --- a/CHANGELOG-2.3.md +++ b/CHANGELOG-2.3.md @@ -7,6 +7,25 @@ in 2.3 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.3.0...v2.3.1 +* 2.3.35 (2015-11-23) + + * security #16631 CVE-2015-8124: Session Fixation in the "Remember Me" Login Feature (xabbuh) + * security #16630 CVE-2015-8125: Potential Remote Timing Attack Vulnerability in Security Remember-Me Service (xabbuh) + * bug #16588 Sent out a status text for unknown HTTP headers. (dawehner) + * bug #16295 [DependencyInjection] Unescape parameters for all types of injection (Nicofuma) + * bug #16574 [Process] Fix PhpProcess with phpdbg runtime (nicolas-grekas) + * bug #16352 Fix the server variables in the router_*.php files (leofeyer) + * bug #16537 [Validator] Allow an empty path with a non empty fragment or a query (jakzal) + * bug #16528 [Translation] Add support for Armenian pluralization. (marcosdsanchez) + * bug #16510 [Process] fix Proccess run with pts enabled (ewgRa) + * bug #16292 fix race condition at mkdir (#16258) (ewgRa) + * bug #16462 [PropertyAccess] Fix dynamic property accessing. (dunglas) + * bug #16294 [PropertyAccess] Major performance improvement (dunglas) + * bug #16331 fixed Twig deprecation notices (fabpot) + * bug #16306 [DoctrineBridge] Fix issue which prevent the profiler to explain a query (Baachi) + * bug #16359 Use mb_detect_encoding with $strict = true (nicolas-grekas) + * bug #16144 [Security] don't allow to install the split Security packages (xabbuh) + * 2.3.34 (2015-10-27) * bug #16288 [Process] Inherit env vars by default in PhpProcess (nicolas-grekas) diff --git a/CHANGELOG-2.7.md b/CHANGELOG-2.7.md index 05bf4d856d835..19e9036c82bb2 100644 --- a/CHANGELOG-2.7.md +++ b/CHANGELOG-2.7.md @@ -7,6 +7,34 @@ in 2.7 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.7.0...v2.7.1 +* 2.7.7 (2015-11-23) + + * security #16631 CVE-2015-8124: Session Fixation in the "Remember Me" Login Feature (xabbuh) + * security #16630 CVE-2015-8125: Potential Remote Timing Attack Vulnerability in Security Remember-Me Service (xabbuh) + * bug #16588 Sent out a status text for unknown HTTP headers. (dawehner) + * bug #16295 [DependencyInjection] Unescape parameters for all types of injection (Nicofuma) + * bug #16574 [Process] Fix PhpProcess with phpdbg runtime (nicolas-grekas) + * bug #16578 [Console] Fix bug in windows detection (kbond) + * bug #16546 [Serializer] ObjectNormalizer: don't serialize static methods and props (dunglas) + * bug #16352 Fix the server variables in the router_*.php files (leofeyer) + * bug #16537 [Validator] Allow an empty path with a non empty fragment or a query (jakzal) + * bug #16528 [Translation] Add support for Armenian pluralization. (marcosdsanchez) + * bug #16510 [Process] fix Proccess run with pts enabled (ewgRa) + * bug #16292 fix race condition at mkdir (#16258) (ewgRa) + * bug #15945 [Form] trigger deprecation warning when using empty_value (xabbuh) + * bug #16384 [FrameworkBundle] JsonDescriptor - encode container params only once (xabbuh) + * bug #16480 [VarDumper] Fix PHP7 type-hints compat (nicolas-grekas) + * bug #16463 [PropertyAccess] Port of the performance optimization from 2.3 (dunglas) + * bug #16462 [PropertyAccess] Fix dynamic property accessing. (dunglas) + * bug #16454 [Serializer] GetSetNormalizer shouldn't set/get static methods (boekkooi) + * bug #16453 [Serializer] PropertyNormalizer shouldn't set static properties (boekkooi) + * bug #16471 [VarDumper] Fix casting for ReflectionParameter (nicolas-grekas) + * bug #16294 [PropertyAccess] Major performance improvement (dunglas) + * bug #16331 fixed Twig deprecation notices (fabpot) + * bug #16306 [DoctrineBridge] Fix issue which prevent the profiler to explain a query (Baachi) + * bug #16359 Use mb_detect_encoding with $strict = true (nicolas-grekas) + * bug #16144 [Security] don't allow to install the split Security packages (xabbuh) + * 2.7.6 (2015-10-27) * bug #16338 [VarDumper] Fix anonymous class dumping (nicolas-grekas) diff --git a/CHANGELOG-2.8.md b/CHANGELOG-2.8.md index f36da583f128a..34168508a3cb1 100644 --- a/CHANGELOG-2.8.md +++ b/CHANGELOG-2.8.md @@ -7,6 +7,57 @@ in 2.8 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.8.0...v2.8.1 +* 2.8.0 (2015-11-30) + + * bug #16758 Fix BC for the default root form name (stof) + * bug #16753 [Process] Fix signaling/stopping logic on Windows (nicolas-grekas) + * feature #16755 [Security] add subject variable to expression context (xabbuh) + * bug #16642 [DI][autowiring] throw exception when many services use the same class. (aitboudad) + * bug #16745 [Yaml] look for colon in parsed inline string (xabbuh) + * bug #16733 [Console] do not encode backslashes in console default description (Tobion) + * feature #16735 [WIP] [Ldap] Marked the Ldap component as internal (csarrazi) + * bug #16734 Make sure security.role_hierarchy.roles always exists (WouterJ) + * feature #16722 [Security][SecurityBundle] Use csrf_token_id instead of deprecated intention (jakzal) + * bug #16312 [HttpKernel] clearstatcache() so the Cache sees when a .lck file has been released (mpdude) + * bug #16351 [WIP] [Form] [TwigBridge] Bootstrap horizontal theme missing tests (pieter2627) + * bug #16685 [Form] Fixed: Duplicate choice labels are remembered when using "choices_as_values" = false (webmozart) + * feature #16709 [Bridge\PhpUnit] Display the stack trace of a deprecation on-demand (nicolas-grekas) + * bug #16704 [Form+SecurityBundle] Trigger deprecation for csrf_provider+intention options (nicolas-grekas) + * feature #16706 [HttpFoundation] Deprecate $deep parameter on ParameterBag (nicolas-grekas) + * bug #16705 [Form] Deprecated setting "choices_as_values" to "false" (webmozart) + * feature #16690 [Form] Deprecated ArrayKeyChoiceList (webmozart) + * feature #16687 [Form] Deprecated TimezoneType::getTimezones() (webmozart) + * bug #16681 [Form] Deprecated setting "choices_as_values" to "false" (webmozart) + * bug #16695 [SecurityBundle] disable the init:acl command if ACL is not used (Tobion) + * bug #16677 [Form] Fixed wrong usages of the "text" type (webmozart) + * bug #16679 [Form] Disabled view data validation if "data_class" is set to null (webmozart) + * bug #16621 [Console] Fix bug with $output overloading (WouterJ) + * feature #16601 [Security] Deprecate "AbstractVoter" in favor of "Voter" (nicolas-grekas, lyrixx) + * bug #16676 [HttpFoundation] Workaround HHVM rewriting HTTP response line (nicolas-grekas) + * bug #16668 [ClassLoader] Fix parsing namespace when token_get_all() is missing (nicolas-grekas) + * bug #16386 Bug #16343 [Router] Too many Routes ? (jelte) + * bug #16498 fix unused variable warning (eventhorizonpl) + * feature #16031 [Translation][Form] Do not translate form labels and placeholders when 'translation_domain' is false (Restless-ET) + * bug #16651 [Debug] Ensure class declarations are loaded only once (nicolas-grekas) + * security #16631 CVE-2015-8124: Session Fixation in the "Remember Me" Login Feature (xabbuh) + * security #16630 CVE-2015-8125: Potential Remote Timing Attack Vulnerability in Security Remember-Me Service (xabbuh) + * bug #16633 [Filesystem] Fixed failing test due to tempdir symlink (toretto460) + * bug #16609 [HttpKernel] Don't reset on shutdown but in FrameworkBundle/Test/KernelTestCase (nicolas-grekas) + * bug #16477 [Routing] Changing RouteCollectionBuilder::import() behavior to add to the builder (weaverryan) + * bug #16588 Sent out a status text for unknown HTTP headers. (dawehner) + * bug #16295 [DependencyInjection] Unescape parameters for all types of injection (Nicofuma) + * bug #16377 [WebProfilerBundle] Fix minitoolbar height (rvanlaak) + * bug #16585 Add support for HTTP status code 418 back (dawehner) + * bug #16574 [Process] Fix PhpProcess with phpdbg runtime (nicolas-grekas) + * bug #16581 Fix call to undefined function json_last_error_message (dawehner) + * bug #16573 [FrameworkBundle] Fix PropertyInfo extractor namespace in framework bundle (jvasseur) + * bug #16578 [Console] Fix bug in windows detection (kbond) + * bug #16546 [Serializer] ObjectNormalizer: don't serialize static methods and props (dunglas) + * bug #16352 Fix the server variables in the router_*.php files (leofeyer) + * bug #16537 [Validator] Allow an empty path with a non empty fragment or a query (jakzal) + * bug #16528 [Translation] Add support for Armenian pluralization. (marcosdsanchez) + * bug #16510 [Process] fix Proccess run with pts enabled (ewgRa) + * 2.8.0-BETA1 (2015-11-16) * feature #16156 [Filesystem] Changed dumpFile to allow dumping to streams (markchalloner, pierredup) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b78fd1ff5ee94..9b30d35c1a0ae 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -51,19 +51,19 @@ Symfony is the result of the work of many people who made the code better - Florin Patan (florinpatan) - Eric Clemmons (ericclemmons) - Andrej Hudec (pulzarraider) - - Deni - Maxime Steinhausser (ogizanagi) + - Deni - Henrik Westphal (snc) - Dariusz Górecki (canni) - Gábor Egyed (1ed) - Christian Raue - Arnout Boks (aboks) - - Michel Weimerskirch (mweimerskirch) - Kevin Bond (kbond) + - Michel Weimerskirch (mweimerskirch) + - Douglas Greenshields (shieldo) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) - - Douglas Greenshields (shieldo) - Daniel Holmes (dholmes) - Bart van den Burg (burgov) - Jordan Alliot (jalliot) @@ -85,15 +85,17 @@ Symfony is the result of the work of many people who made the code better - Adrien Brault (adrienbrault) - excelwebzone - Jacob Dreesen (jdreesen) + - Michal Piotrowski (eventhorizon) + - Peter Kokot (maastermedia) - Fabien Pennequin (fabienpennequin) - Peter Rehm (rpet) - - Peter Kokot (maastermedia) + - Pierre du Plessis (pierredup) - Alexander Schwenn (xelaris) - Gordon Franke (gimler) - Robert Schönthal (digitalkaoz) - Jérémy DERUSSÉ (jderusse) - Dariusz Ruminski - - Michal Piotrowski (eventhorizon) + - Joshua Thijssen - Stefano Sala (stefano.sala) - David Buchmann (dbu) - Issei Murasawa (issei_m) @@ -103,7 +105,6 @@ Symfony is the result of the work of many people who made the code better - Sebastian Hörl (blogsh) - Daniel Gomes (danielcsgomes) - Hidenori Goto (hidenorigoto) - - Joshua Thijssen - Guilherme Blanco (guilhermeblanco) - Pablo Godel (pgodel) - Vladimir Reznichenko (kalessil) @@ -116,6 +117,7 @@ Symfony is the result of the work of many people who made the code better - Clemens Tolboom - Helmer Aaviksoo - Baptiste Clavié (talus) + - Tugdual Saunier (tucksaun) - Hiromi Hishida (77web) - Matthieu Ouellette-Vachon (maoueh) - Michał Pipa (michal.pipa) @@ -124,8 +126,7 @@ Symfony is the result of the work of many people who made the code better - Artur Kotyrba - Rouven Weßling (realityking) - Charles Sarrazin (csarrazi) - - Pierre du Plessis (pierredup) - - Tugdual Saunier (tucksaun) + - Warnar Boekkooi (boekkooi) - Andréia Bohner (andreia) - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) @@ -159,15 +160,17 @@ Symfony is the result of the work of many people who made the code better - Justin Hileman (bobthecow) - Michele Orselli (orso) - Sven Paulus (subsven) - - Warnar Boekkooi (boekkooi) - Lars Strojny (lstrojny) + - Daniel Wehner - Rui Marinho (ruimarinho) + - Evgeniy (ewgraf) - Julien Brochet (mewt) - Sergey Linnik (linniksa) - Jáchym Toušek - Marcel Beerta (mazen) - Vincent AUBERT (vincent) - julien pauli (jpauli) + - Florian Lonqueu-Brochard (florianlb) - Francois Zaninotto - Alexander Kotynia (olden) - Daniel Tschinder @@ -185,6 +188,7 @@ Symfony is the result of the work of many people who made the code better - Eugene Leonovich (rybakit) - Joseph Rouff (rouffj) - Félix Labrecque (woodspire) + - Tomáš Votruba (tomas_votruba) - GordonsLondon - Jan Sorgalla (jsor) - Ray @@ -201,17 +205,19 @@ Symfony is the result of the work of many people who made the code better - Beau Simensen (simensen) - Robert Kiss (kepten) - Ruben Gonzalez (rubenrua) + - Marcos Sánchez - Kim Hemsø Rasmussen (kimhemsoe) - Diego Saint Esteben (dosten) - - Florian Lonqueu-Brochard (florianlb) - Tom Van Looy (tvlooy) - Wouter Van Hecke - Peter Kruithof (pkruithof) - Michael Holm (hollo) - Marc Weistroff (futurecat) + - Blanchon Vincent (blanchonvincent) - Dawid Nowak - Kristen Gilden (kgilden) - Chris Smith (cs278) + - Richard van Laak (rvanlaak) - Florian Klein (docteurklein) - Manuel Kiessling (manuelkiessling) - Daniel Wehner @@ -245,7 +251,7 @@ Symfony is the result of the work of many people who made the code better - Giorgio Premi - Erin Millard - Matthew Lewinski (lewinski) - - Marcos Sánchez + - Antonio J. García Lagar (ajgarlag) - alquerci - Francesco Levorato - Vitaliy Zakharov (zakharovvi) @@ -255,14 +261,11 @@ Symfony is the result of the work of many people who made the code better - Christian Gärtner (dagardner) - Tomasz Kowalczyk (thunderer) - François-Xavier de Guillebon (de-gui_f) - - Daniel Wehner - Felix Labrecque - Yaroslav Kiliba - Stepan Anchugov (kix) - Terje Bråten - - Evgeniy (ewgraf) - Robbert Klarenbeek (robbertkl) - - Blanchon Vincent (blanchonvincent) - hossein zolfi (ocean) - Clément Gautier (clementgautier) - Eduardo Gulias (egulias) @@ -285,6 +288,7 @@ Symfony is the result of the work of many people who made the code better - Shein Alexey - Joe Lencioni - Daniel Tschinder + - Diego Agulló (aeoris) - Kai - Lee Rowlands - Maximilian Reichel (phramz) @@ -313,8 +317,9 @@ Symfony is the result of the work of many people who made the code better - Thomas Lallement (raziel057) - Jan Schumann - Niklas Fiekas + - Mark Challoner (markchalloner) + - Markus Bachmann (baachi) - lancergr - - Antonio J. García Lagar (ajgarlag) - Olivier Dolbeau (odolbeau) - Roumen Damianoff (roumen) - vagrant @@ -365,9 +370,10 @@ Symfony is the result of the work of many people who made the code better - Daniel Beyer - Andrew Udvare (audvare) - alexpods - - Richard van Laak (rvanlaak) + - Tristan Darricau (nicofuma) - Erik Trapman (eriktrapman) - De Cock Xavier (xdecock) + - Possum - Scott Arciszewski - Norbert Orzechowicz (norzechowicz) - Matthijs van den Bos (matthijs) @@ -381,6 +387,7 @@ Symfony is the result of the work of many people who made the code better - Dawid Pakuła (zulusx) - Florian Rey (nervo) - Rodrigo Borrego Bernabé (rodrigobb) + - Leo Feyer - MatTheCat - John Bafford (jbafford) - Denis Gorbachev (starfall) @@ -410,7 +417,6 @@ Symfony is the result of the work of many people who made the code better - Christopher Davis (chrisguitarguy) - alcaeus - vitaliytv - - Markus Bachmann (baachi) - Sebastian Blum - aubx - Ricky Su (ricky) @@ -475,6 +481,7 @@ Symfony is the result of the work of many people who made the code better - Tiago Brito (blackmx) - Richard van den Brand (ricbra) - develop + - Hidde Wieringa - Mark Sonnabaum - Alexander Obuhovich (aik099) - jochenvdv @@ -496,7 +503,6 @@ Symfony is the result of the work of many people who made the code better - avorobiev - Venu - Lars Vierbergen - - Mark Challoner - Dennis Hotson - Andrew Tchircoff (andrewtch) - michaelwilliams @@ -534,7 +540,6 @@ Symfony is the result of the work of many people who made the code better - Rafał Wrzeszcz (rafalwrzeszcz) - Reen Lokum - Martin Parsiegla (spea) - - Possum - Denis Charrier (brucewouaigne) - Quentin Schuler - Pierre Vanliefland (pvanliefland) @@ -558,12 +563,10 @@ Symfony is the result of the work of many people who made the code better - Patrick Allaert - Gustavo Falco (gfalco) - Matt Robinson (inanimatt) - - Tristan Darricau (nicofuma) - Aleksey Podskrebyshev - Steffen Roßkamp - David Marín Carreño (davefx) - Jörn Lang (j.lang) - - Leo Feyer - mwsaz - Benoît Bourgeois - corphi @@ -589,7 +592,6 @@ Symfony is the result of the work of many people who made the code better - Balazs Csaba (balazscsaba2006) - Harry Walter (haswalt) - Johnson Page (jwpage) - - Tomáš Votruba (tomas_votruba) - Michael Roterman (wtfzdotnet) - Arno Geurts - Adán Lobato (adanlobato) @@ -638,6 +640,7 @@ Symfony is the result of the work of many people who made the code better - Jérôme Vasseur - Sander Coolen (scoolen) - Nicolas Le Goff (nlegoff) + - Anne-Sophie Bachelard (annesophie) - Manuele Menozzi - Anton Babenko (antonbabenko) - Irmantas Šiupšinskas (irmantas) @@ -921,7 +924,6 @@ Symfony is the result of the work of many people who made the code better - Shane Preece (shane) - wusuopu - povilas - - Diego Agulló - Alessandro Tagliapietra (alex88) - Biji (biji) - Gunnar Lium (gunnarlium) @@ -1082,7 +1084,6 @@ 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 @@ -1106,7 +1107,6 @@ Symfony is the result of the work of many people who made the code better - Alan Chen - Maerlyn - Even André Fiskvik - - Diego Agulló - Dane Powell - Gerrit Drost - Lenar Lõhmus @@ -1219,6 +1219,7 @@ Symfony is the result of the work of many people who made the code better - Sam Williams - Adrian Philipp - James Michael DuPont + - Eugene Wissner - Kasperki - Tammy D - Ondrej Slinták @@ -1276,6 +1277,7 @@ Symfony is the result of the work of many people who made the code better - arduanov - sualko - Nicolas Roudaire + - Jérôme Vasseur - Alfonso (afgar) - Andreas Forsblom (aforsblo) - Alex Olmos (alexolmos) diff --git a/UPGRADE-2.8.md b/UPGRADE-2.8.md index ae8bb7fa4ac98..1166e6ad56491 100644 --- a/UPGRADE-2.8.md +++ b/UPGRADE-2.8.md @@ -213,6 +213,14 @@ Form } } ``` + + * The option "options" of the CollectionType has been renamed to "entry_options". + The usage of the option "options" is deprecated and will be removed in Symfony 3.0. + + * The option "type" of the CollectionType has been renamed to "entry_type". + The usage of the option "type" is deprecated and will be removed in Symfony 3.0. + As a value for the option you should provide the fully-qualified class name (FQCN) + now as well. * Passing type instances to `Form::add()`, `FormBuilder::add()` and the `FormFactory::create*()` methods is deprecated and will not be supported @@ -252,6 +260,12 @@ Form ``` + * The `TimezoneType::getTimezones()` method was deprecated and will be removed + in Symfony 3.0. You should not use this method. + + * The class `ArrayKeyChoiceList` was deprecated and will be removed in Symfony + 3.0. Use `ArrayChoiceList` instead. + Translator ---------- @@ -442,38 +456,24 @@ FrameworkBundle Security -------- - * The AbstractToken::isGranted() method was deprecated. Instead, - override the voteOnAttribute() method. This method has one small - difference: it's passed the TokenInterface instead of the user: + * The `object` variable passed to expressions evaluated by the `ExpressionVoter` + is deprecated. Instead use the new `subject` variable. - Before: + * The `AbstractVoter` class was deprecated. Instead, extend the `Voter` class and + move your voting logic in the `supports($attribute, $subject)` and + `voteOnAttribute($attribute, $object, TokenInterface $token)` methods. - ```php - class MyCustomVoter extends AbstractVoter - { - // ... + * The `VoterInterface::supportsClass` and `supportsAttribute` methods were + deprecated and will be removed from the interface in 3.0. - protected function isGranted($attribute, $object, $user = null) - { - // ... - } - } - ``` + * The `intention` option is deprecated for all the authentication listeners, + and will be removed in 3.0. Use the `csrf_token_id` option instead. - After: +SecurityBundle +-------------- - ```php - class MyCustomVoter extends AbstractVoter - { - // ... - - protected function voteOnAttribute($attribute, $object, TokenInterface $token) - { - $user = $token->getUser(); - // ... - } - } - ``` + * The `intention` firewall listener setting is deprecated, and will be removed in 3.0. + Use the `csrf_token_id` option instead. Config ------ @@ -505,3 +505,23 @@ Config Additionally, if you have implemented cache validation strategies *using* `isFresh()` yourself, you should have a look at the new cache validation system based on `ResourceChecker`s. + +Yaml +---- + + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of `@`, `` ` ``, `|`, and `>` at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 65386359a5adf..7acdcddfe990e 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -264,6 +264,12 @@ UPGRADE FROM 2.x to 3.0 // ... } ``` + + * The option "options" of the CollectionType has been renamed to "entry_options". + + * The option "type" of the CollectionType has been renamed to "entry_type". + As a value for the option you must provide the fully-qualified class name (FQCN) + now as well. * The `FormIntegrationTestCase` and `FormPerformanceTestCase` classes were moved form the `Symfony\Component\Form\Tests` namespace to the `Symfony\Component\Form\Test` namespace. @@ -311,6 +317,12 @@ UPGRADE FROM 2.x to 3.0 * The `Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList` class has been removed in favor of `Symfony\Component\Form\ChoiceList\ArrayChoiceList`. + + * The `TimezoneType::getTimezones()` method was removed. You should not use + this method. + + * The `Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList` class has been removed in + favor of `Symfony\Component\Form\ChoiceList\ArrayChoiceList`. ### FrameworkBundle @@ -1228,6 +1240,24 @@ UPGRADE FROM 2.x to 3.0 ### Yaml + * Using a colon in an unquoted mapping value leads to a `ParseException`. + * Starting an unquoted string with `@`, `` ` ``, `|`, or `>` leads to a `ParseException`. + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) leads to a `ParseException`. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + + * The ability to pass file names to `Yaml::parse()` has been removed. Before: diff --git a/appveyor.yml b/appveyor.yml index 97f9b6663a109..478b1d951d071 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,8 +26,8 @@ install: - IF %PHP%==1 cd ext - IF %PHP%==1 appveyor DownloadFile http://nebm.ist.utl.pt/~glopes/misc/intl_win/php_intl-3.0.0-5.3-nts-vc9-x86.zip - IF %PHP%==1 7z x php_intl-3.0.0-5.3-nts-vc9-x86.zip -y >nul - - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/apcu/4.0.7/php_apcu-4.0.7-5.3-nts-vc9-x86.zip - - IF %PHP%==1 7z x php_apcu-4.0.7-5.3-nts-vc9-x86.zip -y >nul + - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/apcu/4.0.8/php_apcu-4.0.8-5.3-nts-vc9-x86.zip + - IF %PHP%==1 7z x php_apcu-4.0.8-5.3-nts-vc9-x86.zip -y >nul - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/memcache/3.0.8/php_memcache-3.0.8-5.3-nts-vc9-x86.zip - IF %PHP%==1 7z x php_memcache-3.0.8-5.3-nts-vc9-x86.zip -y >nul - IF %PHP%==1 del /Q *.zip diff --git a/phpunit b/phpunit index 0b8e2e3d3c0f6..78710c12744ce 100755 --- a/phpunit +++ b/phpunit @@ -11,7 +11,7 @@ */ // Please update when phpunit needs to be reinstalled with fresh deps: -// Cache-Id-Version: 2015-11-09 12:13 UTC +// Cache-Id-Version: 2015-11-28 09:05 UTC use Symfony\Component\Process\ProcessUtils; @@ -23,12 +23,15 @@ $PHPUNIT_VERSION = PHP_VERSION_ID >= 70000 ? '5.0' : '4.8'; $PHPUNIT_DIR = __DIR__.'/.phpunit'; $PHP = defined('PHP_BINARY') ? PHP_BINARY : 'php'; $PHP = ProcessUtils::escapeArgument($PHP); +if ('phpdbg' === PHP_SAPI) { + $PHP .= ' -qrr'; +} $COMPOSER = file_exists($COMPOSER = __DIR__.'/composer.phar') || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? `where.exe composer.phar` : `which composer.phar`)) ? $PHP.' '.ProcessUtils::escapeArgument($COMPOSER) : 'composer'; -if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__FILE__) !== @file_get_contents("$PHPUNIT_DIR/.md5")) { +if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__FILE__) !== @file_get_contents("$PHPUNIT_DIR/.$PHPUNIT_VERSION.md5")) { // Build a standalone phpunit without symfony/yaml $oldPwd = getcwd(); @@ -49,7 +52,6 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ $zip->close(); chdir("phpunit-$PHPUNIT_VERSION"); passthru("$COMPOSER remove --no-update symfony/yaml"); - passthru("$COMPOSER require --no-update phpunit/phpunit-mock-objects \"<=3.0.0\""); passthru("$COMPOSER require --dev --no-update symfony/phpunit-bridge \">=2.8@dev\""); passthru("$COMPOSER install --prefer-source --no-progress --ansi"); file_put_contents('phpunit', <<nul': 'rm -rf %s', str_replace('/', DIRECTORY_SEPARATOR, "phpunit-$PHPUNIT_VERSION/vendor/symfony/phpunit-bridge"))); symlink(realpath('../src/Symfony/Bridge/PhpUnit'), "phpunit-$PHPUNIT_VERSION/vendor/symfony/phpunit-bridge"); } - file_put_contents('.md5', md5_file(__FILE__)); + file_put_contents(".$PHPUNIT_VERSION.md5", md5_file(__FILE__)); chdir($oldPwd); } @@ -162,7 +164,8 @@ if (isset($argv[1]) && 'symfony' === $argv[1]) { unlink($file); } - if ($procStatus) { + // Fail on any individual component failures but ignore STATUS_STACK_BUFFER_OVERRUN (-1073740791) on Windows when APCu is enabled + if ($procStatus && ('\\' !== DIRECTORY_SEPARATOR || !extension_loaded('apcu') || !ini_get('apc.enable_cli') || -1073740791 !== $procStatus)) { $exit = 1; echo "\033[41mKO\033[0m $component\n\n"; } else { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 726b91ef23668..0538884cc43f5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -40,7 +40,21 @@ ./src/Symfony/Bundle/*/Resources ./src/Symfony/Component/*/Resources ./src/Symfony/Component/*/*/Resources + ./src/Symfony/Bridge/*/vendor + ./src/Symfony/Bundle/*/vendor + ./src/Symfony/Component/*/vendor + ./src/Symfony/Component/*/*/vendor + + + + + + Symfony\Component\HttpFoundation + + + + diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php index 5980d9c734c54..8bf2fb804f939 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php @@ -13,6 +13,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php index 7324f721ec340..02e117d7baec4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php @@ -15,6 +15,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * Test choices generated from an entity with a primary foreign key. * diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php index 74af66db360e2..85ea10a451c22 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php @@ -13,6 +13,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php index 56b4c21319826..1bd5ac8de4695 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php @@ -13,6 +13,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php index 4f3d54a30f15f..b2d5b4a888b37 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php @@ -16,6 +16,10 @@ use Doctrine\ORM\Tools\SchemaTool; use Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php index a2ee7cdc8a64f..21fa4c56a0b57 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php index 60e3797bac865..73344be670eba 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Premi Giorgio * @author Bernhard Schussek diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php index f655784004fbb..f64e0cec3e538 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php index 629b399ac36a7..d0b04b8013656 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php index 114eee661efe5..305f365075ebe 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php index 422295feb1e04..75e36f90cf4e3 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php index 20267bd020775..ee3afeb89909f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Premi Giorgio * @author Bernhard Schussek diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php index 45ab799585cb6..1b637949bbd09 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Premi Giorgio * @author Bernhard Schussek diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php index 0668c09bdb63f..0197f19b3b1fa 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php index c093782ff0ec7..4e75cf7491cca 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php index 39363bae26a25..3e9847f0e2eff 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php index 23329e80df3c2..51adaabba80db 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index fb78d65ef1813..81d00fc7354ba 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -995,13 +995,13 @@ public function testLoaderCaching() 'property3' => 2, )); - $choiceList1 = $form->get('property1')->getConfig()->getOption('choice_list'); - $choiceList2 = $form->get('property2')->getConfig()->getOption('choice_list'); - $choiceList3 = $form->get('property3')->getConfig()->getOption('choice_list'); + $choiceLoader1 = $form->get('property1')->getConfig()->getOption('choice_loader'); + $choiceLoader2 = $form->get('property2')->getConfig()->getOption('choice_loader'); + $choiceLoader3 = $form->get('property3')->getConfig()->getOption('choice_loader'); - $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $choiceList1); - $this->assertSame($choiceList1, $choiceList2); - $this->assertSame($choiceList1, $choiceList3); + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $choiceLoader1); + $this->assertSame($choiceLoader1, $choiceLoader2); + $this->assertSame($choiceLoader1, $choiceLoader3); } public function testCacheChoiceLists() @@ -1024,8 +1024,8 @@ public function testCacheChoiceLists() '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')); + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $field1->getConfig()->getOption('choice_loader')); + $this->assertSame($field1->getConfig()->getOption('choice_loader'), $field2->getConfig()->getOption('choice_loader')); } /** diff --git a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist index 5b576e52c95e9..c006d232219a4 100644 --- a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist +++ b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist @@ -22,6 +22,7 @@ ./Resources ./Tests + ./vendor diff --git a/src/Symfony/Bridge/Monolog/phpunit.xml.dist b/src/Symfony/Bridge/Monolog/phpunit.xml.dist index 34063ac548676..8a60f06a7a310 100644 --- a/src/Symfony/Bridge/Monolog/phpunit.xml.dist +++ b/src/Symfony/Bridge/Monolog/phpunit.xml.dist @@ -22,6 +22,7 @@ ./Resources ./Tests + ./vendor diff --git a/src/Symfony/Bridge/PhpUnit/ClockMock.php b/src/Symfony/Bridge/PhpUnit/ClockMock.php index 4b745e38e87fc..b81fee965880a 100644 --- a/src/Symfony/Bridge/PhpUnit/ClockMock.php +++ b/src/Symfony/Bridge/PhpUnit/ClockMock.php @@ -66,7 +66,7 @@ public static function microtime($asFloat = false) return self::$now; } - return sprintf("%0.6f %d\n", $now - (int) $now, (int) self::$now); + return sprintf("%0.6f %d\n", self::$now - (int) self::$now, (int) self::$now); } public static function register($class) diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 063723d32db29..81f59beeccde2 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -47,16 +47,37 @@ public static function register($mode = false) // No-op } - if (0 !== error_reporting()) { - $group = 'unsilenced'; - $ref = &$deprecations[$group][$msg]['count']; - ++$ref; - } elseif (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { + if (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { $class = isset($trace[$i]['object']) ? get_class($trace[$i]['object']) : $trace[$i]['class']; $method = $trace[$i]['function']; - $group = 0 === strpos($method, 'testLegacy') || 0 === strpos($method, 'provideLegacy') || 0 === strpos($method, 'getLegacy') || strpos($class, '\Legacy') || in_array('legacy', \PHPUnit_Util_Test::getGroups($class, $method), true) ? 'legacy' : 'remaining'; + if (0 !== error_reporting()) { + $group = 'unsilenced'; + } elseif (0 === strpos($method, 'testLegacy') + || 0 === strpos($method, 'provideLegacy') + || 0 === strpos($method, 'getLegacy') + || strpos($class, '\Legacy') + || in_array('legacy', \PHPUnit_Util_Test::getGroups($class, $method), true) + ) { + $group = 'legacy'; + } else { + $group = 'remaining'; + } + + if (isset($mode[0]) && '/' === $mode[0] && preg_match($mode, $class.'::'.$method)) { + $e = new \Exception($msg); + $r = new \ReflectionProperty($e, 'trace'); + $r->setAccessible(true); + $r->setValue($e, array_slice($trace, 1, $i)); + echo "\n".ucfirst($group).' deprecation triggered by '.$class.'::'.$method.':'; + echo "\n".$msg; + echo "\nStack trace:"; + echo "\n".str_replace(' '.getcwd().DIRECTORY_SEPARATOR, ' ', $e->getTraceAsString()); + echo "\n"; + + exit(1); + } if ('legacy' !== $group && 'weak' !== $mode) { $ref = &$deprecations[$group][$msg]['count']; ++$ref; @@ -78,7 +99,7 @@ public static function register($mode = false) restore_error_handler(); self::register($mode); } - } else { + } elseif (!isset($mode[0]) || '/' !== $mode[0]) { self::$isRegistered = true; if (self::hasColorSupport()) { $colorize = function ($str, $red) { diff --git a/src/Symfony/Bridge/PhpUnit/README.md b/src/Symfony/Bridge/PhpUnit/README.md index 7b3a7ef67762c..9d7ca89838c6e 100644 --- a/src/Symfony/Bridge/PhpUnit/README.md +++ b/src/Symfony/Bridge/PhpUnit/README.md @@ -8,7 +8,8 @@ It comes with the following features: * disable the garbage collector; * enforce a consistent `C` locale; * auto-register `class_exists` to load Doctrine annotations; - * print a user deprecation notices summary at the end of the test suite. + * print a user deprecation notices summary at the end of the test suite; + * display the stack trace of a deprecation on-demand. By default any non-legacy-tagged or any non-@-silenced deprecation notices will make tests fail. @@ -51,3 +52,9 @@ You have to decide either to: * update your code to not use deprecated interfaces anymore, thus gaining better forward compatibility; * or move them to the **Legacy** section (by using one of the above way). + +In case you need to inspect the stack trace of a particular deprecation triggered by +one of your unit tests, you can set the `SYMFONY_DEPRECATIONS_HELPER` env var to +a regexp that matches this test case's `class::method` name. For example, +`SYMFONY_DEPRECATIONS_HELPER=/^MyTest::testMethod$/ phpunit` will stop your test +suite once a deprecation is triggered by the `MyTest::testMethod` test. diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig index 767e2798f3ee5..e997615d11378 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig @@ -25,15 +25,13 @@ col-sm-2 {# Rows #} {% block form_row -%} -{% spaceless %}
- {{ form_label(form) }} + {{- form_label(form) -}}
- {{ form_widget(form) }} - {{ form_errors(form) }} + {{- form_widget(form) -}} + {{- form_errors(form) -}}
-
-{% endspaceless %} +{##} {%- endblock form_row %} {% block checkbox_row -%} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 89f9910890879..81b6dcee9135d 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -57,7 +57,7 @@ {%- endif -%} {% if placeholder is not none -%} - + {%- endif %} {%- if preferred_choices|length > 0 -%} {% set options = preferred_choices %} @@ -253,7 +253,7 @@ {% endif %} {{ widget|raw }} - {{ label|trans({}, translation_domain) }} + {{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }} {%- endblock checkbox_radio_label %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php new file mode 100644 index 0000000000000..bf26dda05be92 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Form\TwigRenderer; +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\FormView; +use Symfony\Component\Form\Tests\AbstractBootstrap3HorizontalLayoutTest; + +class FormExtensionBootstrap3HorizontalLayoutTest extends AbstractBootstrap3HorizontalLayoutTest +{ + /** + * @var FormExtension + */ + protected $extension; + + protected $testableFeatures = array( + 'choice_attr', + ); + + protected function setUp() + { + parent::setUp(); + + $rendererEngine = new TwigRendererEngine(array( + 'bootstrap_3_horizontal_layout.html.twig', + 'custom_widgets.html.twig', + )); + $renderer = new TwigRenderer($rendererEngine, $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')); + + $this->extension = new FormExtension($renderer); + + $loader = new StubFilesystemLoader(array( + __DIR__.'/../../Resources/views/Form', + __DIR__.'/Fixtures/templates/form', + )); + + $environment = new \Twig_Environment($loader, array('strict_variables' => true)); + $environment->addExtension(new TranslationExtension(new StubTranslator())); + $environment->addExtension($this->extension); + + $this->extension->initRuntime($environment); + } + + protected function tearDown() + { + parent::tearDown(); + + $this->extension = null; + } + + protected function renderForm(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form', $vars); + } + + protected function renderEnctype(FormView $view) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'enctype'); + } + + protected function renderLabel(FormView $view, $label = null, array $vars = array()) + { + if ($label !== null) { + $vars += array('label' => $label); + } + + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'label', $vars); + } + + protected function renderErrors(FormView $view) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'errors'); + } + + protected function renderWidget(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'widget', $vars); + } + + protected function renderRow(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'row', $vars); + } + + protected function renderRest(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'rest', $vars); + } + + protected function renderStart(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form_start', $vars); + } + + protected function renderEnd(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form_end', $vars); + } + + protected function setTheme(FormView $view, array $themes) + { + $this->extension->renderer->setTheme($view, $themes); + } +} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 32fcb84a080d9..5b002979a88df 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -22,7 +22,7 @@ "require-dev": { "symfony/asset": "~2.7|~3.0.0", "symfony/finder": "~2.3|~3.0.0", - "symfony/form": "~2.8", + "symfony/form": "~2.8,>2.8-BETA1", "symfony/http-kernel": "~2.8|~3.0.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/routing": "~2.2|~3.0.0", diff --git a/src/Symfony/Bundle/DebugBundle/phpunit.xml.dist b/src/Symfony/Bundle/DebugBundle/phpunit.xml.dist index cbde5628a3987..90ec0a5dba514 100644 --- a/src/Symfony/Bundle/DebugBundle/phpunit.xml.dist +++ b/src/Symfony/Bundle/DebugBundle/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./Tests ./Resources + ./Tests ./vendor diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php index 309ede6d068d3..f743cfbe3312e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php @@ -74,7 +74,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $output = new SymfonyStyle($input, $output); + $output = new SymfonyStyle($input, $cliOutput = $output); if (!extension_loaded('pcntl')) { $output->error(array( @@ -85,7 +85,7 @@ protected function execute(InputInterface $input, OutputInterface $output) if ($output->ask('Do you want to execute server:run immediately? [Yn] ', true)) { $command = $this->getApplication()->find('server:run'); - return $command->run($input, $output); + return $command->run($input, $cliOutput); } return 1; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php index 4b36468aa3f0a..c5d639b808e18 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php @@ -67,7 +67,7 @@ public function process(ContainerBuilder $container) @trigger_error('The alias option of the form.type_extension tag is deprecated since version 2.8 and will be removed in 3.0. Use the extended_type option instead.', E_USER_DEPRECATED); $extendedType = $tag[0]['alias']; } else { - @trigger_error('The extended_type option of the form.type_extension tag is required since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + @trigger_error('The extended_type option of the form.type_extension tag is required since version 2.8.', E_USER_DEPRECATED); $extendedType = $serviceId; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml index d4ce4f5d246f6..33a0a661f8991 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml @@ -13,7 +13,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php index ca0cd1ee1cefb..432ccff9204f1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php @@ -33,6 +33,10 @@ $_SERVER = array_merge($_SERVER, $_ENV); $_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.'app_dev.php'; +// Since we are rewriting to app_dev.php, adjust SCRIPT_NAME and PHP_SELF accordingly +$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR.'app_dev.php'; +$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR.'app_dev.php'; + require 'app_dev.php'; error_log(sprintf('%s:%d [%d]: %s', $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT'], http_response_code(), $_SERVER['REQUEST_URI']), 4); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_prod.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_prod.php index 1c6b99b866b74..97613a6248f71 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_prod.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_prod.php @@ -33,6 +33,10 @@ $_SERVER = array_merge($_SERVER, $_ENV); $_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.'app.php'; +// Since we are rewriting to app.php, adjust SCRIPT_NAME and PHP_SELF accordingly +$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR.'app.php'; +$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR.'app.php'; + require 'app.php'; error_log(sprintf('%s:%d [%d]: %s', $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT'], http_response_code(), $_SERVER['REQUEST_URI']), 4); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php index ac1077a205ae3..2be960d0e179c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php @@ -1,7 +1,7 @@ id="escape($id) ?>" name="escape($full_name) ?>" disabled="disabled" $v): ?> -escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> +escape($k), $view->escape(false !== $translation_domain ? $view['translator']->trans($v, array(), $translation_domain) : $v)) ?> escape($k), $view->escape($k)) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php index 9dac32fc994c0..4b63876984506 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php @@ -1,4 +1,4 @@ $name, '%id%' => $id)) : $view['form']->humanize($name); } ?> - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php index fe4fbdb348a32..29ea388010758 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_collapsed.html.php @@ -7,7 +7,7 @@ )) ?> multiple="multiple" > - + 0): ?> block($form, 'choice_widget_options', array('choices' => $preferred_choices)) ?> 0 && null !== $separator): ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php index ac5a481d0b55e..3fefa47c15c99 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php @@ -2,7 +2,7 @@ required="required" $v): ?> -escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> +escape($k), $view->escape(false !== $translation_domain ? $view['translator']->trans($v, array(), $translation_domain) : $v)) ?> escape($k), $view->escape($k)) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php index 327925a537196..dc2e5ebea84e6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_container_attributes.html.php @@ -1,7 +1,7 @@ id="escape($id) ?>" $v): ?> -escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> +escape($k), $view->escape(false !== $translation_domain ? $view['translator']->trans($v, array(), $translation_domain) : $v)) ?> escape($k), $view->escape($k)) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php index 04f934c4fe525..aa8b66929c7ce 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Test; +use Symfony\Component\DependencyInjection\ResettableContainerInterface; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\KernelInterface; @@ -171,7 +172,11 @@ protected static function createKernel(array $options = array()) protected static function ensureKernelShutdown() { if (null !== static::$kernel) { + $container = static::$kernel->getContainer(); static::$kernel->shutdown(); + if ($container instanceof ResettableContainerInterface) { + $container->reset(); + } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist b/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist index 269adda917a1b..1d25eeb3304c5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist +++ b/src/Symfony/Bundle/FrameworkBundle/phpunit.xml.dist @@ -20,9 +20,9 @@ ./ - ./vendor ./Resources ./Tests + ./vendor diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 21083ddbc92c0..11873ee9fae7d 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * deprecated the `key` setting of `anonymous`, `remember_me` and `http_digest` in favor of the `secret` setting. + * deprecated the `intention` firewall listener setting in favor of the `csrf_token_id`. 2.6.0 ----- diff --git a/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php index fff5b1e929503..14271dc459a08 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php @@ -23,6 +23,18 @@ */ class InitAclCommand extends ContainerAwareCommand { + /** + * {@inheritdoc} + */ + public function isEnabled() + { + if (!$this->getContainer()->has('security.acl.dbal.connection')) { + return false; + } + + return parent::isEnabled(); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index c07794b6b4727..c85c3df907c38 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -242,6 +242,8 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->beforeNormalization() ->ifTrue(function ($v) { return isset($v['csrf_provider']); }) ->then(function ($v) { + @trigger_error("Setting the 'csrf_provider' configuration key on a security firewall is deprecated since version 2.8 and will be removed in 3.0. Use the 'csrf_token_generator' configuration key instead.", E_USER_DEPRECATED); + $v['csrf_token_generator'] = $v['csrf_provider']; unset($v['csrf_provider']); @@ -251,6 +253,8 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->beforeNormalization() ->ifTrue(function ($v) { return isset($v['intention']); }) ->then(function ($v) { + @trigger_error("Setting the 'intention' configuration key on a security firewall is deprecated since version 2.8 and will be removed in 3.0. Use the 'csrf_token_id' key instead.", E_USER_DEPRECATED); + $v['csrf_token_id'] = $v['intention']; unset($v['intention']); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php index ac9523c507208..aa81aa8b92f63 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php @@ -29,7 +29,7 @@ public function __construct() $this->addOption('username_parameter', '_username'); $this->addOption('password_parameter', '_password'); $this->addOption('csrf_parameter', '_csrf_token'); - $this->addOption('intention', 'authenticate'); + $this->addOption('csrf_token_id', 'authenticate'); $this->addOption('post_only', true); } @@ -48,8 +48,23 @@ public function addConfiguration(NodeDefinition $node) parent::addConfiguration($node); $node + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['csrf_provider']) && isset($v['csrf_token_generator']); }) + ->thenInvalid("You should define a value for only one of 'csrf_provider' and 'csrf_token_generator' on a security firewall. Use 'csrf_token_generator' as this replaces 'csrf_provider'.") + ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['csrf_provider']); }) + ->then(function ($v) { + @trigger_error("Setting the 'csrf_provider' configuration key on a security firewall is deprecated since version 2.8 and will be removed in 3.0. Use the 'csrf_token_generator' configuration key instead.", E_USER_DEPRECATED); + + $v['csrf_token_generator'] = $v['csrf_provider']; + unset($v['csrf_provider']); + + return $v; + }) + ->end() ->children() - ->scalarNode('csrf_provider')->cannotBeEmpty()->end() + ->scalarNode('csrf_token_generator')->cannotBeEmpty()->end() ->end() ; } @@ -78,7 +93,7 @@ protected function createListener($container, $id, $config, $userProvider) $container ->getDefinition($listenerId) - ->addArgument(isset($config['csrf_provider']) ? new Reference($config['csrf_provider']) : null) + ->addArgument(isset($config['csrf_token_generator']) ? new Reference($config['csrf_token_generator']) : null) ; return $listenerId; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 5914e3fe2be33..5f45b1f794bdb 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -168,7 +168,7 @@ private function configureDbalAclProvider(array $config, ContainerBuilder $conta */ private function createRoleHierarchy($config, ContainerBuilder $container) { - if (!isset($config['role_hierarchy'])) { + if (!isset($config['role_hierarchy']) || 0 === count($config['role_hierarchy'])) { $container->removeDefinition('security.access.role_hierarchy_voter'); return; @@ -299,7 +299,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.logout_listener')); $listener->replaceArgument(3, array( 'csrf_parameter' => $firewall['logout']['csrf_parameter'], - 'intention' => $firewall['logout']['csrf_token_id'], + 'csrf_token_id' => $firewall['logout']['csrf_token_id'], 'logout_path' => $firewall['logout']['path'], )); $listeners[] = new Reference($listenerId); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index a729b421f4b09..bbcae99853aa2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -47,6 +47,7 @@ Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator Symfony\Component\Security\Core\Authorization\ExpressionLanguage + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php index 9d8009ea8a9e0..915c2326ca594 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php @@ -69,6 +69,32 @@ public function testManyConfigForProvider() } public function testCsrfAliases() + { + $config = array( + 'firewalls' => array( + 'stub' => array( + 'logout' => array( + 'csrf_token_generator' => 'a_token_generator', + 'csrf_token_id' => 'a_token_id', + ), + ), + ), + ); + $config = array_merge(static::$minimalConfig, $config); + + $processor = new Processor(); + $configuration = new MainConfiguration(array(), array()); + $processedConfig = $processor->processConfiguration($configuration, array($config)); + $this->assertTrue(isset($processedConfig['firewalls']['stub']['logout']['csrf_token_generator'])); + $this->assertEquals('a_token_generator', $processedConfig['firewalls']['stub']['logout']['csrf_token_generator']); + $this->assertTrue(isset($processedConfig['firewalls']['stub']['logout']['csrf_token_id'])); + $this->assertEquals('a_token_id', $processedConfig['firewalls']['stub']['logout']['csrf_token_id']); + } + + /** + * @group legacy + */ + public function testLegacyCsrfAliases() { $config = array( 'firewalls' => array( diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 4dd33020c572d..dce30c758b976 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -94,6 +94,30 @@ public function testFirewallWithInvalidUserProvider() $container->compile(); } + public function testDisableRoleHierarchyVoter() + { + $container = $this->getRawContainer(); + + $container->loadFromExtension('security', array( + 'providers' => array( + 'default' => array('id' => 'foo'), + ), + + 'role_hierarchy' => null, + + 'firewalls' => array( + 'some_firewall' => array( + 'pattern' => '/.*', + 'http_basic' => null, + ), + ), + )); + + $container->compile(); + + $this->assertFalse($container->hasDefinition('security.access.role_hierarchy_voter')); + } + protected function getRawContainer() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php index 48b87fbecbfc1..0f4cd6bb844eb 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php @@ -79,12 +79,12 @@ public function buildForm(FormBuilderInterface $builder, array $options) */ public function configureOptions(OptionsResolver $resolver) { - /* Note: the form's intention must correspond to that for the form login + /* Note: the form's csrf_token_id must correspond to that for the form login * listener in order for the CSRF token to validate successfully. */ $resolver->setDefaults(array( - 'intention' => 'authenticate', + 'csrf_token_id' => 'authenticate', )); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml index ffcc9352d8260..5a00ac329895d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml @@ -36,12 +36,12 @@ security: username_parameter: "user_login[username]" password_parameter: "user_login[password]" csrf_parameter: "user_login[_token]" - csrf_provider: security.csrf.token_manager + csrf_token_generator: security.csrf.token_manager anonymous: ~ logout: path: /logout_path target: / - csrf_provider: security.csrf.token_manager + csrf_token_generator: security.csrf.token_manager access_control: - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/phpunit.xml.dist b/src/Symfony/Bundle/SecurityBundle/phpunit.xml.dist index 0ff2b570e2736..a7fdc326c4571 100644 --- a/src/Symfony/Bundle/SecurityBundle/phpunit.xml.dist +++ b/src/Symfony/Bundle/SecurityBundle/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./Tests ./Resources + ./Tests ./vendor diff --git a/src/Symfony/Bundle/TwigBundle/phpunit.xml.dist b/src/Symfony/Bundle/TwigBundle/phpunit.xml.dist index 763b8b68bf826..9a8c38f26ef33 100644 --- a/src/Symfony/Bundle/TwigBundle/phpunit.xml.dist +++ b/src/Symfony/Bundle/TwigBundle/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./Tests ./Resources + ./Tests ./vendor diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index 8ba4d7025ac63..3fb596b0960d2 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -162,13 +162,14 @@ {% endif %} {% if stack %} - + {% endif %} {% for index, call in stack if index > 1 %} {% if index == 2 %}
    {% endif %} + {% if call.class is defined %} {% set from = call.class|abbr_class ~ '::' ~ call.function|abbr_method() %} {% elseif call.function is defined %} @@ -179,7 +180,14 @@ {% set from = '-' %} {% endif %} -
  • Called from {{ call.file is defined and call.line is defined ? call.file|format_file(call.line, from) : from|raw }}
  • + {% set file_name = (call.file is defined and call.line is defined) ? call.file|replace({'\\': '/'})|split('/')|last %} + +
  • + {{ from|raw }} + {% if file_name %} + (called from {{ call.file|format_file(call.line, file_name)|raw }}) + {% endif %} +
  • {% if index == stack|length - 1 %}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig index 2f057958ca473..0e0ffe52ddf59 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig @@ -808,6 +808,16 @@ table.logs .metadata strong { color: #222; } +table.logs .sf-call-stack { + margin: 1em 0 1em 1.5em; +} +table.logs .sf-call-stack li { + margin-bottom: 5px; +} +table.logs .sf-call-stack abbr { + border: none; +} + {# Doctrine panel ========================================================================= #} .sql-runnable { @@ -815,6 +825,11 @@ table.logs .metadata strong { margin: .5em 0; padding: 1em; } +.queries-table pre { + {{ mixins.break_long_words|raw }} + margin: 0; + white-space: pre-wrap; +} {# Dump panel ========================================================================= #} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig index 2cb44f87335bb..c0456f61310fb 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -1,14 +1,24 @@ .sf-minitoolbar { background-color: #222; + border-top-left-radius: 4px; bottom: 0; display: none; - height: 36px; - padding: 5px 6px 0; + height: 30px; + padding: 6px 6px 0; position: fixed; right: 0; z-index: 99999; } +.sf-minitoolbar a { + display: block; +} +.sf-minitoolbar svg, +.sf-minitoolbar img { + max-height: 24px; + max-width: 24px; +} + .sf-toolbarreset * { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; @@ -36,7 +46,8 @@ } .sf-toolbarreset svg, .sf-toolbarreset img { - max-height: 20px; + max-height: 24px; + max-width: 24px; } .sf-toolbarreset .hide-button { @@ -348,6 +359,8 @@ /* Override the setting when the toolbar is on the top */ {% if position == 'top' %} .sf-minitoolbar { + border-bottom-left-radius: 4px; + border-top-left-radius: 0; bottom: auto; right: 0; top: 0; diff --git a/src/Symfony/Bundle/WebProfilerBundle/phpunit.xml.dist b/src/Symfony/Bundle/WebProfilerBundle/phpunit.xml.dist index f449adf319706..2bcccd6667a26 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/phpunit.xml.dist +++ b/src/Symfony/Bundle/WebProfilerBundle/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./Tests ./Resources + ./Tests ./vendor diff --git a/src/Symfony/Component/Asset/Package.php b/src/Symfony/Component/Asset/Package.php index 43bdcf21db68a..77b1c934eb172 100644 --- a/src/Symfony/Component/Asset/Package.php +++ b/src/Symfony/Component/Asset/Package.php @@ -60,6 +60,9 @@ protected function getContext() return $this->context; } + /** + * @return VersionStrategyInterface + */ protected function getVersionStrategy() { return $this->versionStrategy; diff --git a/src/Symfony/Component/Asset/UrlPackage.php b/src/Symfony/Component/Asset/UrlPackage.php index 6381a9c1cdbdf..3626ec843c48b 100644 --- a/src/Symfony/Component/Asset/UrlPackage.php +++ b/src/Symfony/Component/Asset/UrlPackage.php @@ -39,10 +39,11 @@ class UrlPackage extends Package private $sslPackage; /** - * @param string|array $baseUrls Base asset URLs + * @param string|string[] $baseUrls Base asset URLs * @param VersionStrategyInterface $versionStrategy The version strategy + * @param ContextInterface|null $context Context */ - public function __construct($baseUrls = array(), VersionStrategyInterface $versionStrategy, ContextInterface $context = null) + public function __construct($baseUrls, VersionStrategyInterface $versionStrategy, ContextInterface $context = null) { parent::__construct($versionStrategy, $context); diff --git a/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php index 6028eb57fe5dd..857cf9432bfa3 100644 --- a/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php +++ b/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php @@ -21,6 +21,10 @@ class StaticVersionStrategy implements VersionStrategyInterface private $version; private $format; + /** + * @param string $version Version number + * @param string $format Url format + */ public function __construct($version, $format = null) { $this->version = $version; diff --git a/src/Symfony/Component/Asset/phpunit.xml.dist b/src/Symfony/Component/Asset/phpunit.xml.dist index 3e53da45d9ea3..b66906d6dab39 100644 --- a/src/Symfony/Component/Asset/phpunit.xml.dist +++ b/src/Symfony/Component/Asset/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./vendor ./Tests + ./vendor diff --git a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php index c7dc3ae12e0f0..e33bcbbbc6274 100644 --- a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php +++ b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php @@ -137,8 +137,8 @@ public static function load($classes, $cacheDir, $name, $autoReload, $adaptive = public static function fixNamespaceDeclarations($source) { if (!function_exists('token_get_all') || !self::$useTokenizer) { - if (preg_match('/namespace(.*?)\s*;/', $source)) { - $source = preg_replace('/namespace(.*?)\s*;/', "namespace$1\n{", $source)."}\n"; + if (preg_match('/(^|\s)namespace(.*?)\s*;/', $source)) { + $source = preg_replace('/(^|\s)namespace(.*?)\s*;/', "$1namespace$2\n{", $source)."}\n"; } return $source; diff --git a/src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php b/src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php index 2d78941538191..5019f26ee84c8 100644 --- a/src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php +++ b/src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php @@ -205,7 +205,7 @@ public function getFixNamespaceDeclarationsDataWithoutTokenizer() array("namespace Bar ;\nclass Foo {}\n", "namespace Bar\n{\nclass Foo {}\n}\n"), array("namespace Foo\Bar;\nclass Foo {}\n", "namespace Foo\Bar\n{\nclass Foo {}\n}\n"), array("namespace Foo\Bar\Bar\n{\nclass Foo {}\n}\n", "namespace Foo\Bar\Bar\n{\nclass Foo {}\n}\n"), - array("namespace\n{\nclass Foo {}\n}\n", "namespace\n{\nclass Foo {}\n}\n"), + array("\nnamespace\n{\nclass Foo {}\n\$namespace=123;}\n", "\nnamespace\n{\nclass Foo {}\n\$namespace=123;}\n"), ); } diff --git a/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php b/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php index 631755e8fe926..565ff8bb50e8b 100644 --- a/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php +++ b/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php @@ -29,7 +29,7 @@ public function supports(ResourceInterface $metadata) public function isFresh(ResourceInterface $resource, $timestamp) { - @trigger_error('Resource checking through ResourceInterface::isFresh() is deprecated since 2.8 and will be removed in 3.0', E_USER_DEPRECATED); + @trigger_error(sprintf('The class "%s" is performing resource checking through ResourceInterface::isFresh(), which is deprecated since 2.8 and will be removed in 3.0', get_class($resource)), E_USER_DEPRECATED); return parent::isFresh($resource, $timestamp); // For now, $metadata features the isFresh() method, so off we go (quack quack) } diff --git a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php index 57e09e4d81c36..f2895bd8e4d17 100644 --- a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php @@ -236,10 +236,10 @@ private function writeText($content, array $options = array()) private function formatDefaultValue($default) { if (PHP_VERSION_ID < 50400) { - return str_replace('\/', '/', json_encode($default)); + return str_replace(array('\/', '\\\\'), array('/', '\\'), json_encode($default)); } - return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } /** diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php index aeb393b4d4f11..c69b0dc61dfc4 100644 --- a/src/Symfony/Component/Console/Style/SymfonyStyle.php +++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php @@ -303,7 +303,7 @@ public function createProgressBar($max = 0) { $progressBar = parent::createProgressBar($max); - if ('\\' === DIRECTORY_SEPARATOR) { + if ('\\' !== DIRECTORY_SEPARATOR) { $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 $progressBar->setProgressCharacter(''); $progressBar->setBarCharacter('▓'); // dark shade character \u2593 diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php index 8c797744d7b05..95ccc8798e8c1 100644 --- a/src/Symfony/Component/Debug/DebugClassLoader.php +++ b/src/Symfony/Component/Debug/DebugClassLoader.php @@ -147,7 +147,7 @@ public function loadClass($class) try { if ($this->isFinder) { if ($file = $this->classLoader[0]->findFile($class)) { - require $file; + require_once $file; } } else { call_user_func($this->classLoader, $class); diff --git a/src/Symfony/Component/Debug/README.md b/src/Symfony/Component/Debug/README.md index 67e6d6c2785c6..82c6e30322de4 100644 --- a/src/Symfony/Component/Debug/README.md +++ b/src/Symfony/Component/Debug/README.md @@ -15,6 +15,7 @@ Debug::enable(); You can also use the tools individually: ```php +use Symfony\Component\Debug\DebugClassLoader; use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\Debug\ExceptionHandler; @@ -25,11 +26,9 @@ if ('cli' !== php_sapi_name()) { ini_set('display_errors', 1); } ErrorHandler::register(); +DebugClassLoader::enable(); ``` -Note that the `Debug::enable()` call also registers the debug class loader -from the Symfony ClassLoader component when available. - This component can optionally take advantage of the features of the HttpKernel and HttpFoundation components. diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 81470a8398eed..ec7880be89b95 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -227,7 +227,7 @@ private function set($type, $id) */ private function createAutowiredDefinition(\ReflectionClass $typeHint, $id) { - if (!$typeHint->isInstantiable()) { + if (isset($this->notGuessableTypes[$typeHint->name]) || !$typeHint->isInstantiable()) { throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s".', $typeHint->name, $id)); } diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 9351258db0be2..e11e3df4bfd6a 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -312,10 +312,7 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE $service = $this->$method(); } catch (\Exception $e) { unset($this->loading[$id]); - - if (array_key_exists($id, $this->services)) { - unset($this->services[$id]); - } + unset($this->services[$id]); if ($e instanceof InactiveScopeException && self::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) { return; diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index e322e1b745bd9..1347f99f1f628 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -936,7 +936,7 @@ public function createService(Definition $definition, $id, $tryProxy = true) $this->callMethod($service, $call); } - $properties = $this->resolveServices($parameterBag->resolveValue($definition->getProperties())); + $properties = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties()))); foreach ($properties as $name => $value) { $service->$name = $value; } @@ -1134,7 +1134,7 @@ private function callMethod($service, $call) } } - call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->resolveValue($call[1]))); + call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])))); } /** diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index 66ea2cc0e4146..6b7138e0d7e5c 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -501,9 +501,7 @@ public function hasTag($name) */ public function clearTag($name) { - if (isset($this->tags[$name])) { - unset($this->tags[$name]); - } + unset($this->tags[$name]); return $this; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index cc29ea93e02f2..f1ed72e94a4cd 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -117,6 +117,56 @@ public function testTypeCollision() $pass->process($container); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" for the service "a". + */ + public function testTypeNotGuessable() + { + $container = new ContainerBuilder(); + + $container->register('a1', __NAMESPACE__.'\Foo'); + $container->register('a2', __NAMESPACE__.'\Foo'); + $aDefinition = $container->register('a', __NAMESPACE__.'\NotGuessableArgument'); + $aDefinition->setAutowired(true); + + $pass = new AutowirePass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\A" for the service "a". + */ + public function testTypeNotGuessableWithSubclass() + { + $container = new ContainerBuilder(); + + $container->register('a1', __NAMESPACE__.'\B'); + $container->register('a2', __NAMESPACE__.'\B'); + $aDefinition = $container->register('a', __NAMESPACE__.'\NotGuessableArgumentForSubclass'); + $aDefinition->setAutowired(true); + + $pass = new AutowirePass(); + $pass->process($container); + } + + public function testTypeNotGuessableWithTypeSet() + { + $container = new ContainerBuilder(); + + $container->register('a1', __NAMESPACE__.'\Foo'); + $container->register('a2', __NAMESPACE__.'\Foo')->addAutowiringType(__NAMESPACE__.'\Foo'); + $aDefinition = $container->register('a', __NAMESPACE__.'\NotGuessableArgument'); + $aDefinition->setAutowired(true); + + $pass = new AutowirePass(); + $pass->process($container); + + $this->assertCount(1, $container->getDefinition('a')->getArguments()); + $this->assertEquals('a2', (string) $container->getDefinition('a')->getArgument(0)); + } + public function testWithTypeSet() { $container = new ContainerBuilder(); @@ -335,3 +385,15 @@ public function __construct(Dunglas $k, NotARealClass $r) { } } +class NotGuessableArgument +{ + public function __construct(Foo $k) + { + } +} +class NotGuessableArgumentForSubclass +{ + public function __construct(A $k) + { + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index c95bdd38cae03..15a4af481b6ad 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -355,6 +355,24 @@ public function testCreateServiceMethodCalls() $this->assertEquals(array('bar', $builder->get('bar')), $builder->get('foo1')->bar, '->createService() replaces the values in the method calls arguments'); } + public function testCreateServiceMethodCallsWithEscapedParam() + { + $builder = new ContainerBuilder(); + $builder->register('bar', 'stdClass'); + $builder->register('foo1', 'Bar\FooClass')->addMethodCall('setBar', array(array('%%unescape_it%%'))); + $builder->setParameter('value', 'bar'); + $this->assertEquals(array('%unescape_it%'), $builder->get('foo1')->bar, '->createService() replaces the values in the method calls arguments'); + } + + public function testCreateServiceProperties() + { + $builder = new ContainerBuilder(); + $builder->register('bar', 'stdClass'); + $builder->register('foo1', 'Bar\FooClass')->setProperty('bar', array('%value%', new Reference('bar'), '%%unescape_it%%')); + $builder->setParameter('value', 'bar'); + $this->assertEquals(array('bar', $builder->get('bar'), '%unescape_it%'), $builder->get('foo1')->bar, '->createService() replaces the values in the properties'); + } + public function testCreateServiceConfigurator() { $builder = new ContainerBuilder(); diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index ea38e17df567e..d8d3cf33e94e4 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -1042,8 +1042,8 @@ public function testTempnamOnUnwritableFallsBackToSysTmp() $dirname = $scheme.$this->workspace.DIRECTORY_SEPARATOR.'does_not_exist'; $filename = $this->filesystem->tempnam($dirname, 'bar'); - - $this->assertStringStartsWith(rtrim($scheme.sys_get_temp_dir(), DIRECTORY_SEPARATOR), $filename); + $realTempDir = realpath(sys_get_temp_dir()); + $this->assertStringStartsWith(rtrim($scheme.$realTempDir, DIRECTORY_SEPARATOR), $filename); $this->assertFileExists($filename); // Tear down diff --git a/src/Symfony/Component/Filesystem/phpunit.xml.dist b/src/Symfony/Component/Filesystem/phpunit.xml.dist index 7c6ba7aba3fb3..d066ed7969868 100644 --- a/src/Symfony/Component/Filesystem/phpunit.xml.dist +++ b/src/Symfony/Component/Filesystem/phpunit.xml.dist @@ -21,6 +21,7 @@ ./ ./Tests + ./vendor diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php index 156735b81751e..b1e6b3bf06b5c 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php @@ -74,6 +74,12 @@ public function __construct($choices, $value = null) $choices = iterator_to_array($choices); } + if (null === $value && $this->castableToString($choices)) { + $value = function ($choice) { + return (string) $choice; + }; + } + if (null !== $value) { // If a deterministic value generator was passed, use it later $this->valueCallback = $value; @@ -207,4 +213,35 @@ protected function flatten(array $choices, $value, &$choicesByValues, &$keysByVa $structuredValues[$key] = $choiceValue; } } + + /** + * Checks whether the given choices can be cast to strings without + * generating duplicates. + * + * @param array $choices The choices. + * @param array|null $cache The cache for previously checked entries. Internal + * + * @return bool Returns true if the choices can be cast to strings and + * false otherwise. + */ + private function castableToString(array $choices, array &$cache = array()) + { + foreach ($choices as $choice) { + if (is_array($choice)) { + if (!$this->castableToString($choice, $cache)) { + return false; + } + + continue; + } elseif (!is_scalar($choice)) { + return false; + } elseif (isset($cache[(string) $choice])) { + return false; + } + + $cache[(string) $choice] = true; + } + + return true; + } } diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php index 7c3c107d0f720..e2e68f16f2db2 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Form\ChoiceList; +@trigger_error('The '.__NAMESPACE__.'\ArrayKeyChoiceList class is deprecated since version 2.8 and will be removed in 3.0. Use '.__NAMESPACE__.'\ArrayChoiceList instead.', E_USER_DEPRECATED); + use Symfony\Component\Form\Exception\InvalidArgumentException; /** @@ -39,6 +41,8 @@ * ``` * * @author Bernhard Schussek + * + * @deprecated since version 2.8, to be removed in 3.0. Use ArrayChoiceList instead. */ class ArrayKeyChoiceList extends ArrayChoiceList { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index c325521d36174..d53c3d4106ec5 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -238,6 +238,7 @@ public function finishView(FormView $view, FormInterface $form, array $options) */ public function configureOptions(OptionsResolver $resolver) { + $choiceLabels = (object) array('labels' => array()); $choiceListFactory = $this->choiceListFactory; $emptyData = function (Options $options) { @@ -252,6 +253,45 @@ public function configureOptions(OptionsResolver $resolver) return $options['required'] ? null : ''; }; + // BC closure, to be removed in 3.0 + $choicesNormalizer = function (Options $options, $choices) use ($choiceLabels) { + // Unset labels from previous invocations + $choiceLabels->labels = array(); + + // This closure is irrelevant when "choices_as_values" is set to true + if ($options['choices_as_values']) { + return $choices; + } + + ChoiceType::normalizeLegacyChoices($choices, $choiceLabels); + + return $choices; + }; + + // BC closure, to be removed in 3.0 + $choiceLabel = function (Options $options) use ($choiceLabels) { + // If the choices contain duplicate labels, the normalizer of the + // "choices" option stores them in the $choiceLabels variable + + // Trigger the normalizer + $options->offsetGet('choices'); + + // Pick labels from $choiceLabels if available + if ($choiceLabels->labels) { + // Don't pass the labels by reference. We do want to create a + // copy here so that every form has an own version of that + // variable (contrary to the $choiceLabels object shared by all + // forms) + $labels = $choiceLabels->labels; + + return function ($choice, $key) use ($labels) { + return $labels[$key]; + }; + } + + return; + }; + $choiceListNormalizer = function (Options $options, $choiceList) use ($choiceListFactory) { if ($choiceList) { @trigger_error('The "choice_list" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_loader" instead.', E_USER_DEPRECATED); @@ -281,6 +321,14 @@ public function configureOptions(OptionsResolver $resolver) return $choiceListFactory->createListFromChoices($choices, $options['choice_value']); }; + $choicesAsValuesNormalizer = function (Options $options, $choicesAsValues) { + if (true !== $choicesAsValues) { + @trigger_error('The value "false" for the "choices_as_values" option is deprecated since version 2.8 and will not be supported anymore in 3.0. Set this option to "true" and flip the contents of the "choices" option instead.', E_USER_DEPRECATED); + } + + return $choicesAsValues; + }; + $placeholderNormalizer = function (Options $options, $placeholder) { if (!is_object($options['empty_value']) || !$options['empty_value'] instanceof \Exception) { @trigger_error('The form option "empty_value" is deprecated since version 2.6 and will be removed in 3.0. Use "placeholder" instead.', E_USER_DEPRECATED); @@ -322,7 +370,7 @@ public function configureOptions(OptionsResolver $resolver) 'choices' => array(), 'choices_as_values' => false, 'choice_loader' => null, - 'choice_label' => null, + 'choice_label' => $choiceLabel, 'choice_name' => null, 'choice_value' => null, 'choice_attr' => null, @@ -340,9 +388,11 @@ public function configureOptions(OptionsResolver $resolver) 'choice_translation_domain' => true, )); + $resolver->setNormalizer('choices', $choicesNormalizer); $resolver->setNormalizer('choice_list', $choiceListNormalizer); $resolver->setNormalizer('placeholder', $placeholderNormalizer); $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); + $resolver->setNormalizer('choices_as_values', $choicesAsValuesNormalizer); $resolver->setAllowedTypes('choice_list', array('null', 'Symfony\Component\Form\ChoiceList\ChoiceListInterface', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface')); $resolver->setAllowedTypes('choices', array('null', 'array', '\Traversable')); @@ -462,4 +512,34 @@ private function createChoiceListView(ChoiceListInterface $choiceList, array $op $options['choice_attr'] ); } + + /** + * When "choices_as_values" is set to false, the choices are in the keys and + * their labels in the values. Labels may occur twice. The form component + * flips the choices array in the new implementation, so duplicate labels + * are lost. Store them in a utility array that is used from the + * "choice_label" closure by default. + * + * @param array $choices The choice labels indexed by choices. + * Labels are replaced by generated keys. + * @param object $choiceLabels The object that receives the choice labels + * indexed by generated keys. + * @param int $nextKey The next generated key. + * + * @internal Public only to be accessible from closures on PHP 5.3. Don't + * use this method as it may be removed without notice and will be in 3.0. + */ + public static function normalizeLegacyChoices(array &$choices, $choiceLabels, &$nextKey = 0) + { + foreach ($choices as $choice => $choiceLabel) { + if (is_array($choiceLabel)) { + $choiceLabel = ''; // Dereference $choices[$choice] + self::normalizeLegacyChoices($choices[$choice], $choiceLabels, $nextKey); + continue; + } + + $choiceLabels->labels[$nextKey] = $choiceLabel; + $choices[$choice] = $nextKey++; + } + } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php index 45da8d509a56a..2f114d9aa2f6b 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php @@ -23,7 +23,8 @@ class CountryType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getRegionBundle()->getCountryNames(), + 'choices' => array_flip(Intl::getRegionBundle()->getCountryNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php index 7a26316e01a47..1608890d4a9ad 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php @@ -23,7 +23,8 @@ class CurrencyType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getCurrencyBundle()->getCurrencyNames(), + 'choices' => array_flip(Intl::getCurrencyBundle()->getCurrencyNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index df5835b8b37f6..921b24ff0fd69 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -93,12 +93,15 @@ public function buildForm(FormBuilderInterface $builder, array $options) if ('choice' === $options['widget']) { // Only pass a subset of the options to children $yearOptions['choices'] = $this->formatTimestamps($formatter, '/y+/', $this->listYears($options['years'])); + $yearOptions['choices_as_values'] = true; $yearOptions['placeholder'] = $options['placeholder']['year']; $yearOptions['choice_translation_domain'] = $options['choice_translation_domain']['year']; $monthOptions['choices'] = $this->formatTimestamps($formatter, '/[M|L]+/', $this->listMonths($options['months'])); + $monthOptions['choices_as_values'] = true; $monthOptions['placeholder'] = $options['placeholder']['month']; $monthOptions['choice_translation_domain'] = $options['choice_translation_domain']['month']; $dayOptions['choices'] = $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days'])); + $dayOptions['choices_as_values'] = true; $dayOptions['placeholder'] = $options['placeholder']['day']; $dayOptions['choice_translation_domain'] = $options['choice_translation_domain']['day']; } @@ -297,6 +300,7 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ { $pattern = $formatter->getPattern(); $timezone = $formatter->getTimezoneId(); + $formattedTimestamps = array(); if ($setTimeZone = PHP_VERSION_ID >= 50500 || method_exists($formatter, 'setTimeZone')) { $formatter->setTimeZone('UTC'); @@ -307,8 +311,8 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ if (preg_match($regex, $pattern, $matches)) { $formatter->setPattern($matches[0]); - foreach ($timestamps as $key => $timestamp) { - $timestamps[$key] = $formatter->format($timestamp); + foreach ($timestamps as $timestamp => $choice) { + $formattedTimestamps[$formatter->format($timestamp)] = $choice; } // I'd like to clone the formatter above, but then we get a @@ -322,7 +326,7 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ $formatter->setTimeZoneId($timezone); } - return $timestamps; + return $formattedTimestamps; } private function listYears(array $years) @@ -331,7 +335,7 @@ private function listYears(array $years) foreach ($years as $year) { if (false !== $y = gmmktime(0, 0, 0, 6, 15, $year)) { - $result[$year] = $y; + $result[$y] = $year; } } @@ -343,7 +347,7 @@ private function listMonths(array $months) $result = array(); foreach ($months as $month) { - $result[$month] = gmmktime(0, 0, 0, $month, 15); + $result[gmmktime(0, 0, 0, $month, 15)] = $month; } return $result; @@ -354,7 +358,7 @@ private function listDays(array $days) $result = array(); foreach ($days as $day) { - $result[$day] = gmmktime(0, 0, 0, 5, $day); + $result[gmmktime(0, 0, 0, 5, $day)] = $day; } return $result; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php index 6fc6031af11c2..da5cbc75e435f 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php @@ -23,7 +23,8 @@ class LanguageType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getLanguageBundle()->getLanguageNames(), + 'choices' => array_flip(Intl::getLanguageBundle()->getLanguageNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php index a6d42f8c917ee..25ae92ded903e 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php @@ -23,7 +23,8 @@ class LocaleType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getLocaleBundle()->getLocaleNames(), + 'choices' => array_flip(Intl::getLocaleBundle()->getLocaleNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index ed9132863a0f8..1ab2030b1d174 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -63,20 +63,22 @@ public function buildForm(FormBuilderInterface $builder, array $options) $hours = $minutes = array(); foreach ($options['hours'] as $hour) { - $hours[$hour] = str_pad($hour, 2, '0', STR_PAD_LEFT); + $hours[str_pad($hour, 2, '0', STR_PAD_LEFT)] = $hour; } // Only pass a subset of the options to children $hourOptions['choices'] = $hours; + $hourOptions['choices_as_values'] = true; $hourOptions['placeholder'] = $options['placeholder']['hour']; $hourOptions['choice_translation_domain'] = $options['choice_translation_domain']['hour']; if ($options['with_minutes']) { foreach ($options['minutes'] as $minute) { - $minutes[$minute] = str_pad($minute, 2, '0', STR_PAD_LEFT); + $minutes[str_pad($minute, 2, '0', STR_PAD_LEFT)] = $minute; } $minuteOptions['choices'] = $minutes; + $minuteOptions['choices_as_values'] = true; $minuteOptions['placeholder'] = $options['placeholder']['minute']; $minuteOptions['choice_translation_domain'] = $options['choice_translation_domain']['minute']; } @@ -85,10 +87,11 @@ public function buildForm(FormBuilderInterface $builder, array $options) $seconds = array(); foreach ($options['seconds'] as $second) { - $seconds[$second] = str_pad($second, 2, '0', STR_PAD_LEFT); + $seconds[str_pad($second, 2, '0', STR_PAD_LEFT)] = $second; } $secondOptions['choices'] = $seconds; + $secondOptions['choices_as_values'] = true; $secondOptions['placeholder'] = $options['placeholder']['second']; $secondOptions['choice_translation_domain'] = $options['choice_translation_domain']['second']; } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index 3277a1838636c..6117fcd8ea3f3 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -23,13 +23,21 @@ class TimezoneType extends AbstractType */ private static $timezones; + /** + * Stores the available timezone choices. + * + * @var array + */ + private static $flippedTimezones; + /** * {@inheritdoc} */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => self::getTimezones(), + 'choices' => self::getFlippedTimezones(), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } @@ -67,9 +75,13 @@ public function getBlockPrefix() * overhead. * * @return array The timezone choices + * + * @deprecated Deprecated since version 2.8 */ public static function getTimezones() { + @trigger_error('The TimezoneType::getTimezones() method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + if (null === static::$timezones) { static::$timezones = array(); @@ -93,4 +105,40 @@ public static function getTimezones() return static::$timezones; } + + /** + * Returns the timezone choices. + * + * The choices are generated from the ICU function + * \DateTimeZone::listIdentifiers(). They are cached during a single request, + * so multiple timezone fields on the same page don't lead to unnecessary + * overhead. + * + * @return array The timezone choices + */ + private static function getFlippedTimezones() + { + if (null === self::$timezones) { + self::$timezones = array(); + + foreach (\DateTimeZone::listIdentifiers() as $timezone) { + $parts = explode('/', $timezone); + + if (count($parts) > 2) { + $region = $parts[0]; + $name = $parts[1].' - '.$parts[2]; + } elseif (count($parts) > 1) { + $region = $parts[0]; + $name = $parts[1]; + } else { + $region = 'Other'; + $name = $parts[0]; + } + + self::$timezones[$region][str_replace('_', ' ', $name)] = $timezone; + } + } + + return self::$timezones; + } } diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php index eadd7e2912775..def181d217ae6 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php @@ -61,7 +61,9 @@ public function generateCsrfToken($intention) */ public function isCsrfTokenValid($intention, $token) { - return $token === $this->generateCsrfToken($intention); + $expectedToken = $this->generateCsrfToken($intention); + + return hash_equals($expectedToken, $token); } /** diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index cf5038ae1f6cd..34a0144f4e8a4 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -123,6 +123,10 @@ public function configureOptions(OptionsResolver $resolver) { // BC clause for the "intention" option $csrfTokenId = function (Options $options) { + if (null !== $options['intention']) { + @trigger_error('The form option "intention" is deprecated since version 2.8 and will be removed in 3.0. Use "csrf_token_id" instead.', E_USER_DEPRECATED); + } + return $options['intention']; }; @@ -137,15 +141,28 @@ public function configureOptions(OptionsResolver $resolver) : new CsrfProviderAdapter($options['csrf_provider']); }; + $defaultTokenManager = $this->defaultTokenManager; + $csrfProviderNormalizer = function (Options $options, $csrfProvider) use ($defaultTokenManager) { + if (null !== $csrfProvider) { + @trigger_error('The form option "csrf_provider" is deprecated since version 2.8 and will be removed in 3.0. Use "csrf_token_manager" instead.', E_USER_DEPRECATED); + + return $csrfProvider; + } + + return $defaultTokenManager; + }; + $resolver->setDefaults(array( 'csrf_protection' => $this->defaultEnabled, 'csrf_field_name' => $this->defaultFieldName, 'csrf_message' => 'The CSRF token is invalid. Please try to resubmit the form.', 'csrf_token_manager' => $csrfTokenManager, 'csrf_token_id' => $csrfTokenId, - 'csrf_provider' => $this->defaultTokenManager, - 'intention' => null, + 'csrf_provider' => null, // deprecated + 'intention' => null, // deprecated )); + + $resolver->setNormalizer('csrf_provider', $csrfProviderNormalizer); } /** diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php index 4fb08772d7de4..d1375df8894b8 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php @@ -217,7 +217,7 @@ public function getData() return $this->data; } - private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output = null, array &$outputByHash) + private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output, array &$outputByHash) { $hash = spl_object_hash($form); @@ -236,7 +236,7 @@ private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output } } - private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output = null, array &$outputByHash) + private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output, array &$outputByHash) { $viewHash = spl_object_hash($view); $formHash = null; diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index a277b580c5f2a..15bf3d31506f6 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -356,21 +356,11 @@ public function setData($modelData) if (!FormUtil::isEmpty($viewData)) { $dataClass = $this->config->getDataClass(); - $actualType = is_object($viewData) ? 'an instance of class '.get_class($viewData) : 'a(n) '.gettype($viewData); - - if (null === $dataClass && is_object($viewData) && !$viewData instanceof \ArrayAccess) { - $expectedType = 'scalar, array or an instance of \ArrayAccess'; - - throw new LogicException( - 'The form\'s view data is expected to be of type '.$expectedType.', '. - 'but is '.$actualType.'. You '. - 'can avoid this error by setting the "data_class" option to '. - '"'.get_class($viewData).'" or by adding a view transformer '. - 'that transforms '.$actualType.' to '.$expectedType.'.' - ); - } - if (null !== $dataClass && !$viewData instanceof $dataClass) { + $actualType = is_object($viewData) + ? 'an instance of class '.get_class($viewData) + : 'a(n) '.gettype($viewData); + throw new LogicException( 'The form\'s view data is expected to be an instance of class '. $dataClass.', but is '.$actualType.'. You can avoid this error '. @@ -910,7 +900,7 @@ public function add($child, $type = null, array $options = array()) $options['auto_initialize'] = false; if (null === $type && null === $this->config->getDataClass()) { - $type = 'text'; + $type = 'Symfony\Component\Form\Extension\Core\Type\TextType'; } if (null === $type) { diff --git a/src/Symfony/Component/Form/FormFactory.php b/src/Symfony/Component/Form/FormFactory.php index e00d8ab4fad29..f7160d550742d 100644 --- a/src/Symfony/Component/Form/FormFactory.php +++ b/src/Symfony/Component/Form/FormFactory.php @@ -62,34 +62,29 @@ public function createForProperty($class, $property, $data = null, array $option public function createBuilder($type = 'Symfony\Component\Form\Extension\Core\Type\FormType', $data = null, array $options = array()) { $name = null; - $typeName = null; if ($type instanceof ResolvedFormTypeInterface) { - if (method_exists($type, 'getBlockPrefix')) { - // As of Symfony 3.0, the block prefix of the type is used as - // default name - $name = $type->getBlockPrefix(); - } else { - // BC - $typeName = $type->getName(); - } + $typeObject = $type; } elseif ($type instanceof FormTypeInterface) { - // BC - $typeName = $type->getName(); + $typeObject = $type; } elseif (is_string($type)) { - // BC - $typeName = $type; + $typeObject = $this->registry->getType($type); + $name = $type; } else { throw new UnexpectedTypeException($type, 'string, Symfony\Component\Form\ResolvedFormTypeInterface or Symfony\Component\Form\FormTypeInterface'); } - if (null === $name) { - if (false === strpos($typeName, '\\')) { - // No FQCN - leave unchanged for BC - $name = $typeName; - } else { + if (method_exists($typeObject, 'getBlockPrefix')) { + // As of Symfony 3.0, the block prefix of the type is used as default name + $name = $typeObject->getBlockPrefix(); + } else { + // BC when there is no block prefix + if (null === $name) { + $name = $typeObject->getName(); + } + if (false !== strpos($name, '\\')) { // FQCN - $name = StringUtil::fqcnToBlockPrefix($typeName); + $name = StringUtil::fqcnToBlockPrefix($name); } } diff --git a/src/Symfony/Component/Form/FormRegistry.php b/src/Symfony/Component/Form/FormRegistry.php index 66d5a1f7c2b38..a8995afe89f01 100644 --- a/src/Symfony/Component/Form/FormRegistry.php +++ b/src/Symfony/Component/Form/FormRegistry.php @@ -120,7 +120,7 @@ private function resolveAndAddType(FormTypeInterface $type) $hasCustomName = $name !== $fqcn; if ($parentType instanceof FormTypeInterface) { - @trigger_error('Returning a FormTypeInterface from FormTypeInterface::getParent() is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + @trigger_error(sprintf('Returning a FormTypeInterface from %s::getParent() is deprecated since version 2.8 and will be removed in 3.0. Return the fully-qualified type class name instead.', $fqcn), E_USER_DEPRECATED); $this->resolveAndAddType($parentType); $parentType = $parentType->getName(); @@ -128,14 +128,11 @@ private function resolveAndAddType(FormTypeInterface $type) if ($hasCustomName) { foreach ($this->extensions as $extension) { - $typeExtensions = array_merge( - $typeExtensions, - $extension->getTypeExtensions($name) - ); - } + if ($x = $extension->getTypeExtensions($name)) { + @trigger_error(sprintf('Returning a type name from %s::getExtendedType() is deprecated since version 2.8 and will be removed in 3.0. Return the fully-qualified type class name instead.', get_class($x[0])), E_USER_DEPRECATED); - if ($typeExtensions) { - @trigger_error('Returning a type name from FormTypeExtensionInterface::getExtendedType() is deprecated since version 2.8 and will be removed in 3.0. Return the fully-qualified type class name instead.', E_USER_DEPRECATED); + $typeExtensions = array_merge($typeExtensions, $x); + } } } diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php index b334acff3c655..d637d223a4631 100644 --- a/src/Symfony/Component/Form/ResolvedFormType.php +++ b/src/Symfony/Component/Form/ResolvedFormType.php @@ -71,12 +71,12 @@ public function __construct(FormTypeInterface $innerType, array $typeExtensions // Anyone else should only override getBlockPrefix() if they actually // want to have a different block prefix than the default one if ($isOldOverwritten && !$isNewOverwritten) { - @trigger_error(get_class($this->innerType).': The FormTypeInterface::getName() method is deprecated since version 2.8 and will be removed in 3.0. Remove it from your classes. Use getBlockPrefix() if you want to customize the template block prefix. This method will be added to the FormTypeInterface with Symfony 3.0.', E_USER_DEPRECATED); + @trigger_error(get_class($innerType).': The FormTypeInterface::getName() method is deprecated since version 2.8 and will be removed in 3.0. Remove it from your classes. Use getBlockPrefix() if you want to customize the template block prefix. This method will be added to the FormTypeInterface with Symfony 3.0.', E_USER_DEPRECATED); } $blockPrefix = $innerType->getBlockPrefix(); } else { - @trigger_error(get_class($this->innerType).': The FormTypeInterface::getBlockPrefix() method will be added in version 3.0. You should extend AbstractType or add it to your implementation.', E_USER_DEPRECATED); + @trigger_error(get_class($innerType).': The FormTypeInterface::getBlockPrefix() method will be added in version 3.0. You should extend AbstractType or add it to your implementation.', E_USER_DEPRECATED); // Deal with classes that don't extend AbstractType // Calculate block prefix from the FQCN by default diff --git a/src/Symfony/Component/Form/Resources/translations/validators.ja.xlf b/src/Symfony/Component/Form/Resources/translations/validators.ja.xlf index 2e8585a75c0c8..0db5eddbe68a6 100644 --- a/src/Symfony/Component/Form/Resources/translations/validators.ja.xlf +++ b/src/Symfony/Component/Form/Resources/translations/validators.ja.xlf @@ -11,8 +11,8 @@ アップロードされたファイルが大きすぎます。小さなファイルで再度アップロードしてください。 - The CSRF token is invalid. - CSRFトークンが無効です。 + The CSRF token is invalid. Please try to resubmit the form. + CSRFトークンが無効です、再送信してください。 diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap3HorizontalLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap3HorizontalLayoutTest.php new file mode 100644 index 0000000000000..1273fa505bd2c --- /dev/null +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap3HorizontalLayoutTest.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests; + +abstract class AbstractBootstrap3HorizontalLayoutTest extends AbstractBootstrap3LayoutTest +{ + public function testLabelOnForm() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); + $view = $form->createView(); + $this->renderWidget($view, array('label' => 'foo')); + $html = $this->renderLabel($view); + + $this->assertMatchesXpath($html, +'/label + [@class="col-sm-2 control-label required"] + [.="[trans]Name[/trans]"] +' + ); + } + + public function testLabelDoesNotRenderFieldAttributes() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), null, array( + 'attr' => array( + 'class' => 'my&class', + ), + )); + + $this->assertMatchesXpath($html, +'/label + [@for="name"] + [@class="col-sm-2 control-label required"] +' + ); + } + + public function testLabelWithCustomAttributesPassedDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), null, array( + 'label_attr' => array( + 'class' => 'my&class', + ), + )); + + $this->assertMatchesXpath($html, +'/label + [@for="name"] + [@class="my&class col-sm-2 control-label required"] +' + ); + } + + public function testLabelWithCustomTextAndCustomAttributesPassedDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), 'Custom label', array( + 'label_attr' => array( + 'class' => 'my&class', + ), + )); + + $this->assertMatchesXpath($html, +'/label + [@for="name"] + [@class="my&class col-sm-2 control-label required"] + [.="[trans]Custom label[/trans]"] +' + ); + } + + public function testLabelWithCustomTextAsOptionAndCustomAttributesPassedDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, array( + 'label' => 'Custom label', + )); + $html = $this->renderLabel($form->createView(), null, array( + 'label_attr' => array( + 'class' => 'my&class', + ), + )); + + $this->assertMatchesXpath($html, +'/label + [@for="name"] + [@class="my&class col-sm-2 control-label required"] + [.="[trans]Custom label[/trans]"] +' + ); + } + + public function testStartTag() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => 'http://example.com/directory', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('
', $html); + } + + public function testStartTagWithOverriddenVars() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'put', + 'action' => 'http://example.com/directory', + )); + + $html = $this->renderStart($form->createView(), array( + 'method' => 'post', + 'action' => 'http://foo.com/directory', + )); + + $this->assertSame('', $html); + } + + public function testStartTagForMultipartForm() + { + $form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => 'http://example.com/directory', + )) + ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType') + ->getForm(); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + + public function testStartTagWithExtraAttributes() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => 'http://example.com/directory', + )); + + $html = $this->renderStart($form->createView(), array( + 'attr' => array('class' => 'foobar'), + )); + + $this->assertSame('', $html); + } +} diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php index 13da0c486071a..07f827d31231f 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php @@ -212,7 +212,8 @@ public function testCheckboxWithValue() public function testSingleChoice() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, )); @@ -234,7 +235,8 @@ public function testSingleChoice() public function testSingleChoiceWithoutTranslation() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, 'choice_translation_domain' => false, @@ -254,10 +256,38 @@ public function testSingleChoiceWithoutTranslation() ); } + public function testSingleChoiceWithPlaceholderWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => false, + 'required' => false, + 'translation_domain' => false, + 'placeholder' => 'Placeholder&Not&Translated', + )); + + $this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'my&class')), +'/select + [@name="name"] + [@class="my&class form-control"] + [not(@required)] + [ + ./option[@value=""][not(@selected)][not(@disabled)][.="Placeholder&Not&Translated"] + /following-sibling::option[@value="&a"][@selected="selected"][.="Choice&A"] + /following-sibling::option[@value="&b"][not(@selected)][.="Choice&B"] + ] + [count(./option)=3] +' + ); + } + public function testSingleChoiceAttributes() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => false, @@ -282,7 +312,8 @@ public function testSingleChoiceAttributes() public function testSingleChoiceWithPreferred() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -306,7 +337,8 @@ public function testSingleChoiceWithPreferred() public function testSingleChoiceWithPreferredAndNoSeparator() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -329,7 +361,8 @@ public function testSingleChoiceWithPreferredAndNoSeparator() public function testSingleChoiceWithPreferredAndBlankSeparator() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -353,7 +386,8 @@ public function testSingleChoiceWithPreferredAndBlankSeparator() public function testChoiceWithOnlyPreferred() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&a', '&b'), 'multiple' => false, 'expanded' => false, @@ -370,7 +404,8 @@ public function testChoiceWithOnlyPreferred() public function testSingleChoiceNonRequired() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => false, 'expanded' => false, @@ -394,7 +429,8 @@ public function testSingleChoiceNonRequired() public function testSingleChoiceNonRequiredNoneSelected() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => false, 'expanded' => false, @@ -418,7 +454,8 @@ public function testSingleChoiceNonRequiredNoneSelected() public function testSingleChoiceNonRequiredWithPlaceholder() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, 'required' => false, @@ -443,7 +480,8 @@ public function testSingleChoiceNonRequiredWithPlaceholder() public function testSingleChoiceRequiredWithPlaceholder() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => false, 'expanded' => false, @@ -468,7 +506,8 @@ public function testSingleChoiceRequiredWithPlaceholder() public function testSingleChoiceRequiredWithPlaceholderViaView() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => false, 'expanded' => false, @@ -493,9 +532,10 @@ public function testSingleChoiceGrouped() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( 'choices' => array( - 'Group&1' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), - 'Group&2' => array('&c' => 'Choice&C'), + 'Group&1' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'Group&2' => array('Choice&C' => '&c'), ), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, )); @@ -523,7 +563,8 @@ public function testSingleChoiceGrouped() public function testMultipleChoice() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => true, 'expanded' => false, @@ -547,7 +588,8 @@ public function testMultipleChoice() public function testMultipleChoiceAttributes() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'required' => true, 'multiple' => true, @@ -574,7 +616,8 @@ public function testMultipleChoiceAttributes() public function testMultipleChoiceSkipsPlaceholder() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => false, 'placeholder' => 'Test&Me', @@ -597,7 +640,8 @@ public function testMultipleChoiceSkipsPlaceholder() public function testMultipleChoiceNonRequired() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => true, 'expanded' => false, @@ -620,7 +664,8 @@ public function testMultipleChoiceNonRequired() public function testSingleChoiceExpanded() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, )); @@ -655,7 +700,8 @@ public function testSingleChoiceExpanded() public function testSingleChoiceExpandedWithoutTranslation() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, 'choice_translation_domain' => false, @@ -691,7 +737,8 @@ public function testSingleChoiceExpandedWithoutTranslation() public function testSingleChoiceExpandedAttributes() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => true, @@ -729,7 +776,8 @@ public function testSingleChoiceExpandedAttributes() public function testSingleChoiceExpandedWithPlaceholder() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, 'placeholder' => 'Test&Me', @@ -771,10 +819,58 @@ public function testSingleChoiceExpandedWithPlaceholder() ); } + public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => true, + 'translation_domain' => false, + 'placeholder' => 'Placeholder&Not&Translated', + )); + + $this->assertWidgetMatchesXpath($form->createView(), array(), +'/div + [ + ./div + [@class="radio"] + [ + ./label + [.=" Placeholder&Not&Translated"] + [ + ./input[@type="radio"][@name="name"][@id="name_placeholder"][not(@checked)] + ] + ] + /following-sibling::div + [@class="radio"] + [ + ./label + [.=" Choice&A"] + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@checked] + ] + ] + /following-sibling::div + [@class="radio"] + [ + ./label + [.=" Choice&B"] + [ + ./input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] + ] + ] + /following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"] + ] +' + ); + } + public function testSingleChoiceExpandedWithBooleanValue() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', true, array( - 'choices' => array('1' => 'Choice&A', '0' => 'Choice&B'), + 'choices' => array('Choice&A' => '1', 'Choice&B' => '0'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, )); @@ -809,7 +905,8 @@ public function testSingleChoiceExpandedWithBooleanValue() public function testMultipleChoiceExpanded() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => true, 'required' => true, @@ -854,7 +951,8 @@ public function testMultipleChoiceExpanded() public function testMultipleChoiceExpandedWithoutTranslation() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => true, 'required' => true, @@ -900,7 +998,8 @@ public function testMultipleChoiceExpandedWithoutTranslation() public function testMultipleChoiceExpandedAttributes() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => true, 'expanded' => true, @@ -2023,6 +2122,17 @@ public function testButton() ); } + public function testButtonlabelWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', null, array( + 'translation_domain' => false, + )); + + $this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'my&class')), + '/button[@type="button"][@name="name"][.="Name"][@class="my&class btn"]' + ); + } + public function testSubmit() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\SubmitType'); diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index 30a8225f144b3..461c885795fa6 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -691,7 +691,8 @@ public function testCollectionRowWithCustomBlock() public function testChoiceRowWithCustomBlock() { $form = $this->factory->createNamedBuilder('name_c', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', 'a', array( - 'choices' => array('a' => 'ChoiceA', 'b' => 'ChoiceB'), + 'choices' => array('ChoiceA' => 'a', 'ChoiceB' => 'b'), + 'choices_as_values' => true, 'expanded' => true, )) ->getForm(); diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 8d5d9ea8a2065..2476a96e85b22 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -381,6 +381,25 @@ public function testLabelFormatOverriddenOption() ); } + public function testLabelWithoutTranslationOnButton() + { + $form = $this->factory->createNamedBuilder('myform', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'translation_domain' => false, + )) + ->add('mybutton', 'Symfony\Component\Form\Extension\Core\Type\ButtonType') + ->getForm(); + $view = $form->get('mybutton')->createView(); + $html = $this->renderWidget($view); + + $this->assertMatchesXpath($html, +'/button + [@type="button"] + [@name="myform[mybutton]"] + [.="Mybutton"] +' + ); + } + public function testLabelFormatOnButton() { $form = $this->factory->createNamedBuilder('myform') @@ -497,7 +516,8 @@ public function testCheckboxWithValue() public function testSingleChoice() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, )); @@ -530,7 +550,8 @@ public function testSingleChoice() public function testSingleChoiceWithoutTranslation() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, 'choice_translation_domain' => false, @@ -549,10 +570,37 @@ public function testSingleChoiceWithoutTranslation() ); } + public function testSingleChoiceWithPlaceholderWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => false, + 'required' => false, + 'translation_domain' => false, + 'placeholder' => 'Placeholder&Not&Translated', + )); + + $this->assertWidgetMatchesXpath($form->createView(), array(), +'/select + [@name="name"] + [not(@required)] + [ + ./option[@value=""][not(@selected)][not(@disabled)][.="Placeholder&Not&Translated"] + /following-sibling::option[@value="&a"][@selected="selected"][.="Choice&A"] + /following-sibling::option[@value="&b"][not(@selected)][.="Choice&B"] + ] + [count(./option)=3] +' + ); + } + public function testSingleChoiceAttributes() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => false, @@ -576,7 +624,8 @@ public function testSingleChoiceAttributes() public function testSingleChoiceWithPreferred() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -599,7 +648,8 @@ public function testSingleChoiceWithPreferred() public function testSingleChoiceWithPreferredAndNoSeparator() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -621,7 +671,8 @@ public function testSingleChoiceWithPreferredAndNoSeparator() public function testSingleChoiceWithPreferredAndBlankSeparator() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -644,7 +695,8 @@ public function testSingleChoiceWithPreferredAndBlankSeparator() public function testChoiceWithOnlyPreferred() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&a', '&b'), 'multiple' => false, 'expanded' => false, @@ -660,7 +712,8 @@ public function testChoiceWithOnlyPreferred() public function testSingleChoiceNonRequired() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => false, 'expanded' => false, @@ -683,7 +736,8 @@ public function testSingleChoiceNonRequired() public function testSingleChoiceNonRequiredNoneSelected() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => false, 'expanded' => false, @@ -706,7 +760,8 @@ public function testSingleChoiceNonRequiredNoneSelected() public function testSingleChoiceNonRequiredWithPlaceholder() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, 'required' => false, @@ -730,7 +785,8 @@ public function testSingleChoiceNonRequiredWithPlaceholder() public function testSingleChoiceRequiredWithPlaceholder() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => false, 'expanded' => false, @@ -757,7 +813,8 @@ public function testSingleChoiceRequiredWithPlaceholder() public function testSingleChoiceRequiredWithPlaceholderViaView() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => false, 'expanded' => false, @@ -784,9 +841,10 @@ public function testSingleChoiceGrouped() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( 'choices' => array( - 'Group&1' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), - 'Group&2' => array('&c' => 'Choice&C'), + 'Group&1' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'Group&2' => array('Choice&C' => '&c'), ), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, )); @@ -813,7 +871,8 @@ public function testSingleChoiceGrouped() public function testMultipleChoice() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => true, 'expanded' => false, @@ -836,7 +895,8 @@ public function testMultipleChoice() public function testMultipleChoiceAttributes() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'required' => true, 'multiple' => true, @@ -862,7 +922,8 @@ public function testMultipleChoiceAttributes() public function testMultipleChoiceSkipsPlaceholder() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => false, 'placeholder' => 'Test&Me', @@ -884,7 +945,8 @@ public function testMultipleChoiceSkipsPlaceholder() public function testMultipleChoiceNonRequired() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => true, 'expanded' => false, @@ -906,7 +968,8 @@ public function testMultipleChoiceNonRequired() public function testSingleChoiceExpanded() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, )); @@ -928,7 +991,8 @@ public function testSingleChoiceExpanded() public function testSingleChoiceExpandedWithoutTranslation() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, 'choice_translation_domain' => false, @@ -951,7 +1015,8 @@ public function testSingleChoiceExpandedWithoutTranslation() public function testSingleChoiceExpandedAttributes() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => true, @@ -976,7 +1041,8 @@ public function testSingleChoiceExpandedAttributes() public function testSingleChoiceExpandedWithPlaceholder() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, 'placeholder' => 'Test&Me', @@ -998,10 +1064,38 @@ public function testSingleChoiceExpandedWithPlaceholder() ); } + public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', array( + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => true, + 'translation_domain' => false, + 'placeholder' => 'Placeholder&Not&Translated', + )); + + $this->assertWidgetMatchesXpath($form->createView(), array(), +'/div + [ + ./input[@type="radio"][@name="name"][@id="name_placeholder"][not(@checked)] + /following-sibling::label[@for="name_placeholder"][.="Placeholder&Not&Translated"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked] + /following-sibling::label[@for="name_0"][.="Choice&A"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] + /following-sibling::label[@for="name_1"][.="Choice&B"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=4] +' + ); + } + public function testSingleChoiceExpandedWithBooleanValue() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', true, array( - 'choices' => array('1' => 'Choice&A', '0' => 'Choice&B'), + 'choices' => array('Choice&A' => '1', 'Choice&B' => '0'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, )); @@ -1023,7 +1117,8 @@ public function testSingleChoiceExpandedWithBooleanValue() public function testMultipleChoiceExpanded() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => true, 'required' => true, @@ -1048,7 +1143,8 @@ public function testMultipleChoiceExpanded() public function testMultipleChoiceExpandedWithoutTranslation() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => true, 'required' => true, @@ -1074,7 +1170,8 @@ public function testMultipleChoiceExpandedWithoutTranslation() public function testMultipleChoiceExpandedAttributes() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => true, 'expanded' => true, @@ -2141,6 +2238,17 @@ public function testButtonLabelIsEmpty() $this->assertSame('', $this->renderLabel($form->createView())); } + public function testButtonlabelWithoutTranslation() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', null, array( + 'translation_domain' => false, + )); + + $this->assertWidgetMatchesXpath($form->createView(), array(), + '/button[@type="button"][@name="name"][.="Name"]' + ); + } + public function testSubmit() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\SubmitType'); @@ -2349,4 +2457,20 @@ public function testTranslatedAttributes() $this->assertMatchesXpath($html, '/form//input[@title="[trans]Foo[/trans]"]'); $this->assertMatchesXpath($html, '/form//input[@placeholder="[trans]Bar[/trans]"]'); } + + public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() + { + $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'translation_domain' => false, + )) + ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType', array('attr' => array('title' => 'Foo'))) + ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType', array('attr' => array('placeholder' => 'Bar'))) + ->getForm() + ->createView(); + + $html = $this->renderForm($view); + + $this->assertMatchesXpath($html, '/form//input[@title="Foo"]'); + $this->assertMatchesXpath($html, '/form//input[@placeholder="Bar"]'); + } } diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php index 50d4df8a9b7cb..03cb7fce5705a 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php @@ -65,6 +65,40 @@ public function testCreateChoiceListWithValueCallback() $this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz'))); } + public function testCreateChoiceListWithoutValueCallbackAndDuplicateFreeToStringChoices() + { + $choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 123)); + + $this->assertSame(array('foo', 'bar', '123'), $choiceList->getValues()); + $this->assertSame(array('foo' => 'foo', 'bar' => 'bar', '123' => 123), $choiceList->getChoices()); + $this->assertSame(array('foo' => 2, 'bar' => 7, '123' => 10), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'foo', 2 => 123), $choiceList->getChoicesForValues(array(1 => 'foo', 2 => '123'))); + $this->assertSame(array(1 => 'foo', 2 => '123'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 123))); + } + + public function testCreateChoiceListWithoutValueCallbackAndToStringDuplicates() + { + $choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => '123', 10 => 123)); + + $this->assertSame(array('0', '1', '2'), $choiceList->getValues()); + $this->assertSame(array('0' => 'foo', '1' => '123', '2' => 123), $choiceList->getChoices()); + $this->assertSame(array('0' => 2, '1' => 7, '2' => 10), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'foo', 2 => 123), $choiceList->getChoicesForValues(array(1 => '0', 2 => '2'))); + $this->assertSame(array(1 => '0', 2 => '2'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 123))); + } + + public function testCreateChoiceListWithoutValueCallbackAndMixedChoices() + { + $object = new \stdClass(); + $choiceList = new ArrayChoiceList(array(2 => 'foo', 5 => array(7 => '123'), 10 => $object)); + + $this->assertSame(array('0', '1', '2'), $choiceList->getValues()); + $this->assertSame(array('0' => 'foo', '1' => '123', '2' => $object), $choiceList->getChoices()); + $this->assertSame(array('0' => 2, '1' => 7, '2' => 10), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'foo', 2 => $object), $choiceList->getChoicesForValues(array(1 => '0', 2 => '2'))); + $this->assertSame(array(1 => '0', 2 => '2'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => $object))); + } + public function testCreateChoiceListWithGroupedChoices() { $choiceList = new ArrayChoiceList(array( @@ -72,15 +106,15 @@ public function testCreateChoiceListWithGroupedChoices() 'Group 2' => array('C' => 'c', 'D' => 'd'), )); - $this->assertSame(array('0', '1', '2', '3'), $choiceList->getValues()); + $this->assertSame(array('a', 'b', 'c', 'd'), $choiceList->getValues()); $this->assertSame(array( - 'Group 1' => array('A' => '0', 'B' => '1'), - 'Group 2' => array('C' => '2', 'D' => '3'), + 'Group 1' => array('A' => 'a', 'B' => 'b'), + 'Group 2' => array('C' => 'c', 'D' => 'd'), ), $choiceList->getStructuredValues()); - $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $choiceList->getChoices()); - $this->assertSame(array(0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D'), $choiceList->getOriginalKeys()); - $this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getChoicesForValues(array(1 => '0', 2 => '1'))); - $this->assertSame(array(1 => '0', 2 => '1'), $choiceList->getValuesForChoices(array(1 => 'a', 2 => 'b'))); + $this->assertSame(array('a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => 'd'), $choiceList->getChoices()); + $this->assertSame(array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getChoicesForValues(array(1 => 'a', 2 => 'b'))); + $this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getValuesForChoices(array(1 => 'a', 2 => 'b'))); } public function testCompareChoicesByIdentityByDefault() diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayKeyChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayKeyChoiceListTest.php index 5cbadf6e0fe7b..1f1643158d48d 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayKeyChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayKeyChoiceListTest.php @@ -15,6 +15,8 @@ /** * @author Bernhard Schussek + * + * @group legacy */ class ArrayKeyChoiceListTest extends AbstractChoiceListTest { diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index a306647234dd9..0a9ed8e67078f 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -172,13 +172,13 @@ public function testAddUsingNameAndType() $this->factory->expects($this->once()) ->method('createNamed') - ->with('foo', 'text', null, array( + ->with('foo', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, array( 'bar' => 'baz', 'auto_initialize' => false, )) ->will($this->returnValue($child)); - $this->form->add('foo', 'text', array('bar' => 'baz')); + $this->form->add('foo', 'Symfony\Component\Form\Extension\Core\Type\TextType', array('bar' => 'baz')); $this->assertTrue($this->form->has('foo')); $this->assertSame($this->form, $child->getParent()); @@ -191,14 +191,14 @@ public function testAddUsingIntegerNameAndType() $this->factory->expects($this->once()) ->method('createNamed') - ->with('0', 'text', null, array( + ->with('0', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, array( 'bar' => 'baz', 'auto_initialize' => false, )) ->will($this->returnValue($child)); // in order to make casting unnecessary - $this->form->add(0, 'text', array('bar' => 'baz')); + $this->form->add(0, 'Symfony\Component\Form\Extension\Core\Type\TextType', array('bar' => 'baz')); $this->assertTrue($this->form->has(0)); $this->assertSame($this->form, $child->getParent()); @@ -211,7 +211,7 @@ public function testAddWithoutType() $this->factory->expects($this->once()) ->method('createNamed') - ->with('foo', 'text') + ->with('foo', 'Symfony\Component\Form\Extension\Core\Type\TextType') ->will($this->returnValue($child)); $this->form->add('foo'); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php index c58d072f47434..f60ef05abc507 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php @@ -20,7 +20,7 @@ class ChoiceToValueTransformerTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $list = new ArrayChoiceList(array('', 0, 'X')); + $list = new ArrayChoiceList(array('', false, 'X')); $this->transformer = new ChoiceToValueTransformer($list); } @@ -35,7 +35,7 @@ public function transformProvider() return array( // more extensive test set can be found in FormUtilTest array('', '0'), - array(0, '1'), + array(false, '1'), ); } @@ -53,7 +53,7 @@ public function reverseTransformProvider() // values are expected to be valid choice keys already and stay // the same array('0', ''), - array('1', 0), + array('1', false), array('2', 'X'), ); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php index a7dc40aca225f..f7747aaccd0f1 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php @@ -20,7 +20,7 @@ class ChoicesToValuesTransformerTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $list = new ArrayChoiceList(array('A', 'B', 'C')); + $list = new ArrayChoiceList(array('', false, 'X')); $this->transformer = new ChoicesToValuesTransformer($list); } @@ -31,7 +31,7 @@ protected function tearDown() public function testTransform() { - $in = array('A', 'B', 'C'); + $in = array('', false, 'X'); $out = array('0', '1', '2'); $this->assertSame($out, $this->transformer->transform($in)); @@ -54,7 +54,7 @@ public function testReverseTransform() { // values are expected to be valid choices and stay the same $in = array('0', '1', '2'); - $out = array('A', 'B', 'C'); + $out = array('', false, 'X'); $this->assertSame($out, $this->transformer->reverseTransform($in)); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index a8bf8edf12655..976cbc838b077 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -18,14 +18,14 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase { private $choices = array( - 'a' => 'Bernhard', - 'b' => 'Fabien', - 'c' => 'Kris', - 'd' => 'Jon', - 'e' => 'Roman', + 'Bernhard' => 'a', + 'Fabien' => 'b', + 'Kris' => 'c', + 'Jon' => 'd', + 'Roman' => 'e', ); - private $numericChoices = array( + private $numericChoicesFlipped = array( 0 => 'Bernhard', 1 => 'Fabien', 2 => 'Kris', @@ -36,6 +36,18 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase private $objectChoices; protected $groupedChoices = array( + 'Symfony' => array( + 'Bernhard' => 'a', + 'Fabien' => 'b', + 'Kris' => 'c', + ), + 'Doctrine' => array( + 'Jon' => 'd', + 'Roman' => 'e', + ), + ); + + protected $groupedChoicesFlipped = array( 'Symfony' => array( 'a' => 'Bernhard', 'b' => 'Fabien', @@ -109,7 +121,9 @@ public function testChoiceLoaderOptionExpectsChoiceLoaderInterface() public function testChoiceListAndChoicesCanBeEmpty() { - $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType'); + $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( + 'choices_as_values' => true, + )); } public function testExpandedChoicesOptionsTurnIntoChildren() @@ -117,6 +131,20 @@ public function testExpandedChoicesOptionsTurnIntoChildren() $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, + )); + + $this->assertCount(count($this->choices), $form, 'Each choice should become a new field'); + } + + /** + * @group legacy + */ + public function testExpandedFlippedChoicesOptionsTurnIntoChildren() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( + 'expanded' => true, + 'choices' => array_flip($this->choices), )); $this->assertCount(count($this->choices), $form, 'Each choice should become a new field'); @@ -129,6 +157,7 @@ public function testPlaceholderPresentOnNonRequiredExpandedSingleChoice() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $this->assertTrue(isset($form['placeholder'])); @@ -142,6 +171,7 @@ public function testPlaceholderNotPresentIfRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $this->assertFalse(isset($form['placeholder'])); @@ -155,6 +185,7 @@ public function testPlaceholderNotPresentIfMultiple() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $this->assertFalse(isset($form['placeholder'])); @@ -168,9 +199,10 @@ public function testPlaceholderNotPresentIfEmptyChoice() 'expanded' => true, 'required' => false, 'choices' => array( - '' => 'Empty', - 1 => 'Not empty', + 'Empty' => '', + 'Not empty' => 1, ), + 'choices_as_values' => true, )); $this->assertFalse(isset($form['placeholder'])); @@ -182,6 +214,29 @@ public function testExpandedChoicesOptionsAreFlattened() $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'expanded' => true, 'choices' => $this->groupedChoices, + 'choices_as_values' => true, + )); + + $flattened = array(); + foreach ($this->groupedChoices as $choices) { + $flattened = array_merge($flattened, array_keys($choices)); + } + + $this->assertCount($form->count(), $flattened, 'Each nested choice should become a new field, not the groups'); + + foreach ($flattened as $value => $choice) { + $this->assertTrue($form->has($value), 'Flattened choice is named after it\'s value'); + } + } + + /** + * @group legacy + */ + public function testExpandedChoicesFlippedOptionsAreFlattened() + { + $form = $this->factory->create('choice', null, array( + 'expanded' => true, + 'choices' => $this->groupedChoicesFlipped, )); $flattened = array(); @@ -229,6 +284,7 @@ public function testExpandedCheckboxesAreNeverRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); foreach ($form as $child) { @@ -243,6 +299,7 @@ public function testExpandedRadiosAreRequiredIfChoiceChildIsRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); foreach ($form as $child) { @@ -257,6 +314,7 @@ public function testExpandedRadiosAreNotRequiredIfChoiceChildIsNotRequired() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); foreach ($form as $child) { @@ -270,6 +328,7 @@ public function testSubmitSingleNonExpanded() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('b'); @@ -285,6 +344,7 @@ public function testSubmitSingleNonExpandedInvalidChoice() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -300,6 +360,7 @@ public function testSubmitSingleNonExpandedNull() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(null); @@ -318,6 +379,7 @@ public function testSubmitSingleNonExpandedNullNoChoices() 'multiple' => false, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(null); @@ -333,6 +395,7 @@ public function testSubmitSingleNonExpandedEmpty() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(''); @@ -348,8 +411,9 @@ public function testSubmitSingleNonExpandedEmptyExplicitEmptyChoice() 'multiple' => false, 'expanded' => false, 'choices' => array( - 'EMPTY_CHOICE' => 'Empty', + 'Empty' => 'EMPTY_CHOICE', ), + 'choices_as_values' => true, 'choice_value' => function () { return ''; }, @@ -371,6 +435,7 @@ public function testSubmitSingleNonExpandedEmptyNoChoices() 'multiple' => false, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(''); @@ -386,6 +451,7 @@ public function testSubmitSingleNonExpandedFalse() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(false); @@ -404,6 +470,7 @@ public function testSubmitSingleNonExpandedFalseNoChoices() 'multiple' => false, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(false); @@ -465,6 +532,7 @@ public function testSubmitMultipleNonExpanded() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'b')); @@ -480,6 +548,7 @@ public function testSubmitMultipleNonExpandedEmpty() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array()); @@ -498,6 +567,7 @@ public function testSubmitMultipleNonExpandedEmptyNoChoices() 'multiple' => true, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(array()); @@ -513,6 +583,7 @@ public function testSubmitMultipleNonExpandedInvalidScalarChoice() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -528,6 +599,7 @@ public function testSubmitMultipleNonExpandedInvalidArrayChoice() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'foobar')); @@ -588,6 +660,7 @@ public function testSubmitSingleExpandedRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('b'); @@ -616,6 +689,7 @@ public function testSubmitSingleExpandedRequiredInvalidChoice() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -644,6 +718,7 @@ public function testSubmitSingleExpandedNonRequired() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('b'); @@ -674,6 +749,7 @@ public function testSubmitSingleExpandedNonRequiredInvalidChoice() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -702,6 +778,7 @@ public function testSubmitSingleExpandedRequiredNull() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(null); @@ -733,6 +810,7 @@ public function testSubmitSingleExpandedRequiredNullNoChoices() 'expanded' => true, 'required' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(null); @@ -750,6 +828,7 @@ public function testSubmitSingleExpandedRequiredEmpty() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(''); @@ -781,6 +860,7 @@ public function testSubmitSingleExpandedRequiredEmptyNoChoices() 'expanded' => true, 'required' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(''); @@ -798,6 +878,7 @@ public function testSubmitSingleExpandedRequiredFalse() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(false); @@ -829,6 +910,7 @@ public function testSubmitSingleExpandedRequiredFalseNoChoices() 'expanded' => true, 'required' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(false); @@ -846,6 +928,7 @@ public function testSubmitSingleExpandedNonRequiredNull() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(null); @@ -879,6 +962,7 @@ public function testSubmitSingleExpandedNonRequiredNullNoChoices() 'expanded' => true, 'required' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(null); @@ -896,6 +980,7 @@ public function testSubmitSingleExpandedNonRequiredEmpty() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(''); @@ -929,6 +1014,7 @@ public function testSubmitSingleExpandedNonRequiredEmptyNoChoices() 'expanded' => true, 'required' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(''); @@ -946,6 +1032,7 @@ public function testSubmitSingleExpandedNonRequiredFalse() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(false); @@ -979,6 +1066,7 @@ public function testSubmitSingleExpandedNonRequiredFalseNoChoices() 'expanded' => true, 'required' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(false); @@ -995,9 +1083,10 @@ public function testSubmitSingleExpandedWithEmptyChild() 'multiple' => false, 'expanded' => true, 'choices' => array( - '' => 'Empty', - 1 => 'Not empty', + 'Empty' => '', + 'Not empty' => 1, ), + 'choices_as_values' => true, )); $form->submit(''); @@ -1075,12 +1164,15 @@ public function testLegacySubmitSingleExpandedObjectChoices() $this->assertNull($form[4]->getViewData()); } - public function testSubmitSingleExpandedNumericChoices() + /** + * @group legacy + */ + public function testSubmitSingleExpandedNumericChoicesFlipped() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'multiple' => false, 'expanded' => true, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->submit('1'); @@ -1106,6 +1198,7 @@ public function testSubmitMultipleExpanded() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'c')); @@ -1133,6 +1226,7 @@ public function testSubmitMultipleExpandedInvalidScalarChoice() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -1160,6 +1254,7 @@ public function testSubmitMultipleExpandedInvalidArrayChoice() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'foobar')); @@ -1187,6 +1282,7 @@ public function testSubmitMultipleExpandedEmpty() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array()); @@ -1215,6 +1311,7 @@ public function testSubmitMultipleExpandedEmptyNoChoices() 'multiple' => true, 'expanded' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(array()); @@ -1229,10 +1326,11 @@ public function testSubmitMultipleExpandedWithEmptyChild() 'multiple' => true, 'expanded' => true, 'choices' => array( - '' => 'Empty', - 1 => 'Not Empty', - 2 => 'Not Empty 2', + 'Empty' => '', + 'Not Empty' => 1, + 'Not Empty 2' => 2, ), + 'choices_as_values' => true, )); $form->submit(array('', '2')); @@ -1312,12 +1410,15 @@ public function testLegacySubmitMultipleExpandedObjectChoices() $this->assertNull($form[4]->getViewData()); } + /** + * @group legacy + */ public function testSubmitMultipleExpandedNumericChoices() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'multiple' => true, 'expanded' => true, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->submit(array('1', '2')); @@ -1373,16 +1474,18 @@ public function testMultipleSelectedObjectChoices() $this->assertFalse($selectedChecker($view->vars['choices'][1]->value, $view->vars['value'])); } - /* + /** * We need this functionality to create choice fields for Boolean types, - * e.g. false => 'No', true => 'Yes' + * e.g. false => 'No', true => 'Yes'. + * + * @group legacy */ public function testSetDataSingleNonExpandedAcceptsBoolean() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'multiple' => false, 'expanded' => false, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->setData(false); @@ -1392,12 +1495,15 @@ public function testSetDataSingleNonExpandedAcceptsBoolean() $this->assertTrue($form->isSynchronized()); } + /** + * @group legacy + */ public function testSetDataMultipleNonExpandedAcceptsBoolean() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'multiple' => true, 'expanded' => false, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->setData(array(false, true)); @@ -1411,6 +1517,7 @@ public function testPassRequiredToView() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1422,6 +1529,7 @@ public function testPassNonRequiredToView() $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1433,6 +1541,7 @@ public function testPassMultipleToView() $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'multiple' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1444,6 +1553,7 @@ public function testPassExpandedToView() $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1454,6 +1564,7 @@ public function testPassChoiceTranslationDomainToView() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1464,6 +1575,7 @@ public function testChoiceTranslationDomainWithTrueValueToView() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, 'choice_translation_domain' => true, )); $view = $form->createView(); @@ -1475,6 +1587,7 @@ public function testDefaultChoiceTranslationDomainIsSameAsTranslationDomainToVie { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, 'translation_domain' => 'foo', )); $view = $form->createView(); @@ -1488,7 +1601,10 @@ public function testInheritChoiceTranslationDomainFromParent() ->createNamedBuilder('parent', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, array( 'translation_domain' => 'domain', )) - ->add('child', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType') + ->add('child', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array( + 'choices' => array(), + 'choices_as_values' => true, + )) ->getForm() ->createView(); @@ -1501,6 +1617,7 @@ public function testPlaceholderIsNullByDefaultIfRequired() 'multiple' => false, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1513,6 +1630,7 @@ public function testPlaceholderIsEmptyStringByDefaultIfNotRequired() 'multiple' => false, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1530,6 +1648,7 @@ public function testPassPlaceholderToView($multiple, $expanded, $required, $plac 'required' => $required, 'placeholder' => $placeholder, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1549,6 +1668,7 @@ public function testPassEmptyValueBC($multiple, $expanded, $required, $placehold 'required' => $required, 'empty_value' => $placeholder, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1568,7 +1688,8 @@ public function testDontPassPlaceholderIfContainedInChoices($multiple, $expanded 'expanded' => $expanded, 'required' => $required, 'placeholder' => $placeholder, - 'choices' => array('a' => 'A', '' => 'Empty'), + 'choices' => array('A' => 'a', 'Empty' => ''), + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1622,9 +1743,10 @@ public function getOptionsWithPlaceholder() public function testPassChoicesToView() { - $choices = array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'); + $choices = array('A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd'); $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'choices' => $choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1638,9 +1760,10 @@ public function testPassChoicesToView() public function testPassPreferredChoicesToView() { - $choices = array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'); + $choices = array('A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd'); $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'choices' => $choices, + 'choices_as_values' => true, 'preferred_choices' => array('b', 'd'), )); $view = $form->createView(); @@ -1659,6 +1782,7 @@ public function testPassHierarchicalChoicesToView() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'choices' => $this->groupedChoices, + 'choices_as_values' => true, 'preferred_choices' => array('b', 'd'), )); $view = $form->createView(); @@ -1704,12 +1828,30 @@ public function testPassChoiceDataToView() ), $view->vars['choices']); } + /** + * @group legacy + */ + public function testDuplicateChoiceLabels() + { + $form = $this->factory->create('choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'A'), + )); + $view = $form->createView(); + + $this->assertEquals(array( + new ChoiceView('a', 'a', 'A'), + new ChoiceView('b', 'b', 'B'), + new ChoiceView('c', 'c', 'A'), + ), $view->vars['choices']); + } + public function testAdjustFullNameForMultipleNonExpanded() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1721,6 +1863,7 @@ public function testInitializeWithEmptyChoices() { $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'choices' => array(), + 'choices_as_values' => true, )); } diff --git a/src/Symfony/Component/Form/Tests/FormFactoryTest.php b/src/Symfony/Component/Form/Tests/FormFactoryTest.php index 1f65c3c0c1499..32b6e73394c7c 100644 --- a/src/Symfony/Component/Form/Tests/FormFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/FormFactoryTest.php @@ -302,13 +302,55 @@ public function testCreateThrowsUnderstandableException() $this->factory->create(new \stdClass()); } + public function testCreateUsesBlockPrefixIfTypeGivenAsString() + { + $options = array('a' => '1', 'b' => '2'); + $resolvedOptions = array('a' => '2', 'b' => '3'); + + // the interface does not have the method, so use the real class + $resolvedType = $this->getMockBuilder('Symfony\Component\Form\ResolvedFormType') + ->disableOriginalConstructor() + ->getMock(); + + $resolvedType->expects($this->any()) + ->method('getBlockPrefix') + ->willReturn('TYPE_PREFIX'); + + $this->registry->expects($this->any()) + ->method('getType') + ->with('TYPE') + ->will($this->returnValue($resolvedType)); + + $resolvedType->expects($this->once()) + ->method('createBuilder') + ->with($this->factory, 'TYPE_PREFIX', $options) + ->will($this->returnValue($this->builder)); + + $this->builder->expects($this->any()) + ->method('getOptions') + ->will($this->returnValue($resolvedOptions)); + + $resolvedType->expects($this->once()) + ->method('buildForm') + ->with($this->builder, $resolvedOptions); + + $this->builder->expects($this->once()) + ->method('getForm') + ->will($this->returnValue('FORM')); + + $this->assertSame('FORM', $this->factory->create('TYPE', null, $options)); + } + + /** + * @group legacy + */ public function testCreateUsesTypeNameIfTypeGivenAsString() { $options = array('a' => '1', 'b' => '2'); $resolvedOptions = array('a' => '2', 'b' => '3'); $resolvedType = $this->getMockResolvedType(); - $this->registry->expects($this->once()) + $this->registry->expects($this->any()) ->method('getType') ->with('TYPE') ->will($this->returnValue($resolvedType)); @@ -333,13 +375,16 @@ public function testCreateUsesTypeNameIfTypeGivenAsString() $this->assertSame('FORM', $this->factory->create('TYPE', null, $options)); } + /** + * @group legacy + */ public function testCreateStripsNamespaceOffTypeName() { $options = array('a' => '1', 'b' => '2'); $resolvedOptions = array('a' => '2', 'b' => '3'); $resolvedType = $this->getMockResolvedType(); - $this->registry->expects($this->once()) + $this->registry->expects($this->any()) ->method('getType') ->with('Vendor\Name\Space\UserForm') ->will($this->returnValue($resolvedType)); @@ -364,13 +409,16 @@ public function testCreateStripsNamespaceOffTypeName() $this->assertSame('FORM', $this->factory->create('Vendor\Name\Space\UserForm', null, $options)); } + /** + * @group legacy + */ public function testLegacyCreateStripsNamespaceOffTypeNameAccessByFQCN() { $options = array('a' => '1', 'b' => '2'); $resolvedOptions = array('a' => '2', 'b' => '3'); $resolvedType = $this->getMockResolvedType(); - $this->registry->expects($this->once()) + $this->registry->expects($this->any()) ->method('getType') ->with('userform') ->will($this->returnValue($resolvedType)); @@ -395,13 +443,16 @@ public function testLegacyCreateStripsNamespaceOffTypeNameAccessByFQCN() $this->assertSame('FORM', $this->factory->create('userform', null, $options)); } + /** + * @group legacy + */ public function testCreateStripsTypeSuffixOffTypeName() { $options = array('a' => '1', 'b' => '2'); $resolvedOptions = array('a' => '2', 'b' => '3'); $resolvedType = $this->getMockResolvedType(); - $this->registry->expects($this->once()) + $this->registry->expects($this->any()) ->method('getType') ->with('Vendor\Name\Space\UserType') ->will($this->returnValue($resolvedType)); @@ -426,13 +477,16 @@ public function testCreateStripsTypeSuffixOffTypeName() $this->assertSame('FORM', $this->factory->create('Vendor\Name\Space\UserType', null, $options)); } + /** + * @group legacy + */ public function testCreateDoesNotStripTypeSuffixIfResultEmpty() { $options = array('a' => '1', 'b' => '2'); $resolvedOptions = array('a' => '2', 'b' => '3'); $resolvedType = $this->getMockResolvedType(); - $this->registry->expects($this->once()) + $this->registry->expects($this->any()) ->method('getType') ->with('Vendor\Name\Space\Type') ->will($this->returnValue($resolvedType)); @@ -457,13 +511,16 @@ public function testCreateDoesNotStripTypeSuffixIfResultEmpty() $this->assertSame('FORM', $this->factory->create('Vendor\Name\Space\Type', null, $options)); } + /** + * @group legacy + */ public function testCreateConvertsTypeToUnderscoreSyntax() { $options = array('a' => '1', 'b' => '2'); $resolvedOptions = array('a' => '2', 'b' => '3'); $resolvedType = $this->getMockResolvedType(); - $this->registry->expects($this->once()) + $this->registry->expects($this->any()) ->method('getType') ->with('Vendor\Name\Space\MyProfileHTMLType') ->will($this->returnValue($resolvedType)); diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 9f5de6dfd764c..2722026046dbd 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -840,19 +840,19 @@ public function testGetPropertyPathDefaultsToIndexedNameIfDataClassOfFirstParent $this->assertEquals(new PropertyPath('[name]'), $form->getPropertyPath()); } - /** - * @expectedException \Symfony\Component\Form\Exception\LogicException - */ - public function testViewDataMustNotBeObjectIfDataClassIsNull() + public function testViewDataMayBeObjectIfDataClassIsNull() { + $object = new \stdClass(); $config = new FormConfigBuilder('name', null, $this->dispatcher); $config->addViewTransformer(new FixedDataTransformer(array( '' => '', - 'foo' => new \stdClass(), + 'foo' => $object, ))); $form = new Form($config); $form->setData('foo'); + + $this->assertSame($object, $form->getViewData()); } public function testViewDataMayBeArrayAccessIfDataClassIsNull() diff --git a/src/Symfony/Component/Form/phpunit.xml.dist b/src/Symfony/Component/Form/phpunit.xml.dist index fd668a9e8b779..1c4acf476238d 100644 --- a/src/Symfony/Component/Form/phpunit.xml.dist +++ b/src/Symfony/Component/Form/phpunit.xml.dist @@ -20,6 +20,7 @@ ./ + ./Resources ./Tests ./vendor diff --git a/src/Symfony/Component/HttpFoundation/ParameterBag.php b/src/Symfony/Component/HttpFoundation/ParameterBag.php index 35b4869533e46..4560322671470 100644 --- a/src/Symfony/Component/HttpFoundation/ParameterBag.php +++ b/src/Symfony/Component/HttpFoundation/ParameterBag.php @@ -90,7 +90,7 @@ public function add(array $parameters = array()) */ public function get($key, $default = null, $deep = false) { - if (true === $deep) { + if ($deep) { @trigger_error('Using paths to find deeper items in '.__METHOD__.' is deprecated since version 2.8 and will be removed in 3.0. Filter the returned value in your own code instead.', E_USER_DEPRECATED); } @@ -214,7 +214,7 @@ public function getAlnum($key, $default = '', $deep = false) public function getDigits($key, $default = '', $deep = false) { // we need to remove - and + because they're allowed in the filter - return str_replace(array('-', '+'), '', $this->filter($key, $default, $deep, FILTER_SANITIZE_NUMBER_INT)); + return str_replace(array('-', '+'), '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT, array(), $deep)); } /** @@ -242,7 +242,7 @@ public function getInt($key, $default = 0, $deep = false) */ public function getBoolean($key, $default = false, $deep = false) { - return $this->filter($key, $default, $deep, FILTER_VALIDATE_BOOLEAN); + return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN, array(), $deep); } /** @@ -250,16 +250,31 @@ public function getBoolean($key, $default = false, $deep = false) * * @param string $key Key. * @param mixed $default Default = null. - * @param bool $deep Default = false. * @param int $filter FILTER_* constant. * @param mixed $options Filter options. + * @param bool $deep Default = false. * * @see http://php.net/manual/en/function.filter-var.php * * @return mixed */ - public function filter($key, $default = null, $deep = false, $filter = FILTER_DEFAULT, $options = array()) + public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = array(), $deep = false) { + static $filters = null; + + if (null === $filters) { + foreach (filter_list() as $tmp) { + $filters[filter_id($tmp)] = 1; + } + } + if (is_bool($filter) || !isset($filters[$filter]) || is_array($deep)) { + @trigger_error('Passing the $deep boolean as 3rd argument to the '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Remove it altogether as the $deep argument will be removed in 3.0.', E_USER_ERROR); + $tmp = $deep; + $deep = $filter; + $filter = $options; + $options = $tmp; + } + $value = $this->get($key, $default, $deep); // Always turn $options into an array - this allows filter_var option shortcuts. diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index edc77df10d6de..de36953cecef5 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -724,7 +724,7 @@ public static function getHttpMethodParameterOverride() */ public function get($key, $default = null, $deep = false) { - if (true === $deep) { + if ($deep) { @trigger_error('Using paths to find deeper items in '.__METHOD__.' is deprecated since version 2.8 and will be removed in 3.0. Filter the returned value in your own code instead.', E_USER_DEPRECATED); } diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index d67584f13974f..c88fb6cb74392 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -160,6 +160,7 @@ class Response 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 422 => 'Unprocessable Entity', // RFC4918 423 => 'Locked', // RFC4918 424 => 'Failed Dependency', // RFC4918 @@ -328,9 +329,6 @@ public function sendHeaders() $this->setDate(\DateTime::createFromFormat('U', time())); } - // status - header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); - // headers foreach ($this->headers->allPreserveCase() as $name => $values) { foreach ($values as $value) { @@ -338,6 +336,9 @@ public function sendHeaders() } } + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + // cookies foreach ($this->headers->getCookies() as $cookie) { setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); @@ -454,7 +455,7 @@ public function setStatusCode($code, $text = null) } if (null === $text) { - $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : ''; + $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status'; return $this; } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php index d797fb3bbd27b..06a6cbf413c8f 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php @@ -172,26 +172,26 @@ public function testFilter() $this->assertEmpty($bag->filter('nokey'), '->filter() should return empty by default if no key is found'); - $this->assertEquals('0123', $bag->filter('digits', '', false, FILTER_SANITIZE_NUMBER_INT), '->filter() gets a value of parameter as integer filtering out invalid characters'); + $this->assertEquals('0123', $bag->filter('digits', '', FILTER_SANITIZE_NUMBER_INT), '->filter() gets a value of parameter as integer filtering out invalid characters'); - $this->assertEquals('example@example.com', $bag->filter('email', '', false, FILTER_VALIDATE_EMAIL), '->filter() gets a value of parameter as email'); + $this->assertEquals('example@example.com', $bag->filter('email', '', FILTER_VALIDATE_EMAIL), '->filter() gets a value of parameter as email'); - $this->assertEquals('http://example.com/foo', $bag->filter('url', '', false, FILTER_VALIDATE_URL, array('flags' => FILTER_FLAG_PATH_REQUIRED)), '->filter() gets a value of parameter as URL with a path'); + $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, array('flags' => FILTER_FLAG_PATH_REQUIRED)), '->filter() gets a value of parameter as URL with a path'); // This test is repeated for code-coverage - $this->assertEquals('http://example.com/foo', $bag->filter('url', '', false, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED), '->filter() gets a value of parameter as URL with a path'); + $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED), '->filter() gets a value of parameter as URL with a path'); - $this->assertFalse($bag->filter('dec', '', false, FILTER_VALIDATE_INT, array( + $this->assertFalse($bag->filter('dec', '', FILTER_VALIDATE_INT, array( 'flags' => FILTER_FLAG_ALLOW_HEX, 'options' => array('min_range' => 1, 'max_range' => 0xff), )), '->filter() gets a value of parameter as integer between boundaries'); - $this->assertFalse($bag->filter('hex', '', false, FILTER_VALIDATE_INT, array( + $this->assertFalse($bag->filter('hex', '', FILTER_VALIDATE_INT, array( 'flags' => FILTER_FLAG_ALLOW_HEX, 'options' => array('min_range' => 1, 'max_range' => 0xff), )), '->filter() gets a value of parameter as integer between boundaries'); - $this->assertEquals(array('bang'), $bag->filter('array', '', false), '->filter() gets a value of parameter as an array'); + $this->assertEquals(array('bang'), $bag->filter('array', ''), '->filter() gets a value of parameter as an array'); } public function testGetIterator() diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 9321a3243fc17..f13f4791d93c2 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -590,7 +590,7 @@ public function testGetUserInfo() { $request = new Request(); - $server['PHP_AUTH_USER'] = 'fabien'; + $server = array('PHP_AUTH_USER' => 'fabien'); $request->initialize(array(), array(), array(), array(), array(), $server); $this->assertEquals('fabien', $request->getUserInfo()); diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php index 0f4ddfb7f08d0..97674d48fdf55 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php @@ -695,7 +695,7 @@ public function getStatusCodeFixtures() array('200', null, 'OK'), array('200', false, ''), array('200', 'foo', 'foo'), - array('199', null, ''), + array('199', null, 'unknown status'), array('199', false, ''), array('199', 'foo', 'foo'), ); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php index f9f3bdf01e4c2..463caa01da4b6 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php @@ -110,7 +110,10 @@ public function unlock(Request $request) public function isLocked(Request $request) { - return is_file($this->getPath($this->getCacheKey($request).'.lck')); + $path = $this->getPath($this->getCacheKey($request).'.lck'); + clearstatcache(true, $path); + + return is_file($path); } /** diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index ba96278837ac9..c53d4f43a962c 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -23,7 +23,6 @@ use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; use Symfony\Component\DependencyInjection\Loader\ClosureLoader; -use Symfony\Component\DependencyInjection\ResettableContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Bundle\BundleInterface; @@ -60,12 +59,12 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '2.8.0-BETA1'; + const VERSION = '2.8.0'; const VERSION_ID = 20800; const MAJOR_VERSION = 2; const MINOR_VERSION = 8; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'BETA1'; + const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '05/2018'; const END_OF_LIFE = '05/2019'; @@ -171,10 +170,6 @@ public function shutdown() $bundle->setContainer(null); } - if ($this->container instanceof ResettableContainerInterface) { - $this->container->reset(); - } - $this->container = null; } diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php index 32db4e47adc5a..c298c84ec0059 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php @@ -224,7 +224,7 @@ protected function controllerMethod2($foo, $bar = null) { } - protected function controllerMethod3($foo, $bar = null, $foobar) + protected function controllerMethod3($foo, $bar, $foobar) { } diff --git a/src/Symfony/Component/Intl/phpunit.xml.dist b/src/Symfony/Component/Intl/phpunit.xml.dist index e42226a1b1240..9e7ce2f0b2642 100644 --- a/src/Symfony/Component/Intl/phpunit.xml.dist +++ b/src/Symfony/Component/Intl/phpunit.xml.dist @@ -26,6 +26,7 @@ ./ + ./Resources ./Tests ./vendor diff --git a/src/Symfony/Component/Ldap/Exception/ConnectionException.php b/src/Symfony/Component/Ldap/Exception/ConnectionException.php index 80d9af51ea911..d5023c5d02d7c 100644 --- a/src/Symfony/Component/Ldap/Exception/ConnectionException.php +++ b/src/Symfony/Component/Ldap/Exception/ConnectionException.php @@ -15,6 +15,8 @@ * ConnectionException is throw if binding to ldap can not be established. * * @author Grégoire Pineau + * + * @internal */ class ConnectionException extends \RuntimeException { diff --git a/src/Symfony/Component/Ldap/Exception/LdapException.php b/src/Symfony/Component/Ldap/Exception/LdapException.php index 213625b021b2b..ef3bd929bb852 100644 --- a/src/Symfony/Component/Ldap/Exception/LdapException.php +++ b/src/Symfony/Component/Ldap/Exception/LdapException.php @@ -15,6 +15,8 @@ * LdapException is throw if php ldap module is not loaded. * * @author Grégoire Pineau + * + * @internal */ class LdapException extends \RuntimeException { diff --git a/src/Symfony/Component/Ldap/LdapClient.php b/src/Symfony/Component/Ldap/LdapClient.php index ebb263d5cf0c5..0a8fa22c16c6f 100644 --- a/src/Symfony/Component/Ldap/LdapClient.php +++ b/src/Symfony/Component/Ldap/LdapClient.php @@ -18,6 +18,8 @@ * @author Grégoire Pineau * @author Francis Besset * @author Charles Sarrazin + * + * @internal */ class LdapClient implements LdapClientInterface { diff --git a/src/Symfony/Component/Ldap/LdapClientInterface.php b/src/Symfony/Component/Ldap/LdapClientInterface.php index 65dd03b38f8dc..dcdc0818da10b 100644 --- a/src/Symfony/Component/Ldap/LdapClientInterface.php +++ b/src/Symfony/Component/Ldap/LdapClientInterface.php @@ -18,12 +18,11 @@ * * @author Grégoire Pineau * @author Charles Sarrazin + * + * @internal */ interface LdapClientInterface { - const LDAP_ESCAPE_FILTER = 0x01; - const LDAP_ESCAPE_DN = 0x02; - /** * Return a connection bound to the ldap. * diff --git a/src/Symfony/Component/Ldap/README.md b/src/Symfony/Component/Ldap/README.md index 751ce6ad2511d..6de60f10bbb9e 100644 --- a/src/Symfony/Component/Ldap/README.md +++ b/src/Symfony/Component/Ldap/README.md @@ -1,10 +1,14 @@ Ldap Component -============= +============== A Ldap client for PHP on top of PHP's ldap extension. -This component also provides a stub for the missing -`ldap_escape` function in PHP versions lower than 5.6. +Disclaimer +---------- + +This component is currently marked as internal, as it +still needs some work. Breaking changes will be introduced +in the next minor version of Symfony. Documentation ------------- diff --git a/src/Symfony/Component/Ldap/composer.json b/src/Symfony/Component/Ldap/composer.json index 21e4adea9618b..bdbba744b3647 100644 --- a/src/Symfony/Component/Ldap/composer.json +++ b/src/Symfony/Component/Ldap/composer.json @@ -21,7 +21,10 @@ "ext-ldap": "*" }, "autoload": { - "psr-4": { "Symfony\\Component\\Ldap\\": "" } + "psr-4": { "Symfony\\Component\\Ldap\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "minimum-stability": "dev", "extra": { diff --git a/src/Symfony/Component/OptionsResolver/phpunit.xml.dist b/src/Symfony/Component/OptionsResolver/phpunit.xml.dist index 5a2316a1c9df5..abf84614bcf76 100644 --- a/src/Symfony/Component/OptionsResolver/phpunit.xml.dist +++ b/src/Symfony/Component/OptionsResolver/phpunit.xml.dist @@ -22,6 +22,7 @@ ./Resources ./Tests + ./vendor diff --git a/src/Symfony/Component/Process/PhpExecutableFinder.php b/src/Symfony/Component/Process/PhpExecutableFinder.php index f8f57cc536a25..fb297825fe364 100644 --- a/src/Symfony/Component/Process/PhpExecutableFinder.php +++ b/src/Symfony/Component/Process/PhpExecutableFinder.php @@ -35,14 +35,17 @@ public function __construct() */ public function find($includeArgs = true) { + $args = $this->findArguments(); + $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; + // HHVM support if (defined('HHVM_VERSION')) { - return (getenv('PHP_BINARY') ?: PHP_BINARY).($includeArgs ? ' '.implode(' ', $this->findArguments()) : ''); + return (getenv('PHP_BINARY') ?: PHP_BINARY).$args; } // PHP_BINARY return the current sapi executable - if (defined('PHP_BINARY') && PHP_BINARY && in_array(PHP_SAPI, array('cli', 'cli-server')) && is_file(PHP_BINARY)) { - return PHP_BINARY; + if (defined('PHP_BINARY') && PHP_BINARY && in_array(PHP_SAPI, array('cli', 'cli-server', 'phpdbg')) && is_file(PHP_BINARY)) { + return PHP_BINARY.$args; } if ($php = getenv('PHP_PATH')) { @@ -76,9 +79,10 @@ public function findArguments() { $arguments = array(); - // HHVM support if (defined('HHVM_VERSION')) { $arguments[] = '--php'; + } elseif ('phpdbg' === PHP_SAPI) { + $arguments[] = '-qrr'; } return $arguments; diff --git a/src/Symfony/Component/Process/PhpProcess.php b/src/Symfony/Component/Process/PhpProcess.php index 1adbd977adf88..4a2a2625ffae0 100644 --- a/src/Symfony/Component/Process/PhpProcess.php +++ b/src/Symfony/Component/Process/PhpProcess.php @@ -39,6 +39,13 @@ public function __construct($script, $cwd = null, array $env = null, $timeout = if (false === $php = $executableFinder->find()) { $php = null; } + if ('phpdbg' === PHP_SAPI) { + $file = tempnam(sys_get_temp_dir(), 'dbg'); + file_put_contents($file, $script); + register_shutdown_function('unlink', $file); + $php .= ' '.ProcessUtils::escapeArgument($file); + $script = null; + } parent::__construct($php, $cwd, $env, $script, $timeout, $options); } diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 699151f384921..c1e732170f922 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -280,8 +280,20 @@ public function start($callback = null) } } + $ptsWorkaround = null; + + if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + // Workaround for the bug, when PTS functionality is enabled. + // @see : https://bugs.php.net/69442 + $ptsWorkaround = fopen(__FILE__, 'r'); + } + $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options); + if ($ptsWorkaround) { + fclose($ptsWorkaround); + } + if (!is_resource($this->process)) { throw new RuntimeException('Unable to launch a new process.'); } @@ -763,22 +775,14 @@ public function getStatus() * Stops the process. * * @param int|float $timeout The timeout in seconds - * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL + * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) * * @return int The exit-code of the process - * - * @throws RuntimeException if the process got signaled */ public function stop($timeout = 10, $signal = null) { $timeoutMicro = microtime(true) + $timeout; if ($this->isRunning()) { - if ('\\' === DIRECTORY_SEPARATOR && !$this->isSigchildEnabled()) { - exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); - if ($exitCode > 0) { - throw new RuntimeException('Unable to kill the process'); - } - } // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here $this->doSignal(15, false); do { @@ -786,13 +790,9 @@ public function stop($timeout = 10, $signal = null) } while ($this->isRunning() && microtime(true) < $timeoutMicro); if ($this->isRunning() && !$this->isSigchildEnabled()) { - if (null !== $signal || defined('SIGKILL')) { - // avoid exception here : - // process is supposed to be running, but it might have stop - // just after this line. - // in any case, let's silently discard the error, we can not do anything - $this->doSignal($signal ?: SIGKILL, false); - } + // Avoid exception here: process is supposed to be running, but it might have stopped just + // after this line. In any case, let's silently discard the error, we cannot do anything. + $this->doSignal($signal ?: 9, false); } } @@ -1461,7 +1461,18 @@ private function doSignal($signal, $throwException) return false; } - if (true !== @proc_terminate($this->process, $signal)) { + if ('\\' === DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); + if ($exitCode) { + if ($throwException) { + throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); + } + + return false; + } + } + + if (true !== @proc_terminate($this->process, $signal) && '\\' !== DIRECTORY_SEPARATOR) { if ($throwException) { throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); } diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php index b2f60f9ffd984..fca3729be8e20 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -28,7 +28,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase public static function setUpBeforeClass() { $phpBin = new PhpExecutableFinder(); - self::$phpBin = $phpBin->find(); + self::$phpBin = 'phpdbg' === PHP_SAPI ? 'php' : $phpBin->find(); } public function testThatProcessDoesNotThrowWarningDuringRun() @@ -81,7 +81,7 @@ public function testStopWithTimeoutIsActuallyWorking() // exec is mandatory here since we send a signal to the process // see https://github.com/symfony/symfony/issues/5030 about prepending // command with exec - $p = $this->getProcess('exec php '.__DIR__.'/NonStopableProcess.php 3'); + $p = $this->getProcess('exec '.self::$phpBin.' '.__DIR__.'/NonStopableProcess.php 3'); $p->start(); usleep(100000); $start = microtime(true); @@ -470,7 +470,7 @@ public function testTTYCommand() $this->markTestSkipped('Windows does have /dev/tty support'); } - $process = $this->getProcess('echo "foo" >> /dev/null && php -r "usleep(100000);"'); + $process = $this->getProcess('echo "foo" >> /dev/null && '.self::$phpBin.' -r "usleep(100000);"'); $process->setTty(true); $process->start(); $this->assertTrue($process->isRunning()); @@ -730,7 +730,7 @@ public function testProcessThrowsExceptionWhenExternallySignaled() $termSignal = defined('SIGKILL') ? SIGKILL : 9; - $process = $this->getProcess('exec php -r "while (true) {}"'); + $process = $this->getProcess('exec '.self::$phpBin.' -r "while (true) {}"'); $process->start(); posix_kill($process->getPid(), $termSignal); @@ -756,18 +756,6 @@ public function testRestart() $this->assertNotEquals($process1->getOutput(), $process2->getOutput()); } - public function testPhpDeadlock() - { - $this->markTestSkipped('Can cause PHP to hang'); - - // Sleep doesn't work as it will allow the process to handle signals and close - // file handles from the other end. - $process = $this->getProcess(self::$phpBin.' -r "while (true) {}"'); - $process->start(); - - // PHP will deadlock when it tries to cleanup $process - } - public function testRunProcessWithTimeout() { $timeout = 0.5; diff --git a/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php b/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php index cd4abedc9df73..87d0efe9ebf1f 100644 --- a/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php +++ b/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php @@ -43,7 +43,27 @@ public function testFindWithPhpPath() } /** - * tests find() with the env var PHP_PATH. + * tests find() with the constant PHP_BINARY. + * + * @requires PHP 5.4 + */ + public function testFind() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Should not be executed in HHVM context.'); + } + + $f = new PhpExecutableFinder(); + + $current = PHP_BINARY; + $args = 'phpdbg' === PHP_SAPI ? ' -qrr' : ''; + + $this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP'); + $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP'); + } + + /** + * tests find() with the env var / constant PHP_BINARY with HHVM. */ public function testFindWithHHVM() { @@ -68,6 +88,8 @@ public function testFindArguments() if (defined('HHVM_VERSION')) { $this->assertEquals($f->findArguments(), array('--php'), '::findArguments() returns HHVM arguments'); + } elseif ('phpdbg' === PHP_SAPI) { + $this->assertEquals($f->findArguments(), array('-qrr'), '::findArguments() returns phpdbg arguments'); } else { $this->assertEquals($f->findArguments(), array(), '::findArguments() returns no arguments'); } diff --git a/src/Symfony/Component/Process/Tests/PhpProcessTest.php b/src/Symfony/Component/Process/Tests/PhpProcessTest.php index 5dc546cc1ce6e..2cf79aa1a6d15 100644 --- a/src/Symfony/Component/Process/Tests/PhpProcessTest.php +++ b/src/Symfony/Component/Process/Tests/PhpProcessTest.php @@ -30,6 +30,10 @@ public function testNonBlockingWorks() public function testCommandLine() { + if ('phpdbg' === PHP_SAPI) { + $this->markTestSkipped('phpdbg SAPI is not supported by this test.'); + } + $process = new PhpProcess(<<getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); - $process->run(function () use ($process) { - $process->stop(); - }); - } catch (\RuntimeException $e) { - $this->fail('A call to stop() is not expected to cause wait() to throw a RuntimeException'); - } + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + $process->stop(); + }); + $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException'); } public function testKillSignalTerminatesProcessCleanly() { $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - try { - $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); - $process->run(function () use ($process) { - if ($process->isRunning()) { - $process->signal(defined('SIGKILL') ? SIGKILL : 9); - } - }); - } catch (\RuntimeException $e) { - $this->fail('A call to signal() is not expected to cause wait() to throw a RuntimeException'); - } + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(defined('SIGKILL') ? SIGKILL : 9); + } + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); } public function testTermSignalTerminatesProcessCleanly() { $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - try { - $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); - $process->run(function () use ($process) { - if ($process->isRunning()) { - $process->signal(defined('SIGTERM') ? SIGTERM : 15); - } - }); - } catch (\RuntimeException $e) { - $this->fail('A call to signal() is not expected to cause wait() to throw a RuntimeException'); - } + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(defined('SIGTERM') ? SIGTERM : 15); + } + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); } public function testStopWithTimeoutIsActuallyWorking() diff --git a/src/Symfony/Component/Process/phpunit.xml.dist b/src/Symfony/Component/Process/phpunit.xml.dist index b5d605c2efbbe..788500084abf8 100644 --- a/src/Symfony/Component/Process/phpunit.xml.dist +++ b/src/Symfony/Component/Process/phpunit.xml.dist @@ -21,6 +21,7 @@ ./ ./Tests + ./vendor diff --git a/src/Symfony/Component/PropertyAccess/phpunit.xml.dist b/src/Symfony/Component/PropertyAccess/phpunit.xml.dist index fcf5f6ae56dd3..b0b20c1bd9460 100644 --- a/src/Symfony/Component/PropertyAccess/phpunit.xml.dist +++ b/src/Symfony/Component/PropertyAccess/phpunit.xml.dist @@ -22,6 +22,7 @@ ./Resources ./Tests + ./vendor diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json index ee34325b7663e..d7861d5b03d88 100644 --- a/src/Symfony/Component/PropertyInfo/composer.json +++ b/src/Symfony/Component/PropertyInfo/composer.json @@ -39,7 +39,10 @@ "symfony/serializer": "To use Serializer metadata" }, "autoload": { - "psr-4": { "Symfony\\Component\\PropertyInfo\\": "" } + "psr-4": { "Symfony\\Component\\PropertyInfo\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "minimum-stability": "dev", "extra": { diff --git a/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php b/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php index b53caafc9e35e..943e83831305f 100644 --- a/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php +++ b/src/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php @@ -53,7 +53,7 @@ public function dump(array $options = array()) */ class {$options['class']} extends {$options['base_class']} { - private static \$declaredRoutes = {$this->generateDeclaredRoutes()}; + private static \$declaredRoutes; /** * Constructor. @@ -62,6 +62,9 @@ public function __construct(RequestContext \$context, LoggerInterface \$logger = { \$this->context = \$context; \$this->logger = \$logger; + if (null === self::\$declaredRoutes) { + self::\$declaredRoutes = {$this->generateDeclaredRoutes()}; + } } {$this->generateGenerateMethod()} diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index 700ee37234963..a54a049d27d12 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -49,16 +49,17 @@ public function __construct(LoaderInterface $loader = null) /** * Import an external routing resource and returns the RouteCollectionBuilder. * - * $routes->mount('/blog', $routes->import('blog.yml')); + * $routes->import('blog.yml', '/blog'); * - * @param mixed $resource - * @param string $type + * @param mixed $resource + * @param string|null $prefix + * @param string $type * * @return RouteCollectionBuilder * * @throws FileLoaderLoadException */ - public function import($resource, $type = null) + public function import($resource, $prefix = '/', $type = null) { /** @var RouteCollection $collection */ $collection = $this->load($resource, $type); @@ -73,6 +74,9 @@ public function import($resource, $type = null) $builder->addResource($resource); } + // mount into this builder + $this->mount($prefix, $builder); + return $builder; } diff --git a/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php index 91dbd555f9681..9c6c0cd3f158d 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php @@ -34,6 +34,11 @@ class PhpGeneratorDumperTest extends \PHPUnit_Framework_TestCase */ private $testTmpFilepath; + /** + * @var string + */ + private $largeTestTmpFilepath; + protected function setUp() { parent::setUp(); @@ -41,7 +46,9 @@ protected function setUp() $this->routeCollection = new RouteCollection(); $this->generatorDumper = new PhpGeneratorDumper($this->routeCollection); $this->testTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.php'; + $this->largeTestTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.large.php'; @unlink($this->testTmpFilepath); + @unlink($this->largeTestTmpFilepath); } protected function tearDown() @@ -76,6 +83,33 @@ public function testDumpWithRoutes() $this->assertEquals($relativeUrlWithoutParameter, '/app.php/testing2'); } + public function testDumpWithTooManyRoutes() + { + $this->routeCollection->add('Test', new Route('/testing/{foo}')); + for ( $i = 0; $i < 32769; ++$i ) { + $this->routeCollection->add('route_'.$i, new Route('/route_'.$i)); + } + $this->routeCollection->add('Test2', new Route('/testing2')); + + $data = $this->generatorDumper->dump(array( + 'class' => 'ProjectLargeUrlGenerator', + )); + file_put_contents($this->largeTestTmpFilepath, $data); + include $this->largeTestTmpFilepath; + + $projectUrlGenerator = new \ProjectLargeUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals($absoluteUrlWithParameter, 'http://localhost/app.php/testing/bar'); + $this->assertEquals($absoluteUrlWithoutParameter, 'http://localhost/app.php/testing2'); + $this->assertEquals($relativeUrlWithParameter, '/app.php/testing/bar'); + $this->assertEquals($relativeUrlWithoutParameter, '/app.php/testing2'); + } + /** * @expectedException \InvalidArgumentException */ diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php index c19fdcbf9ff69..e3a98e3b72698 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php @@ -45,7 +45,7 @@ public function testImport() // import the file! $routes = new RouteCollectionBuilder($loader); - $importedRoutes = $routes->import('admin_routing.yml', 'yaml'); + $importedRoutes = $routes->import('admin_routing.yml', '/', 'yaml'); // we should get back a RouteCollectionBuilder $this->assertInstanceOf('Symfony\Component\Routing\RouteCollectionBuilder', $importedRoutes); @@ -56,6 +56,9 @@ public function testImport() $this->assertSame($originalRoute, $route); // should return file_resource.yml, which is in the original collection $this->assertCount(1, $addedCollection->getResources()); + + // make sure the routes were imported into the top-level builder + $this->assertCount(1, $routes->build()); } /** @@ -285,7 +288,7 @@ public function testFlushSetsPrefixedWithMultipleLevels() ->method('load') ->will($this->returnValue($importedCollection)); // import this from the /admin route builder - $adminRoutes->mount('/imported', $adminRoutes->import('admin.yml')); + $adminRoutes->import('admin.yml', '/imported'); $collection = $routes->build(); $this->assertEquals('/admin/dashboard', $collection->get('admin_dashboard')->getPath(), 'Routes before mounting have the prefix'); diff --git a/src/Symfony/Component/Routing/phpunit.xml.dist b/src/Symfony/Component/Routing/phpunit.xml.dist index a9083088a9644..b69f066ac1b2f 100644 --- a/src/Symfony/Component/Routing/phpunit.xml.dist +++ b/src/Symfony/Component/Routing/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./vendor ./Tests + ./vendor diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 84fe742b729cc..b33f053a0b049 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -18,6 +18,8 @@ CHANGELOG `Symfony\Component\Security\Core\Authorization\Voter\VoterInterface`. * deprecated `getSupportedAttributes()` and `getSupportedClasses()` methods of `Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter`, use `supports()` instead. + * deprecated the `intention` option for all the authentication listeners, + use the `csrf_token_id` option instead. 2.7.0 ----- diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php index fab7d80a284e8..adc42ef3b38f5 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php @@ -74,7 +74,7 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke $password = $token->getCredentials(); try { - $username = $this->ldap->escape($username, '', LdapClientInterface::LDAP_ESCAPE_DN); + $username = $this->ldap->escape($username, '', LDAP_ESCAPE_DN); $dn = str_replace('{username}', $username, $this->dnString); $this->ldap->bind($dn, $password); diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php index 7b04222eae921..5dcf787c9968c 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\Authorization\Voter; +@trigger_error('The '.__NAMESPACE__.'\AbstractVoter class is deprecated since version 2.8, to be removed in 3.0. Upgrade to Symfony\Component\Security\Core\Authorization\Voter\Voter instead.', E_USER_DEPRECATED); + use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -18,6 +20,8 @@ * Abstract Voter implementation that reduces boilerplate code required to create a custom Voter. * * @author Roman Marintšenko + * + * @deprecated since version 2.8, to be removed in 3.0. Upgrade to Symfony\Component\Security\Core\Authorization\Voter\Voter instead. */ abstract class AbstractVoter implements VoterInterface { @@ -26,8 +30,6 @@ abstract class AbstractVoter implements VoterInterface */ public function supportsAttribute($attribute) { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); - return in_array($attribute, $this->getSupportedAttributes()); } @@ -36,8 +38,6 @@ public function supportsAttribute($attribute) */ public function supportsClass($class) { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); - foreach ($this->getSupportedClasses() as $supportedClass) { if ($supportedClass === $class || is_subclass_of($class, $supportedClass)) { return true; @@ -62,7 +62,7 @@ public function supportsClass($class) */ public function vote(TokenInterface $token, $object, array $attributes) { - if (!$object) { + if (!$object || !$this->supportsClass(get_class($object))) { return self::ACCESS_ABSTAIN; } @@ -70,14 +70,14 @@ public function vote(TokenInterface $token, $object, array $attributes) $vote = self::ACCESS_ABSTAIN; foreach ($attributes as $attribute) { - if (!$this->supports($attribute, $object)) { + if (!$this->supportsAttribute($attribute)) { continue; } // as soon as at least one attribute is supported, default is to deny access $vote = self::ACCESS_DENIED; - if ($this->voteOnAttribute($attribute, $object, $token)) { + if ($this->isGranted($attribute, $object, $token->getUser())) { // grant access as soon as at least one voter returns a positive response return self::ACCESS_GRANTED; } @@ -86,62 +86,19 @@ public function vote(TokenInterface $token, $object, array $attributes) return $vote; } - /** - * Determines if the attribute and object are supported by this voter. - * - * This method will become abstract in 3.0. - * - * @param string $attribute An attribute - * @param string $object The object to secure - * - * @return bool True if the attribute and object is supported, false otherwise - */ - protected function supports($attribute, $object) - { - @trigger_error('The getSupportedClasses and getSupportedAttributes methods are deprecated since version 2.8 and will be removed in version 3.0. Overwrite supports instead.', E_USER_DEPRECATED); - - $classIsSupported = false; - foreach ($this->getSupportedClasses() as $supportedClass) { - if ($object instanceof $supportedClass) { - $classIsSupported = true; - break; - } - } - - if (!$classIsSupported) { - return false; - } - - if (!in_array($attribute, $this->getSupportedAttributes())) { - return false; - } - - return true; - } - /** * Return an array of supported classes. This will be called by supportsClass. * * @return array an array of supported classes, i.e. array('Acme\DemoBundle\Model\Product') - * - * @deprecated since version 2.8, to be removed in 3.0. Use supports() instead. */ - protected function getSupportedClasses() - { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); - } + abstract protected function getSupportedClasses(); /** * Return an array of supported attributes. This will be called by supportsAttribute. * * @return array an array of supported attributes, i.e. array('CREATE', 'READ') - * - * @deprecated since version 2.8, to be removed in 3.0. Use supports() instead. */ - protected function getSupportedAttributes() - { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); - } + abstract protected function getSupportedAttributes(); /** * Perform a single access check operation on a given attribute, object and (optionally) user @@ -154,33 +111,7 @@ protected function getSupportedAttributes() * @param object $object * @param UserInterface|string $user * - * @deprecated This method will be removed in 3.0 - override voteOnAttribute instead. - * - * @return bool - */ - protected function isGranted($attribute, $object, $user = null) - { - // forces isGranted() or voteOnAttribute() to be overridden - throw new \BadMethodCallException(sprintf('You must override the voteOnAttribute() method in "%s".', get_class($this))); - } - - /** - * Perform a single access check operation on a given attribute, object and token. - * It is safe to assume that $attribute and $object's class pass supports method call. - * - * This method will become abstract in 3.0. - * - * @param string $attribute - * @param object $object - * @param TokenInterface $token - * * @return bool */ - protected function voteOnAttribute($attribute, $object, TokenInterface $token) - { - // the user should override this method, and not rely on the deprecated isGranted() - @trigger_error(sprintf("The AbstractVoter::isGranted() method is deprecated since 2.8 and won't be called anymore in 3.0. Override voteOnAttribute() in %s instead.", get_class($this)), E_USER_DEPRECATED); - - return $this->isGranted($attribute, $object, $token->getUser()); - } + abstract protected function isGranted($attribute, $object, $user = null); } diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php index 98b8f50f15d03..96a7ece998cce 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php @@ -102,6 +102,7 @@ private function getVariables(TokenInterface $token, $object) 'token' => $token, 'user' => $token->getUser(), 'object' => $object, + 'subject' => $object, 'roles' => array_map(function ($role) { return $role->getRole(); }, $roles), 'trust_resolver' => $this->trustResolver, ); diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php new file mode 100644 index 0000000000000..8d36fd8f8c919 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * Voter is an abstract default implementation of a voter. + * + * @author Roman Marintšenko + * @author Grégoire Pineau + */ +abstract class Voter implements VoterInterface +{ + /** + * {@inheritdoc} + */ + public function supportsAttribute($attribute) + { + throw new \BadMethodCallException('supportsAttribute method is deprecated since version 2.8, to be removed in 3.0'); + } + + /** + * {@inheritdoc} + */ + public function supportsClass($class) + { + throw new \BadMethodCallException('supportsClass method is deprecated since version 2.8, to be removed in 3.0'); + } + + /** + * {@inheritdoc} + */ + public function vote(TokenInterface $token, $object, array $attributes) + { + // abstain vote by default in case none of the attributes are supported + $vote = self::ACCESS_ABSTAIN; + + foreach ($attributes as $attribute) { + if (!$this->supports($attribute, $object)) { + continue; + } + + // as soon as at least one attribute is supported, default is to deny access + $vote = self::ACCESS_DENIED; + + if ($this->voteOnAttribute($attribute, $object, $token)) { + // grant access as soon as at least one attribute returns a positive response + return self::ACCESS_GRANTED; + } + } + + return $vote; + } + + /** + * Determines if the attribute and subject are supported by this voter. + * + * @param string $attribute An attribute + * @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type + * + * @return bool True if the attribute and subject are supported, false otherwise + */ + abstract protected function supports($attribute, $subject); + + /** + * Perform a single access check operation on a given attribute, subject and token. + * + * @param string $attribute + * @param mixed $subject + * @param TokenInterface $token + * + * @return bool + */ + abstract protected function voteOnAttribute($attribute, $subject, TokenInterface $token); +} diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php index f1b5c0355ff66..844bceff019cb 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php @@ -16,6 +16,9 @@ use Symfony\Component\Security\Core\User\User; use Symfony\Component\Ldap\Exception\ConnectionException; +/** + * @requires extension ldap + */ class LdapBindAuthenticationProviderTest extends \PHPUnit_Framework_TestCase { /** diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php index 5ea77320cf610..b537c1b2effc9 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php @@ -11,10 +11,11 @@ namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; +/** + * @group legacy + */ class AbstractVoterTest extends \PHPUnit_Framework_TestCase { protected $token; @@ -50,75 +51,8 @@ public function getTests() */ public function testVote(array $attributes, $expectedVote, $object, $message) { - $voter = new AbstractVoterTest_Voter(); + $voter = new Fixtures\MyVoter(); $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); } - - /** - * @dataProvider getTests - * @group legacy - */ - public function testVoteLegacy(array $attributes, $expectedVote, $object, $message) - { - $voter = new AbstractVoterTest_LegacyVoter(); - - $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); - } - - /** - * @group legacy - * @expectedException \BadMethodCallException - */ - public function testNoOverriddenMethodsThrowsException() - { - $voter = new AbstractVoterTest_NothingImplementedVoter(); - $voter->vote($this->token, new \stdClass(), array('EDIT')); - } -} - -class AbstractVoterTest_Voter extends AbstractVoter -{ - protected function voteOnAttribute($attribute, $object, TokenInterface $token) - { - return 'EDIT' === $attribute; - } - - protected function supports($attribute, $object) - { - return $object instanceof \stdClass && in_array($attribute, array('EDIT', 'CREATE')); - } -} - -class AbstractVoterTest_LegacyVoter extends AbstractVoter -{ - protected function getSupportedClasses() - { - return array('stdClass'); - } - - protected function getSupportedAttributes() - { - return array('EDIT', 'CREATE'); - } - - protected function isGranted($attribute, $object, $user = null) - { - return 'EDIT' === $attribute; - } -} - -class AbstractVoterTest_NothingImplementedVoter extends AbstractVoter -{ - protected function getSupportedClasses() - { - return array('stdClass'); - } - - protected function getSupportedAttributes() - { - return array('EDIT', 'CREATE'); - } - - // this is a bad voter that hasn't overridden isGranted or voteOnAttribute } diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/Fixtures/MyVoter.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/Fixtures/MyVoter.php new file mode 100644 index 0000000000000..b75f79851be99 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/Fixtures/MyVoter.php @@ -0,0 +1,27 @@ +assertSame($expected, $voter->vote($this->getToken($roles), null, $attributes)); + } + + public function getVoteWithEmptyHierarchyTests() + { + return parent::getVoteTests(); + } } diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php new file mode 100644 index 0000000000000..4bac44d981893 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + +class VoterTest extends \PHPUnit_Framework_TestCase +{ + protected $token; + + protected function setUp() + { + $this->token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + } + + public function getTests() + { + return array( + array(array('EDIT'), VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if attribute and class are supported and attribute grants access'), + array(array('CREATE'), VoterInterface::ACCESS_DENIED, new \stdClass(), 'ACCESS_DENIED if attribute and class are supported and attribute does not grant access'), + + array(array('DELETE', 'EDIT'), VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if one attribute is supported and grants access'), + array(array('DELETE', 'CREATE'), VoterInterface::ACCESS_DENIED, new \stdClass(), 'ACCESS_DENIED if one attribute is supported and denies access'), + + array(array('CREATE', 'EDIT'), VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if one attribute grants access'), + + array(array('DELETE'), VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attribute is supported'), + + array(array('EDIT'), VoterInterface::ACCESS_ABSTAIN, $this, 'ACCESS_ABSTAIN if class is not supported'), + + array(array('EDIT'), VoterInterface::ACCESS_ABSTAIN, null, 'ACCESS_ABSTAIN if object is null'), + + array(array(), VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attributes were provided'), + ); + } + + /** + * @dataProvider getTests + */ + public function testVote(array $attributes, $expectedVote, $object, $message) + { + $voter = new VoterTest_Voter(); + + $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); + } +} + +class VoterTest_Voter extends Voter +{ + protected function voteOnAttribute($attribute, $object, TokenInterface $token) + { + return 'EDIT' === $attribute; + } + + protected function supports($attribute, $object) + { + return $object instanceof \stdClass && in_array($attribute, array('EDIT', 'CREATE')); + } +} diff --git a/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php b/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php index f56648b9f1aaa..9b126e95180f3 100644 --- a/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php @@ -14,6 +14,9 @@ use Symfony\Component\Security\Core\User\LdapUserProvider; use Symfony\Component\Ldap\Exception\ConnectionException; +/** + * @requires extension ldap + */ class LdapUserProviderTest extends \PHPUnit_Framework_TestCase { /** diff --git a/src/Symfony/Component/Security/Core/User/LdapUserProvider.php b/src/Symfony/Component/Security/Core/User/LdapUserProvider.php index ec699fc022739..988a595f58b00 100644 --- a/src/Symfony/Component/Security/Core/User/LdapUserProvider.php +++ b/src/Symfony/Component/Security/Core/User/LdapUserProvider.php @@ -57,7 +57,7 @@ public function loadUserByUsername($username) { try { $this->ldap->bind($this->searchDn, $this->searchPassword); - $username = $this->ldap->escape($username, '', LdapClientInterface::LDAP_ESCAPE_FILTER); + $username = $this->ldap->escape($username, '', LDAP_ESCAPE_FILTER); $query = str_replace('{username}', $username, $this->defaultSearch); $search = $this->ldap->find($this->baseDn, $query); } catch (ConnectionException $e) { diff --git a/src/Symfony/Component/Security/Core/phpunit.xml.dist b/src/Symfony/Component/Security/Core/phpunit.xml.dist index 8a1a2914b095b..2dc341abcba02 100644 --- a/src/Symfony/Component/Security/Core/phpunit.xml.dist +++ b/src/Symfony/Component/Security/Core/phpunit.xml.dist @@ -25,8 +25,9 @@ ./ - ./vendor + ./Resources ./Tests + ./vendor diff --git a/src/Symfony/Component/Security/Csrf/phpunit.xml.dist b/src/Symfony/Component/Security/Csrf/phpunit.xml.dist index 8f950a3d3d441..f5b1e65c7192a 100644 --- a/src/Symfony/Component/Security/Csrf/phpunit.xml.dist +++ b/src/Symfony/Component/Security/Csrf/phpunit.xml.dist @@ -25,8 +25,8 @@ ./ - ./vendor ./Tests + ./vendor diff --git a/src/Symfony/Component/Security/Guard/phpunit.xml.dist b/src/Symfony/Component/Security/Guard/phpunit.xml.dist index 093628b332e80..da5a3826f2683 100644 --- a/src/Symfony/Component/Security/Guard/phpunit.xml.dist +++ b/src/Symfony/Component/Security/Guard/phpunit.xml.dist @@ -25,8 +25,9 @@ ./ - ./vendor + ./Resources ./Tests + ./vendor diff --git a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php index 15b71efd4d8bb..41e5d6d759533 100644 --- a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php @@ -99,7 +99,7 @@ public function handle(GetResponseEvent $event) return; } - if ($serverDigestMd5 !== $digestAuth->getResponse()) { + if (!hash_equals($serverDigestMd5, $digestAuth->getResponse())) { if (null !== $this->logger) { $this->logger->debug('Unexpected response from the DigestAuth received; is the header returning a clear text passwords?', array('expected' => $serverDigestMd5, 'received' => $digestAuth->getResponse())); } diff --git a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php index 6211ee0323c71..e19d39cc2950d 100644 --- a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php @@ -57,11 +57,21 @@ public function __construct(TokenStorageInterface $tokenStorage, HttpUtils $http throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); } + if (isset($options['intention'])) { + if (isset($options['csrf_token_id'])) { + throw new \InvalidArgumentException(sprintf('You should only define an option for one of "intention" or "csrf_token_id" for the "%s". Use the "csrf_token_id" as it replaces "intention".', __CLASS__)); + } + + @trigger_error('The "intention" option for the '.__CLASS__.' is deprecated since version 2.8 and will be removed in 3.0. Use the "csrf_token_id" option instead.', E_USER_DEPRECATED); + + $options['csrf_token_id'] = $options['intention']; + } + $this->tokenStorage = $tokenStorage; $this->httpUtils = $httpUtils; $this->options = array_merge(array( 'csrf_parameter' => '_csrf_token', - 'intention' => 'logout', + 'csrf_token_id' => 'logout', 'logout_path' => '/logout', ), $options); $this->successHandler = $successHandler; @@ -101,7 +111,7 @@ public function handle(GetResponseEvent $event) if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); - if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['intention'], $csrfToken))) { + if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new LogoutException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php index ccadf94732d68..4186430708910 100644 --- a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php @@ -21,6 +21,7 @@ use Symfony\Component\Security\Http\SecurityEvents; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; /** * RememberMeListener implements authentication capabilities via a cookie. @@ -56,7 +57,7 @@ public function __construct(TokenStorageInterface $tokenStorage, RememberMeServi $this->logger = $logger; $this->dispatcher = $dispatcher; $this->catchExceptions = $catchExceptions; - $this->sessionStrategy = $sessionStrategy; + $this->sessionStrategy = null === $sessionStrategy ? new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE) : $sessionStrategy; } /** @@ -77,7 +78,7 @@ public function handle(GetResponseEvent $event) try { $token = $this->authenticationManager->authenticate($token); - if (null !== $this->sessionStrategy && $request->hasSession() && $request->getSession()->isStarted()) { + if ($request->hasSession() && $request->getSession()->isStarted()) { $this->sessionStrategy->onAuthentication($request, $token); } $this->tokenStorage->setToken($token); diff --git a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php index fedaa4e62f00e..436376360be3a 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php @@ -70,6 +70,16 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); } + if (isset($options['intention'])) { + if (isset($options['csrf_token_id'])) { + throw new \InvalidArgumentException(sprintf('You should only define an option for one of "intention" or "csrf_token_id" for the "%s". Use the "csrf_token_id" as it replaces "intention".', __CLASS__)); + } + + @trigger_error('The "intention" option for the '.__CLASS__.' is deprecated since version 2.8 and will be removed in 3.0. Use the "csrf_token_id" option instead.', E_USER_DEPRECATED); + + $options['csrf_token_id'] = $options['intention']; + } + $this->simpleAuthenticator = $simpleAuthenticator; $this->csrfTokenManager = $csrfTokenManager; @@ -77,7 +87,7 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM 'username_parameter' => '_username', 'password_parameter' => '_password', 'csrf_parameter' => '_csrf_token', - 'intention' => 'authenticate', + 'csrf_token_id' => 'authenticate', 'post_only' => true, ), $options); @@ -104,7 +114,7 @@ protected function attemptAuthentication(Request $request) if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); - if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['intention'], $csrfToken))) { + if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php index d20ab19f62940..24c3ca6509c26 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php @@ -48,11 +48,21 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); } + if (isset($options['intention'])) { + if (isset($options['csrf_token_id'])) { + throw new \InvalidArgumentException(sprintf('You should only define an option for one of "intention" or "csrf_token_id" for the "%s". Use the "csrf_token_id" as it replaces "intention".', __CLASS__)); + } + + @trigger_error('The "intention" option for the '.__CLASS__.' is deprecated since version 2.8 and will be removed in 3.0. Use the "csrf_token_id" option instead.', E_USER_DEPRECATED); + + $options['csrf_token_id'] = $options['intention']; + } + parent::__construct($tokenStorage, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array( 'username_parameter' => '_username', 'password_parameter' => '_password', 'csrf_parameter' => '_csrf_token', - 'intention' => 'authenticate', + 'csrf_token_id' => 'authenticate', 'post_only' => true, ), $options), $logger, $dispatcher); @@ -79,7 +89,7 @@ protected function attemptAuthentication(Request $request) if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); - if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['intention'], $csrfToken))) { + if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php index 996eca3588276..807a4a72a69f4 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php @@ -93,7 +93,7 @@ protected function processAutoLoginCookie(array $cookieParts, Request $request) list($series, $tokenValue) = $cookieParts; $persistentToken = $this->tokenProvider->loadTokenBySeries($series); - if ($persistentToken->getTokenValue() !== $tokenValue) { + if (!hash_equals($persistentToken->getTokenValue(), $tokenValue)) { throw new CookieTheftException('This token was already used. The account is possibly compromised.'); } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php index 15c996e6261a5..367c810f51f39 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php @@ -213,7 +213,7 @@ private function getListener($successHandler = null, $tokenManager = null) $successHandler ?: $this->getSuccessHandler(), $options = array( 'csrf_parameter' => '_csrf_token', - 'intention' => 'logout', + 'csrf_token_id' => 'logout', 'logout_path' => '/logout', 'target_url' => '/', ), diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php index b16d55b66b02e..7309042d4764b 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php @@ -246,6 +246,69 @@ public function testSessionStrategy() $listener->handle($event); } + public function testSessionIsMigratedByDefault() + { + list($listener, $tokenStorage, $service, $manager, , $dispatcher, $sessionStrategy) = $this->getListener(false, true, false); + + $tokenStorage + ->expects($this->once()) + ->method('getToken') + ->will($this->returnValue(null)) + ; + + $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + $service + ->expects($this->once()) + ->method('autoLogin') + ->will($this->returnValue($token)) + ; + + $tokenStorage + ->expects($this->once()) + ->method('setToken') + ->with($this->equalTo($token)) + ; + + $manager + ->expects($this->once()) + ->method('authenticate') + ->will($this->returnValue($token)) + ; + + $session = $this->getMock('\Symfony\Component\HttpFoundation\Session\SessionInterface'); + $session + ->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)) + ; + $session + ->expects($this->once()) + ->method('migrate') + ; + + $request = $this->getMock('\Symfony\Component\HttpFoundation\Request'); + $request + ->expects($this->any()) + ->method('hasSession') + ->will($this->returnValue(true)) + ; + + $request + ->expects($this->any()) + ->method('getSession') + ->will($this->returnValue($session)) + ; + + $event = $this->getGetResponseEvent(); + $event + ->expects($this->once()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; + + $listener->handle($event); + } + public function testOnCoreSecurityInteractiveLoginEventIsDispatchedIfDispatcherIsPresent() { list($listener, $tokenStorage, $service, $manager, , $dispatcher) = $this->getListener(true); diff --git a/src/Symfony/Component/Security/Http/phpunit.xml.dist b/src/Symfony/Component/Security/Http/phpunit.xml.dist index 49b36f271804e..8d636e93f1be6 100644 --- a/src/Symfony/Component/Security/Http/phpunit.xml.dist +++ b/src/Symfony/Component/Security/Http/phpunit.xml.dist @@ -25,8 +25,8 @@ ./ - ./vendor ./Tests + ./vendor diff --git a/src/Symfony/Component/Security/phpunit.xml.dist b/src/Symfony/Component/Security/phpunit.xml.dist index 0d9fe5fee1485..6b56bf247124c 100644 --- a/src/Symfony/Component/Security/phpunit.xml.dist +++ b/src/Symfony/Component/Security/phpunit.xml.dist @@ -13,10 +13,7 @@ ./Tests/ - ./Acl/Tests/ - ./Core/Tests/ - ./Http/Tests/ - ./Guard/Tests/ + ./*/Tests/ @@ -24,11 +21,12 @@ ./ - ./vendor + ./Resources ./Tests - ./Acl/Tests - ./Core/Tests - ./Http/Tests + ./vendor + ./*/Resources + ./*/Tests + ./*/vendor diff --git a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php index d4070c23040db..8925ec36f7f99 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php @@ -108,7 +108,7 @@ public function decode($data, $format, array $context = array()) } if (JSON_ERROR_NONE !== $this->lastError = json_last_error()) { - throw new UnexpectedValueException(json_last_error_message()); + throw new UnexpectedValueException(json_last_error_msg()); } return $decodedData; diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php index aa7d64530e14d..454c0d6a11470 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php @@ -56,7 +56,7 @@ public function encode($data, $format, array $context = array()) $encodedJson = json_encode($data, $context['json_encode_options']); if (JSON_ERROR_NONE !== $this->lastError = json_last_error()) { - throw new UnexpectedValueException(json_last_error_message()); + throw new UnexpectedValueException(json_last_error_msg()); } return $encodedJson; diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php index ba84ac717f075..c731dd7a97f17 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php @@ -26,6 +26,8 @@ */ class ObjectNormalizer extends AbstractNormalizer { + private static $attributesCache = array(); + /** * @var PropertyAccessorInterface */ @@ -58,39 +60,7 @@ public function normalize($object, $format = null, array $context = array()) } $data = array(); - $attributes = $this->getAllowedAttributes($object, $context, true); - - // If not using groups, detect manually - if (false === $attributes) { - $attributes = array(); - - // methods - $reflClass = new \ReflectionClass($object); - foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) { - if ( - !$reflMethod->isConstructor() && - !$reflMethod->isDestructor() && - 0 === $reflMethod->getNumberOfRequiredParameters() - ) { - $name = $reflMethod->getName(); - - if (strpos($name, 'get') === 0 || strpos($name, 'has') === 0) { - // getters and hassers - $attributes[lcfirst(substr($name, 3))] = true; - } elseif (strpos($name, 'is') === 0) { - // issers - $attributes[lcfirst(substr($name, 2))] = true; - } - } - } - - // properties - foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) { - $attributes[$reflProperty->getName()] = true; - } - - $attributes = array_keys($attributes); - } + $attributes = $this->getAttributes($object, $context); foreach ($attributes as $attribute) { if (in_array($attribute, $this->ignoredAttributes)) { @@ -159,4 +129,64 @@ public function denormalize($data, $class, $format = null, array $context = arra return $object; } + + /** + * Gets and caches attributes for this class and context. + * + * @param object $object + * @param array $context + * + * @return array + */ + private function getAttributes($object, array $context) + { + $key = sprintf('%s-%s', get_class($object), serialize($context)); + + if (isset(self::$attributesCache[$key])) { + return self::$attributesCache[$key]; + } + + $allowedAttributes = $this->getAllowedAttributes($object, $context, true); + + if (false !== $allowedAttributes) { + return self::$attributesCache[$key] = $allowedAttributes; + } + + // If not using groups, detect manually + $attributes = array(); + + // methods + $reflClass = new \ReflectionClass($object); + foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) { + if ( + $reflMethod->getNumberOfRequiredParameters() !== 0 || + $reflMethod->isStatic() || + $reflMethod->isConstructor() || + $reflMethod->isDestructor() + ) { + continue; + } + + $name = $reflMethod->getName(); + + if (strpos($name, 'get') === 0 || strpos($name, 'has') === 0) { + // getters and hassers + $attributes[lcfirst(substr($name, 3))] = true; + } elseif (strpos($name, 'is') === 0) { + // issers + $attributes[lcfirst(substr($name, 2))] = true; + } + } + + // properties + foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) { + if ($reflProperty->isStatic()) { + continue; + } + + $attributes[$reflProperty->getName()] = true; + } + + return self::$attributesCache[$key] = array_keys($attributes); + } } diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/ChainDecoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/ChainDecoderTest.php new file mode 100644 index 0000000000000..7f7392e6a45b1 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Encoder/ChainDecoderTest.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Encoder; + +use Symfony\Component\Serializer\Encoder\ChainDecoder; + +class ChainDecoderTest extends \PHPUnit_Framework_TestCase +{ + const FORMAT_1 = 'format1'; + const FORMAT_2 = 'format2'; + const FORMAT_3 = 'format3'; + + private $chainDecoder; + private $decoder1; + private $decoder2; + + protected function setUp() + { + $this->decoder1 = $this + ->getMockBuilder('Symfony\Component\Serializer\Encoder\DecoderInterface') + ->getMock(); + + $this->decoder1 + ->method('supportsDecoding') + ->will($this->returnValueMap(array( + array(self::FORMAT_1, true), + array(self::FORMAT_2, false), + array(self::FORMAT_3, false), + ))); + + $this->decoder2 = $this + ->getMockBuilder('Symfony\Component\Serializer\Encoder\DecoderInterface') + ->getMock(); + + $this->decoder2 + ->method('supportsDecoding') + ->will($this->returnValueMap(array( + array(self::FORMAT_1, false), + array(self::FORMAT_2, true), + array(self::FORMAT_3, false), + ))); + + $this->chainDecoder = new ChainDecoder(array($this->decoder1, $this->decoder2)); + } + + public function testSupportsDecoding() + { + $this->assertTrue($this->chainDecoder->supportsDecoding(self::FORMAT_1)); + $this->assertTrue($this->chainDecoder->supportsDecoding(self::FORMAT_2)); + $this->assertFalse($this->chainDecoder->supportsDecoding(self::FORMAT_3)); + } + + public function testDecode() + { + $this->decoder1->expects($this->never())->method('decode'); + $this->decoder2->expects($this->once())->method('decode'); + + $this->chainDecoder->decode('string_to_decode', self::FORMAT_2); + } + + /** + * @expectedException Symfony\Component\Serializer\Exception\RuntimeException + */ + public function testDecodeUnsupportedFormat() + { + $this->chainDecoder->decode('string_to_decode', self::FORMAT_3); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/ChainEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/ChainEncoderTest.php new file mode 100644 index 0000000000000..6d3436b33d753 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Encoder/ChainEncoderTest.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Encoder; + +use Symfony\Component\Serializer\Encoder\ChainEncoder; +use Symfony\Component\Serializer\Encoder\NormalizationAwareInterface; + +class ChainEncoderTest extends \PHPUnit_Framework_TestCase +{ + const FORMAT_1 = 'format1'; + const FORMAT_2 = 'format2'; + const FORMAT_3 = 'format3'; + + private $chainEncoder; + private $encoder1; + private $encoder2; + + protected function setUp() + { + $this->encoder1 = $this + ->getMockBuilder('Symfony\Component\Serializer\Encoder\EncoderInterface') + ->getMock(); + + $this->encoder1 + ->method('supportsEncoding') + ->will($this->returnValueMap(array( + array(self::FORMAT_1, true), + array(self::FORMAT_2, false), + array(self::FORMAT_3, false), + ))); + + $this->encoder2 = $this + ->getMockBuilder('Symfony\Component\Serializer\Encoder\EncoderInterface') + ->getMock(); + + $this->encoder2 + ->method('supportsEncoding') + ->will($this->returnValueMap(array( + array(self::FORMAT_1, false), + array(self::FORMAT_2, true), + array(self::FORMAT_3, false), + ))); + + $this->chainEncoder = new ChainEncoder(array($this->encoder1, $this->encoder2)); + } + + public function testSupportsEncoding() + { + $this->assertTrue($this->chainEncoder->supportsEncoding(self::FORMAT_1)); + $this->assertTrue($this->chainEncoder->supportsEncoding(self::FORMAT_2)); + $this->assertFalse($this->chainEncoder->supportsEncoding(self::FORMAT_3)); + } + + public function testEncode() + { + $this->encoder1->expects($this->never())->method('encode'); + $this->encoder2->expects($this->once())->method('encode'); + + $this->chainEncoder->encode(array('foo' => 123), self::FORMAT_2); + } + + /** + * @expectedException Symfony\Component\Serializer\Exception\RuntimeException + */ + public function testEncodeUnsupportedFormat() + { + $this->chainEncoder->encode(array('foo' => 123), self::FORMAT_3); + } + + public function testNeedsNormalizationBasic() + { + $this->assertTrue($this->chainEncoder->needsNormalization(self::FORMAT_1)); + $this->assertTrue($this->chainEncoder->needsNormalization(self::FORMAT_2)); + } + + /** + * @dataProvider booleanProvider + */ + public function testNeedsNormalizationChainNormalizationAware($bool) + { + $chainEncoder = $this + ->getMockBuilder('Symfony\Component\Serializer\Tests\Encoder\ChainNormalizationAwareEncoder') + ->getMock(); + + $chainEncoder->method('supportsEncoding')->willReturn(true); + $chainEncoder->method('needsNormalization')->willReturn($bool); + + $sut = new ChainEncoder(array($chainEncoder)); + + $this->assertEquals($bool, $sut->needsNormalization(self::FORMAT_1)); + } + + public function testNeedsNormalizationNormalizationAware() + { + $encoder = new NormalizationAwareEncoder(); + $sut = new ChainEncoder(array($encoder)); + + $this->assertFalse($sut->needsNormalization(self::FORMAT_1)); + } + + public function booleanProvider() + { + return array( + array(true), + array(false), + ); + } +} + +class ChainNormalizationAwareEncoder extends ChainEncoder implements NormalizationAwareInterface +{ +} + +class NormalizationAwareEncoder implements NormalizationAwareInterface +{ + public function supportsEncoding($format) + { + return true; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php new file mode 100644 index 0000000000000..97930115748d2 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonDecodeTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Encoder; + +use Symfony\Component\Serializer\Encoder\JsonDecode; +use Symfony\Component\Serializer\Encoder\JsonEncoder; + +class JsonDecodeTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Symfony\Component\Serializer\Encoder\JsonDecode */ + private $decode; + + protected function setUp() + { + $this->decode = new JsonDecode(); + } + + public function testSupportsDecoding() + { + $this->assertTrue($this->decode->supportsDecoding(JsonEncoder::FORMAT)); + $this->assertFalse($this->decode->supportsDecoding('foobar')); + } + + /** + * @dataProvider decodeProvider + */ + public function testDecode($toDecode, $expected, $context) + { + $this->assertEquals( + $expected, + $this->decode->decode($toDecode, JsonEncoder::FORMAT, $context) + ); + } + + public function decodeProvider() + { + $stdClass = new \stdClass(); + $stdClass->foo = 'bar'; + + $assoc = array('foo' => 'bar'); + + return array( + array('{"foo": "bar"}', $stdClass, array()), + array('{"foo": "bar"}', $assoc, array('json_decode_associative' => true)), + ); + } + + /** + * @requires function json_last_error_msg + * @dataProvider decodeProviderException + * @expectedException Symfony\Component\Serializer\Exception\UnexpectedValueException + */ + public function testDecodeWithException($value) + { + $this->decode->decode($value, JsonEncoder::FORMAT); + } + + public function decodeProviderException() + { + return array( + array("{'foo': 'bar'}"), + array('kaboom!'), + ); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php new file mode 100644 index 0000000000000..3e6fb7b6ec34e --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Encoder; + +use Symfony\Component\Serializer\Encoder\JsonEncode; +use Symfony\Component\Serializer\Encoder\JsonEncoder; + +class JsonEncodeTest extends \PHPUnit_Framework_TestCase +{ + private $encoder; + + protected function setUp() + { + $this->encode = new JsonEncode(); + } + + public function testSupportsEncoding() + { + $this->assertTrue($this->encode->supportsEncoding(JsonEncoder::FORMAT)); + $this->assertFalse($this->encode->supportsEncoding('foobar')); + } + + /** + * @dataProvider encodeProvider + */ + public function testEncode($toEncode, $expected, $context) + { + $this->assertEquals( + $expected, + $this->encode->encode($toEncode, JsonEncoder::FORMAT, $context) + ); + } + + public function encodeProvider() + { + return array( + array(array(), '[]', array()), + array(array(), '{}', array('json_encode_options' => JSON_FORCE_OBJECT)), + ); + } + + /** + * @requires function json_last_error_msg + * @expectedException Symfony\Component\Serializer\Exception\UnexpectedValueException + */ + public function testEncodeWithError() + { + $this->encode->encode("\xB1\x31", JsonEncoder::FORMAT); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 1cadee52b196f..ec676efe1690e 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -29,7 +29,7 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase { /** - * @var ObjectNormalizerTest + * @var ObjectNormalizer */ private $normalizer; /** @@ -239,6 +239,18 @@ public function testGroupsDenormalize() $this->assertEquals($obj, $normalized); } + public function testNormalizeNoPropertyInGroup() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $this->normalizer = new ObjectNormalizer($classMetadataFactory); + $this->normalizer->setSerializer($this->serializer); + + $obj = new GroupDummy(); + $obj->setFoo('foo'); + + $this->assertEquals(array(), $this->normalizer->normalize($obj, null, array('groups' => array('notExist')))); + } + public function testGroupsNormalizeWithNameConverter() { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); @@ -456,6 +468,11 @@ public function testNoTraversableSupport() { $this->assertFalse($this->normalizer->supportsNormalization(new \ArrayObject())); } + + public function testNormalizeStatic() + { + $this->assertEquals(array('foo' => 'K'), $this->normalizer->normalize(new ObjectWithStaticPropertiesAndMethods())); + } } class ObjectDummy @@ -605,3 +622,14 @@ public function otherMethod() throw new \RuntimeException('Dummy::otherMethod() should not be called'); } } + +class ObjectWithStaticPropertiesAndMethods +{ + public $foo = 'K'; + public static $bar = 'A'; + + public static function getBaz() + { + return 'L'; + } +} diff --git a/src/Symfony/Component/Serializer/phpunit.xml.dist b/src/Symfony/Component/Serializer/phpunit.xml.dist index 279e1eb3aa53c..4799e3cf2f1dd 100644 --- a/src/Symfony/Component/Serializer/phpunit.xml.dist +++ b/src/Symfony/Component/Serializer/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./vendor ./Tests + ./vendor diff --git a/src/Symfony/Component/Templating/phpunit.xml.dist b/src/Symfony/Component/Templating/phpunit.xml.dist index 3da1f5de1371c..109584e805db2 100644 --- a/src/Symfony/Component/Templating/phpunit.xml.dist +++ b/src/Symfony/Component/Templating/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./vendor ./Tests + ./vendor diff --git a/src/Symfony/Component/Translation/PluralizationRules.php b/src/Symfony/Component/Translation/PluralizationRules.php index f250423908035..09748211b13b3 100644 --- a/src/Symfony/Component/Translation/PluralizationRules.php +++ b/src/Symfony/Component/Translation/PluralizationRules.php @@ -131,6 +131,7 @@ public static function get($number, $locale) case 'fr': case 'gun': case 'hi': + case 'hy': case 'ln': case 'mg': case 'nso': diff --git a/src/Symfony/Component/Translation/Tests/PluralizationRulesTest.php b/src/Symfony/Component/Translation/Tests/PluralizationRulesTest.php index 066e07f5ab3f2..43c31672c2ce5 100644 --- a/src/Symfony/Component/Translation/Tests/PluralizationRulesTest.php +++ b/src/Symfony/Component/Translation/Tests/PluralizationRulesTest.php @@ -61,7 +61,7 @@ public function successLangcodes() { return array( array('1', array('ay','bo', 'cgg','dz','id', 'ja', 'jbo', 'ka','kk','km','ko','ky')), - array('2', array('nl', 'fr', 'en', 'de', 'de_GE')), + array('2', array('nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM')), array('3', array('be','bs','cs','hr')), array('4', array('cy','mt', 'sl')), array('5', array()), diff --git a/src/Symfony/Component/Translation/phpunit.xml.dist b/src/Symfony/Component/Translation/phpunit.xml.dist index 16cca4afb7c69..c25ec5eda6940 100644 --- a/src/Symfony/Component/Translation/phpunit.xml.dist +++ b/src/Symfony/Component/Translation/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./vendor ./Tests + ./vendor diff --git a/src/Symfony/Component/Validator/Constraints/UrlValidator.php b/src/Symfony/Component/Validator/Constraints/UrlValidator.php index 2480b36be6eac..7497becef93f3 100644 --- a/src/Symfony/Component/Validator/Constraints/UrlValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UrlValidator.php @@ -34,7 +34,7 @@ class UrlValidator extends ConstraintValidator \] # a IPv6 address ) (:[0-9]+)? # a port (optional) - (/?|/\S+|\?|\#) # a /, nothing, a / with something, a query or a fragment + (/?|/\S+|\?\S*|\#\S*) # a /, nothing, a / with something, a query or a fragment $~ixu'; /** diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf index d874573a7afa0..1fa59dda6ad46 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf @@ -310,6 +310,10 @@ This value does not match the expected {{ charset }} charset. La codificación de caracteres para este valor debería ser {{ charset }}. + + This is not a valid Business Identifier Code (BIC). + No es un Código de Identificación Bancaria (BIC) válido. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf index fe3346973e8b4..371ad9741e612 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf @@ -306,6 +306,10 @@ This value does not match the expected {{ charset }} charset. Deze waarde is niet in de verwachte tekencodering {{ charset }}. + + This is not a valid Business Identifier Code (BIC). + Dit is geen geldige bedrijfsidentificatiecode (BIC/SWIFT). + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf index 4f0e7c6364e37..834db4015e8e4 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sl.xlf @@ -310,6 +310,10 @@ This value does not match the expected {{ charset }} charset. Ta vrednost se ne ujema s pričakovanim naborom znakov {{ charset }}. + + This is not a valid Business Identifier Code (BIC). + To ni veljavna identifikacijska koda podjetja (BIC). + diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php index 0af291d90046d..08dbe6b4c1743 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php @@ -118,7 +118,11 @@ public function getValidUrls() array('http://username:password@symfony.com'), array('http://user-name@symfony.com'), array('http://symfony.com?'), + array('http://symfony.com?query=1'), + array('http://symfony.com/?query=1'), array('http://symfony.com#'), + array('http://symfony.com#fragment'), + array('http://symfony.com/#fragment'), ); } diff --git a/src/Symfony/Component/Validator/phpunit.xml.dist b/src/Symfony/Component/Validator/phpunit.xml.dist index 1bf4391c3c181..cf8c343863e5d 100644 --- a/src/Symfony/Component/Validator/phpunit.xml.dist +++ b/src/Symfony/Component/Validator/phpunit.xml.dist @@ -20,8 +20,9 @@ ./ - ./vendor + ./Resources ./Tests + ./vendor diff --git a/src/Symfony/Component/VarDumper/phpunit.xml.dist b/src/Symfony/Component/VarDumper/phpunit.xml.dist index 05128b9fa1a34..bb16a3a4ec02c 100644 --- a/src/Symfony/Component/VarDumper/phpunit.xml.dist +++ b/src/Symfony/Component/VarDumper/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./Tests ./Resources + ./Tests ./vendor diff --git a/src/Symfony/Component/Yaml/CHANGELOG.md b/src/Symfony/Component/Yaml/CHANGELOG.md index a6a8612f3776a..f55b57047ebef 100644 --- a/src/Symfony/Component/Yaml/CHANGELOG.md +++ b/src/Symfony/Component/Yaml/CHANGELOG.md @@ -4,9 +4,22 @@ CHANGELOG 2.8.0 ----- - * Deprecated usage of @ and \` at the beginning of an unquoted string - * Deprecated non-escaped \ in double-quoted strings when parsing Yaml - ("Foo\Var" is not valid whereas "Foo\\Var" is) + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of @, \`, | and > at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` 2.1.0 ----- diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index e595b404d4ed7..47af338632f20 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -347,7 +347,7 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false) if (null === $indentation) { $newIndent = $this->getCurrentLineIndentation(); - $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem($this->currentLine); + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); @@ -373,7 +373,7 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false) return; } - $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem($this->currentLine); + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); // Comments must not be removed inside a block scalar $removeCommentsPattern = '~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~'; @@ -386,7 +386,7 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false) $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); } - if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem($this->currentLine) && $newIndent === $indent) { + if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { $this->moveToPreviousLine(); break; } @@ -473,15 +473,17 @@ private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $ob return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers)); } - if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($value, ': ')) { - @trigger_error(sprintf('Using a colon in an unquoted mapping value in line %d is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED); + try { + $parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); - // to be thrown in 3.0 - // throw new ParseException('A colon cannot be used in an unquoted mapping value.'); - } + if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { + @trigger_error(sprintf('Using a colon in an unquoted mapping value in line %d is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED); - try { - return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + // to be thrown in 3.0 + // throw new ParseException('A colon cannot be used in an unquoted mapping value.'); + } + + return $parsedValue; } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); @@ -698,7 +700,7 @@ private function isNextLineUnIndentedCollection() if ( $this->getCurrentLineIndentation() == $currentIndentation && - $this->isStringUnIndentedCollectionItem($this->currentLine) + $this->isStringUnIndentedCollectionItem() ) { $ret = true; } diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index e4c02d77b7979..d5a4c35b85cbf 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -808,6 +808,16 @@ public function testColonInMappingValueException() restore_error_handler(); } + + public function testColonInMappingValueExceptionNotTriggeredByColonInComment() + { + $yaml = <<assertSame(array('foo' => array('bar' => 'foobar')), $this->parser->parse($yaml)); + } } class B diff --git a/src/Symfony/Component/Yaml/phpunit.xml.dist b/src/Symfony/Component/Yaml/phpunit.xml.dist index 418b2c6c9ad5a..6bdbea16e6426 100644 --- a/src/Symfony/Component/Yaml/phpunit.xml.dist +++ b/src/Symfony/Component/Yaml/phpunit.xml.dist @@ -20,8 +20,8 @@ ./ - ./vendor ./Tests + ./vendor