diff --git a/.travis.yml b/.travis.yml index f6d9c91520333..9b4448ed99e6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,10 +35,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-3.0.md b/CHANGELOG-3.0.md index a3ff97d0a51b3..fae727429ae15 100644 --- a/CHANGELOG-3.0.md +++ b/CHANGELOG-3.0.md @@ -7,6 +7,70 @@ in 3.0 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.0.0...v3.0.1 +* 3.0.0 (2015-11-30) + + * bug #16758 Fix BC for the default root form name (stof) + * feature #16754 [Security] allow arbitrary types in VoterInterface::vote() (xabbuh) + * 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 #16723 [Form] remove deprecated CSRF options (xabbuh) + * feature #16725 [Form] Removed useless code (webmozart) + * feature #16724 Added getBlockPrefix to FormTypeInterface (WouterJ) + * feature #16722 [Security][SecurityBundle] Use csrf_token_id instead of deprecated intention (jakzal) + * feature #16727 [Form] remove deprecated getTimezones() method (xabbuh) + * 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) + * feature #16715 [Form] Remove choices_as_values option on ChoiceType (nicolas-grekas) + * feature #16692 [Form] Drop remaing CsrfProviderAdapter/Interface mentions (nicolas-grekas) + * feature #16719 [Security] remove deprecated HTTP digest auth key (xabbuh) + * 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 #16629 [HttpFoundation] Remove deprecated class method parameter (belka-ew) + * 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) + * feature #16694 [SecurityBundle] make ACL an optional dependency (Tobion) + * 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 #16615 fix type assignement (rande) + * 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) + * feature #16637 Removed unneeded polyfill (GrahamCampbell) + * security #16631 n/a (xabbuh) + * security #16630 n/a (xabbuh) + * bug #16633 [Filesystem] Fixed failing test due to tempdir symlink (toretto460) + * bug #16607 [HttpFoundation] Delete not existing session handler proxy member (belka-ew) + * 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) + * 3.0.0-BETA1 (2015-11-16) * feature #16516 Remove some more legacy code (nicolas-grekas) 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-3.0.md b/UPGRADE-3.0.md index 931eae5264618..e15b0d3d8310f 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -272,6 +272,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. @@ -319,6 +325,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 @@ -609,6 +621,10 @@ UPGRADE FROM 2.x to 3.0 ### Security + * The `vote()` method from the `VoterInterface` was changed to now accept arbitrary + types and not only objects. You can rely on the new abstract `Voter` class introduced + in 2.8 to ease integrating your own voters. + * The `Resources/` directory was moved to `Core/Resources/` * The `key` settings of `anonymous`, `remember_me` and `http_digest` are @@ -1333,6 +1349,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 99ac6d1253b42..08d045b0a2a88 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,8 +22,8 @@ install: - IF %PHP%==1 7z x php-5.5.9-nts-Win32-VC11-x86.zip -y >nul - IF %PHP%==1 del /Q *.zip - IF %PHP%==1 cd ext - - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/apcu/4.0.7/php_apcu-4.0.7-5.5-nts-vc11-x86.zip - - IF %PHP%==1 7z x php_apcu-4.0.7-5.5-nts-vc11-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.5-nts-vc11-x86.zip + - IF %PHP%==1 7z x php_apcu-4.0.8-5.5-nts-vc11-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.5-nts-vc11-x86.zip - IF %PHP%==1 7z x php_memcache-3.0.8-5.5-nts-vc11-x86.zip -y >nul - IF %PHP%==1 del /Q *.zip diff --git a/composer.json b/composer.json index 7b0692cfd4e82..da34246718a27 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,6 @@ "psr/log": "~1.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php54": "~1.0", - "symfony/polyfill-php55": "~1.0", "symfony/polyfill-php56": "~1.0", "symfony/polyfill-php70": "~1.0", "symfony/polyfill-util": "~1.0" 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/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index c6612246804b2..353c18528710f 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -259,7 +259,6 @@ public function configureOptions(OptionsResolver $resolver) 'em' => null, 'query_builder' => null, 'choices' => null, - 'choices_as_values' => true, 'choice_loader' => $choiceLoader, 'choice_label' => array(__CLASS__, 'createChoiceLabel'), 'choice_name' => $choiceName, diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index 9ee98c73277b9..f0c8a9e785157 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -56,14 +56,6 @@ public function getLoader(ObjectManager $manager, $queryBuilder, $class) return new ORMQueryBuilderLoader($queryBuilder); } - /** - * {@inheritdoc} - */ - public function getName() - { - return $this->getBlockPrefix(); - } - /** * {@inheritdoc} */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php deleted file mode 100644 index 5980d9c734c54..0000000000000 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; - -use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; - -/** - * @author Bernhard Schussek - */ -abstract class AbstractEntityChoiceListCompositeIdTest extends AbstractEntityChoiceListTest -{ - protected function getEntityClass() - { - return 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity'; - } - - /** - * @return \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface - */ - protected function createObjects() - { - return array( - new CompositeIntIdEntity(10, 11, 'A'), - new CompositeIntIdEntity(20, 21, 'B'), - new CompositeIntIdEntity(30, 31, 'C'), - new CompositeIntIdEntity(40, 41, 'D'), - ); - } - - protected function getChoices() - { - return array(0 => $this->obj1, 1 => $this->obj2, 2 => $this->obj3, 3 => $this->obj4); - } - - protected function getLabels() - { - return array(0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D'); - } - - protected function getValues() - { - return array(0 => '0', 1 => '1', 2 => '2', 3 => '3'); - } - - protected function getIndices() - { - return array(0, 1, 2, 3); - } -} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php deleted file mode 100644 index 74af66db360e2..0000000000000 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; - -use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; - -/** - * @author Bernhard Schussek - */ -abstract class AbstractEntityChoiceListSingleIntIdTest extends AbstractEntityChoiceListTest -{ - protected function getEntityClass() - { - return 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity'; - } - - /** - * @return \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface - */ - protected function createObjects() - { - return array( - new SingleIntIdEntity(-10, 'A'), - new SingleIntIdEntity(10, 'B'), - new SingleIntIdEntity(20, 'C'), - new SingleIntIdEntity(30, 'D'), - ); - } - - protected function getChoices() - { - return array('_10' => $this->obj1, 10 => $this->obj2, 20 => $this->obj3, 30 => $this->obj4); - } - - protected function getLabels() - { - return array('_10' => 'A', 10 => 'B', 20 => 'C', 30 => 'D'); - } - - protected function getValues() - { - return array('_10' => '-10', 10 => '10', 20 => '20', 30 => '30'); - } - - protected function getIndices() - { - return array('_10', 10, 20, 30); - } -} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php deleted file mode 100644 index 56b4c21319826..0000000000000 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; - -use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; - -/** - * @author Bernhard Schussek - */ -abstract class AbstractEntityChoiceListSingleStringIdTest extends AbstractEntityChoiceListTest -{ - protected function getEntityClass() - { - return 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity'; - } - - /** - * @return \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface - */ - protected function createObjects() - { - return array( - new SingleStringIdEntity('a', 'A'), - new SingleStringIdEntity('b', 'B'), - new SingleStringIdEntity('c', 'C'), - new SingleStringIdEntity('d', 'D'), - ); - } - - protected function getChoices() - { - return array(0 => $this->obj1, 1 => $this->obj2, 2 => $this->obj3, 3 => $this->obj4); - } - - protected function getLabels() - { - return array(0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D'); - } - - protected function getValues() - { - return array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'); - } - - protected function getIndices() - { - return array(0, 1, 2, 3); - } -} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php deleted file mode 100644 index 4f3d54a30f15f..0000000000000 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; - -use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; -use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; -use Doctrine\ORM\Tools\SchemaTool; -use Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest; - -/** - * @author Bernhard Schussek - */ -abstract class AbstractEntityChoiceListTest extends AbstractChoiceListTest -{ - /** - * @var \Doctrine\ORM\EntityManager - */ - protected $em; - - protected $obj1; - - protected $obj2; - - protected $obj3; - - protected $obj4; - - protected function setUp() - { - $this->em = DoctrineTestHelper::createTestEntityManager(); - - $schemaTool = new SchemaTool($this->em); - $classes = $this->getClassesMetadata(); - - try { - $schemaTool->dropSchema($classes); - } catch (\Exception $e) { - } - - try { - $schemaTool->createSchema($classes); - } catch (\Exception $e) { - } - - list($this->obj1, $this->obj2, $this->obj3, $this->obj4) = $this->createObjects(); - - $this->em->persist($this->obj1); - $this->em->persist($this->obj2); - $this->em->persist($this->obj3); - $this->em->persist($this->obj4); - $this->em->flush(); - - parent::setUp(); - } - - protected function tearDown() - { - parent::tearDown(); - - $this->em = null; - } - - abstract protected function getEntityClass(); - - abstract protected function createObjects(); - - protected function getClassesMetadata() - { - return array($this->em->getClassMetadata($this->getEntityClass())); - } - - /** - * @return \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface - */ - protected function createChoiceList() - { - return new EntityChoiceList($this->em, $this->getEntityClass()); - } -} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 1f0cee39828d4..7acc7d276fc09 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -982,37 +982,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); - } - - public function testCacheChoiceLists() - { - $entity1 = new SingleIntIdEntity(1, 'Foo'); - - $this->persist(array($entity1)); - - $field1 = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( - 'em' => 'default', - 'class' => self::SINGLE_IDENT_CLASS, - 'required' => false, - 'choice_label' => 'name', - )); - - $field2 = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( - 'em' => 'default', - 'class' => self::SINGLE_IDENT_CLASS, - 'required' => false, - 'choice_label' => 'name', - )); - - $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $field1->getConfig()->getOption('choice_list')); - $this->assertSame($field1->getConfig()->getOption('choice_list'), $field2->getConfig()->getOption('choice_list')); + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $choiceLoader1); + $this->assertSame($choiceLoader1, $choiceLoader2); + $this->assertSame($choiceLoader1, $choiceLoader3); } protected function createRegistryMock($name, $em) diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 0e686e1ccee2e..d1443b369858d 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -22,7 +22,7 @@ "require-dev": { "symfony/stopwatch": "~2.8|~3.0", "symfony/dependency-injection": "~2.8|~3.0", - "symfony/form": "~2.8|~3.0", + "symfony/form": "~3.0,>3.0-BETA1", "symfony/http-kernel": "~2.8|~3.0", "symfony/property-access": "~2.8|~3.0", "symfony/property-info": "~2.8|3.0", 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 09926bd680070..3b29f259259fa 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/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index d6ee8349e15d1..dab1a6a35ad05 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -94,15 +94,11 @@ public function getTests() } /** - * Renders a CSRF token. - * - * @param string $intention The intention of the protected action. - * - * @return string A CSRF token. + * {@inheritdoc} */ - public function renderCsrfToken($intention) + public function renderCsrfToken($tokenId) { - return $this->renderer->renderCsrfToken($intention); + return $this->renderer->renderCsrfToken($tokenId); } /** 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 c3fda4acd37fb..20c6e02c7ec01 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..833829fd97178 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php @@ -0,0 +1,113 @@ + + * + * 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 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 ba90af09a66b2..8f30ab476f663 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -22,7 +22,7 @@ "require-dev": { "symfony/asset": "~2.8|~3.0", "symfony/finder": "~2.8|~3.0", - "symfony/form": "~2.8|~3.0", + "symfony/form": "~2.8,>2.8-BETA1|~3.0,>3.0-BETA1", "symfony/http-kernel": "~2.8|~3.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/routing": "~2.8|~3.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/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/Templating/Helper/FormHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php index 2bd2336ee5919..067bca3511303 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php @@ -223,7 +223,7 @@ public function block(FormView $view, $blockName, array $variables = array()) * echo $view['form']->csrfToken('rm_user_'.$user->getId()); * * - * Check the token in your action using the same intention. + * Check the token in your action using the same CSRF token id. * * * $csrfProvider = $this->get('security.csrf.token_generator'); @@ -232,15 +232,15 @@ public function block(FormView $view, $blockName, array $variables = array()) * } * * - * @param string $intention The intention of the protected action + * @param string $tokenId The CSRF token id of the protected action * * @return string A CSRF token * * @throws \BadMethodCallException When no CSRF provider was injected in the constructor. */ - public function csrfToken($intention) + public function csrfToken($tokenId) { - return $this->renderer->renderCsrfToken($intention); + return $this->renderer->renderCsrfToken($tokenId); } public function humanize($text) 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 f826744a19995..3d2cf370c3745 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -11,6 +11,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 83a1e9638e67e..bf828ed22aa0e 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -231,32 +231,6 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->arrayNode('logout') ->treatTrueLike(array()) ->canBeUnset() - ->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['intention']) && isset($v['csrf_token_id']); }) - ->thenInvalid("You should define a value for only one of 'intention' and 'csrf_token_id' on a security firewall. Use 'csrf_token_id' as this replaces 'intention'.") - ->end() - ->beforeNormalization() - ->ifTrue(function ($v) { return isset($v['csrf_provider']); }) - ->then(function ($v) { - $v['csrf_token_generator'] = $v['csrf_provider']; - unset($v['csrf_provider']); - - return $v; - }) - ->end() - ->beforeNormalization() - ->ifTrue(function ($v) { return isset($v['intention']); }) - ->then(function ($v) { - $v['csrf_token_id'] = $v['intention']; - unset($v['intention']); - - return $v; - }) - ->end() ->children() ->scalarNode('csrf_parameter')->defaultValue('_csrf_token')->end() ->scalarNode('csrf_token_generator')->cannotBeEmpty()->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php index ac9523c507208..021c050b79247 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); } @@ -49,7 +49,7 @@ public function addConfiguration(NodeDefinition $node) $node ->children() - ->scalarNode('csrf_provider')->cannotBeEmpty()->end() + ->scalarNode('csrf_token_generator')->cannotBeEmpty()->end() ->end() ; } @@ -78,7 +78,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/Security/Factory/HttpDigestFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php index 63875f83081bc..ae9322f229c21 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php @@ -58,22 +58,6 @@ public function getKey() public function addConfiguration(NodeDefinition $node) { $node - ->beforeNormalization() - ->ifTrue(function ($v) { return isset($v['key']); }) - ->then(function ($v) { - if (isset($v['secret'])) { - throw new \LogicException('Cannot set both key and secret options for http_digest, use only secret instead.'); - } - - @trigger_error('http_digest.key is deprecated since version 2.8 and will be removed in 3.0. Use http_digest.secret instead.', E_USER_DEPRECATED); - - $v['secret'] = $v['key']; - - unset($v['key']); - - return $v; - }) - ->end() ->children() ->scalarNode('provider')->end() ->scalarNode('realm')->defaultValue('Secured Area')->end() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index bd0d2959b287a..ac052111b6c9d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -116,6 +116,10 @@ public function load(array $configs, ContainerBuilder $container) private function aclLoad($config, ContainerBuilder $container) { + if (!interface_exists('Symfony\Component\Security\Acl\Model\AclInterface')) { + throw new \LogicException('You must install symfony/security-acl in order to use the ACL functionality.'); + } + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('security_acl.xml'); @@ -168,7 +172,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 +303,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 edb6382a98335..d19ae1eab81dd 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -7,6 +7,7 @@ Symfony\Component\Security\Core\Authentication\Token\AnonymousToken Symfony\Component\Security\Core\Authentication\Token\RememberMeToken + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php index 9d8009ea8a9e0..8a8f5c8329dea 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/MainConfigurationTest.php @@ -74,8 +74,8 @@ public function testCsrfAliases() 'firewalls' => array( 'stub' => array( 'logout' => array( - 'csrf_provider' => 'a_token_generator', - 'intention' => 'a_token_id', + 'csrf_token_generator' => 'a_token_generator', + 'csrf_token_id' => 'a_token_id', ), ), ), @@ -91,28 +91,6 @@ public function testCsrfAliases() $this->assertEquals('a_token_id', $processedConfig['firewalls']['stub']['logout']['csrf_token_id']); } - /** - * @expectedException \InvalidArgumentException - */ - public function testCsrfOriginalAndAliasValueCausesException() - { - $config = array( - 'firewalls' => array( - 'stub' => array( - 'logout' => array( - 'csrf_token_id' => 'a_token_id', - 'intention' => 'old_name', - ), - ), - ), - ); - $config = array_merge(static::$minimalConfig, $config); - - $processor = new Processor(); - $configuration = new MainConfiguration(array(), array()); - $processor->processConfiguration($configuration, array($config)); - } - public function testDefaultUserCheckers() { $processor = new Processor(); 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 aa5305ec528fd..b82e1f827897d 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 @@ -76,12 +76,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/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 7803025b2b9c7..b185aac3832c9 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,6 @@ "require": { "php": ">=5.5.9", "symfony/security": "~2.8|~3.0", - "symfony/security-acl": "~2.8|~3.0", "symfony/http-kernel": "~2.8|~3.0", "symfony/polyfill-php70": "~1.0" }, @@ -30,6 +29,7 @@ "symfony/form": "~2.8|~3.0", "symfony/framework-bundle": "~2.8|~3.0", "symfony/http-foundation": "~2.8|~3.0", + "symfony/security-acl": "~2.8|~3.0", "symfony/twig-bundle": "~2.8|~3.0", "symfony/twig-bridge": "~2.8|~3.0", "symfony/process": "~2.8|~3.0", @@ -39,6 +39,9 @@ "doctrine/doctrine-bundle": "~1.4", "twig/twig": "~1.23|~2.0" }, + "suggest": { + "symfony/security-acl": "For using the ACL functionality of this bundle" + }, "autoload": { "psr-4": { "Symfony\\Bundle\\SecurityBundle\\": "" }, "exclude-from-classmap": [ 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 2b4c83eafbae0..9a500bc27ee6e 100644 --- a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php +++ b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php @@ -134,8 +134,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 5e5ee06fa7d3c..7af10d8e9a1de 100644 --- a/src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php +++ b/src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php @@ -198,7 +198,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/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php index 8edc14d5455f7..fd5590029ec55 100644 --- a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php @@ -235,7 +235,7 @@ private function writeText($content, array $options = array()) */ private function formatDefaultValue($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 1ab382f213381..463af6ea59565 100644 --- a/src/Symfony/Component/Debug/DebugClassLoader.php +++ b/src/Symfony/Component/Debug/DebugClassLoader.php @@ -120,7 +120,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/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index 73f5bb08394e8..19cdb1fdb1ed6 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Debug; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Debug\Exception\FlattenException; use Symfony\Component\Debug\Exception\OutOfMemoryException; diff --git a/src/Symfony/Component/Debug/README.md b/src/Symfony/Component/Debug/README.md index 67e6d6c2785c6..eec5e1f922757 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,13 +26,11 @@ 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. +component. Resources --------- 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 388e3e2ddf2e3..3edf12445c8e7 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -272,17 +272,13 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE try { $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]); throw $e; + } finally { + unset($this->loading[$id]); } - unset($this->loading[$id]); - return $service; } } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 8ac1e54c83e66..cd0b64c731c38 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -872,7 +872,7 @@ private 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; } @@ -1038,7 +1038,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 243b46c32d281..98d027731f68f 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -393,9 +393,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 98b011cd81709..f252b56b13b64 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -304,6 +304,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 a49cf7ed34282..6ac81299eb3bd 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/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php index 9be91dfcede86..e99f1073b1359 100644 --- a/src/Symfony/Component/Form/AbstractType.php +++ b/src/Symfony/Component/Form/AbstractType.php @@ -48,12 +48,7 @@ public function configureOptions(OptionsResolver $resolver) } /** - * Returns the prefix of the template block name for this type. - * - * The block prefixes default to the underscored short class name with - * the "Type" suffix removed (e.g. "UserProfileType" => "user_profile"). - * - * @return string The prefix of the template block name + * {@inheritdoc} */ public function getBlockPrefix() { diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php index 5ac149a9bd387..0de6c95bd2b0b 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php @@ -68,6 +68,12 @@ public function __construct($choices, callable $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; @@ -201,4 +207,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 deleted file mode 100644 index 55cfab399f4ed..0000000000000 --- a/src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php +++ /dev/null @@ -1,186 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\ChoiceList; - -use Symfony\Component\Form\Exception\InvalidArgumentException; - -/** - * A list of choices that can be stored in the keys of a PHP array. - * - * PHP arrays accept only strings and integers as array keys. Other scalar types - * are cast to integers and strings according to the description of - * {@link toArrayKey()}. This implementation applies the same casting rules for - * the choices passed to the constructor and to {@link getValuesForChoices()}. - * - * By default, the choices are cast to strings and used as values. Optionally, - * you may pass custom values. The keys of the value array must match the keys - * of the choice array. - * - * Example: - * - * ```php - * $choices = array('' => 'Don\'t know', 0 => 'No', 1 => 'Yes'); - * $choiceList = new ArrayKeyChoiceList(array_keys($choices)); - * - * $values = $choiceList->getValues() - * // => array('', '0', '1') - * - * $selectedValues = $choiceList->getValuesForChoices(array(true)); - * // => array('1') - * ``` - * - * @author Bernhard Schussek - */ -class ArrayKeyChoiceList extends ArrayChoiceList -{ - /** - * Whether the choices are used as values. - * - * @var bool - */ - private $useChoicesAsValues = false; - - /** - * Casts the given choice to an array key. - * - * PHP arrays accept only strings and integers as array keys. Integer - * strings such as "42" are automatically cast to integers. The boolean - * values "true" and "false" are cast to the integers 1 and 0. Every other - * scalar value is cast to a string. - * - * @param mixed $choice The choice - * - * @return int|string The choice as PHP array key - * - * @throws InvalidArgumentException If the choice is not scalar - * - * @internal Must not be used outside this class - */ - public static function toArrayKey($choice) - { - if (!is_scalar($choice) && null !== $choice) { - throw new InvalidArgumentException(sprintf( - 'The value of type "%s" cannot be converted to a valid array key.', - gettype($choice) - )); - } - - if (is_bool($choice) || (string) (int) $choice === (string) $choice) { - return (int) $choice; - } - - return (string) $choice; - } - - /** - * Creates a list with the given choices and values. - * - * The given choice array must have the same array keys as the value array. - * Each choice must be castable to an integer/string according to the - * casting rules described in {@link toArrayKey()}. - * - * If no values are given, the choices are cast to strings and used as - * values. - * - * @param array|\Traversable $choices The selectable choices - * @param callable $value The callable for creating the value - * for a choice. If `null` is passed, the - * choices are cast to strings and used - * as values - * - * @throws InvalidArgumentException If the keys of the choices don't match - * the keys of the values or if any of the - * choices is not scalar - */ - public function __construct($choices, callable $value = null) - { - // If no values are given, use the choices as values - // Since the choices are stored in the collection keys, i.e. they are - // strings or integers, we are guaranteed to be able to convert them - // to strings - if (null === $value) { - $value = function ($choice) { - return (string) $choice; - }; - - $this->useChoicesAsValues = true; - } - - parent::__construct($choices, $value); - } - - /** - * {@inheritdoc} - */ - public function getChoicesForValues(array $values) - { - if ($this->useChoicesAsValues) { - $values = array_map('strval', $values); - - // If the values are identical to the choices, so we can just return - // them to improve performance a little bit - return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, array_keys($this->choices))); - } - - return parent::getChoicesForValues($values); - } - - /** - * {@inheritdoc} - */ - public function getValuesForChoices(array $choices) - { - $choices = array_map(array(__CLASS__, 'toArrayKey'), $choices); - - if ($this->useChoicesAsValues) { - // If the choices are identical to the values, we can just return - // them to improve performance a little bit - return array_map('strval', array_intersect($choices, $this->choices)); - } - - return parent::getValuesForChoices($choices); - } - - /** - * Flattens and flips an array into the given output variable. - * - * @param array $choices The array to flatten - * @param callable $value The callable for generating choice values - * @param array $choicesByValues The flattened choices indexed by the - * corresponding values - * @param array $keysByValues The original keys indexed by the - * corresponding values - * - * @internal Must not be used by user-land code - */ - protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues) - { - if (null === $choicesByValues) { - $choicesByValues = array(); - $keysByValues = array(); - $structuredValues = array(); - } - - foreach ($choices as $choice => $key) { - if (is_array($key)) { - $this->flatten($key, $value, $choicesByValues, $keysByValues, $structuredValues[$choice]); - - continue; - } - - $choiceValue = (string) call_user_func($value, $choice); - $choicesByValues[$choiceValue] = $choice; - $keysByValues[$choiceValue] = $key; - $structuredValues[$key] = $choiceValue; - } - } -} diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php index ae7f2375bb896..6580e661d4d66 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php @@ -135,35 +135,6 @@ public function createListFromChoices($choices, $value = null) return $this->lists[$hash]; } - /** - * {@inheritdoc} - * - * @deprecated Added for backwards compatibility in Symfony 2.7, to be - * removed in Symfony 3.0. - */ - public function createListFromFlippedChoices($choices, $value = null, $triggerDeprecationNotice = true) - { - if ($choices instanceof \Traversable) { - $choices = iterator_to_array($choices); - } - - // The value is not validated on purpose. The decorated factory may - // decide which values to accept and which not. - - // We ignore the choice groups for caching. If two choice lists are - // requested with the same choices, but a different grouping, the same - // choice list is returned. - self::flatten($choices, $flatChoices); - - $hash = self::generateHash(array($flatChoices, $value), 'fromFlippedChoices'); - - if (!isset($this->lists[$hash])) { - $this->lists[$hash] = $this->decoratedFactory->createListFromFlippedChoices($choices, $value, $triggerDeprecationNotice); - } - - return $this->lists[$hash]; - } - /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php index 7933dd91d48d7..1c6f24d6986f8 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php @@ -39,28 +39,6 @@ interface ChoiceListFactoryInterface */ public function createListFromChoices($choices, $value = null); - /** - * Creates a choice list for the given choices. - * - * The choices should be passed in the keys of the choices array. Since the - * choices array will be flipped, the entries of the array must be strings - * or integers. - * - * Optionally, a callable can be passed for generating the choice values. - * The callable receives the choice as first and the array key as the second - * argument. - * - * @param array|\Traversable $choices The choices - * @param null|callable $value The callable generating the choice - * values - * - * @return ChoiceListInterface The choice list - * - * @deprecated Added for backwards compatibility in Symfony 2.7, to be - * removed in Symfony 3.0. - */ - public function createListFromFlippedChoices($choices, $value = null); - /** * Creates a choice list that is loaded with the given loader. * diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index f49be10c36392..e632c1238c861 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Form\ChoiceList\Factory; -use Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList; use Symfony\Component\Form\ChoiceList\ArrayChoiceList; use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\ChoiceList\LazyChoiceList; @@ -35,21 +34,6 @@ public function createListFromChoices($choices, $value = null) return new ArrayChoiceList($choices, $value); } - /** - * {@inheritdoc} - * - * @deprecated Added for backwards compatibility in Symfony 2.7, to be - * removed in Symfony 3.0. - */ - public function createListFromFlippedChoices($choices, $value = null, $triggerDeprecationNotice = true) - { - if ($triggerDeprecationNotice) { - @trigger_error('The '.__METHOD__.' is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); - } - - return new ArrayKeyChoiceList($choices, $value); - } - /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php index 1b68fd8924284..130cd49f78bd2 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -104,25 +104,6 @@ public function createListFromChoices($choices, $value = null) return $this->decoratedFactory->createListFromChoices($choices, $value); } - /** - * {@inheritdoc} - * - * @param array|\Traversable $choices The choices - * @param null|callable|string|PropertyPath $value The callable or path for - * generating the choice values - * - * @return ChoiceListInterface The choice list - * - * @deprecated Added for backwards compatibility in Symfony 2.7, to be - * removed in Symfony 3.0. - */ - public function createListFromFlippedChoices($choices, $value = null, $triggerDeprecationNotice = true) - { - // Property paths are not supported here, because array keys can never - // be objects - return $this->decoratedFactory->createListFromFlippedChoices($choices, $value, $triggerDeprecationNotice); - } - /** * {@inheritdoc} * diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index c85aca26ec3b4..549d46f0ee97e 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -52,10 +52,13 @@ public function __construct(ChoiceListFactoryInterface $choiceListFactory = null */ public function buildForm(FormBuilderInterface $builder, array $options) { + $choiceList = $this->createChoiceList($options); + $builder->setAttribute('choice_list', $choiceList); + if ($options['expanded']) { $builder->setDataMapper($options['multiple'] - ? new CheckboxListMapper($options['choice_list']) - : new RadioListMapper($options['choice_list'])); + ? new CheckboxListMapper($choiceList) + : new RadioListMapper($choiceList)); // Initialize all choices before doing the index check below. // This helps in cases where index checks are optimized for non @@ -64,12 +67,12 @@ public function buildForm(FormBuilderInterface $builder, array $options) // requires another SQL query. When the initialization is done first, // one SQL query is sufficient. - $choiceListView = $this->createChoiceListView($options['choice_list'], $options); + $choiceListView = $this->createChoiceListView($choiceList, $options); $builder->setAttribute('choice_list_view', $choiceListView); // Check if the choices already contain the empty value // Only add the placeholder option if this is not the case - if (null !== $options['placeholder'] && 0 === count($options['choice_list']->getChoicesForValues(array('')))) { + if (null !== $options['placeholder'] && 0 === count($choiceList->getChoicesForValues(array('')))) { $placeholderView = new ChoiceView(null, '', $options['placeholder']); // "placeholder" is a reserved name @@ -139,10 +142,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } elseif ($options['multiple']) { // tag without "multiple" option - $builder->addViewTransformer(new ChoiceToValueTransformer($options['choice_list'])); + $builder->addViewTransformer(new ChoiceToValueTransformer($choiceList)); } if ($options['multiple'] && $options['by_reference']) { @@ -162,10 +165,13 @@ public function buildView(FormView $view, FormInterface $form, array $options) $choiceTranslationDomain = $view->vars['translation_domain']; } + /** @var ChoiceListInterface $choiceList */ + $choiceList = $form->getConfig()->getAttribute('choice_list'); + /** @var ChoiceListView $choiceListView */ $choiceListView = $form->getConfig()->hasAttribute('choice_list_view') ? $form->getConfig()->getAttribute('choice_list_view') - : $this->createChoiceListView($options['choice_list'], $options); + : $this->createChoiceListView($choiceList, $options); $view->vars = array_replace($view->vars, array( 'multiple' => $options['multiple'], @@ -192,7 +198,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) } // Check if the choices already contain the empty value - $view->vars['placeholder_in_choices'] = 0 !== count($options['choice_list']->getChoicesForValues(array(''))); + $view->vars['placeholder_in_choices'] = 0 !== count($choiceList->getChoicesForValues(array(''))); // Only add the empty value option if this is not the case if (null !== $options['placeholder'] && !$view->vars['placeholder_in_choices']) { @@ -235,8 +241,6 @@ public function finishView(FormView $view, FormInterface $form, array $options) */ public function configureOptions(OptionsResolver $resolver) { - $choiceListFactory = $this->choiceListFactory; - $emptyData = function (Options $options) { if ($options['multiple'] || $options['expanded']) { return array(); @@ -249,23 +253,21 @@ public function configureOptions(OptionsResolver $resolver) return $options['required'] ? null : ''; }; - $choiceListNormalizer = function (Options $options) use ($choiceListFactory) { - if (null !== $options['choice_loader']) { - return $choiceListFactory->createListFromLoader( - $options['choice_loader'], - $options['choice_value'] - ); + $choicesAsValuesNormalizer = function (Options $options, $choicesAsValues) { + // Not set by the user + if (null === $choicesAsValues) { + return true; } - // Harden against NULL values (like in EntityType and ModelType) - $choices = null !== $options['choices'] ? $options['choices'] : array(); - - // BC when choices are in the keys, not in the values - if (!$options['choices_as_values']) { - return $choiceListFactory->createListFromFlippedChoices($choices, $options['choice_value'], false); + // Set by the user + if (true !== $choicesAsValues) { + throw new \RuntimeException('The "choices_as_values" option should not be used. Remove it and flip the contents of the "choices" option instead.'); } - return $choiceListFactory->createListFromChoices($choices, $options['choice_value']); + // To be uncommented in 3.1 + //@trigger_error('The "choices_as_values" option is deprecated since version 3.1 and will be removed in 4.0. You should not use it anymore.', E_USER_DEPRECATED); + + return true; }; $placeholderNormalizer = function (Options $options, $placeholder) { @@ -299,9 +301,8 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setDefaults(array( 'multiple' => false, 'expanded' => false, - 'choice_list' => null, // deprecated 'choices' => array(), - 'choices_as_values' => false, + 'choices_as_values' => null, // to be deprecated in 3.1 'choice_loader' => null, 'choice_label' => null, 'choice_name' => null, @@ -320,14 +321,12 @@ public function configureOptions(OptionsResolver $resolver) 'choice_translation_domain' => true, )); - $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')); $resolver->setAllowedTypes('choices', array('null', 'array', '\Traversable')); $resolver->setAllowedTypes('choice_translation_domain', array('null', 'bool', 'string')); - $resolver->setAllowedTypes('choices_as_values', 'bool'); $resolver->setAllowedTypes('choice_loader', array('null', 'Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface')); $resolver->setAllowedTypes('choice_label', array('null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath')); $resolver->setAllowedTypes('choice_name', array('null', 'callable', 'string', 'Symfony\Component\PropertyAccess\PropertyPath')); @@ -415,6 +414,21 @@ private function addSubForm(FormBuilderInterface $builder, $name, ChoiceView $ch $builder->add($name, $choiceType, $choiceOpts); } + private function createChoiceList(array $options) + { + if (null !== $options['choice_loader']) { + return $this->choiceListFactory->createListFromLoader( + $options['choice_loader'], + $options['choice_value'] + ); + } + + // Harden against NULL values (like in EntityType and ModelType) + $choices = null !== $options['choices'] ? $options['choices'] : array(); + + return $this->choiceListFactory->createListFromChoices($choices, $options['choice_value']); + } + private function createChoiceListView(ChoiceListInterface $choiceList, array $options) { // If no explicit grouping information is given, use the structural diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php index aff38d9dae2a9..b4a65f2978b64 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php @@ -23,7 +23,7 @@ class CountryType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getRegionBundle()->getCountryNames(), + 'choices' => array_flip(Intl::getRegionBundle()->getCountryNames()), '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 8acf5994b7ff9..7e0497fcc92fc 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php @@ -23,7 +23,7 @@ class CurrencyType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getCurrencyBundle()->getCurrencyNames(), + 'choices' => array_flip(Intl::getCurrencyBundle()->getCurrencyNames()), '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 981fc6eb550ca..be96084e21f33 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -282,14 +282,15 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ { $pattern = $formatter->getPattern(); $timezone = $formatter->getTimezoneId(); + $formattedTimestamps = array(); $formatter->setTimeZone('UTC'); 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 @@ -299,7 +300,7 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ $formatter->setTimeZone($timezone); - return $timestamps; + return $formattedTimestamps; } private function listYears(array $years) @@ -308,7 +309,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; } } @@ -320,7 +321,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; @@ -331,7 +332,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 cd09542245f74..022ab24a8bda3 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php @@ -23,7 +23,7 @@ class LanguageType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getLanguageBundle()->getLanguageNames(), + 'choices' => array_flip(Intl::getLanguageBundle()->getLanguageNames()), '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 27d698543ef86..86ff038b74a74 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php @@ -23,7 +23,7 @@ class LocaleType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getLocaleBundle()->getLocaleNames(), + 'choices' => array_flip(Intl::getLocaleBundle()->getLocaleNames()), '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 e6ca8d47fdd14..8b148b685dcdc 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -63,7 +63,7 @@ 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 @@ -73,7 +73,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) 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; @@ -85,7 +85,7 @@ 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; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index 30c8784afe47a..7f2f34f705e87 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -60,10 +60,10 @@ public function getBlockPrefix() * * @return array The timezone choices */ - public static function getTimezones() + private static function getTimezones() { - if (null === static::$timezones) { - static::$timezones = array(); + if (null === self::$timezones) { + self::$timezones = array(); foreach (\DateTimeZone::listIdentifiers() as $timezone) { $parts = explode('/', $timezone); @@ -79,10 +79,10 @@ public static function getTimezones() $name = $parts[0]; } - static::$timezones[$region][$timezone] = str_replace('_', ' ', $name); + self::$timezones[$region][str_replace('_', ' ', $name)] = $timezone; } } - return static::$timezones; + return self::$timezones; } } diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php index 0bc8ca7db19d0..7e599ab62b933 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php @@ -11,9 +11,6 @@ namespace Symfony\Component\Form\Extension\Csrf; -use Symfony\Component\Form\Exception\UnexpectedTypeException; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\AbstractExtension; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatorInterface; @@ -47,14 +44,8 @@ class CsrfExtension extends AbstractExtension * @param TranslatorInterface $translator The translator for translating error messages * @param null|string $translationDomain The translation domain for translating */ - public function __construct($tokenManager, TranslatorInterface $translator = null, $translationDomain = null) + public function __construct(CsrfTokenManagerInterface $tokenManager, TranslatorInterface $translator = null, $translationDomain = null) { - if ($tokenManager instanceof CsrfProviderInterface) { - $tokenManager = new CsrfProviderAdapter($tokenManager); - } elseif (!$tokenManager instanceof CsrfTokenManagerInterface) { - throw new UnexpectedTypeException($tokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); - } - $this->tokenManager = $tokenManager; $this->translator = $translator; $this->translationDomain = $translationDomain; diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php index e96c1c930d5c1..f3c9db0df82b9 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -12,9 +12,6 @@ namespace Symfony\Component\Form\Extension\Csrf\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Form\Exception\UnexpectedTypeException; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; @@ -75,14 +72,8 @@ public static function getSubscribedEvents() ); } - public function __construct($fieldName, $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) + public function __construct($fieldName, CsrfTokenManagerInterface $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) { - if ($tokenManager instanceof CsrfProviderInterface) { - $tokenManager = new CsrfProviderAdapter($tokenManager); - } elseif (!$tokenManager instanceof CsrfTokenManagerInterface) { - throw new UnexpectedTypeException($tokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); - } - $this->fieldName = $fieldName; $this->tokenManager = $tokenManager; $this->tokenId = $tokenId; diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index cf5038ae1f6cd..2fca459aa29f0 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -12,10 +12,6 @@ namespace Symfony\Component\Form\Extension\Csrf\Type; use Symfony\Component\Form\AbstractTypeExtension; -use Symfony\Component\Form\Exception\UnexpectedTypeException; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfTokenManagerAdapter; use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormView; @@ -55,14 +51,8 @@ class FormTypeCsrfExtension extends AbstractTypeExtension */ private $translationDomain; - public function __construct($defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) + public function __construct(CsrfTokenManagerInterface $defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) { - if ($defaultTokenManager instanceof CsrfProviderInterface) { - $defaultTokenManager = new CsrfProviderAdapter($defaultTokenManager); - } elseif (!$defaultTokenManager instanceof CsrfTokenManagerInterface) { - throw new UnexpectedTypeException($defaultTokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); - } - $this->defaultTokenManager = $defaultTokenManager; $this->defaultEnabled = $defaultEnabled; $this->defaultFieldName = $defaultFieldName; @@ -121,30 +111,12 @@ public function finishView(FormView $view, FormInterface $form, array $options) */ public function configureOptions(OptionsResolver $resolver) { - // BC clause for the "intention" option - $csrfTokenId = function (Options $options) { - return $options['intention']; - }; - - // BC clause for the "csrf_provider" option - $csrfTokenManager = function (Options $options) { - if ($options['csrf_provider'] instanceof CsrfTokenManagerInterface) { - return $options['csrf_provider']; - } - - return $options['csrf_provider'] instanceof CsrfTokenManagerAdapter - ? $options['csrf_provider']->getTokenManager(false) - : new CsrfProviderAdapter($options['csrf_provider']); - }; - $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_token_manager' => $this->defaultTokenManager, + 'csrf_token_id' => null, )); } 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/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php index 65430f1d222b0..ede4873be3843 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php @@ -43,20 +43,12 @@ public function __construct(ResolvedFormTypeInterface $proxiedType, FormDataColl $this->dataCollector = $dataCollector; } - /** - * {@inheritdoc} - */ - public function getName() - { - return $this->proxiedType->getName(); - } - /** * {@inheritdoc} */ public function getBlockPrefix() { - return method_exists($this->proxiedType, 'getBlockPrefix') ? $this->proxiedType->getBlockPrefix() : $this->getName(); + return $this->proxiedType->getBlockPrefix(); } /** diff --git a/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php b/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php index 3c29bef5742af..6d47d73167c80 100644 --- a/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php +++ b/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php @@ -12,9 +12,6 @@ namespace Symfony\Component\Form\Extension\Templating; use Symfony\Component\Form\AbstractExtension; -use Symfony\Component\Form\Exception\UnexpectedTypeException; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Templating\PhpEngine; @@ -27,14 +24,8 @@ */ class TemplatingExtension extends AbstractExtension { - public function __construct(PhpEngine $engine, $csrfTokenManager = null, array $defaultThemes = array()) + public function __construct(PhpEngine $engine, CsrfTokenManagerInterface $csrfTokenManager = null, array $defaultThemes = array()) { - if ($csrfTokenManager instanceof CsrfProviderInterface) { - $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); - } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { - throw new UnexpectedTypeException($csrfTokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); - } - $engine->addHelpers(array( new FormHelper(new FormRenderer(new TemplatingRendererEngine($engine, $defaultThemes), $csrfTokenManager)), )); diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 35fb035cf8004..f7d639c0cb113 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -355,21 +355,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 '. @@ -856,7 +846,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 d8cab0478d30d..169bc3f6b5a32 100644 --- a/src/Symfony/Component/Form/FormFactory.php +++ b/src/Symfony/Component/Form/FormFactory.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Form; use Symfony\Component\Form\Exception\UnexpectedTypeException; -use Symfony\Component\Form\Util\StringUtil; class FormFactory implements FormFactoryInterface { @@ -65,7 +64,7 @@ public function createBuilder($type = 'Symfony\Component\Form\Extension\Core\Typ throw new UnexpectedTypeException($type, 'string'); } - return $this->createNamedBuilder(StringUtil::fqcnToBlockPrefix($type), $type, $data, $options); + return $this->createNamedBuilder($this->registry->getType($type)->getBlockPrefix(), $type, $data, $options); } /** diff --git a/src/Symfony/Component/Form/FormRegistry.php b/src/Symfony/Component/Form/FormRegistry.php index 49db05ba5fd15..b449f20315d03 100644 --- a/src/Symfony/Component/Form/FormRegistry.php +++ b/src/Symfony/Component/Form/FormRegistry.php @@ -88,7 +88,7 @@ public function getType($name) } } - $this->resolveAndAddType($type); + $this->types[$name] = $this->resolveType($type); } return $this->types[$name]; @@ -102,7 +102,7 @@ public function getType($name) * * @return ResolvedFormTypeInterface The resolved type. */ - private function resolveAndAddType(FormTypeInterface $type) + private function resolveType(FormTypeInterface $type) { $typeExtensions = array(); $parentType = $type->getParent(); @@ -115,13 +115,11 @@ private function resolveAndAddType(FormTypeInterface $type) ); } - $resolvedType = $this->resolvedTypeFactory->createResolvedType( + return $this->resolvedTypeFactory->createResolvedType( $type, $typeExtensions, $parentType ? $this->getType($parentType) : null ); - - $this->types[$fqcn] = $resolvedType; } /** diff --git a/src/Symfony/Component/Form/FormRenderer.php b/src/Symfony/Component/Form/FormRenderer.php index b286ffdf41b24..5a32c9d4308d5 100644 --- a/src/Symfony/Component/Form/FormRenderer.php +++ b/src/Symfony/Component/Form/FormRenderer.php @@ -13,9 +13,6 @@ use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\Form\Exception\BadMethodCallException; -use Symfony\Component\Form\Exception\UnexpectedTypeException; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; /** @@ -57,17 +54,9 @@ class FormRenderer implements FormRendererInterface * * @param FormRendererEngineInterface $engine * @param CsrfTokenManagerInterface|null $csrfTokenManager - * - * @throws UnexpectedTypeException */ - public function __construct(FormRendererEngineInterface $engine, $csrfTokenManager = null) + public function __construct(FormRendererEngineInterface $engine, CsrfTokenManagerInterface $csrfTokenManager = null) { - if ($csrfTokenManager instanceof CsrfProviderInterface) { - $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); - } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { - throw new UnexpectedTypeException($csrfTokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface or null'); - } - $this->engine = $engine; $this->csrfTokenManager = $csrfTokenManager; } diff --git a/src/Symfony/Component/Form/FormTypeInterface.php b/src/Symfony/Component/Form/FormTypeInterface.php index 584e650a1d8b2..1e80f477ca6bb 100644 --- a/src/Symfony/Component/Form/FormTypeInterface.php +++ b/src/Symfony/Component/Form/FormTypeInterface.php @@ -75,6 +75,16 @@ public function finishView(FormView $view, FormInterface $form, array $options); */ public function configureOptions(OptionsResolver $resolver); + /** + * Returns the prefix of the template block name for this type. + * + * The block prefix defaults to the underscored short class name with + * the "Type" suffix removed (e.g. "UserProfileType" => "user_profile"). + * + * @return string The prefix of the template block name + */ + public function getBlockPrefix(); + /** * Returns the name of the parent type. * diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php index 02b101d390fa4..42b0352a88129 100644 --- a/src/Symfony/Component/Form/ResolvedFormType.php +++ b/src/Symfony/Component/Form/ResolvedFormType.php @@ -56,9 +56,7 @@ public function __construct(FormTypeInterface $innerType, array $typeExtensions } /** - * Returns the prefix of the template block name for this type. - * - * @return string The prefix of the template block name + * {@inheritdoc} */ public function getBlockPrefix() { diff --git a/src/Symfony/Component/Form/ResolvedFormTypeInterface.php b/src/Symfony/Component/Form/ResolvedFormTypeInterface.php index f496cdc578899..592f459e25168 100644 --- a/src/Symfony/Component/Form/ResolvedFormTypeInterface.php +++ b/src/Symfony/Component/Form/ResolvedFormTypeInterface.php @@ -20,6 +20,13 @@ */ interface ResolvedFormTypeInterface { + /** + * Returns the prefix of the template block name for this type. + * + * @return string The prefix of the template block name + */ + public function getBlockPrefix(); + /** * Returns the parent type. * 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 650b1518afc71..32a4af7dde8c6 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php @@ -212,7 +212,7 @@ 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'), 'multiple' => false, 'expanded' => false, )); @@ -234,7 +234,7 @@ 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'), 'multiple' => false, 'expanded' => false, 'choice_translation_domain' => false, @@ -254,10 +254,36 @@ 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'), + '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'), 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => false, @@ -280,7 +306,7 @@ 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'), 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -304,7 +330,7 @@ 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'), 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -327,7 +353,7 @@ 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'), 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -351,7 +377,7 @@ 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'), 'preferred_choices' => array('&a', '&b'), 'multiple' => false, 'expanded' => false, @@ -368,7 +394,7 @@ 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'), 'required' => false, 'multiple' => false, 'expanded' => false, @@ -392,7 +418,7 @@ 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'), 'required' => false, 'multiple' => false, 'expanded' => false, @@ -416,7 +442,7 @@ 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'), 'multiple' => false, 'expanded' => false, 'required' => false, @@ -441,7 +467,7 @@ 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'), 'required' => true, 'multiple' => false, 'expanded' => false, @@ -466,7 +492,7 @@ 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'), 'required' => true, 'multiple' => false, 'expanded' => false, @@ -491,8 +517,8 @@ 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'), ), 'multiple' => false, 'expanded' => false, @@ -521,7 +547,7 @@ 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'), 'required' => true, 'multiple' => true, 'expanded' => false, @@ -545,7 +571,7 @@ 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'), 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'required' => true, 'multiple' => true, @@ -570,7 +596,7 @@ 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'), 'multiple' => true, 'expanded' => false, 'placeholder' => 'Test&Me', @@ -593,7 +619,7 @@ 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'), 'required' => false, 'multiple' => true, 'expanded' => false, @@ -616,7 +642,7 @@ 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'), 'multiple' => false, 'expanded' => true, )); @@ -651,7 +677,7 @@ 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'), 'multiple' => false, 'expanded' => true, 'choice_translation_domain' => false, @@ -687,7 +713,7 @@ 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'), 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => true, @@ -723,7 +749,7 @@ 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'), 'multiple' => false, 'expanded' => true, 'placeholder' => 'Test&Me', @@ -765,10 +791,56 @@ 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'), + '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'), 'multiple' => false, 'expanded' => true, )); @@ -803,7 +875,7 @@ 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'), 'multiple' => true, 'expanded' => true, 'required' => true, @@ -848,7 +920,7 @@ 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'), 'multiple' => true, 'expanded' => true, 'required' => true, @@ -894,7 +966,7 @@ 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'), 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => true, 'expanded' => true, @@ -1996,6 +2068,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..b5f6017084369 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -691,7 +691,7 @@ 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'), 'expanded' => true, )) ->getForm(); diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 48561daa6b880..7bade490f9b2b 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -352,6 +352,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') @@ -468,7 +487,7 @@ 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'), 'multiple' => false, 'expanded' => false, )); @@ -501,7 +520,7 @@ 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'), 'multiple' => false, 'expanded' => false, 'choice_translation_domain' => false, @@ -520,10 +539,35 @@ 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'), + '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'), 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => false, @@ -545,7 +589,7 @@ 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'), 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -568,7 +612,7 @@ 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'), 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -590,7 +634,7 @@ 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'), 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -613,7 +657,7 @@ 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'), 'preferred_choices' => array('&a', '&b'), 'multiple' => false, 'expanded' => false, @@ -629,7 +673,7 @@ 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'), 'required' => false, 'multiple' => false, 'expanded' => false, @@ -652,7 +696,7 @@ 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'), 'required' => false, 'multiple' => false, 'expanded' => false, @@ -675,7 +719,7 @@ 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'), 'multiple' => false, 'expanded' => false, 'required' => false, @@ -699,7 +743,7 @@ 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'), 'required' => true, 'multiple' => false, 'expanded' => false, @@ -726,7 +770,7 @@ 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'), 'required' => true, 'multiple' => false, 'expanded' => false, @@ -753,8 +797,8 @@ 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'), ), 'multiple' => false, 'expanded' => false, @@ -782,7 +826,7 @@ 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'), 'required' => true, 'multiple' => true, 'expanded' => false, @@ -805,7 +849,7 @@ 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'), 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'required' => true, 'multiple' => true, @@ -829,7 +873,7 @@ 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'), 'multiple' => true, 'expanded' => false, 'placeholder' => 'Test&Me', @@ -851,7 +895,7 @@ 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'), 'required' => false, 'multiple' => true, 'expanded' => false, @@ -873,7 +917,7 @@ 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'), 'multiple' => false, 'expanded' => true, )); @@ -895,7 +939,7 @@ 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'), 'multiple' => false, 'expanded' => true, 'choice_translation_domain' => false, @@ -918,7 +962,7 @@ 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'), 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => true, @@ -941,7 +985,7 @@ 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'), 'multiple' => false, 'expanded' => true, 'placeholder' => 'Test&Me', @@ -963,10 +1007,36 @@ 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'), + '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'), 'multiple' => false, 'expanded' => true, )); @@ -988,7 +1058,7 @@ 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'), 'multiple' => true, 'expanded' => true, 'required' => true, @@ -1013,7 +1083,7 @@ 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'), 'multiple' => true, 'expanded' => true, 'required' => true, @@ -1039,7 +1109,7 @@ 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'), 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => true, 'expanded' => true, @@ -2086,6 +2156,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'); @@ -2294,4 +2375,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 f71ba67a9afbf..9d144f941d5e7 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php @@ -57,6 +57,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( @@ -64,15 +98,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 deleted file mode 100644 index 5cbadf6e0fe7b..0000000000000 --- a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayKeyChoiceListTest.php +++ /dev/null @@ -1,181 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Tests\ChoiceList; - -use Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList; - -/** - * @author Bernhard Schussek - */ -class ArrayKeyChoiceListTest extends AbstractChoiceListTest -{ - private $object; - - protected function setUp() - { - parent::setUp(); - - $this->object = new \stdClass(); - } - - protected function createChoiceList() - { - return new ArrayKeyChoiceList(array_flip($this->getChoices())); - } - - protected function getChoices() - { - return array(0, 1, 'a', 'b', ''); - } - - protected function getValues() - { - return array('0', '1', 'a', 'b', ''); - } - - public function testUseChoicesAsValuesByDefault() - { - $list = new ArrayKeyChoiceList(array('' => 'Empty', 0 => 'Zero', 1 => 'One', '1.23' => 'Float')); - - $this->assertSame(array('', '0', '1', '1.23'), $list->getValues()); - $this->assertSame(array('' => '', 0 => 0, 1 => 1, '1.23' => '1.23'), $list->getChoices()); - $this->assertSame(array('' => 'Empty', 0 => 'Zero', 1 => 'One', '1.23' => 'Float'), $list->getOriginalKeys()); - } - - public function testNoChoices() - { - $list = new ArrayKeyChoiceList(array()); - - $this->assertSame(array(), $list->getValues()); - } - - public function testGetChoicesForValuesConvertsValuesToStrings() - { - $this->assertSame(array(0), $this->list->getChoicesForValues(array(0))); - $this->assertSame(array(0), $this->list->getChoicesForValues(array('0'))); - $this->assertSame(array(1), $this->list->getChoicesForValues(array(1))); - $this->assertSame(array(1), $this->list->getChoicesForValues(array('1'))); - $this->assertSame(array('a'), $this->list->getChoicesForValues(array('a'))); - $this->assertSame(array('b'), $this->list->getChoicesForValues(array('b'))); - $this->assertSame(array(''), $this->list->getChoicesForValues(array(''))); - // "1" === (string) true - $this->assertSame(array(1), $this->list->getChoicesForValues(array(true))); - // "" === (string) false - $this->assertSame(array(''), $this->list->getChoicesForValues(array(false))); - // "" === (string) null - $this->assertSame(array(''), $this->list->getChoicesForValues(array(null))); - $this->assertSame(array(), $this->list->getChoicesForValues(array(1.23))); - } - - public function testGetValuesForChoicesConvertsChoicesToArrayKeys() - { - $this->assertSame(array('0'), $this->list->getValuesForChoices(array(0))); - $this->assertSame(array('0'), $this->list->getValuesForChoices(array('0'))); - $this->assertSame(array('1'), $this->list->getValuesForChoices(array(1))); - $this->assertSame(array('1'), $this->list->getValuesForChoices(array('1'))); - $this->assertSame(array('a'), $this->list->getValuesForChoices(array('a'))); - $this->assertSame(array('b'), $this->list->getValuesForChoices(array('b'))); - // Always cast booleans to 0 and 1, because: - // array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No') - // see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean - $this->assertSame(array('0'), $this->list->getValuesForChoices(array(false))); - $this->assertSame(array('1'), $this->list->getValuesForChoices(array(true))); - } - - /** - * @dataProvider provideConvertibleChoices - */ - public function testConvertChoicesIfNecessary(array $choices, array $converted) - { - $list = new ArrayKeyChoiceList($choices); - - $this->assertSame($converted, $list->getChoices()); - } - - public function provideConvertibleChoices() - { - return array( - array(array(0 => 'Label'), array(0 => 0)), - array(array(1 => 'Label'), array(1 => 1)), - array(array('1.23' => 'Label'), array('1.23' => '1.23')), - array(array('foobar' => 'Label'), array('foobar' => 'foobar')), - // The default value of choice fields is NULL. It should be treated - // like the empty value for this choice list type - array(array(null => 'Label'), array('' => '')), - array(array('1.23' => 'Label'), array('1.23' => '1.23')), - // Always cast booleans to 0 and 1, because: - // array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No') - // see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean - array(array(true => 'Label'), array(1 => 1)), - array(array(false => 'Label'), array(0 => 0)), - ); - } - - /** - * @dataProvider provideInvalidChoices - * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException - */ - public function testGetValuesForChoicesFailsIfInvalidChoices(array $choices) - { - $this->list->getValuesForChoices($choices); - } - - public function provideInvalidChoices() - { - return array( - array(array(new \stdClass())), - array(array(array(1, 2))), - ); - } - - /** - * @dataProvider provideConvertibleValues - */ - public function testConvertValuesToStrings($value, $converted) - { - $callback = function () use ($value) { - return $value; - }; - - $list = new ArrayKeyChoiceList(array('choice' => 'Label'), $callback); - - $this->assertSame(array($converted), $list->getValues()); - } - - public function provideConvertibleValues() - { - return array( - array(0, '0'), - array(1, '1'), - array('0', '0'), - array('1', '1'), - array('1.23', '1.23'), - array('foobar', 'foobar'), - array('', ''), - ); - } - - public function testCreateChoiceListWithValueCallback() - { - $callback = function ($choice) { - return ':'.$choice; - }; - - $choiceList = new ArrayKeyChoiceList(array('foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz'), $callback); - - $this->assertSame(array(':foo', ':bar', ':baz'), $choiceList->getValues()); - $this->assertSame(array(':foo' => 'foo', ':bar' => 'bar', ':baz' => 'baz'), $choiceList->getChoices()); - $this->assertSame(array(':foo' => 'Foo', ':bar' => 'Bar', ':baz' => 'Baz'), $choiceList->getOriginalKeys()); - $this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz'))); - $this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz'))); - } -} diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index ad7bc873db693..bfe716680c3fd 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/ChoiceList/AbstractChoiceListTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/AbstractChoiceListTest.php deleted file mode 100644 index a17d672f62679..0000000000000 --- a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/AbstractChoiceListTest.php +++ /dev/null @@ -1,297 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Tests\Extension\Core\ChoiceList; - -/** - * @author Bernhard Schussek - * - * @group legacy - */ -abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface - */ - protected $list; - - /** - * @var array - */ - protected $choices; - - /** - * @var array - */ - protected $values; - - /** - * @var array - */ - protected $indices; - - /** - * @var array - */ - protected $labels; - - /** - * @var mixed - */ - protected $choice1; - - /** - * @var mixed - */ - protected $choice2; - - /** - * @var mixed - */ - protected $choice3; - - /** - * @var mixed - */ - protected $choice4; - - /** - * @var string - */ - protected $value1; - - /** - * @var string - */ - protected $value2; - - /** - * @var string - */ - protected $value3; - - /** - * @var string - */ - protected $value4; - - /** - * @var int|string - */ - protected $index1; - - /** - * @var int|string - */ - protected $index2; - - /** - * @var int|string - */ - protected $index3; - - /** - * @var int|string - */ - protected $index4; - - /** - * @var string - */ - protected $label1; - - /** - * @var string - */ - protected $label2; - - /** - * @var string - */ - protected $label3; - - /** - * @var string - */ - protected $label4; - - protected function setUp() - { - parent::setUp(); - - $this->list = $this->createChoiceList(); - - $this->choices = $this->getChoices(); - $this->indices = $this->getIndices(); - $this->values = $this->getValues(); - $this->labels = $this->getLabels(); - - // allow access to the individual entries without relying on their indices - reset($this->choices); - reset($this->indices); - reset($this->values); - reset($this->labels); - - for ($i = 1; $i <= 4; ++$i) { - $this->{'choice'.$i} = current($this->choices); - $this->{'index'.$i} = current($this->indices); - $this->{'value'.$i} = current($this->values); - $this->{'label'.$i} = current($this->labels); - - next($this->choices); - next($this->indices); - next($this->values); - next($this->labels); - } - } - - public function testGetChoices() - { - $this->assertSame($this->choices, $this->list->getChoices()); - } - - public function testGetValues() - { - $this->assertSame($this->values, $this->list->getValues()); - } - - public function testGetIndicesForChoices() - { - $choices = array($this->choice1, $this->choice2); - $this->assertSame(array($this->index1, $this->index2), $this->list->getIndicesForChoices($choices)); - } - - public function testGetIndicesForChoicesPreservesKeys() - { - $choices = array(5 => $this->choice1, 8 => $this->choice2); - $this->assertSame(array(5 => $this->index1, 8 => $this->index2), $this->list->getIndicesForChoices($choices)); - } - - public function testGetIndicesForChoicesPreservesOrder() - { - $choices = array($this->choice2, $this->choice1); - $this->assertSame(array($this->index2, $this->index1), $this->list->getIndicesForChoices($choices)); - } - - public function testGetIndicesForChoicesIgnoresNonExistingChoices() - { - $choices = array($this->choice1, $this->choice2, 'foobar'); - $this->assertSame(array($this->index1, $this->index2), $this->list->getIndicesForChoices($choices)); - } - - public function testGetIndicesForChoicesEmpty() - { - $this->assertSame(array(), $this->list->getIndicesForChoices(array())); - } - - public function testGetIndicesForValues() - { - // values and indices are always the same - $values = array($this->value1, $this->value2); - $this->assertSame(array($this->index1, $this->index2), $this->list->getIndicesForValues($values)); - } - - public function testGetIndicesForValuesPreservesKeys() - { - // values and indices are always the same - $values = array(5 => $this->value1, 8 => $this->value2); - $this->assertSame(array(5 => $this->index1, 8 => $this->index2), $this->list->getIndicesForValues($values)); - } - - public function testGetIndicesForValuesPreservesOrder() - { - $values = array($this->value2, $this->value1); - $this->assertSame(array($this->index2, $this->index1), $this->list->getIndicesForValues($values)); - } - - public function testGetIndicesForValuesIgnoresNonExistingValues() - { - $values = array($this->value1, $this->value2, 'foobar'); - $this->assertSame(array($this->index1, $this->index2), $this->list->getIndicesForValues($values)); - } - - public function testGetIndicesForValuesEmpty() - { - $this->assertSame(array(), $this->list->getIndicesForValues(array())); - } - - public function testGetChoicesForValues() - { - $values = array($this->value1, $this->value2); - $this->assertSame(array($this->choice1, $this->choice2), $this->list->getChoicesForValues($values)); - } - - public function testGetChoicesForValuesPreservesKeys() - { - $values = array(5 => $this->value1, 8 => $this->value2); - $this->assertSame(array(5 => $this->choice1, 8 => $this->choice2), $this->list->getChoicesForValues($values)); - } - - public function testGetChoicesForValuesPreservesOrder() - { - $values = array($this->value2, $this->value1); - $this->assertSame(array($this->choice2, $this->choice1), $this->list->getChoicesForValues($values)); - } - - public function testGetChoicesForValuesIgnoresNonExistingValues() - { - $values = array($this->value1, $this->value2, 'foobar'); - $this->assertSame(array($this->choice1, $this->choice2), $this->list->getChoicesForValues($values)); - } - - // https://github.com/symfony/symfony/issues/3446 - public function testGetChoicesForValuesEmpty() - { - $this->assertSame(array(), $this->list->getChoicesForValues(array())); - } - - public function testGetValuesForChoices() - { - $choices = array($this->choice1, $this->choice2); - $this->assertSame(array($this->value1, $this->value2), $this->list->getValuesForChoices($choices)); - } - - public function testGetValuesForChoicesPreservesKeys() - { - $choices = array(5 => $this->choice1, 8 => $this->choice2); - $this->assertSame(array(5 => $this->value1, 8 => $this->value2), $this->list->getValuesForChoices($choices)); - } - - public function testGetValuesForChoicesPreservesOrder() - { - $choices = array($this->choice2, $this->choice1); - $this->assertSame(array($this->value2, $this->value1), $this->list->getValuesForChoices($choices)); - } - - public function testGetValuesForChoicesIgnoresNonExistingChoices() - { - $choices = array($this->choice1, $this->choice2, 'foobar'); - $this->assertSame(array($this->value1, $this->value2), $this->list->getValuesForChoices($choices)); - } - - public function testGetValuesForChoicesEmpty() - { - $this->assertSame(array(), $this->list->getValuesForChoices(array())); - } - - /** - * @return \Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface - */ - abstract protected function createChoiceList(); - - abstract protected function getChoices(); - - abstract protected function getLabels(); - - abstract protected function getValues(); - - abstract protected function getIndices(); -} 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 d62bd0d3a4ca2..f5fff1ea74a5d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -13,37 +13,28 @@ use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceView; -use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList; class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase { private $choices = array( - 'a' => 'Bernhard', - 'b' => 'Fabien', - 'c' => 'Kris', - 'd' => 'Jon', - 'e' => 'Roman', - ); - - private $numericChoices = array( - 0 => 'Bernhard', - 1 => 'Fabien', - 2 => 'Kris', - 3 => 'Jon', - 4 => 'Roman', + 'Bernhard' => 'a', + 'Fabien' => 'b', + 'Kris' => 'c', + 'Jon' => 'd', + 'Roman' => 'e', ); private $objectChoices; protected $groupedChoices = array( 'Symfony' => array( - 'a' => 'Bernhard', - 'b' => 'Fabien', - 'c' => 'Kris', + 'Bernhard' => 'a', + 'Fabien' => 'b', + 'Kris' => 'c', ), 'Doctrine' => array( - 'd' => 'Jon', - 'e' => 'Roman', + 'Jon' => 'd', + 'Roman' => 'e', ), ); @@ -77,16 +68,6 @@ public function testChoicesOptionExpectsArrayOrTraversable() )); } - /** - * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException - */ - public function testChoiceListOptionExpectsChoiceListInterface() - { - $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( - 'choice_list' => array('foo' => 'foo'), - )); - } - /** * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException */ @@ -99,7 +80,8 @@ 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( + )); } public function testExpandedChoicesOptionsTurnIntoChildren() @@ -158,8 +140,8 @@ public function testPlaceholderNotPresentIfEmptyChoice() 'expanded' => true, 'required' => false, 'choices' => array( - '' => 'Empty', - 1 => 'Not empty', + 'Empty' => '', + 'Not empty' => 1, ), )); @@ -200,7 +182,6 @@ public function testExpandedChoicesOptionsAreFlattenedObjectChoices() 'Symfony' => array($obj1, $obj2, $obj3), 'Doctrine' => array($obj4, $obj5), ), - 'choices_as_values' => true, 'choice_name' => 'id', )); @@ -338,7 +319,7 @@ public function testSubmitSingleNonExpandedEmptyExplicitEmptyChoice() 'multiple' => false, 'expanded' => false, 'choices' => array( - 'EMPTY_CHOICE' => 'Empty', + 'Empty' => 'EMPTY_CHOICE', ), 'choice_value' => function () { return ''; @@ -409,7 +390,6 @@ public function testSubmitSingleNonExpandedObjectChoices() 'multiple' => false, 'expanded' => false, 'choices' => $this->objectChoices, - 'choices_as_values' => true, 'choice_label' => 'name', 'choice_value' => 'id', )); @@ -506,7 +486,6 @@ public function testSubmitMultipleNonExpandedObjectChoices() 'multiple' => true, 'expanded' => false, 'choices' => $this->objectChoices, - 'choices_as_values' => true, 'choice_label' => 'name', 'choice_value' => 'id', )); @@ -932,8 +911,8 @@ public function testSubmitSingleExpandedWithEmptyChild() 'multiple' => false, 'expanded' => true, 'choices' => array( - '' => 'Empty', - 1 => 'Not empty', + 'Empty' => '', + 'Not empty' => 1, ), )); @@ -954,7 +933,6 @@ public function testSubmitSingleExpandedObjectChoices() 'multiple' => false, 'expanded' => true, 'choices' => $this->objectChoices, - 'choices_as_values' => true, 'choice_label' => 'name', 'choice_value' => 'id', )); @@ -976,31 +954,6 @@ public function testSubmitSingleExpandedObjectChoices() $this->assertNull($form[4]->getViewData()); } - public function testSubmitSingleExpandedNumericChoices() - { - $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( - 'multiple' => false, - 'expanded' => true, - 'choices' => $this->numericChoices, - )); - - $form->submit('1'); - - $this->assertSame(1, $form->getData()); - $this->assertTrue($form->isSynchronized()); - - $this->assertFalse($form[0]->getData()); - $this->assertTrue($form[1]->getData()); - $this->assertFalse($form[2]->getData()); - $this->assertFalse($form[3]->getData()); - $this->assertFalse($form[4]->getData()); - $this->assertNull($form[0]->getViewData()); - $this->assertSame('1', $form[1]->getViewData()); - $this->assertNull($form[2]->getViewData()); - $this->assertNull($form[3]->getViewData()); - $this->assertNull($form[4]->getViewData()); - } - public function testSubmitMultipleExpanded() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( @@ -1130,9 +1083,9 @@ 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, ), )); @@ -1155,7 +1108,6 @@ public function testSubmitMultipleExpandedObjectChoices() 'multiple' => true, 'expanded' => true, 'choices' => $this->objectChoices, - 'choices_as_values' => true, 'choice_label' => 'name', 'choice_value' => 'id', )); @@ -1177,38 +1129,12 @@ public function testSubmitMultipleExpandedObjectChoices() $this->assertNull($form[4]->getViewData()); } - public function testSubmitMultipleExpandedNumericChoices() - { - $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( - 'multiple' => true, - 'expanded' => true, - 'choices' => $this->numericChoices, - )); - - $form->submit(array('1', '2')); - - $this->assertSame(array(1, 2), $form->getData()); - $this->assertTrue($form->isSynchronized()); - - $this->assertFalse($form[0]->getData()); - $this->assertTrue($form[1]->getData()); - $this->assertTrue($form[2]->getData()); - $this->assertFalse($form[3]->getData()); - $this->assertFalse($form[4]->getData()); - $this->assertNull($form[0]->getViewData()); - $this->assertSame('1', $form[1]->getViewData()); - $this->assertSame('2', $form[2]->getViewData()); - $this->assertNull($form[3]->getViewData()); - $this->assertNull($form[4]->getViewData()); - } - public function testSingleSelectedObjectChoices() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', $this->objectChoices[3], array( 'multiple' => false, 'expanded' => false, 'choices' => $this->objectChoices, - 'choices_as_values' => true, 'choice_label' => 'name', 'choice_value' => 'id', )); @@ -1226,7 +1152,6 @@ public function testMultipleSelectedObjectChoices() 'multiple' => true, 'expanded' => false, 'choices' => $this->objectChoices, - 'choices_as_values' => true, 'choice_label' => 'name', 'choice_value' => 'id', )); @@ -1238,40 +1163,6 @@ 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' - */ - public function testSetDataSingleNonExpandedAcceptsBoolean() - { - $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( - 'multiple' => false, - 'expanded' => false, - 'choices' => $this->numericChoices, - )); - - $form->setData(false); - - $this->assertFalse($form->getData()); - $this->assertEquals('0', $form->getViewData()); - $this->assertTrue($form->isSynchronized()); - } - - public function testSetDataMultipleNonExpandedAcceptsBoolean() - { - $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( - 'multiple' => true, - 'expanded' => false, - 'choices' => $this->numericChoices, - )); - - $form->setData(array(false, true)); - - $this->assertEquals(array(false, true), $form->getData()); - $this->assertEquals(array('0', '1'), $form->getViewData()); - $this->assertTrue($form->isSynchronized()); - } - public function testPassRequiredToView() { $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( @@ -1353,7 +1244,9 @@ 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(), + )) ->getForm() ->createView(); @@ -1412,7 +1305,7 @@ public function testDontPassPlaceholderIfContainedInChoices($multiple, $expanded 'expanded' => $expanded, 'required' => $required, 'placeholder' => $placeholder, - 'choices' => array('a' => 'A', '' => 'Empty'), + 'choices' => array('A' => 'a', 'Empty' => ''), )); $view = $form->createView(); @@ -1466,7 +1359,7 @@ 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, )); @@ -1482,7 +1375,7 @@ 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, 'preferred_choices' => array('b', 'd'), @@ -1534,7 +1427,6 @@ public function testPassChoiceDataToView() $obj4 = (object) array('value' => 'd', 'label' => 'D'); $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'choices' => array($obj1, $obj2, $obj3, $obj4), - 'choices_as_values' => true, 'choice_label' => 'label', 'choice_value' => 'value', )); @@ -1577,7 +1469,6 @@ public function testInitializeWithDefaultObjectChoice() $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( 'choices' => array($obj1, $obj2, $obj3, $obj4), - 'choices_as_values' => true, 'choice_label' => 'label', 'choice_value' => 'value', // Used to break because "data_class" was inferred, which needs to diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php index 905cd258157be..690e4bd1f606a 100644 --- a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataExtractorTest.php @@ -67,9 +67,6 @@ protected function setUp() public function testExtractConfiguration() { $type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface'); - $type->expects($this->any()) - ->method('getName') - ->will($this->returnValue('type_name')); $type->expects($this->any()) ->method('getInnerType') ->will($this->returnValue(new \stdClass())); @@ -91,9 +88,6 @@ public function testExtractConfiguration() public function testExtractConfigurationSortsPassedOptions() { $type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface'); - $type->expects($this->any()) - ->method('getName') - ->will($this->returnValue('type_name')); $type->expects($this->any()) ->method('getInnerType') ->will($this->returnValue(new \stdClass())); @@ -128,9 +122,6 @@ public function testExtractConfigurationSortsPassedOptions() public function testExtractConfigurationSortsResolvedOptions() { $type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface'); - $type->expects($this->any()) - ->method('getName') - ->will($this->returnValue('type_name')); $type->expects($this->any()) ->method('getInnerType') ->will($this->returnValue(new \stdClass())); @@ -162,9 +153,6 @@ public function testExtractConfigurationSortsResolvedOptions() public function testExtractConfigurationBuildsIdRecursively() { $type = $this->getMock('Symfony\Component\Form\ResolvedFormTypeInterface'); - $type->expects($this->any()) - ->method('getName') - ->will($this->returnValue('type_name')); $type->expects($this->any()) ->method('getInnerType') ->will($this->returnValue(new \stdClass())); diff --git a/src/Symfony/Component/Form/Tests/FormFactoryTest.php b/src/Symfony/Component/Form/Tests/FormFactoryTest.php index 3efb18eabb216..22e8884213b0e 100644 --- a/src/Symfony/Component/Form/Tests/FormFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/FormFactoryTest.php @@ -169,144 +169,28 @@ public function testCreateThrowsUnderstandableException() $this->factory->create(new \stdClass()); } - public function testCreateUsesTypeNameIfTypeGivenAsString() + public function testCreateUsesBlockPrefixIfTypeGivenAsString() { $options = array('a' => '1', 'b' => '2'); $resolvedOptions = array('a' => '2', 'b' => '3'); - $resolvedType = $this->getMockResolvedType(); - - $this->registry->expects($this->once()) - ->method('getType') - ->with('TYPE') - ->will($this->returnValue($resolvedType)); - - $resolvedType->expects($this->once()) - ->method('createBuilder') - ->with($this->factory, 'type', $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)); - } - - public function testCreateStripsNamespaceOffTypeName() - { - $options = array('a' => '1', 'b' => '2'); - $resolvedOptions = array('a' => '2', 'b' => '3'); - $resolvedType = $this->getMockResolvedType(); - - $this->registry->expects($this->once()) - ->method('getType') - ->with('Vendor\Name\Space\UserForm') - ->will($this->returnValue($resolvedType)); - - $resolvedType->expects($this->once()) - ->method('createBuilder') - ->with($this->factory, 'user_form', $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('Vendor\Name\Space\UserForm', null, $options)); - } - - public function testCreateStripsTypeSuffixOffTypeName() - { - $options = array('a' => '1', 'b' => '2'); - $resolvedOptions = array('a' => '2', 'b' => '3'); - $resolvedType = $this->getMockResolvedType(); - $this->registry->expects($this->once()) - ->method('getType') - ->with('Vendor\Name\Space\UserType') - ->will($this->returnValue($resolvedType)); - - $resolvedType->expects($this->once()) - ->method('createBuilder') - ->with($this->factory, 'user', $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('Vendor\Name\Space\UserType', null, $options)); - } - - public function testCreateDoesNotStripTypeSuffixIfResultEmpty() - { - $options = array('a' => '1', 'b' => '2'); - $resolvedOptions = array('a' => '2', 'b' => '3'); - $resolvedType = $this->getMockResolvedType(); - - $this->registry->expects($this->once()) - ->method('getType') - ->with('Vendor\Name\Space\Type') - ->will($this->returnValue($resolvedType)); - - $resolvedType->expects($this->once()) - ->method('createBuilder') - ->with($this->factory, 'type', $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('Vendor\Name\Space\Type', null, $options)); - } + // the interface does not have the method, so use the real class + $resolvedType = $this->getMockBuilder('Symfony\Component\Form\ResolvedFormType') + ->disableOriginalConstructor() + ->getMock(); - public function testCreateConvertsTypeToUnderscoreSyntax() - { - $options = array('a' => '1', 'b' => '2'); - $resolvedOptions = array('a' => '2', 'b' => '3'); - $resolvedType = $this->getMockResolvedType(); + $resolvedType->expects($this->any()) + ->method('getBlockPrefix') + ->willReturn('TYPE_PREFIX'); - $this->registry->expects($this->once()) + $this->registry->expects($this->any()) ->method('getType') - ->with('Vendor\Name\Space\MyProfileHTMLType') + ->with('TYPE') ->will($this->returnValue($resolvedType)); $resolvedType->expects($this->once()) ->method('createBuilder') - ->with($this->factory, 'my_profile_html', $options) + ->with($this->factory, 'TYPE_PREFIX', $options) ->will($this->returnValue($this->builder)); $this->builder->expects($this->any()) @@ -321,7 +205,7 @@ public function testCreateConvertsTypeToUnderscoreSyntax() ->method('getForm') ->will($this->returnValue('FORM')); - $this->assertSame('FORM', $this->factory->create('Vendor\Name\Space\MyProfileHTMLType', null, $options)); + $this->assertSame('FORM', $this->factory->create('TYPE', null, $options)); } public function testCreateNamed() diff --git a/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php b/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php index 51cea54356f74..15025e98b5a48 100644 --- a/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/ResolvedFormTypeTest.php @@ -361,7 +361,7 @@ public function provideTypeClassBlockPrefixTuples() */ private function getMockFormType($typeClass = 'Symfony\Component\Form\AbstractType') { - return $this->getMock($typeClass, array('getName', 'getBlockPrefix', 'configureOptions', 'finishView', 'buildView', 'buildForm')); + return $this->getMock($typeClass, array('getBlockPrefix', 'configureOptions', 'finishView', 'buildView', 'buildForm')); } /** diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 345c638372924..36edea5da9832 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -829,19 +829,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 71477432bcec8..6402602956aa7 100644 --- a/src/Symfony/Component/HttpFoundation/ParameterBag.php +++ b/src/Symfony/Component/HttpFoundation/ParameterBag.php @@ -128,13 +128,12 @@ public function remove($key) * * @param string $key The parameter key * @param string $default The default value if the parameter key does not exist - * @param bool $deep If true, a path like foo[bar] will find deeper items * * @return string The filtered value */ - public function getAlpha($key, $default = '', $deep = false) + public function getAlpha($key, $default = '') { - return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default, $deep)); + return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); } /** @@ -142,13 +141,12 @@ public function getAlpha($key, $default = '', $deep = false) * * @param string $key The parameter key * @param string $default The default value if the parameter key does not exist - * @param bool $deep If true, a path like foo[bar] will find deeper items * * @return string The filtered value */ - public function getAlnum($key, $default = '', $deep = false) + public function getAlnum($key, $default = '') { - return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default, $deep)); + return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); } /** @@ -156,14 +154,13 @@ public function getAlnum($key, $default = '', $deep = false) * * @param string $key The parameter key * @param string $default The default value if the parameter key does not exist - * @param bool $deep If true, a path like foo[bar] will find deeper items * * @return string The filtered value */ - public function getDigits($key, $default = '', $deep = false) + public function getDigits($key, $default = '') { // 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)); } /** @@ -171,13 +168,12 @@ public function getDigits($key, $default = '', $deep = false) * * @param string $key The parameter key * @param int $default The default value if the parameter key does not exist - * @param bool $deep If true, a path like foo[bar] will find deeper items * * @return int The filtered value */ - public function getInt($key, $default = 0, $deep = false) + public function getInt($key, $default = 0) { - return (int) $this->get($key, $default, $deep); + return (int) $this->get($key, $default); } /** @@ -185,13 +181,12 @@ public function getInt($key, $default = 0, $deep = false) * * @param string $key The parameter key * @param mixed $default The default value if the parameter key does not exist - * @param bool $deep If true, a path like foo[bar] will find deeper items * * @return bool The filtered value */ - public function getBoolean($key, $default = false, $deep = false) + public function getBoolean($key, $default = false) { - return $this->filter($key, $default, $deep, FILTER_VALIDATE_BOOLEAN); + return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN); } /** @@ -199,7 +194,6 @@ 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. * @@ -207,9 +201,9 @@ public function getBoolean($key, $default = false, $deep = false) * * @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()) { - $value = $this->get($key, $default, $deep); + $value = $this->get($key, $default); // Always turn $options into an array - this allows filter_var option shortcuts. if (!is_array($options) && $options) { diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index 933475f51a50e..780c799391f7a 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/Session/Storage/Proxy/SessionHandlerProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php index 81643c74b4001..c5e97d415bbe2 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -42,13 +42,7 @@ public function __construct(\SessionHandlerInterface $handler) */ public function open($savePath, $sessionName) { - $return = (bool) $this->handler->open($savePath, $sessionName); - - if (true === $return) { - $this->active = true; - } - - return $return; + return (bool) $this->handler->open($savePath, $sessionName); } /** @@ -56,8 +50,6 @@ public function open($savePath, $sessionName) */ public function close() { - $this->active = false; - return (bool) $this->handler->close(); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php index 3d1b7f8e1c3a1..1f724d41fa8b2 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php @@ -137,26 +137,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 8edf3c99f41a6..1601c68ccdede 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 8b9ffde0c1da7..94754bf47b4b0 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,15 +59,15 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '3.0.0-BETA1'; + const VERSION = '3.0.0'; const VERSION_ID = 30000; const MAJOR_VERSION = 3; const MINOR_VERSION = 0; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'BETA1'; + const EXTRA_VERSION = ''; - const END_OF_MAINTENANCE = '05/2018'; - const END_OF_LIFE = '05/2019'; + const END_OF_MAINTENANCE = '07/2016'; + const END_OF_LIFE = '01/2017'; /** * Constructor. @@ -155,10 +154,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 d1fc9db8ecb43..0460898f9ae54 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 cc49931308deb..440415d697476 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..db31cc1b3ce8b 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 (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 47d5e7634a898..93b3c458dc9ff 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 1e764c428f0a4..09856a70b2da2 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -280,8 +280,20 @@ public function start(callable $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); } } @@ -1422,7 +1422,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 f76068195d7f5..a68939b62f602 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); @@ -452,7 +452,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()); @@ -712,7 +712,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); @@ -738,18 +738,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 7e7f4772b0d83..6de9b9d9b4a62 100644 --- a/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php +++ b/src/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php @@ -19,7 +19,25 @@ class PhpExecutableFinderTest extends \PHPUnit_Framework_TestCase { /** - * tests find() with the env var PHP_PATH. + * tests find() with the constant PHP_BINARY. + */ + 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() { @@ -44,6 +62,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 3113366f1adfa..d838870a72f56 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 8c2c83aeb97c0..4860c7701823c 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 3fa59f4f2abf7..107ed1df6fa44 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -23,6 +23,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 deleted file mode 100644 index 665d5f18ca201..0000000000000 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/AbstractVoter.php +++ /dev/null @@ -1,83 +0,0 @@ - - * - * 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; - -/** - * Abstract Voter implementation that reduces boilerplate code required to create a custom Voter. - * - * @author Roman Marintšenko - */ -abstract class AbstractVoter implements VoterInterface -{ - /** - * Iteratively check all given attributes by calling isGranted. - * - * This method terminates as soon as it is able to return ACCESS_GRANTED - * If at least one attribute is supported, but access not granted, then ACCESS_DENIED is returned - * Otherwise it will return ACCESS_ABSTAIN - * - * @param TokenInterface $token A TokenInterface instance - * @param object $object The object to secure - * @param array $attributes An array of attributes associated with the method being invoked - * - * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED - */ - public function vote(TokenInterface $token, $object, array $attributes) - { - if (!$object) { - return self::ACCESS_ABSTAIN; - } - - // 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 voter returns a positive response - return self::ACCESS_GRANTED; - } - } - - return $vote; - } - - /** - * Determines if the attribute and object are supported by this voter. - * - * @param string $attribute An attribute - * @param string $object The object to secure - * - * @return bool True if the attribute and object is supported, false otherwise - */ - abstract protected function supports($attribute, $class); - - /** - * 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. - * - * @param string $attribute - * @param object $object - * @param TokenInterface $token - * - * @return bool - */ - abstract protected function voteOnAttribute($attribute, $object, TokenInterface $token); -} diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/AuthenticatedVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/AuthenticatedVoter.php index 762e9bc50d2bd..dc1407b9435db 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/AuthenticatedVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/AuthenticatedVoter.php @@ -44,7 +44,7 @@ public function __construct(AuthenticationTrustResolverInterface $authentication /** * {@inheritdoc} */ - public function vote(TokenInterface $token, $object, array $attributes) + public function vote(TokenInterface $token, $subject, array $attributes) { $result = VoterInterface::ACCESS_ABSTAIN; foreach ($attributes as $attribute) { diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php index 084285624be38..5fd8b83cf3077 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php @@ -52,7 +52,7 @@ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterfac /** * {@inheritdoc} */ - public function vote(TokenInterface $token, $object, array $attributes) + public function vote(TokenInterface $token, $subject, array $attributes) { $result = VoterInterface::ACCESS_ABSTAIN; $variables = null; @@ -62,7 +62,7 @@ public function vote(TokenInterface $token, $object, array $attributes) } if (null === $variables) { - $variables = $this->getVariables($token, $object); + $variables = $this->getVariables($token, $subject); } $result = VoterInterface::ACCESS_DENIED; @@ -74,7 +74,7 @@ public function vote(TokenInterface $token, $object, array $attributes) return $result; } - private function getVariables(TokenInterface $token, $object) + private function getVariables(TokenInterface $token, $subject) { if (null !== $this->roleHierarchy) { $roles = $this->roleHierarchy->getReachableRoles($token->getRoles()); @@ -85,7 +85,8 @@ private function getVariables(TokenInterface $token, $object) $variables = array( 'token' => $token, 'user' => $token->getUser(), - 'object' => $object, + 'object' => $subject, + 'subject' => $subject, 'roles' => array_map(function ($role) { return $role->getRole(); }, $roles), 'trust_resolver' => $this->trustResolver, ); @@ -93,8 +94,8 @@ private function getVariables(TokenInterface $token, $object) // this is mainly to propose a better experience when the expression is used // in an access control rule, as the developer does not know that it's going // to be handled by this voter - if ($object instanceof Request) { - $variables['request'] = $object; + if ($subject instanceof Request) { + $variables['request'] = $subject; } return $variables; diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/RoleVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/RoleVoter.php index 74e2363ed283e..b017c81334a5d 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/RoleVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/RoleVoter.php @@ -35,7 +35,7 @@ public function __construct($prefix = 'ROLE_') /** * {@inheritdoc} */ - public function vote(TokenInterface $token, $object, array $attributes) + public function vote(TokenInterface $token, $subject, array $attributes) { $result = VoterInterface::ACCESS_ABSTAIN; $roles = $this->extractRoles($token); 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..ba4d6af5a8b56 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php @@ -0,0 +1,69 @@ + + * + * 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 vote(TokenInterface $token, $subject, 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, $subject)) { + continue; + } + + // as soon as at least one attribute is supported, default is to deny access + $vote = self::ACCESS_DENIED; + + if ($this->voteOnAttribute($attribute, $subject, $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/Authorization/Voter/VoterInterface.php b/src/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.php index 1697eaf74afc1..4bb73672c069d 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/VoterInterface.php @@ -31,10 +31,10 @@ interface VoterInterface * ACCESS_GRANTED, ACCESS_DENIED, or ACCESS_ABSTAIN. * * @param TokenInterface $token A TokenInterface instance - * @param object|null $object The object to secure + * @param mixed $subject The subject to secure * @param array $attributes An array of attributes associated with the method being invoked * * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED */ - public function vote(TokenInterface $token, $object, array $attributes); + public function vote(TokenInterface $token, $subject, array $attributes); } 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/RoleHierarchyVoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php index c50ecf38c587d..4b03bacd784a5 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php @@ -33,4 +33,19 @@ public function getVoteTests() array(array('ROLE_FOO'), array('ROLE_FOOBAR'), VoterInterface::ACCESS_GRANTED), )); } + + /** + * @dataProvider getVoteWithEmptyHierarchyTests + */ + public function testVoteWithEmptyHierarchy($roles, $attributes, $expected) + { + $voter = new RoleHierarchyVoter(new RoleHierarchy(array())); + + $this->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/AbstractVoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php similarity index 91% rename from src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php rename to src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php index 537dc4c161971..4bac44d981893 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/AbstractVoterTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php @@ -12,10 +12,10 @@ 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\Voter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; -class AbstractVoterTest extends \PHPUnit_Framework_TestCase +class VoterTest extends \PHPUnit_Framework_TestCase { protected $token; @@ -50,13 +50,13 @@ public function getTests() */ public function testVote(array $attributes, $expectedVote, $object, $message) { - $voter = new AbstractVoterTest_Voter(); + $voter = new VoterTest_Voter(); $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); } } -class AbstractVoterTest_Voter extends AbstractVoter +class VoterTest_Voter extends Voter { protected function voteOnAttribute($attribute, $object, TokenInterface $token) { 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/EntryPoint/DigestAuthenticationEntryPoint.php b/src/Symfony/Component/Security/Http/EntryPoint/DigestAuthenticationEntryPoint.php index cdb98ebb83e7b..9dfd5929459fb 100644 --- a/src/Symfony/Component/Security/Http/EntryPoint/DigestAuthenticationEntryPoint.php +++ b/src/Symfony/Component/Security/Http/EntryPoint/DigestAuthenticationEntryPoint.php @@ -64,16 +64,6 @@ public function start(Request $request, AuthenticationException $authException = return $response; } - /** - * @deprecated Since version 2.8, to be removed in 3.0. Use getSecret() instead. - */ - public function getKey() - { - @trigger_error(__method__.'() is deprecated since version 2.8 and will be removed in 3.0. Use getSecret() instead.', E_USER_DEPRECATED); - - return $this->getSecret(); - } - /** * @return string */ diff --git a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php index 15b71efd4d8bb..ef723ea070c34 100644 --- a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php @@ -78,7 +78,7 @@ public function handle(GetResponseEvent $event) } try { - $digestAuth->validateAndDecode($this->authenticationEntryPoint->getKey(), $this->authenticationEntryPoint->getRealmName()); + $digestAuth->validateAndDecode($this->authenticationEntryPoint->getSecret(), $this->authenticationEntryPoint->getRealmName()); } catch (BadCredentialsException $e) { $this->fail($event, $request, $e); @@ -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..47583bebf56e5 100644 --- a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php @@ -11,13 +11,10 @@ namespace Symfony\Component\Security\Http\Firewall; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\Security\Core\Exception\LogoutException; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; @@ -49,19 +46,13 @@ class LogoutListener implements ListenerInterface * @param array $options An array of options to process a logout attempt * @param CsrfTokenManagerInterface $csrfTokenManager A CsrfTokenManagerInterface instance */ - public function __construct(TokenStorageInterface $tokenStorage, HttpUtils $httpUtils, LogoutSuccessHandlerInterface $successHandler, array $options = array(), $csrfTokenManager = null) + public function __construct(TokenStorageInterface $tokenStorage, HttpUtils $httpUtils, LogoutSuccessHandlerInterface $successHandler, array $options = array(), CsrfTokenManagerInterface $csrfTokenManager = null) { - if ($csrfTokenManager instanceof CsrfProviderInterface) { - $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); - } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { - throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); - } - $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 +92,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 36f7bb50578d1..76c66bcf249ae 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php @@ -12,10 +12,7 @@ namespace Symfony\Component\Security\Http\Firewall; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; @@ -56,20 +53,13 @@ class SimpleFormAuthenticationListener extends AbstractAuthenticationListener * @param SimpleFormAuthenticatorInterface $simpleAuthenticator A SimpleFormAuthenticatorInterface instance * * @throws \InvalidArgumentException In case no simple authenticator is provided - * @throws InvalidArgumentException In case an invalid CSRF token manager is passed */ - public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, $csrfTokenManager = null, SimpleFormAuthenticatorInterface $simpleAuthenticator = null) + public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenManagerInterface $csrfTokenManager = null, SimpleFormAuthenticatorInterface $simpleAuthenticator = null) { if (!$simpleAuthenticator) { throw new \InvalidArgumentException('Missing simple authenticator'); } - if ($csrfTokenManager instanceof CsrfProviderInterface) { - $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); - } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { - throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); - } - $this->simpleAuthenticator = $simpleAuthenticator; $this->csrfTokenManager = $csrfTokenManager; @@ -77,7 +67,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 +94,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..c8195ce583cfd 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Security\Http\Firewall; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\HttpFoundation\Request; use Psr\Log\LoggerInterface; use Symfony\Component\Security\Csrf\CsrfToken; @@ -25,7 +23,6 @@ use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Core\Security; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -40,19 +37,13 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL { private $csrfTokenManager; - public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, $csrfTokenManager = null) + public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenManagerInterface $csrfTokenManager = null) { - if ($csrfTokenManager instanceof CsrfProviderInterface) { - $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); - } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { - throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); - } - 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 +70,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/Logout/LogoutUrlGenerator.php b/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php index ff7ea5b775e91..991b1fcc818e2 100644 --- a/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php +++ b/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Security\Http\Logout; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -47,14 +45,8 @@ public function __construct(RequestStack $requestStack = null, UrlGeneratorInter * @param string $csrfParameter The CSRF token parameter name * @param CsrfTokenManagerInterface $csrfTokenManager A CsrfTokenManagerInterface instance */ - public function registerListener($key, $logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager = null) + public function registerListener($key, $logoutPath, $csrfTokenId, $csrfParameter, CsrfTokenManagerInterface $csrfTokenManager = null) { - if ($csrfTokenManager instanceof CsrfProviderInterface) { - $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); - } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { - throw new \InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); - } - $this->listeners[$key] = array($logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager); } diff --git a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php index a8c086c2e0e19..0a19d704be404 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php @@ -71,7 +71,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 6bc2341feb320..e9ca7ce570e59 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php @@ -88,7 +88,7 @@ public function decode($data, $format, array $context = array()) $decodedData = json_decode($data, $associative, $recursionDepth, $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 $decodedData; diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php index 38b448ed85485..14cd2c949a991 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php @@ -40,7 +40,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 d1d3c547a25c5..2a16d69f906ba 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; /** @@ -211,6 +211,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())); @@ -428,6 +440,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 @@ -577,3 +594,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 7ca2a8146bf29..ef2be7097718b 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 480db47ba2950..82ca31d962095 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php @@ -113,7 +113,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 6e775150ae266..fe77de73d4645 100644 --- a/src/Symfony/Component/Yaml/CHANGELOG.md +++ b/src/Symfony/Component/Yaml/CHANGELOG.md @@ -10,9 +10,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 841b5ea35edd0..a80e0a6d330fb 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -345,7 +345,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); @@ -371,7 +371,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.'$~'; @@ -384,7 +384,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; } @@ -693,7 +693,7 @@ private function isNextLineUnIndentedCollection() if ( $this->getCurrentLineIndentation() == $currentIndentation && - $this->isStringUnIndentedCollectionItem($this->currentLine) + $this->isStringUnIndentedCollectionItem() ) { $ret = true; } 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