diff --git a/.travis.yml b/.travis.yml index 18d4646a95664..ef1ef5c7987f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,9 @@ addons: - parallel - language-pack-fr-base -cache: - directories: - - .phpunit +env: + global: + - MIN_PHP=5.3.9 matrix: include: @@ -19,43 +19,48 @@ matrix: - php: 5.5 - php: 5.6 env: deps=high - - php: 7 + - php: 7.0 env: deps=low fast_finish: true -services: mongodb +cache: + directories: + - .phpunit + - php-$MIN_PHP -env: - global: - - deps=no - - SYMFONY_DEPRECATIONS_HELPER=weak +services: mongodb before_install: - - if [[ "$deps" = "no" ]] && [[ "$TRAVIS_PHP_VERSION" =~ 5.[45] ]] && [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then export deps=skip; fi; - - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi; - - 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" = 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 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 php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi; - - if [ "$deps" != "skip" ]; then ./phpunit install; fi; - - export PHPUNIT="$(readlink -f ./phpunit)" + - if [[ ! $deps && ! $TRAVIS_PHP_VERSION = ${MIN_PHP%.*} && $TRAVIS_PHP_VERSION != hhvm && $TRAVIS_PULL_REQUEST != false ]]; then deps=skip; fi; + - if [[ ! $deps && ! -d php-$MIN_PHP/sapi ]]; then wget http://museum.php.net/php5/php-$MIN_PHP.tar.bz2 -O - | tar -xj; (cd php-$MIN_PHP; ./configure --enable-sigchild --enable-pcntl; make -j2); fi; + - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi; + - echo memory_limit = -1 >> $INI_FILE + - echo session.gc_probability = 0 >> $INI_FILE + - 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.10 && 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 ]]; 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; + - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then phpenv config-rm xdebug.ini; fi; + - if [[ $deps != skip ]]; then composer self-update; fi; + - if [[ $deps != skip ]]; then ./phpunit install; fi; + - export PHPUNIT=$(readlink -f ./phpunit) install: - - if [ "$TRAVIS_BRANCH" = "master" ]; then export COMPOSER_ROOT_VERSION=dev-master; else export COMPOSER_ROOT_VERSION="$TRAVIS_BRANCH".x-dev; fi; - - if [ "$deps" = "no" ]; then export SYMFONY_DEPRECATIONS_HELPER=strict; fi; - - if [ "$deps" = "no" ]; then composer --prefer-source install; fi; - - if [ "$deps" != "skip" ]; then COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi; - - if [ "$deps" != "skip" ] && [ "$deps" != "no" ]; then php .travis.php $TRAVIS_COMMIT_RANGE $TRAVIS_BRANCH $COMPONENTS; fi; + - if [[ $deps != skip ]]; then COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi; + - if [[ $deps != skip && $deps ]]; then php .travis.php $TRAVIS_COMMIT_RANGE $TRAVIS_BRANCH $COMPONENTS; fi; + - if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then SYMFONY_VERSION=$(git ls-remote --heads | grep -o '/[1-9].*' | tail -n 1 | sed s/.//); else SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*'); fi; + - if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then git fetch origin $SYMFONY_VERSION; git checkout -m FETCH_HEAD; fi; + - if [[ $deps = high && ${SYMFONY_VERSION%.*} != $(git show $(git ls-remote --heads | grep -FA1 /$SYMFONY_VERSION | tail -n 1):composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9]*' | head -n 1) ]]; then LEGACY=,legacy; fi; + - export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev; + - if [[ ! $deps ]]; then composer --prefer-source install; else export SYMFONY_DEPRECATIONS_HELPER=weak; fi; + - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi; script: - - if [ "$deps" = "no" ]; then echo "$COMPONENTS" | parallel --gnu '$PHPUNIT --exclude-group tty,benchmark,intl-data {}'; fi; - - if [ "$deps" = "no" ]; then echo -e "\\nRunning tests requiring tty"; $PHPUNIT --group tty; fi; - - if [ "$deps" = "high" ]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer --prefer-source update; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi; - - if [ "$deps" = "low" ]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer --prefer-source --prefer-lowest --prefer-stable update; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi; - - if [ "$deps" = "skip" ]; then echo 'This matrix line is skipped for pull requests.'; fi; + - if [[ ! $deps ]]; then echo "$COMPONENTS" | parallel --gnu '$PHPUNIT --exclude-group tty,benchmark,intl-data {}'; fi; + - if [[ ! $deps ]]; then echo -e "\\nRunning tests requiring tty"; $PHPUNIT --group tty; fi; + - if [[ ! $deps && $TRAVIS_PHP_VERSION = ${MIN_PHP%.*} ]]; then echo -e "1\\n0" | parallel --gnu 'echo -e "\\nPHP --enable-sigchild enhanced={}" && ENHANCE_SIGCHLD={} php-$MIN_PHP/sapi/cli/php .phpunit/phpunit-4.8/phpunit --colors=always src/Symfony/Component/Process/'; fi; + - if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer --prefer-source update; $PHPUNIT --exclude-group tty,benchmark,intl-data'$LEGACY; fi; + - if [[ $deps = low ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer --prefer-source --prefer-lowest --prefer-stable update; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi; + - if [[ $deps = skip ]]; then echo This matrix line is skipped for pull requests.; fi; diff --git a/CHANGELOG-2.3.md b/CHANGELOG-2.3.md index 9c9d593aa08c7..4974aa08b3a2c 100644 --- a/CHANGELOG-2.3.md +++ b/CHANGELOG-2.3.md @@ -7,6 +7,25 @@ in 2.3 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.3.0...v2.3.1 +* 2.3.35 (2015-11-23) + + * security #16631 CVE-2015-8124: Session Fixation in the "Remember Me" Login Feature (xabbuh) + * security #16630 CVE-2015-8125: Potential Remote Timing Attack Vulnerability in Security Remember-Me Service (xabbuh) + * bug #16588 Sent out a status text for unknown HTTP headers. (dawehner) + * bug #16295 [DependencyInjection] Unescape parameters for all types of injection (Nicofuma) + * bug #16574 [Process] Fix PhpProcess with phpdbg runtime (nicolas-grekas) + * bug #16352 Fix the server variables in the router_*.php files (leofeyer) + * bug #16537 [Validator] Allow an empty path with a non empty fragment or a query (jakzal) + * bug #16528 [Translation] Add support for Armenian pluralization. (marcosdsanchez) + * bug #16510 [Process] fix Proccess run with pts enabled (ewgRa) + * bug #16292 fix race condition at mkdir (#16258) (ewgRa) + * bug #16462 [PropertyAccess] Fix dynamic property accessing. (dunglas) + * bug #16294 [PropertyAccess] Major performance improvement (dunglas) + * bug #16331 fixed Twig deprecation notices (fabpot) + * bug #16306 [DoctrineBridge] Fix issue which prevent the profiler to explain a query (Baachi) + * bug #16359 Use mb_detect_encoding with $strict = true (nicolas-grekas) + * bug #16144 [Security] don't allow to install the split Security packages (xabbuh) + * 2.3.34 (2015-10-27) * bug #16288 [Process] Inherit env vars by default in PhpProcess (nicolas-grekas) diff --git a/CHANGELOG-2.7.md b/CHANGELOG-2.7.md index 19e9036c82bb2..0055a236352aa 100644 --- a/CHANGELOG-2.7.md +++ b/CHANGELOG-2.7.md @@ -7,6 +7,50 @@ in 2.7 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.7.0...v2.7.1 +* 2.7.8 (2015-12-26) + + * bug #16864 [Yaml] fix indented line handling in folded blocks (xabbuh) + * bug #17052 Fixed flatten exception recursion with errors (GrahamCampbell) + * bug #16826 Embedded identifier support (mihai-stancu) + * bug #17079 Also transform inline mappings to objects (WouterJ) + * bug #17129 [Config] Fix array sort on normalization in edge case (romainneutron) + * bug #17094 [Process] More robustness and deterministic tests (nicolas-grekas) + * bug #17112 [PropertyAccess] Reorder elements array after PropertyPathBuilder::replace (alekitto) + * bug #16797 [Filesystem] Recursivly widen non-executable directories (Slamdunk) + * bug #17040 [Console] Avoid extra blank lines when rendering exceptions (ogizanagi) + * bug #17055 [Security] Verify if a password encoded with bcrypt is no longer than 72 characters (jakzal) + * bug #16959 [Form] fix #15544 when a collection type attribute "required" is false, "prototype" should too (HeahDude) + * bug #16860 [Yaml] do not remove "comments" in scalar blocks (xabbuh) + * bug #17002 [Console][Table] fixed render row that contains multiple cells. (aitboudad) + * bug #16971 [HttpFoundation] Added the ability of using BinaryFileResponse with stream wrappers (jakzal, Sander-Toonen) + * bug #17048 Fix the logout path when not using the router (stof) + * bug #17049 Fix the logout path when not using the router (stof) + * bug #17057 [FrameworkBundle][HttpKernel] the finder is required to discover bundle commands (xabbuh) + * bug #17006 [Form] Fix casting regression in DoctrineChoiceLoader (bendavies) + * bug #16915 [Process] Enhance compatiblity with --enable-sigchild (nicolas-grekas) + * bug #16829 [FrameworkBundle] prevent cache:clear creating too long paths (Tobion) + * bug #16921 Fix short array syntax for php 5.3 (ewgRa) + * bug #16450 [Serializer] Fixed `array_unique` on array of objects in `getAllowedAttributes`. (CornyPhoenix) + * bug #16757 [FrameworkBundle] [Translation] Fixed translations not written when no translations directory in update command (jeremyFreeAgent) + * bug #16871 [FrameworkBundle] Disable built-in server commands when Process component is missing (gnugat, xabbuh) + * bug #16870 [FrameworkBundle] Disable the server:run command when Process component is missing (gnugat, xabbuh) + * bug #16742 [Console][ProgressBar] redrawFrequency should never be 0 (dritter) + * bug #16799 Improve error message for undefined DIC aliases (mpdude) + * bug #16825 [VarDumper] fix .sf-dump z-index (debug bar conflict) (Antoine LA) + * bug #16772 Refactoring EntityUserProvider::__construct() to not do work, cause cache warm error (weaverryan) + * bug #16753 [Process] Fix signaling/stopping logic on Windows (nicolas-grekas) + * bug #16733 [Console] do not encode backslashes in console default description (Tobion) + * bug #16312 [HttpKernel] clearstatcache() so the Cache sees when a .lck file has been released (mpdude) + * bug #16351 [WIP] [Form] [TwigBridge] Bootstrap horizontal theme missing tests (pieter2627) + * bug #16685 [Form] Fixed: Duplicate choice labels are remembered when using "choices_as_values" = false (webmozart) + * bug #16705 [Form] Deprecated setting "choices_as_values" to "false" (webmozart) + * bug #16695 [SecurityBundle] disable the init:acl command if ACL is not used (Tobion) + * bug #16679 [Form] Disabled view data validation if "data_class" is set to null (webmozart) + * bug #16676 [HttpFoundation] Workaround HHVM rewriting HTTP response line (nicolas-grekas) + * bug #16668 [ClassLoader] Fix parsing namespace when token_get_all() is missing (nicolas-grekas) + * bug #16386 Bug #16343 [Router] Too many Routes ? (jelte) + * bug #16651 [Debug] Ensure class declarations are loaded only once (nicolas-grekas) + * 2.7.7 (2015-11-23) * security #16631 CVE-2015-8124: Session Fixation in the "Remember Me" Login Feature (xabbuh) 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/appveyor.yml b/appveyor.yml index ce46c6ffb6f82..8ff51be4edd11 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,8 +26,8 @@ install: - IF %PHP%==1 cd ext - IF %PHP%==1 appveyor DownloadFile http://nebm.ist.utl.pt/~glopes/misc/intl_win/php_intl-3.0.0-5.3-nts-vc9-x86.zip - IF %PHP%==1 7z x php_intl-3.0.0-5.3-nts-vc9-x86.zip -y >nul - - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/apcu/4.0.7/php_apcu-4.0.7-5.3-nts-vc9-x86.zip - - IF %PHP%==1 7z x php_apcu-4.0.7-5.3-nts-vc9-x86.zip -y >nul + - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/apcu/4.0.10/php_apcu-4.0.10-5.3-nts-vc9-x86.zip + - IF %PHP%==1 7z x php_apcu-4.0.10-5.3-nts-vc9-x86.zip -y >nul - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/memcache/3.0.8/php_memcache-3.0.8-5.3-nts-vc9-x86.zip - IF %PHP%==1 7z x php_memcache-3.0.8-5.3-nts-vc9-x86.zip -y >nul - IF %PHP%==1 del /Q *.zip @@ -55,9 +55,10 @@ install: test_script: - cd c:\projects\symfony + - Setlocal EnableDelayedExpansion - SET X=0 - copy /Y c:\php\php.ini-min c:\php\php.ini - - php phpunit symfony --exclude-group benchmark,intl-data || SET X=1 + - php phpunit symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! - copy /Y c:\php\php.ini-max c:\php\php.ini - - php phpunit symfony --exclude-group benchmark,intl-data || SET X=1 + - php phpunit symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! - exit %X% diff --git a/phpunit b/phpunit index 2ab4f25e75cc2..146ab9d37610c 100755 --- a/phpunit +++ b/phpunit @@ -11,15 +11,15 @@ */ // Please update when phpunit needs to be reinstalled with fresh deps: -// Cache-Id-Version: 2015-11-18 14:14 UTC +// Cache-Id-Version: 2015-11-28 09:05 UTC use Symfony\Component\Process\ProcessUtils; error_reporting(-1); require __DIR__.'/src/Symfony/Component/Process/ProcessUtils.php'; -// PHPUnit 4.8 does not support PHP 7, while 5.0 requires PHP 5.6+ -$PHPUNIT_VERSION = PHP_VERSION_ID >= 70000 ? '5.0' : '4.8'; +// PHPUnit 4.8 does not support PHP 7, while 5.1 requires PHP 5.6+ +$PHPUNIT_VERSION = PHP_VERSION_ID >= 50600 ? '5.1' : '4.8'; $PHPUNIT_DIR = __DIR__.'/.phpunit'; $PHP = defined('PHP_BINARY') ? PHP_BINARY : 'php'; $PHP = ProcessUtils::escapeArgument($PHP); @@ -64,10 +64,6 @@ Symfony\Bridge\PhpUnit\TextUI\Command::main(); EOPHP ); chdir('..'); - if (file_exists('../src/Symfony/Bridge/PhpUnit') && `git diff --name-only HEAD^ -- ../src/Symfony/Bridge/PhpUnit`) { - passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? '(del /S /F /Q %s & rmdir %1$s) >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(".$PHPUNIT_VERSION.md5", md5_file(__FILE__)); chdir($oldPwd); @@ -164,8 +160,9 @@ if (isset($argv[1]) && 'symfony' === $argv[1]) { unlink($file); } - if ($procStatus) { - $exit = 1; + // 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 = $procStatus; echo "\033[41mKO\033[0m $component\n\n"; } else { echo "\033[32mOK\033[0m $component\n\n"; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b22aede91d94e..0538884cc43f5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -47,4 +47,14 @@ + + + + + + Symfony\Component\HttpFoundation + + + + diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php index 2335af713128b..1a09609b28556 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php @@ -156,7 +156,7 @@ public function loadChoicesForValues(array $values, $value = null) // "INDEX BY" clause to the Doctrine query in the loader, // but I'm not sure whether that's doable in a generic fashion. foreach ($unorderedObjects as $object) { - $objectsById[$this->idReader->getIdValue($object)] = $object; + $objectsById[(string) $this->idReader->getIdValue($object)] = $object; } foreach ($values as $i => $id) { diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index fe0ad19367103..dae4bb0919ad3 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -89,9 +89,10 @@ public function getEntities() */ public function getEntitiesByIds($identifier, array $values) { - $qb = clone ($this->queryBuilder); + $qb = clone $this->queryBuilder; $alias = current($qb->getRootAliases()); $parameter = 'ORMQueryBuilderLoader_getEntitiesByIds_'.$identifier; + $parameter = str_replace('.', '_', $parameter); $where = $qb->expr()->in($alias.'.'.$identifier, ':'.$parameter); // Guess type diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index 562a2952daf05..b2216b7ae31df 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -92,12 +92,16 @@ public function updateToken($series, $tokenValue, \DateTime $lastUsed) { $sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed' .' WHERE series=:series'; - $paramValues = array('value' => $tokenValue, - 'lastUsed' => $lastUsed, - 'series' => $series,); - $paramTypes = array('value' => \PDO::PARAM_STR, - 'lastUsed' => DoctrineType::DATETIME, - 'series' => \PDO::PARAM_STR,); + $paramValues = array( + 'value' => $tokenValue, + 'lastUsed' => $lastUsed, + 'series' => $series, + ); + $paramTypes = array( + 'value' => \PDO::PARAM_STR, + 'lastUsed' => DoctrineType::DATETIME, + 'series' => \PDO::PARAM_STR, + ); $updated = $this->conn->executeUpdate($sql, $paramValues, $paramTypes); if ($updated < 1) { throw new TokenNotFoundException('No token found.'); @@ -112,16 +116,20 @@ public function createNewToken(PersistentTokenInterface $token) $sql = 'INSERT INTO rememberme_token' .' (class, username, series, value, lastUsed)' .' VALUES (:class, :username, :series, :value, :lastUsed)'; - $paramValues = array('class' => $token->getClass(), - 'username' => $token->getUsername(), - 'series' => $token->getSeries(), - 'value' => $token->getTokenValue(), - 'lastUsed' => $token->getLastUsed(),); - $paramTypes = array('class' => \PDO::PARAM_STR, - 'username' => \PDO::PARAM_STR, - 'series' => \PDO::PARAM_STR, - 'value' => \PDO::PARAM_STR, - 'lastUsed' => DoctrineType::DATETIME,); + $paramValues = array( + 'class' => $token->getClass(), + 'username' => $token->getUsername(), + 'series' => $token->getSeries(), + 'value' => $token->getTokenValue(), + 'lastUsed' => $token->getLastUsed(), + ); + $paramTypes = array( + 'class' => \PDO::PARAM_STR, + 'username' => \PDO::PARAM_STR, + 'series' => \PDO::PARAM_STR, + 'value' => \PDO::PARAM_STR, + 'lastUsed' => DoctrineType::DATETIME, + ); $this->conn->executeUpdate($sql, $paramValues, $paramTypes); } } diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index b34b9bdec4a67..cb8a59458324d 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -27,22 +27,17 @@ */ class EntityUserProvider implements UserProviderInterface { + private $registry; + private $managerName; + private $classOrAlias; private $class; - private $repository; private $property; - private $metadata; - public function __construct(ManagerRegistry $registry, $class, $property = null, $managerName = null) + public function __construct(ManagerRegistry $registry, $classOrAlias, $property = null, $managerName = null) { - $em = $registry->getManager($managerName); - $this->class = $class; - $this->metadata = $em->getClassMetadata($class); - - if (false !== strpos($this->class, ':')) { - $this->class = $this->metadata->getName(); - } - - $this->repository = $em->getRepository($class); + $this->registry = $registry; + $this->managerName = $managerName; + $this->classOrAlias = $classOrAlias; $this->property = $property; } @@ -51,14 +46,15 @@ public function __construct(ManagerRegistry $registry, $class, $property = null, */ public function loadUserByUsername($username) { + $repository = $this->getRepository(); if (null !== $this->property) { - $user = $this->repository->findOneBy(array($this->property => $username)); + $user = $repository->findOneBy(array($this->property => $username)); } else { - if (!$this->repository instanceof UserProviderInterface) { - throw new \InvalidArgumentException(sprintf('The Doctrine repository "%s" must implement UserProviderInterface.', get_class($this->repository))); + if (!$repository instanceof UserProviderInterface) { + throw new \InvalidArgumentException(sprintf('The Doctrine repository "%s" must implement UserProviderInterface.', get_class($repository))); } - $user = $this->repository->loadUserByUsername($username); + $user = $repository->loadUserByUsername($username); } if (null === $user) { @@ -73,18 +69,20 @@ public function loadUserByUsername($username) */ public function refreshUser(UserInterface $user) { - if (!$user instanceof $this->class) { + $class = $this->getClass(); + if (!$user instanceof $class) { throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); } - if ($this->repository instanceof UserProviderInterface) { - $refreshedUser = $this->repository->refreshUser($user); + $repository = $this->getRepository(); + if ($repository instanceof UserProviderInterface) { + $refreshedUser = $repository->refreshUser($user); } else { // The user must be reloaded via the primary key as all other data // might have changed without proper persistence in the database. // That's the case when the user has been changed by a form with // validation errors. - if (!$id = $this->metadata->getIdentifierValues($user)) { + if (!$id = $this->getClassMetadata()->getIdentifierValues($user)) { throw new \InvalidArgumentException('You cannot refresh a user '. 'from the EntityUserProvider that does not contain an identifier. '. 'The user object has to be serialized with its own identifier '. @@ -92,7 +90,7 @@ public function refreshUser(UserInterface $user) ); } - $refreshedUser = $this->repository->find($id); + $refreshedUser = $repository->find($id); if (null === $refreshedUser) { throw new UsernameNotFoundException(sprintf('User with id %s not found', json_encode($id))); } @@ -106,6 +104,36 @@ public function refreshUser(UserInterface $user) */ public function supportsClass($class) { - return $class === $this->class || is_subclass_of($class, $this->class); + return $class === $this->getClass() || is_subclass_of($class, $this->getClass()); + } + + private function getObjectManager() + { + return $this->registry->getManager($this->managerName); + } + + private function getRepository() + { + return $this->getObjectManager()->getRepository($this->classOrAlias); + } + + private function getClass() + { + if (null === $this->class) { + $class = $this->classOrAlias; + + if (false !== strpos($class, ':')) { + $class = $this->getClassMetadata()->getName(); + } + + $this->class = $class; + } + + return $this->class; + } + + private function getClassMetadata() + { + return $this->getObjectManager()->getClassMetadata($this->classOrAlias); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php new file mode 100644 index 0000000000000..f8000dbfd9814 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures\Embeddable; + +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Embeddable + */ +class Identifier +{ + /** + * @var int + * + * @ORM\Id + * @ORM\Column(type="integer") + */ + protected $value; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php new file mode 100644 index 0000000000000..6d7b2670962c7 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Entity + */ +class EmbeddedIdentifierEntity +{ + /** + * @var Embeddable\Identifier + * + * @ORM\Embedded(class="Symfony\Bridge\Doctrine\Tests\Fixtures\Embeddable\Identifier") + */ + protected $id; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php new file mode 100644 index 0000000000000..e457f69dd091b --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\GeneratedValue; +use Doctrine\ORM\Mapping\Id; + +/** @Entity */ +class SingleStringCastableIdEntity +{ + /** + * @Id + * @Column(type="string") + * @GeneratedValue(strategy="NONE") + */ + protected $id; + + /** @Column(type="string", nullable=true) */ + public $name; + + public function __construct($id, $name) + { + $this->id = new StringCastableObjectIdentity($id); + $this->name = $name; + } + + public function __toString() + { + return (string) $this->name; + } +} + +class StringCastableObjectIdentity +{ + protected $id; + + public function __construct($id) + { + $this->id = $id; + } + + public function __toString() + { + return (string) $this->id; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php index 5af59445e1825..d755af5430bc4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php @@ -14,6 +14,7 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; use Doctrine\DBAL\Connection; +use Doctrine\ORM\Version; class ORMQueryBuilderLoaderTest extends \PHPUnit_Framework_TestCase { @@ -104,4 +105,38 @@ public function testFilterNonIntegerValues() $loader = new ORMQueryBuilderLoader($qb); $loader->getEntitiesByIds('id', array(1, '', 2, 3, 'foo')); } + + public function testEmbeddedIdentifierName() + { + if (Version::compare('2.5.0') > 0) { + $this->markTestSkipped('Applicable only for Doctrine >= 2.5.0'); + + return; + } + + $em = DoctrineTestHelper::createTestEntityManager(); + + $query = $this->getMockBuilder('QueryMock') + ->setMethods(array('setParameter', 'getResult', 'getSql', '_doExecute')) + ->getMock(); + + $query->expects($this->once()) + ->method('setParameter') + ->with('ORMQueryBuilderLoader_getEntitiesByIds_id_value', array(1, 2, 3), Connection::PARAM_INT_ARRAY) + ->willReturn($query); + + $qb = $this->getMockBuilder('Doctrine\ORM\QueryBuilder') + ->setConstructorArgs(array($em)) + ->setMethods(array('getQuery')) + ->getMock(); + $qb->expects($this->once()) + ->method('getQuery') + ->willReturn($query); + + $qb->select('e') + ->from('Symfony\Bridge\Doctrine\Tests\Fixtures\EmbeddedIdentifierEntity', 'e'); + + $loader = new ORMQueryBuilderLoader($qb); + $loader->getEntitiesByIds('id.value', array(1, '', 2, 3, 'foo')); + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 78f10848bccbf..ce37d261c06e3 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -24,6 +24,7 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringCastableIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceView; @@ -40,6 +41,7 @@ class EntityTypeTest extends TypeTestCase const SINGLE_IDENT_NO_TO_STRING_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity'; const SINGLE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity'; const SINGLE_ASSOC_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity'; + const SINGLE_STRING_CASTABLE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringCastableIdEntity'; const COMPOSITE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity'; const COMPOSITE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity'; @@ -67,6 +69,7 @@ protected function setUp() $this->em->getClassMetadata(self::SINGLE_IDENT_NO_TO_STRING_CLASS), $this->em->getClassMetadata(self::SINGLE_STRING_IDENT_CLASS), $this->em->getClassMetadata(self::SINGLE_ASSOC_IDENT_CLASS), + $this->em->getClassMetadata(self::SINGLE_STRING_CASTABLE_IDENT_CLASS), $this->em->getClassMetadata(self::COMPOSITE_IDENT_CLASS), $this->em->getClassMetadata(self::COMPOSITE_STRING_IDENT_CLASS), ); @@ -580,6 +583,139 @@ public function testSubmitMultipleExpandedWithNegativeIntegerId() $this->assertFalse($field['2']->getData()); } + public function testSubmitSingleNonExpandedStringCastableIdentifier() + { + $entity1 = new SingleStringCastableIdEntity(1, 'Foo'); + $entity2 = new SingleStringCastableIdEntity(2, 'Bar'); + + $this->persist(array($entity1, $entity2)); + + $field = $this->factory->createNamed('name', 'entity', null, array( + 'multiple' => false, + 'expanded' => false, + 'em' => 'default', + 'class' => self::SINGLE_STRING_CASTABLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $field->submit('2'); + + $this->assertTrue($field->isSynchronized()); + $this->assertSame($entity2, $field->getData()); + $this->assertSame('2', $field->getViewData()); + } + + public function testSubmitSingleStringCastableIdentifierExpanded() + { + $entity1 = new SingleStringCastableIdEntity(1, 'Foo'); + $entity2 = new SingleStringCastableIdEntity(2, 'Bar'); + + $this->persist(array($entity1, $entity2)); + + $field = $this->factory->createNamed('name', 'entity', null, array( + 'multiple' => false, + 'expanded' => true, + 'em' => 'default', + 'class' => self::SINGLE_STRING_CASTABLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $field->submit('2'); + + $this->assertTrue($field->isSynchronized()); + $this->assertSame($entity2, $field->getData()); + $this->assertFalse($field['0']->getData()); + $this->assertTrue($field['1']->getData()); + $this->assertNull($field['0']->getViewData()); + $this->assertSame('2', $field['1']->getViewData()); + } + + public function testSubmitMultipleNonExpandedStringCastableIdentifierForExistingData() + { + $entity1 = new SingleStringCastableIdEntity(1, 'Foo'); + $entity2 = new SingleStringCastableIdEntity(2, 'Bar'); + $entity3 = new SingleStringCastableIdEntity(3, 'Baz'); + + $this->persist(array($entity1, $entity2, $entity3)); + + $field = $this->factory->createNamed('name', 'entity', null, array( + 'multiple' => true, + 'expanded' => false, + 'em' => 'default', + 'class' => self::SINGLE_STRING_CASTABLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $existing = new ArrayCollection(array(0 => $entity2)); + + $field->setData($existing); + $field->submit(array('1', '3')); + + // entry with index 0 ($entity2) was replaced + $expected = new ArrayCollection(array(0 => $entity1, 1 => $entity3)); + + $this->assertTrue($field->isSynchronized()); + $this->assertEquals($expected, $field->getData()); + // same object still, useful if it is a PersistentCollection + $this->assertSame($existing, $field->getData()); + $this->assertSame(array('1', '3'), $field->getViewData()); + } + + public function testSubmitMultipleNonExpandedStringCastableIdentifier() + { + $entity1 = new SingleStringCastableIdEntity(1, 'Foo'); + $entity2 = new SingleStringCastableIdEntity(2, 'Bar'); + $entity3 = new SingleStringCastableIdEntity(3, 'Baz'); + + $this->persist(array($entity1, $entity2, $entity3)); + + $field = $this->factory->createNamed('name', 'entity', null, array( + 'multiple' => true, + 'expanded' => false, + 'em' => 'default', + 'class' => self::SINGLE_STRING_CASTABLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $field->submit(array('1', '3')); + + $expected = new ArrayCollection(array($entity1, $entity3)); + + $this->assertTrue($field->isSynchronized()); + $this->assertEquals($expected, $field->getData()); + $this->assertSame(array('1', '3'), $field->getViewData()); + } + + public function testSubmitMultipleStringCastableIdentifierExpanded() + { + $entity1 = new SingleStringCastableIdEntity(1, 'Foo'); + $entity2 = new SingleStringCastableIdEntity(2, 'Bar'); + $entity3 = new SingleStringCastableIdEntity(3, 'Bar'); + + $this->persist(array($entity1, $entity2, $entity3)); + + $field = $this->factory->createNamed('name', 'entity', null, array( + 'multiple' => true, + 'expanded' => true, + 'em' => 'default', + 'class' => self::SINGLE_STRING_CASTABLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $field->submit(array('1', '3')); + + $expected = new ArrayCollection(array($entity1, $entity3)); + + $this->assertTrue($field->isSynchronized()); + $this->assertEquals($expected, $field->getData()); + $this->assertTrue($field['0']->getData()); + $this->assertFalse($field['1']->getData()); + $this->assertTrue($field['2']->getData()); + $this->assertSame('1', $field['0']->getViewData()); + $this->assertNull($field['1']->getViewData()); + $this->assertSame('3', $field['2']->getViewData()); + } + public function testOverrideChoices() { $entity1 = new SingleIntIdEntity(1, 'Foo'); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index 8c179cd31f246..6203b9dfb29a7 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -92,7 +92,7 @@ public function testSupportProxy() private function getManager($em, $name = null) { $manager = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); - $manager->expects($this->once()) + $manager->expects($this->any()) ->method('getManager') ->with($this->equalTo($name)) ->will($this->returnValue($em)); diff --git a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php new file mode 100644 index 0000000000000..3d3c74cb73dfc --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php @@ -0,0 +1,106 @@ +pushHandler($handler); + + $this->assertTrue($logger->emerg('test')); + $this->assertTrue($handler->hasEmergency('test')); + } + + /** + * @group legacy + */ + public function testCrit() + { + $handler = new TestHandler(); + $logger = new Logger('test'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->crit('test')); + $this->assertTrue($handler->hasCritical('test')); + } + + /** + * @group legacy + */ + public function testErr() + { + $handler = new TestHandler(); + $logger = new Logger('test'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->err('test')); + $this->assertTrue($handler->hasError('test')); + } + + /** + * @group legacy + */ + public function testWarn() + { + $handler = new TestHandler(); + $logger = new Logger('test'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->warn('test')); + $this->assertTrue($handler->hasWarning('test')); + } + + public function testGetLogs() + { + $logger = new Logger('test'); + $logger->pushHandler(new DebugHandler()); + + $logger->addInfo('test'); + $this->assertCount(1, $logger->getLogs()); + list($record) = $logger->getLogs(); + + $this->assertEquals('test', $record['message']); + $this->assertEquals(Logger::INFO, $record['priority']); + } + + public function testGetLogsWithoutDebugHandler() + { + $logger = new Logger('test'); + $logger->pushHandler(new TestHandler()); + $logger->addInfo('test'); + + $this->assertSame(array(), $logger->getLogs()); + } + + public function testCountErrors() + { + $logger = new Logger('test'); + $logger->pushHandler(new DebugHandler()); + + $logger->addInfo('test'); + $logger->addError('uh-oh'); + + $this->assertEquals(1, $logger->countErrors()); + } + + public function testCountErrorsWithoutDebugHandler() + { + $logger = new Logger('test'); + $logger->pushHandler(new TestHandler()); + + $logger->addInfo('test'); + $logger->addError('uh-oh'); + + $this->assertEquals(0, $logger->countErrors()); + } +} 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/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php new file mode 100644 index 0000000000000..c7195ab577820 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Form\TwigRenderer; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; +use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\Tests\AbstractBootstrap3HorizontalLayoutTest; + +class FormExtensionBootstrap3HorizontalLayoutTest extends AbstractBootstrap3HorizontalLayoutTest +{ + /** + * @var FormExtension + */ + protected $extension; + + protected $testableFeatures = array( + 'choice_attr', + ); + + protected function setUp() + { + parent::setUp(); + + $rendererEngine = new TwigRendererEngine(array( + 'bootstrap_3_horizontal_layout.html.twig', + 'custom_widgets.html.twig', + )); + $renderer = new TwigRenderer($rendererEngine, $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')); + + $this->extension = new FormExtension($renderer); + + $loader = new StubFilesystemLoader(array( + __DIR__.'/../../Resources/views/Form', + __DIR__.'/Fixtures/templates/form', + )); + + $environment = new \Twig_Environment($loader, array('strict_variables' => true)); + $environment->addExtension(new TranslationExtension(new StubTranslator())); + $environment->addExtension($this->extension); + + $this->extension->initRuntime($environment); + } + + protected function tearDown() + { + parent::tearDown(); + + $this->extension = null; + } + + protected function renderForm(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form', $vars); + } + + protected function renderEnctype(FormView $view) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'enctype'); + } + + protected function renderLabel(FormView $view, $label = null, array $vars = array()) + { + if ($label !== null) { + $vars += array('label' => $label); + } + + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'label', $vars); + } + + protected function renderErrors(FormView $view) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'errors'); + } + + protected function renderWidget(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'widget', $vars); + } + + protected function renderRow(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'row', $vars); + } + + protected function renderRest(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'rest', $vars); + } + + protected function renderStart(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form_start', $vars); + } + + protected function renderEnd(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form_end', $vars); + } + + protected function setTheme(FormView $view, array $themes) + { + $this->extension->renderer->setTheme($view, $themes); + } + + public function testRange() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testRangeWithMinMaxValues() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testLabelWithoutTranslationOnButton() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testButtonlabelWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php index a9d161b2b909a..406c1cef16bf4 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php @@ -125,4 +125,29 @@ public function testRangeWithMinMaxValues() { // No-op for forward compatibility with AbstractLayoutTest 2.8 } + + public function testLabelWithoutTranslationOnButton() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testButtonlabelWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index 1bce43b83780c..9e39ac35dc421 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -218,4 +218,29 @@ public function testRangeWithMinMaxValues() { // No-op for forward compatibility with AbstractLayoutTest 2.8 } + + public function testLabelWithoutTranslationOnButton() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testButtonlabelWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php index 555ea306fca89..7fa88eef00c7e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php @@ -126,4 +126,29 @@ public function testRangeWithMinMaxValues() { // No-op for forward compatibility with AbstractLayoutTest 2.8 } + + public function testLabelWithoutTranslationOnButton() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testButtonlabelWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php index dc06c73dba03e..f4a6dbf21ffa4 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -31,7 +31,7 @@ public function testEscaping() public function testTrans($template, $expected, array $variables = array()) { if ($expected != $this->getTemplate($template)->render($variables)) { - print $template."\n"; + echo $template."\n"; $loader = new \Twig_Loader_Array(array('index' => $template)); $twig = new \Twig_Environment($loader, array('debug' => true, 'cache' => false)); $twig->addExtension(new TranslationExtension(new Translator('en', new MessageSelector()))); diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 7ce35a9fac00b..2e569d7435fc8 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -22,7 +22,7 @@ "require-dev": { "symfony/asset": "~2.7", "symfony/finder": "~2.3", - "symfony/form": "~2.7,>=2.7.6", + "symfony/form": "~2.7,>=2.7.8", "symfony/http-kernel": "~2.3", "symfony/intl": "~2.3", "symfony/routing": "~2.2", diff --git a/src/Symfony/Bundle/FrameworkBundle/Client.php b/src/Symfony/Bundle/FrameworkBundle/Client.php index 82e627a726a07..d9b064e5ec5a6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Client.php +++ b/src/Symfony/Bundle/FrameworkBundle/Client.php @@ -185,7 +185,7 @@ protected function getScript($request) $code = <<getContainer()->getParameter('kernel.cache_dir'); - $oldCacheDir = $realCacheDir.'_old'; + // the old cache dir name must not be longer than the real one to avoid exceeding + // the maximum length of a directory or file path within it (esp. Windows MAX_PATH) + $oldCacheDir = substr($realCacheDir, 0, -1).('~' === substr($realCacheDir, -1) ? '+' : '~'); $filesystem = $this->getContainer()->get('filesystem'); if (!is_writable($realCacheDir)) { @@ -75,7 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // the warmup cache dir name must have the same length than the real one // to avoid the many problems in serialized resources files $realCacheDir = realpath($realCacheDir); - $warmupDir = substr($realCacheDir, 0, -1).'_'; + $warmupDir = substr($realCacheDir, 0, -1).('_' === substr($realCacheDir, -1) ? '-' : '_'); if ($filesystem->exists($warmupDir)) { if ($output->isVerbose()) { @@ -114,8 +116,6 @@ protected function execute(InputInterface $input, OutputInterface $output) */ protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = true) { - $this->getContainer()->get('filesystem')->remove($warmupDir); - // create a temporary kernel $realKernel = $this->getContainer()->get('kernel'); $realKernelClass = get_class($realKernel); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerCommand.php index b979a19f0f73d..acf71c666605a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerCommand.php @@ -27,6 +27,10 @@ public function isEnabled() return false; } + if (!class_exists('Symfony\Component\Process\Process')) { + return false; + } + return parent::isEnabled(); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php index 6af52feaaec57..78b0bd4ee290a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php @@ -25,18 +25,6 @@ */ class ServerRunCommand extends ServerCommand { - /** - * {@inheritdoc} - */ - public function isEnabled() - { - if (PHP_VERSION_ID < 50400 || defined('HHVM_VERSION')) { - return false; - } - - return parent::isEnabled(); - } - /** * {@inheritdoc} */ diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 64143f6e21a19..f8bd0159fcf2f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -188,9 +188,11 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - if ($bundleTransPath) { - $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath, 'default_locale' => $this->getContainer()->getParameter('kernel.default_locale'))); + if (!$bundleTransPath) { + $bundleTransPath = end($transPaths).'translations'; } + + $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath, 'default_locale' => $this->getContainer()->getParameter('kernel.default_locale'))); } $output->newLine(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php index 4af5b929cbb50..217fd5e50e237 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php @@ -138,4 +138,29 @@ public function testRangeWithMinMaxValues() { // No-op for forward compatibility with AbstractLayoutTest 2.8 } + + public function testLabelWithoutTranslationOnButton() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testButtonlabelWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php index 1bf641fe1b93f..99bfba0c4e7cc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php @@ -125,4 +125,29 @@ public function testRangeWithMinMaxValues() { // No-op for forward compatibility with AbstractLayoutTest 2.8 } + + public function testLabelWithoutTranslationOnButton() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testButtonlabelWithoutTranslation() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } + + public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() + { + // No-op for forward compatibility with AbstractLayoutTest 2.8 + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 29ff39c46fac3..045d19c354e73 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -21,6 +21,7 @@ "symfony/dependency-injection": "~2.6,>=2.6.2", "symfony/config": "~2.4", "symfony/event-dispatcher": "~2.5", + "symfony/finder": "~2.0,>=2.0.5", "symfony/http-foundation": "~2.4.9|~2.5,>=2.5.4", "symfony/http-kernel": "~2.7", "symfony/filesystem": "~2.3", @@ -37,7 +38,6 @@ "symfony/console": "~2.7", "symfony/css-selector": "~2.0,>=2.0.5", "symfony/dom-crawler": "~2.0,>=2.0.5", - "symfony/finder": "~2.0,>=2.0.5", "symfony/intl": "~2.3", "symfony/security": "~2.6", "symfony/form": "~2.7,>=2.7.2", @@ -49,11 +49,11 @@ }, "suggest": { "symfony/console": "For using the console commands", - "symfony/finder": "For using the translation loader and cache warmer", "symfony/form": "For using forms", "symfony/serializer": "For using the serializer service", "symfony/validator": "For using validation", "symfony/yaml": "For using the debug:config and lint:yaml commands", + "symfony/process": "For using the server:run, server:start, server:stop, and server:status commands", "doctrine/cache": "For using alternative cache drivers" }, "autoload": { 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/Tests/Functional/AuthenticationCommencingTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php index e94a21e5bae75..6ac0e6a3af772 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php @@ -16,7 +16,6 @@ class AuthenticationCommencingTest extends WebTestCase public function testAuthenticationIsCommencingIfAccessDeniedExceptionIsWrapped() { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => 'config.yml')); - $client->insulate(); $client->request('GET', '/secure-but-not-covered-by-access-control'); $this->assertRedirect($client->getResponse(), '/login'); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/DependencyInjection/FirewallEntryPointExtension.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/DependencyInjection/FirewallEntryPointExtension.php index e4d5bc30f39f9..90b6b3e5eb238 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/DependencyInjection/FirewallEntryPointExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FirewallEntryPointBundle/DependencyInjection/FirewallEntryPointExtension.php @@ -18,7 +18,7 @@ class FirewallEntryPointExtension extends Extension { - public function load(array $config, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container) { $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.xml'); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php index c2299c9a51f11..80211f5251b08 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php @@ -19,7 +19,6 @@ class CsrfFormLoginTest extends WebTestCase public function testFormLoginAndLogoutWithCsrfTokens($config) { $client = $this->createClient(array('test_case' => 'CsrfFormLogin', 'root_config' => $config)); - $client->insulate(); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['user_login[username]'] = 'johannes'; @@ -50,7 +49,6 @@ public function testFormLoginAndLogoutWithCsrfTokens($config) public function testFormLoginWithInvalidCsrfToken($config) { $client = $this->createClient(array('test_case' => 'CsrfFormLogin', 'root_config' => $config)); - $client->insulate(); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['user_login[_token]'] = ''; @@ -68,7 +66,6 @@ public function testFormLoginWithInvalidCsrfToken($config) public function testFormLoginWithCustomTargetPath($config) { $client = $this->createClient(array('test_case' => 'CsrfFormLogin', 'root_config' => $config)); - $client->insulate(); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['user_login[username]'] = 'johannes'; @@ -89,7 +86,6 @@ public function testFormLoginWithCustomTargetPath($config) public function testFormLoginRedirectsToProtectedResourceAfterLogin($config) { $client = $this->createClient(array('test_case' => 'CsrfFormLogin', 'root_config' => $config)); - $client->insulate(); $client->request('GET', '/protected-resource'); $this->assertRedirect($client->getResponse(), '/login'); @@ -113,17 +109,13 @@ public function getConfigs() ); } - protected function setUp() + public static function setUpBeforeClass() { - parent::setUp(); - - $this->deleteTmpDir('CsrfFormLogin'); + parent::deleteTmpDir('CsrfFormLogin'); } - protected function tearDown() + public static function tearDownAfterClass() { - parent::tearDown(); - - $this->deleteTmpDir('CsrfFormLogin'); + parent::deleteTmpDir('CsrfFormLogin'); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php index aab98f273fa8e..30d0935ffddd7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php @@ -18,7 +18,6 @@ class FirewallEntryPointTest extends WebTestCase public function testItUsesTheConfiguredEntryPointWhenUsingUnknownCredentials() { $client = $this->createClient(array('test_case' => 'FirewallEntryPoint')); - $client->insulate(); $client->request('GET', '/secure/resource', array(), array(), array( 'PHP_AUTH_USER' => 'unknown', @@ -35,7 +34,6 @@ public function testItUsesTheConfiguredEntryPointWhenUsingUnknownCredentials() public function testItUsesTheConfiguredEntryPointFromTheExceptionListenerWithFormLoginAndNoCredentials() { $client = $this->createClient(array('test_case' => 'FirewallEntryPoint', 'root_config' => 'config_form_login.yml')); - $client->insulate(); $client->request('GET', '/secure/resource'); @@ -46,17 +44,13 @@ public function testItUsesTheConfiguredEntryPointFromTheExceptionListenerWithFor ); } - protected function setUp() + public static function setUpBeforeClass() { - parent::setUp(); - - $this->deleteTmpDir('FirewallEntryPoint'); + parent::deleteTmpDir('FirewallEntryPoint'); } - protected function tearDown() + public static function tearDownAfterClass() { - parent::tearDown(); - - $this->deleteTmpDir('FirewallEntryPoint'); + parent::deleteTmpDir('FirewallEntryPoint'); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php index 3bb2235d51652..2f19f3f8a1a9a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php @@ -19,7 +19,6 @@ class FormLoginTest extends WebTestCase public function testFormLogin($config) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config)); - $client->insulate(); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['_username'] = 'johannes'; @@ -39,7 +38,6 @@ public function testFormLogin($config) public function testFormLogout($config) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config)); - $client->insulate(); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['_username'] = 'johannes'; @@ -73,7 +71,6 @@ public function testFormLogout($config) public function testFormLoginWithCustomTargetPath($config) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config)); - $client->insulate(); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['_username'] = 'johannes'; @@ -94,7 +91,6 @@ public function testFormLoginWithCustomTargetPath($config) public function testFormLoginRedirectsToProtectedResourceAfterLogin($config) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config)); - $client->insulate(); $client->request('GET', '/protected_resource'); $this->assertRedirect($client->getResponse(), '/login'); @@ -118,17 +114,13 @@ public function getConfigs() ); } - protected function setUp() + public static function setUpBeforeClass() { - parent::setUp(); - - $this->deleteTmpDir('StandardFormLogin'); + parent::deleteTmpDir('StandardFormLogin'); } - protected function tearDown() + public static function tearDownAfterClass() { - parent::tearDown(); - - $this->deleteTmpDir('StandardFormLogin'); + parent::deleteTmpDir('StandardFormLogin'); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php index 84c79055a977c..14c317966e21a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php @@ -19,7 +19,6 @@ class LocalizedRoutesAsPathTest extends WebTestCase public function testLoginLogoutProcedure($locale) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => 'localized_routes.yml')); - $client->insulate(); $crawler = $client->request('GET', '/'.$locale.'/login'); $form = $crawler->selectButton('login')->form(); @@ -41,7 +40,6 @@ public function testLoginLogoutProcedure($locale) public function testLoginFailureWithLocalizedFailurePath($locale) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => 'localized_form_failure_handler.yml')); - $client->insulate(); $crawler = $client->request('GET', '/'.$locale.'/login'); $form = $crawler->selectButton('login')->form(); @@ -58,7 +56,6 @@ public function testLoginFailureWithLocalizedFailurePath($locale) public function testAccessRestrictedResource($locale) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => 'localized_routes.yml')); - $client->insulate(); $client->request('GET', '/'.$locale.'/secure/'); $this->assertRedirect($client->getResponse(), '/'.$locale.'/login'); @@ -70,7 +67,6 @@ public function testAccessRestrictedResource($locale) public function testAccessRestrictedResourceWithForward($locale) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => 'localized_routes_with_forward.yml')); - $client->insulate(); $crawler = $client->request('GET', '/'.$locale.'/secure/'); $this->assertCount(1, $crawler->selectButton('login'), (string) $client->getResponse()); @@ -81,17 +77,13 @@ public function getLocales() return array(array('en'), array('de')); } - protected function setUp() + public static function setUpBeforeClass() { - parent::setUp(); - - $this->deleteTmpDir('StandardFormLogin'); + parent::deleteTmpDir('StandardFormLogin'); } - protected function tearDown() + public static function tearDownAfterClass() { - parent::tearDown(); - - $this->deleteTmpDir('StandardFormLogin'); + parent::deleteTmpDir('StandardFormLogin'); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php index 4c1f0853bb65c..c7db437819a1c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php @@ -19,7 +19,6 @@ class SecurityRoutingIntegrationTest extends WebTestCase public function testRoutingErrorIsNotExposedForProtectedResourceWhenAnonymous($config) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config)); - $client->insulate(); $client->request('GET', '/protected_resource'); $this->assertRedirect($client->getResponse(), '/login'); @@ -31,7 +30,6 @@ public function testRoutingErrorIsNotExposedForProtectedResourceWhenAnonymous($c public function testRoutingErrorIsExposedWhenNotProtected($config) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config)); - $client->insulate(); $client->request('GET', '/unprotected_resource'); $this->assertEquals(404, $client->getResponse()->getStatusCode(), (string) $client->getResponse()); @@ -43,7 +41,6 @@ public function testRoutingErrorIsExposedWhenNotProtected($config) public function testRoutingErrorIsNotExposedForProtectedResourceWhenLoggedInWithInsufficientRights($config) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => $config)); - $client->insulate(); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['_username'] = 'johannes'; @@ -120,17 +117,13 @@ public function getConfigs() return array(array('config.yml'), array('routes_as_path.yml')); } - protected function setUp() + public static function setUpBeforeClass() { - parent::setUp(); - - $this->deleteTmpDir('StandardFormLogin'); + parent::deleteTmpDir('StandardFormLogin'); } - protected function tearDown() + public static function tearDownAfterClass() { - parent::tearDown(); - - $this->deleteTmpDir('StandardFormLogin'); + parent::deleteTmpDir('StandardFormLogin'); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php index f4b3d27f21404..e5079c3283aac 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php @@ -62,7 +62,6 @@ protected function createAuthenticatedClient($username) { $client = $this->createClient(array('test_case' => 'StandardFormLogin', 'root_config' => 'switchuser.yml')); $client->followRedirects(true); - $client->insulate(); $form = $client->request('GET', '/login')->selectButton('login')->form(); $form['_username'] = $username; @@ -72,17 +71,13 @@ protected function createAuthenticatedClient($username) return $client; } - protected function setUp() + public static function setUpBeforeClass() { - parent::setUp(); - - $this->deleteTmpDir('StandardFormLogin'); + parent::deleteTmpDir('StandardFormLogin'); } - protected function tearDown() + public static function tearDownAfterClass() { - parent::tearDown(); - - $this->deleteTmpDir('StandardFormLogin'); + parent::deleteTmpDir('StandardFormLogin'); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php index 731c32073c949..33da9028a3daf 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php @@ -23,7 +23,7 @@ public static function assertRedirect($response, $location) self::assertEquals('http://localhost'.$location, $response->headers->get('Location')); } - protected function deleteTmpDir($testCase) + protected static function deleteTmpDir($testCase) { if (!file_exists($dir = sys_get_temp_dir().'/'.Kernel::VERSION.'/'.$testCase)) { return; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php index 977be9162c636..b828c5acfd91b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/AppKernel.php @@ -65,6 +65,18 @@ public function __construct($testCase, $rootConfig, $environment, $debug) parent::__construct($environment, $debug); } + /** + * {@inheritdoc} + */ + public function getName() + { + if (null === $this->name) { + $this->name = parent::getName().md5($this->rootConfig); + } + + return $this->name; + } + public function registerBundles() { if (!is_file($filename = $this->getRootDir().'/'.$this->testCase.'/bundles.php')) { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php index 5c8a3becb8326..29238a21c439b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php @@ -48,9 +48,9 @@ protected function setUp() $profiler = $this->mockProfiler(); $twigEnvironment = $this->mockTwigEnvironment(); $templates = array( - 'data_collector.foo' => array('foo','FooBundle:Collector:foo'), - 'data_collector.bar' => array('bar','FooBundle:Collector:bar'), - 'data_collector.baz' => array('baz','FooBundle:Collector:baz'), + 'data_collector.foo' => array('foo', 'FooBundle:Collector:foo'), + 'data_collector.bar' => array('bar', 'FooBundle:Collector:bar'), + 'data_collector.baz' => array('baz', 'FooBundle:Collector:baz'), ); $this->templateManager = new TemplateManager($profiler, $twigEnvironment, $templates); 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/BrowserKit/Tests/ClientTest.php b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php index 36a0fbc6ee26b..fe0037638547a 100644 --- a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php @@ -624,4 +624,24 @@ public function testSetServerParameterInRequest() $this->assertArrayHasKey('HTTPS', $server); $this->assertFalse($server['HTTPS']); } + + public function testInternalRequest() + { + $client = new TestClient(); + + $client->request('GET', 'https://www.example.com/https/www.example.com', array(), array(), array( + 'HTTP_HOST' => 'testhost', + 'HTTP_USER_AGENT' => 'testua', + 'HTTPS' => false, + 'NEW_SERVER_KEY' => 'new-server-key-value', + )); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Request', $client->getInternalRequest()); + } + + public function testInternalRequestNull() + { + $client = new TestClient(); + $this->assertNull($client->getInternalRequest()); + } } diff --git a/src/Symfony/Component/BrowserKit/Tests/CookieJarTest.php b/src/Symfony/Component/BrowserKit/Tests/CookieJarTest.php index 4da4404e24949..54a84b43a463b 100644 --- a/src/Symfony/Component/BrowserKit/Tests/CookieJarTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/CookieJarTest.php @@ -174,6 +174,16 @@ public function testCookieExpireWithNullPaths() $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); } + public function testCookieExpireWithDomain() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo', 'http://example2.com/')); + $cookieJar->expire('foo', '/foo', 'http://example2.com/'); + + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example2.com/'))); + } + public function testCookieWithSameNameButDifferentPaths() { $cookieJar = new CookieJar(); @@ -207,6 +217,14 @@ public function testCookieGetWithSubdomain() $this->assertEquals($cookie2, $cookieJar->get('foo1', '/', 'test.example.com')); } + public function testCookieGetWithWrongSubdomain() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo1', 'bar', null, '/', 'test.example.com')); + + $this->assertNull($cookieJar->get('foo1', '/', 'foo.example.com')); + } + public function testCookieGetWithSubdirectory() { $cookieJar = new CookieJar(); diff --git a/src/Symfony/Component/BrowserKit/Tests/CookieTest.php b/src/Symfony/Component/BrowserKit/Tests/CookieTest.php index e1dd0df2c1533..61b364e6d1eac 100644 --- a/src/Symfony/Component/BrowserKit/Tests/CookieTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/CookieTest.php @@ -176,4 +176,13 @@ public function testIsExpired() $cookie = new Cookie('foo', 'bar', 0); $this->assertFalse($cookie->isExpired()); } + + /** + * @expectedException UnexpectedValueException + * @expectedExceptionMessage The cookie expiration time "string" is not valid. + */ + public function testConstructException() + { + $cookie = new Cookie('foo', 'bar', 'string'); + } } diff --git a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php index c7dc3ae12e0f0..e33bcbbbc6274 100644 --- a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php +++ b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php @@ -137,8 +137,8 @@ public static function load($classes, $cacheDir, $name, $autoReload, $adaptive = public static function fixNamespaceDeclarations($source) { if (!function_exists('token_get_all') || !self::$useTokenizer) { - if (preg_match('/namespace(.*?)\s*;/', $source)) { - $source = preg_replace('/namespace(.*?)\s*;/', "namespace$1\n{", $source)."}\n"; + if (preg_match('/(^|\s)namespace(.*?)\s*;/', $source)) { + $source = preg_replace('/(^|\s)namespace(.*?)\s*;/', "$1namespace$2\n{", $source)."}\n"; } return $source; diff --git a/src/Symfony/Component/ClassLoader/ClassLoader.php b/src/Symfony/Component/ClassLoader/ClassLoader.php index fc0a569485bd4..a506dc0941946 100644 --- a/src/Symfony/Component/ClassLoader/ClassLoader.php +++ b/src/Symfony/Component/ClassLoader/ClassLoader.php @@ -97,7 +97,7 @@ public function addPrefix($prefix, $paths) $paths )); } elseif (!in_array($paths, $this->prefixes[$prefix])) { - $this->prefixes[$prefix][] = $paths; + $this->prefixes[$prefix][] = $paths; } } else { $this->prefixes[$prefix] = array_unique((array) $paths); diff --git a/src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php b/src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php index 2d78941538191..5019f26ee84c8 100644 --- a/src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php +++ b/src/Symfony/Component/ClassLoader/Tests/ClassCollectionLoaderTest.php @@ -205,7 +205,7 @@ public function getFixNamespaceDeclarationsDataWithoutTokenizer() array("namespace Bar ;\nclass Foo {}\n", "namespace Bar\n{\nclass Foo {}\n}\n"), array("namespace Foo\Bar;\nclass Foo {}\n", "namespace Foo\Bar\n{\nclass Foo {}\n}\n"), array("namespace Foo\Bar\Bar\n{\nclass Foo {}\n}\n", "namespace Foo\Bar\Bar\n{\nclass Foo {}\n}\n"), - array("namespace\n{\nclass Foo {}\n}\n", "namespace\n{\nclass Foo {}\n}\n"), + array("\nnamespace\n{\nclass Foo {}\n\$namespace=123;}\n", "\nnamespace\n{\nclass Foo {}\n\$namespace=123;}\n"), ); } diff --git a/src/Symfony/Component/Config/Definition/ArrayNode.php b/src/Symfony/Component/Config/Definition/ArrayNode.php index 05ae1fdcd99e1..751aee12278ed 100644 --- a/src/Symfony/Component/Config/Definition/ArrayNode.php +++ b/src/Symfony/Component/Config/Definition/ArrayNode.php @@ -55,14 +55,17 @@ protected function preNormalize($value) return $value; } + $normalized = array(); + foreach ($value as $k => $v) { if (false !== strpos($k, '-') && false === strpos($k, '_') && !array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) { - $value[$normalizedKey] = $v; - unset($value[$k]); + $normalized[$normalizedKey] = $v; + } else { + $normalized[$k] = $v; } } - return $value; + return $normalized; } /** diff --git a/src/Symfony/Component/Config/Tests/Definition/ArrayNodeTest.php b/src/Symfony/Component/Config/Tests/Definition/ArrayNodeTest.php index 291c2fd2cce06..652a153a3d604 100644 --- a/src/Symfony/Component/Config/Tests/Definition/ArrayNodeTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/ArrayNodeTest.php @@ -74,6 +74,10 @@ public function getPreNormalizationTests() array('foo-bar_moo' => 'foo'), array('foo-bar_moo' => 'foo'), ), + array( + array('anything-with-dash-and-no-underscore' => 'first', 'no_dash' => 'second'), + array('anything_with_dash_and_no_underscore' => 'first', 'no_dash' => 'second'), + ), array( array('foo-bar' => null, 'foo_bar' => 'foo'), array('foo-bar' => null, 'foo_bar' => 'foo'), diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index e011beecac95e..9076fbbbbf91b 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -631,6 +631,8 @@ public function asXml($namespace = null, $asDom = false) */ public function renderException($e, $output) { + $output->writeln(''); + do { $title = sprintf(' [%s] ', get_class($e)); @@ -653,7 +655,7 @@ public function renderException($e, $output) } } - $messages = array('', ''); + $messages = array(); $messages[] = $emptyLine = $formatter->format(sprintf('%s', str_repeat(' ', $len))); $messages[] = $formatter->format(sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title))))); foreach ($lines as $line) { @@ -661,7 +663,6 @@ public function renderException($e, $output) } $messages[] = $emptyLine; $messages[] = ''; - $messages[] = ''; $output->writeln($messages, OutputInterface::OUTPUT_RAW); @@ -688,14 +689,12 @@ public function renderException($e, $output) } $output->writeln(''); - $output->writeln(''); } } while ($e = $e->getPrevious()); if (null !== $this->runningCommand) { $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName()))); $output->writeln(''); - $output->writeln(''); } } diff --git a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php index 57e09e4d81c36..f2895bd8e4d17 100644 --- a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php @@ -236,10 +236,10 @@ private function writeText($content, array $options = array()) private function formatDefaultValue($default) { if (PHP_VERSION_ID < 50400) { - return str_replace('\/', '/', json_encode($default)); + return str_replace(array('\/', '\\\\'), array('/', '\\'), json_encode($default)); } - return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } /** diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index 0b64b18a1db06..016e885f4b288 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -67,10 +67,8 @@ public function __construct(OutputInterface $output, $max = 0) // disable overwrite when output does not support ANSI codes. $this->overwrite = false; - if ($this->max > 10) { - // set a reasonable redraw frequency so output isn't flooded - $this->setRedrawFrequency($max / 10); - } + // set a reasonable redraw frequency so output isn't flooded + $this->setRedrawFrequency($max / 10); } $this->startTime = time(); @@ -316,11 +314,11 @@ public function setFormat($format) /** * Sets the redraw frequency. * - * @param int $freq The frequency in steps + * @param int|float $freq The frequency in steps */ public function setRedrawFrequency($freq) { - $this->redrawFreq = (int) $freq; + $this->redrawFreq = max((int) $freq, 1); } /** diff --git a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php index 77130f9776ac2..dc2141e99f8ee 100644 --- a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php @@ -32,7 +32,7 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu { $validator = $question->getValidator(); $question->setValidator(function ($value) use ($validator) { - if (null !== $validator && is_callable($validator)) { + if (null !== $validator) { $value = $validator($value); } diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php index 6f5fbd047cee7..3823e9c4f61ca 100644 --- a/src/Symfony/Component/Console/Helper/Table.php +++ b/src/Symfony/Component/Console/Helper/Table.php @@ -332,7 +332,7 @@ private function calculateNumberOfColumns() $columns[] = $this->getNumberOfColumns($row); } - return $this->numberOfColumns = max($columns); + $this->numberOfColumns = max($columns); } private function buildTableRows($rows) @@ -343,7 +343,6 @@ private function buildTableRows($rows) // Remove any new line breaks and replace it with a new line foreach ($rows[$rowKey] as $column => $cell) { - $rows[$rowKey] = $this->fillCells($rows[$rowKey], $column); if (!strstr($cell, "\n")) { continue; } @@ -363,7 +362,7 @@ private function buildTableRows($rows) $tableRows = array(); foreach ($rows as $rowKey => $row) { - $tableRows[] = $row; + $tableRows[] = $this->fillCells($row); if (isset($unmergedRows[$rowKey])) { $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); } @@ -429,21 +428,23 @@ private function fillNextRows($rows, $line) * fill cells for a row that contains colspan > 1. * * @param array $row - * @param int $column * * @return array */ - private function fillCells($row, $column) + private function fillCells($row) { - $cell = $row[$column]; - if ($cell instanceof TableCell && $cell->getColspan() > 1) { - foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { - // insert empty value into rows at column position - array_splice($row, $position, 0, ''); + $newRow = array(); + foreach ($row as $column => $cell) { + $newRow[] = $cell; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { + // insert empty value at column position + $newRow[] = ''; + } } } - return $row; + return $newRow ?: $row; } /** @@ -487,7 +488,7 @@ private function getNumberOfColumns(array $row) * * @param array $row * - * @return array() + * @return array */ private function getRowColumns($row) { @@ -529,8 +530,6 @@ private function getColumnWidth($column) /** * Gets column width. * - * @param int $column - * * @return int */ private function getColumnSeparatorWidth() diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception1.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception1.txt index 4629345c1e13c..c56f4b603341e 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception1.txt +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception1.txt @@ -1,8 +1,6 @@ - [InvalidArgumentException] Command "foo" is not defined. - diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception2.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception2.txt index 3d9d363b69897..58cd420f93c16 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception2.txt +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception2.txt @@ -1,11 +1,8 @@ - [InvalidArgumentException] The "--foo" option does not exist. - list [--xml] [--raw] [--format FORMAT] [--] [] - diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception3.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception3.txt index 72a72867f3fd7..8276137bd886a 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception3.txt +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception3.txt @@ -1,27 +1,18 @@ - [Exception] Third exception comment - - - [Exception] Second exception comment - - - [Exception] First exception

this is html

- foo3:bar - diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception3decorated.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception3decorated.txt index b44d50b0ed04f..b4a7b018af377 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception3decorated.txt +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception3decorated.txt @@ -1,27 +1,18 @@ -    [Exception]   Third exception comment    - - -    [Exception]   Second exception comment    - - -    [Exception]   First exception 

this is html

    - foo3:bar - diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception4.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception4.txt index 19f893b0c8528..9d881e7d0fe2b 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception4.txt +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception4.txt @@ -1,9 +1,7 @@ - [InvalidArgumentException] Command "foo" is not define d. - diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1.txt index 6a98660364219..1ba5f8fdd914d 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1.txt +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1.txt @@ -1,11 +1,8 @@ - [Exception] エラーメッセージ - foo - diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt index 8c8801b13ff2b..20644251c2f9b 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth1decorated.txt @@ -1,11 +1,8 @@ -    [Exception]   エラーメッセージ    - foo - diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth2.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth2.txt index 545cd7b0b49f9..e41fcfcf675af 100644 --- a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth2.txt +++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_doublewidth2.txt @@ -1,12 +1,9 @@ - [Exception] コマンドの実行中にエラーが 発生しました。 - foo - diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index e22734a29e8ef..ffe5bed086348 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -296,7 +296,7 @@ public function testRegressProgress() public function testRedrawFrequency() { - $bar = $this->getMock('Symfony\Component\Console\Helper\ProgressBar', array('display'), array($output = $this->getOutputStream(), 6)); + $bar = $this->getMock('Symfony\Component\Console\Helper\ProgressBar', array('display'), array($this->getOutputStream(), 6)); $bar->expects($this->exactly(4))->method('display'); $bar->setRedrawFrequency(2); @@ -307,6 +307,26 @@ public function testRedrawFrequency() $bar->advance(1); } + public function testRedrawFrequencyIsAtLeastOneIfZeroGiven() + { + $bar = $this->getMock('Symfony\Component\Console\Helper\ProgressBar', array('display'), array($this->getOutputStream())); + + $bar->expects($this->exactly(2))->method('display'); + $bar->setRedrawFrequency(0); + $bar->start(); + $bar->advance(); + } + + public function testRedrawFrequencyIsAtLeastOneIfSmallerOneGiven() + { + $bar = $this->getMock('Symfony\Component\Console\Helper\ProgressBar', array('display'), array($this->getOutputStream())); + + $bar->expects($this->exactly(2))->method('display'); + $bar->setRedrawFrequency(0.9); + $bar->start(); + $bar->advance(); + } + /** * @requires extension mbstring */ diff --git a/src/Symfony/Component/Console/Tests/Helper/TableTest.php b/src/Symfony/Component/Console/Tests/Helper/TableTest.php index 19bdf00e0662d..ab562fdb57708 100644 --- a/src/Symfony/Component/Console/Tests/Helper/TableTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/TableTest.php @@ -459,6 +459,24 @@ public function testRenderProvider() | ISBN | Title | Author | +------+-------+--------+ +TABLE + ), + 'Row with multiple cells' => array( + array(), + array( + array( + new TableCell('1', array('colspan' => 3)), + new TableCell('2', array('colspan' => 2)), + new TableCell('3', array('colspan' => 2)), + new TableCell('4', array('colspan' => 2)), + ), + ), + 'default', +<<tokenize() parses quoted arguments'), array("'quoted'", array('quoted'), '->tokenize() parses quoted arguments'), array("'a\rb\nc\td'", array("a\rb\nc\td"), '->tokenize() parses whitespace chars in strings'), - array("'a'\r'b'\n'c'\t'd'", array('a','b','c','d'), '->tokenize() parses whitespace chars between args as spaces'), + array("'a'\r'b'\n'c'\t'd'", array('a', 'b', 'c', 'd'), '->tokenize() parses whitespace chars between args as spaces'), array('\"quoted\"', array('"quoted"'), '->tokenize() parses escaped-quoted arguments'), array("\'quoted\'", array('\'quoted\''), '->tokenize() parses escaped-quoted arguments'), array('-a', array('-a'), '->tokenize() parses short options'), diff --git a/src/Symfony/Component/CssSelector/Tests/Node/ElementNodeTest.php b/src/Symfony/Component/CssSelector/Tests/Node/ElementNodeTest.php index 1db6a591a2f5c..6d24789320561 100644 --- a/src/Symfony/Component/CssSelector/Tests/Node/ElementNodeTest.php +++ b/src/Symfony/Component/CssSelector/Tests/Node/ElementNodeTest.php @@ -29,7 +29,7 @@ public function getSpecificityValueTestData() return array( array(new ElementNode(), 0), array(new ElementNode(null, 'element'), 1), - array(new ElementNode('namespace', 'element'),1), + array(new ElementNode('namespace', 'element'), 1), ); } } diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php index 8c797744d7b05..95ccc8798e8c1 100644 --- a/src/Symfony/Component/Debug/DebugClassLoader.php +++ b/src/Symfony/Component/Debug/DebugClassLoader.php @@ -147,7 +147,7 @@ public function loadClass($class) try { if ($this->isFinder) { if ($file = $this->classLoader[0]->findFile($class)) { - require $file; + require_once $file; } } else { call_user_func($this->classLoader, $class); diff --git a/src/Symfony/Component/Debug/Exception/FlattenException.php b/src/Symfony/Component/Debug/Exception/FlattenException.php index d8d5c5b9214d5..33c9becf35f6f 100644 --- a/src/Symfony/Component/Debug/Exception/FlattenException.php +++ b/src/Symfony/Component/Debug/Exception/FlattenException.php @@ -94,8 +94,13 @@ public static function create(\Exception $exception, $statusCode = null, array $ $e->setClass(get_class($exception)); $e->setFile($exception->getFile()); $e->setLine($exception->getLine()); - if ($exception->getPrevious()) { - $e->setPrevious(static::create($exception->getPrevious())); + + $previous = $exception->getPrevious(); + + if ($previous instanceof \Exception) { + $e->setPrevious(static::create($previous)); + } elseif ($previous instanceof \Throwable) { + $e->setPrevious(static::create(new FatalThrowableError($previous))); } return $e; diff --git a/src/Symfony/Component/Debug/README.md b/src/Symfony/Component/Debug/README.md index 67e6d6c2785c6..82c6e30322de4 100644 --- a/src/Symfony/Component/Debug/README.md +++ b/src/Symfony/Component/Debug/README.md @@ -15,6 +15,7 @@ Debug::enable(); You can also use the tools individually: ```php +use Symfony\Component\Debug\DebugClassLoader; use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\Debug\ExceptionHandler; @@ -25,11 +26,9 @@ if ('cli' !== php_sapi_name()) { ini_set('display_errors', 1); } ErrorHandler::register(); +DebugClassLoader::enable(); ``` -Note that the `Debug::enable()` call also registers the debug class loader -from the Symfony ClassLoader component when available. - This component can optionally take advantage of the features of the HttpKernel and HttpFoundation components. diff --git a/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php index 99eaf497d5b4d..6c570e235def7 100644 --- a/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php +++ b/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php @@ -131,6 +131,20 @@ public function testPrevious(\Exception $exception, $statusCode) $this->assertSame(array($flattened2), $flattened->getAllPrevious()); } + /** + * @requires PHP 7.0 + */ + public function testPreviousError() + { + $exception = new \Exception('test', 123, new \ParseError('Oh noes!', 42)); + + $flattened = FlattenException::create($exception)->getPrevious(); + + $this->assertEquals($flattened->getMessage(), 'Parse error: Oh noes!', 'The message is copied from the original exception.'); + $this->assertEquals($flattened->getCode(), 42, 'The code is copied from the original exception.'); + $this->assertEquals($flattened->getClass(), 'Symfony\Component\Debug\Exception\FatalThrowableError', 'The class is set to the class of the original exception'); + } + /** * @dataProvider flattenDataProvider */ diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php index 972d708c593c9..8308937d4a512 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php @@ -45,7 +45,7 @@ public function process(ContainerBuilder $container) try { $definition = $container->getDefinition($aliasId); } catch (InvalidArgumentException $e) { - throw new InvalidArgumentException(sprintf('Unable to replace alias "%s" with "%s".', $alias, $id), null, $e); + throw new InvalidArgumentException(sprintf('Unable to replace alias "%s" with actual definition "%s".', $id, $alias), null, $e); } if ($definition->isPublic()) { diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 510d030517948..355ba26f243d9 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -305,10 +305,7 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE $service = $this->$method(); } catch (\Exception $e) { unset($this->loading[$id]); - - if (array_key_exists($id, $this->services)) { - unset($this->services[$id]); - } + unset($this->services[$id]); if ($e instanceof InactiveScopeException && self::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) { return; diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index 777f4a60548a0..74da38203cf75 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -495,9 +495,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/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index bb3df8bef1580..d4010f1f1a199 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -759,6 +759,10 @@ private function addNewInstance($id, Definition $definition, $return, $instantia if (null !== $definition->getFactory()) { $callable = $definition->getFactory(); if (is_array($callable)) { + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) { + throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $callable[1] ?: 'n/a')); + } + if ($callable[0] instanceof Reference || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) { return sprintf(" $return{$instantiation}%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); @@ -1310,8 +1314,12 @@ private function dumpValue($value, $interpolate = true) } if (is_array($factory)) { + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $factory[1])) { + throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $factory[1] ?: 'n/a')); + } + if (is_string($factory[0])) { - return sprintf('\\%s::%s(%s)', $factory[0], $factory[1], implode(', ', $arguments)); + return sprintf('%s::%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory[0])), $factory[1], implode(', ', $arguments)); } if ($factory[0] instanceof Definition) { @@ -1342,12 +1350,8 @@ private function dumpValue($value, $interpolate = true) if (null === $class) { throw new RuntimeException('Cannot dump definitions which have no class nor factory.'); } - $class = $this->dumpValue($class); - if (false !== strpos($class, '$')) { - throw new RuntimeException('Cannot dump definitions which have a variable class name.'); - } - return sprintf('new \\%s(%s)', substr(str_replace('\\\\', '\\', $class), 1, -1), implode(', ', $arguments)); + return sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)); } elseif ($value instanceof Variable) { return '$'.$value; } elseif ($value instanceof Reference) { @@ -1388,9 +1392,18 @@ private function dumpValue($value, $interpolate = true) * @param string $class * * @return string + * + * @throws RuntimeException */ private function dumpLiteralClass($class) { + if (false !== strpos($class, '$')) { + throw new RuntimeException('Cannot dump definitions which have a variable class name.'); + } + if (0 !== strpos($class, "'") || !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + throw new RuntimeException(sprintf('Cannot dump definition because of invalid class name (%s)', $class ?: 'n/a')); + } + return '\\'.substr(str_replace('\\\\', '\\', $class), 1, -1); } diff --git a/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php b/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php index 1fd1baa477137..6e926fa7a8adc 100644 --- a/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php +++ b/src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php @@ -23,12 +23,12 @@ interface ExtensionInterface /** * Loads a specific configuration. * - * @param array $config An array of configuration values + * @param array $configs An array of configuration values * @param ContainerBuilder $container A ContainerBuilder instance * * @throws \InvalidArgumentException When provided tag is not defined in this extension */ - public function load(array $config, ContainerBuilder $container); + public function load(array $configs, ContainerBuilder $container); /** * Returns the namespace to be used for this extension (XML namespace). diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 063007e5d34e3..7207a143e6f66 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -154,6 +154,31 @@ public function testAddServiceInvalidServiceId() $dumper->dump(); } + /** + * @dataProvider provideInvalidFactories + * @expectedException Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Cannot dump definition + */ + public function testInvalidFactories($factory) + { + $container = new ContainerBuilder(); + $def = new Definition('stdClass'); + $def->setFactory($factory); + $container->setDefinition('bar', $def); + $dumper = new PhpDumper($container); + $dumper->dump(); + } + + public function provideInvalidFactories() + { + return array( + array(array('', 'method')), + array(array('class', '')), + array(array('...', 'method')), + array(array('class', '...')), + ); + } + public function testAliases() { $container = include self::$fixturesPath.'/containers/container9.php'; diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 93a1f0de4c530..a883fcafa60ab 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -192,12 +192,12 @@ public function remove($files) public function chmod($files, $mode, $umask = 0000, $recursive = false) { foreach ($this->toIterator($files) as $file) { - if ($recursive && is_dir($file) && !is_link($file)) { - $this->chmod(new \FilesystemIterator($file), $mode, $umask, true); - } if (true !== @chmod($file, $mode & ~$umask)) { throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file); } + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chmod(new \FilesystemIterator($file), $mode, $umask, true); + } } } @@ -443,13 +443,13 @@ public function mirror($originDir, $targetDir, \Traversable $iterator = null, $o */ public function isAbsolutePath($file) { - return (strspn($file, '/\\', 0, 1) + return strspn($file, '/\\', 0, 1) || (strlen($file) > 3 && ctype_alpha($file[0]) && substr($file, 1, 1) === ':' && (strspn($file, '/\\', 2, 1)) ) || null !== parse_url($file, PHP_URL_SCHEME) - ); + ; } /** diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index 45254c3c712e6..80a17cae35620 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -483,6 +483,22 @@ public function testChmodChangesModeOfTraversableFileObject() $this->assertFilePermissions(753, $directory); } + public function testChmodChangesZeroModeOnSubdirectoriesOnRecursive() + { + $this->markAsSkippedIfChmodIsMissing(); + + $directory = $this->workspace.DIRECTORY_SEPARATOR.'directory'; + $subdirectory = $directory.DIRECTORY_SEPARATOR.'subdirectory'; + + mkdir($directory); + mkdir($subdirectory); + chmod($subdirectory, 0000); + + $this->filesystem->chmod($directory, 0753, 0000, true); + + $this->assertFilePermissions(753, $subdirectory); + } + public function testChown() { $this->markAsSkippedIfPosixIsMissing(); diff --git a/src/Symfony/Component/Finder/Iterator/SortableIterator.php b/src/Symfony/Component/Finder/Iterator/SortableIterator.php index b32ac8d6df4bb..fa3458077acf1 100644 --- a/src/Symfony/Component/Finder/Iterator/SortableIterator.php +++ b/src/Symfony/Component/Finder/Iterator/SortableIterator.php @@ -55,15 +55,15 @@ public function __construct(\Traversable $iterator, $sort) }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { $this->sort = function ($a, $b) { - return ($a->getATime() - $b->getATime()); + return $a->getATime() - $b->getATime(); }; } elseif (self::SORT_BY_CHANGED_TIME === $sort) { $this->sort = function ($a, $b) { - return ($a->getCTime() - $b->getCTime()); + return $a->getCTime() - $b->getCTime(); }; } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { $this->sort = function ($a, $b) { - return ($a->getMTime() - $b->getMTime()); + return $a->getMTime() - $b->getMTime(); }; } elseif (is_callable($sort)) { $this->sort = $sort; diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php index 156735b81751e..b1e6b3bf06b5c 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php @@ -74,6 +74,12 @@ public function __construct($choices, $value = null) $choices = iterator_to_array($choices); } + if (null === $value && $this->castableToString($choices)) { + $value = function ($choice) { + return (string) $choice; + }; + } + if (null !== $value) { // If a deterministic value generator was passed, use it later $this->valueCallback = $value; @@ -207,4 +213,35 @@ protected function flatten(array $choices, $value, &$choicesByValues, &$keysByVa $structuredValues[$key] = $choiceValue; } } + + /** + * Checks whether the given choices can be cast to strings without + * generating duplicates. + * + * @param array $choices The choices. + * @param array|null $cache The cache for previously checked entries. Internal + * + * @return bool Returns true if the choices can be cast to strings and + * false otherwise. + */ + private function castableToString(array $choices, array &$cache = array()) + { + foreach ($choices as $choice) { + if (is_array($choice)) { + if (!$this->castableToString($choice, $cache)) { + return false; + } + + continue; + } elseif (!is_scalar($choice)) { + return false; + } elseif (isset($cache[(string) $choice])) { + return false; + } + + $cache[(string) $choice] = true; + } + + return true; + } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 528834d1e3cb8..1465551b906a6 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -238,6 +238,7 @@ public function finishView(FormView $view, FormInterface $form, array $options) */ public function configureOptions(OptionsResolver $resolver) { + $choiceLabels = (object) array('labels' => array()); $choiceListFactory = $this->choiceListFactory; $emptyData = function (Options $options) { @@ -252,6 +253,45 @@ public function configureOptions(OptionsResolver $resolver) return $options['required'] ? null : ''; }; + // BC closure, to be removed in 3.0 + $choicesNormalizer = function (Options $options, $choices) use ($choiceLabels) { + // Unset labels from previous invocations + $choiceLabels->labels = array(); + + // This closure is irrelevant when "choices_as_values" is set to true + if ($options['choices_as_values']) { + return $choices; + } + + ChoiceType::normalizeLegacyChoices($choices, $choiceLabels); + + return $choices; + }; + + // BC closure, to be removed in 3.0 + $choiceLabel = function (Options $options) use ($choiceLabels) { + // If the choices contain duplicate labels, the normalizer of the + // "choices" option stores them in the $choiceLabels variable + + // Trigger the normalizer + $options->offsetGet('choices'); + + // Pick labels from $choiceLabels if available + if ($choiceLabels->labels) { + // Don't pass the labels by reference. We do want to create a + // copy here so that every form has an own version of that + // variable (contrary to the $choiceLabels object shared by all + // forms) + $labels = $choiceLabels->labels; + + return function ($choice, $key) use ($labels) { + return $labels[$key]; + }; + } + + return; + }; + $choiceListNormalizer = function (Options $options, $choiceList) use ($choiceListFactory) { if ($choiceList) { @trigger_error('The "choice_list" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_loader" instead.', E_USER_DEPRECATED); @@ -322,7 +362,7 @@ public function configureOptions(OptionsResolver $resolver) 'choices' => array(), 'choices_as_values' => false, 'choice_loader' => null, - 'choice_label' => null, + 'choice_label' => $choiceLabel, 'choice_name' => null, 'choice_value' => null, 'choice_attr' => null, @@ -340,6 +380,7 @@ public function configureOptions(OptionsResolver $resolver) 'choice_translation_domain' => true, )); + $resolver->setNormalizer('choices', $choicesNormalizer); $resolver->setNormalizer('choice_list', $choiceListNormalizer); $resolver->setNormalizer('placeholder', $placeholderNormalizer); $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); @@ -454,4 +495,34 @@ private function createChoiceListView(ChoiceListInterface $choiceList, array $op $options['choice_attr'] ); } + + /** + * When "choices_as_values" is set to false, the choices are in the keys and + * their labels in the values. Labels may occur twice. The form component + * flips the choices array in the new implementation, so duplicate labels + * are lost. Store them in a utility array that is used from the + * "choice_label" closure by default. + * + * @param array $choices The choice labels indexed by choices. + * Labels are replaced by generated keys. + * @param object $choiceLabels The object that receives the choice labels + * indexed by generated keys. + * @param int $nextKey The next generated key. + * + * @internal Public only to be accessible from closures on PHP 5.3. Don't + * use this method as it may be removed without notice and will be in 3.0. + */ + public static function normalizeLegacyChoices(array &$choices, $choiceLabels, &$nextKey = 0) + { + foreach ($choices as $choice => $choiceLabel) { + if (is_array($choiceLabel)) { + $choiceLabel = ''; // Dereference $choices[$choice] + self::normalizeLegacyChoices($choices[$choice], $choiceLabels, $nextKey); + continue; + } + + $choiceLabels->labels[$nextKey] = $choiceLabel; + $choices[$choice] = $nextKey++; + } + } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php index 599aa457b7343..8caacb2dc539d 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php @@ -28,6 +28,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) { if ($options['allow_add'] && $options['prototype']) { $prototype = $builder->create($options['prototype_name'], $options['type'], array_replace(array( + 'required' => $options['required'], 'label' => $options['prototype_name'].'label__', ), $options['options'])); $builder->setAttribute('prototype', $prototype->getForm()); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php index 30ee0a0f9e89c..19395a82fe5af 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php @@ -23,7 +23,8 @@ class CountryType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getRegionBundle()->getCountryNames(), + 'choices' => array_flip(Intl::getRegionBundle()->getCountryNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php index b473d139e6566..71b660f9bed63 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php @@ -23,7 +23,8 @@ class CurrencyType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getCurrencyBundle()->getCurrencyNames(), + 'choices' => array_flip(Intl::getCurrencyBundle()->getCurrencyNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index fb3e9a7182fc8..b1eb4382ed3ec 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -88,10 +88,13 @@ public function buildForm(FormBuilderInterface $builder, array $options) if ('choice' === $options['widget']) { // Only pass a subset of the options to children $yearOptions['choices'] = $this->formatTimestamps($formatter, '/y+/', $this->listYears($options['years'])); + $yearOptions['choices_as_values'] = true; $yearOptions['placeholder'] = $options['placeholder']['year']; $monthOptions['choices'] = $this->formatTimestamps($formatter, '/[M|L]+/', $this->listMonths($options['months'])); + $monthOptions['choices_as_values'] = true; $monthOptions['placeholder'] = $options['placeholder']['month']; $dayOptions['choices'] = $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days'])); + $dayOptions['choices_as_values'] = true; $dayOptions['placeholder'] = $options['placeholder']['day']; } @@ -262,6 +265,7 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ { $pattern = $formatter->getPattern(); $timezone = $formatter->getTimezoneId(); + $formattedTimestamps = array(); if ($setTimeZone = PHP_VERSION_ID >= 50500 || method_exists($formatter, 'setTimeZone')) { $formatter->setTimeZone('UTC'); @@ -272,8 +276,8 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ if (preg_match($regex, $pattern, $matches)) { $formatter->setPattern($matches[0]); - foreach ($timestamps as $key => $timestamp) { - $timestamps[$key] = $formatter->format($timestamp); + foreach ($timestamps as $timestamp => $choice) { + $formattedTimestamps[$formatter->format($timestamp)] = $choice; } // I'd like to clone the formatter above, but then we get a @@ -287,7 +291,7 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ $formatter->setTimeZoneId($timezone); } - return $timestamps; + return $formattedTimestamps; } private function listYears(array $years) @@ -296,7 +300,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; } } @@ -308,7 +312,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; @@ -319,7 +323,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 9d071eb8b03ee..1fc0ed1b676f7 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php @@ -23,7 +23,8 @@ class LanguageType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getLanguageBundle()->getLanguageNames(), + 'choices' => array_flip(Intl::getLanguageBundle()->getLanguageNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php index f09f5a62f1e29..1631dc431ad7b 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php @@ -23,7 +23,8 @@ class LocaleType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getLocaleBundle()->getLocaleNames(), + 'choices' => array_flip(Intl::getLocaleBundle()->getLocaleNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index 8002f0b4ee7c1..57c9a44c8f7e5 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -58,19 +58,21 @@ public function buildForm(FormBuilderInterface $builder, array $options) $hours = $minutes = array(); foreach ($options['hours'] as $hour) { - $hours[$hour] = str_pad($hour, 2, '0', STR_PAD_LEFT); + $hours[str_pad($hour, 2, '0', STR_PAD_LEFT)] = $hour; } // Only pass a subset of the options to children $hourOptions['choices'] = $hours; + $hourOptions['choices_as_values'] = true; $hourOptions['placeholder'] = $options['placeholder']['hour']; if ($options['with_minutes']) { foreach ($options['minutes'] as $minute) { - $minutes[$minute] = str_pad($minute, 2, '0', STR_PAD_LEFT); + $minutes[str_pad($minute, 2, '0', STR_PAD_LEFT)] = $minute; } $minuteOptions['choices'] = $minutes; + $minuteOptions['choices_as_values'] = true; $minuteOptions['placeholder'] = $options['placeholder']['minute']; } @@ -78,10 +80,11 @@ public function buildForm(FormBuilderInterface $builder, array $options) $seconds = array(); foreach ($options['seconds'] as $second) { - $seconds[$second] = str_pad($second, 2, '0', STR_PAD_LEFT); + $seconds[str_pad($second, 2, '0', STR_PAD_LEFT)] = $second; } $secondOptions['choices'] = $seconds; + $secondOptions['choices_as_values'] = true; $secondOptions['placeholder'] = $options['placeholder']['second']; } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index 82c07e2f121ba..13c27f9da8c7f 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -23,13 +23,21 @@ class TimezoneType extends AbstractType */ private static $timezones; + /** + * Stores the available timezone choices. + * + * @var array + */ + private static $flippedTimezones; + /** * {@inheritdoc} */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => self::getTimezones(), + 'choices' => self::getFlippedTimezones(), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } @@ -85,4 +93,40 @@ public static function getTimezones() return static::$timezones; } + + /** + * Returns the timezone choices. + * + * The choices are generated from the ICU function + * \DateTimeZone::listIdentifiers(). They are cached during a single request, + * so multiple timezone fields on the same page don't lead to unnecessary + * overhead. + * + * @return array The timezone choices + */ + private static function getFlippedTimezones() + { + if (null === self::$timezones) { + self::$timezones = array(); + + foreach (\DateTimeZone::listIdentifiers() as $timezone) { + $parts = explode('/', $timezone); + + if (count($parts) > 2) { + $region = $parts[0]; + $name = $parts[1].' - '.$parts[2]; + } elseif (count($parts) > 1) { + $region = $parts[0]; + $name = $parts[1]; + } else { + $region = 'Other'; + $name = $parts[0]; + } + + self::$timezones[$region][str_replace('_', ' ', $name)] = $timezone; + } + } + + return self::$timezones; + } } diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php index 4fb08772d7de4..d1375df8894b8 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php @@ -217,7 +217,7 @@ public function getData() return $this->data; } - private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output = null, array &$outputByHash) + private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output, array &$outputByHash) { $hash = spl_object_hash($form); @@ -236,7 +236,7 @@ private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output } } - private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output = null, array &$outputByHash) + private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output, array &$outputByHash) { $viewHash = spl_object_hash($view); $formHash = null; diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index a277b580c5f2a..0f68dc851a58f 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -356,21 +356,11 @@ public function setData($modelData) if (!FormUtil::isEmpty($viewData)) { $dataClass = $this->config->getDataClass(); - $actualType = is_object($viewData) ? 'an instance of class '.get_class($viewData) : 'a(n) '.gettype($viewData); - - if (null === $dataClass && is_object($viewData) && !$viewData instanceof \ArrayAccess) { - $expectedType = 'scalar, array or an instance of \ArrayAccess'; - - throw new LogicException( - 'The form\'s view data is expected to be of type '.$expectedType.', '. - 'but is '.$actualType.'. You '. - 'can avoid this error by setting the "data_class" option to '. - '"'.get_class($viewData).'" or by adding a view transformer '. - 'that transforms '.$actualType.' to '.$expectedType.'.' - ); - } - if (null !== $dataClass && !$viewData instanceof $dataClass) { + $actualType = is_object($viewData) + ? 'an instance of class '.get_class($viewData) + : 'a(n) '.gettype($viewData); + throw new LogicException( 'The form\'s view data is expected to be an instance of class '. $dataClass.', but is '.$actualType.'. You can avoid this error '. 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..0fcfd60e47c1c --- /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', 'date'); + $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', 'text'); + $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', 'text'); + $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', 'text'); + $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', 'text', 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('form', null, array( + 'method' => 'get', + 'action' => 'http://example.com/directory', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + + public function testStartTagWithOverriddenVars() + { + $form = $this->factory->create('form', 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('form', null, array( + 'method' => 'get', + 'action' => 'http://example.com/directory', + )) + ->add('file', 'file') + ->getForm(); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + + public function testStartTagWithExtraAttributes() + { + $form = $this->factory->create('form', 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 7ee2d5b352723..3bafd0ea24591 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php @@ -212,7 +212,8 @@ public function testCheckboxWithValue() public function testSingleChoice() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, )); @@ -234,7 +235,8 @@ public function testSingleChoice() public function testSingleChoiceWithoutTranslation() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, 'choice_translation_domain' => false, @@ -257,7 +259,8 @@ public function testSingleChoiceWithoutTranslation() public function testSingleChoiceAttributes() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => false, @@ -282,7 +285,8 @@ public function testSingleChoiceAttributes() public function testSingleChoiceWithPreferred() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -306,7 +310,8 @@ public function testSingleChoiceWithPreferred() public function testSingleChoiceWithPreferredAndNoSeparator() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -329,7 +334,8 @@ public function testSingleChoiceWithPreferredAndNoSeparator() public function testSingleChoiceWithPreferredAndBlankSeparator() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -353,7 +359,8 @@ public function testSingleChoiceWithPreferredAndBlankSeparator() public function testChoiceWithOnlyPreferred() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&a', '&b'), 'multiple' => false, 'expanded' => false, @@ -370,7 +377,8 @@ public function testChoiceWithOnlyPreferred() public function testSingleChoiceNonRequired() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => false, 'expanded' => false, @@ -394,7 +402,8 @@ public function testSingleChoiceNonRequired() public function testSingleChoiceNonRequiredNoneSelected() { $form = $this->factory->createNamed('name', 'choice', null, array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => false, 'expanded' => false, @@ -418,7 +427,8 @@ public function testSingleChoiceNonRequiredNoneSelected() public function testSingleChoiceNonRequiredWithPlaceholder() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, 'required' => false, @@ -443,7 +453,8 @@ public function testSingleChoiceNonRequiredWithPlaceholder() public function testSingleChoiceRequiredWithPlaceholder() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => false, 'expanded' => false, @@ -468,7 +479,8 @@ public function testSingleChoiceRequiredWithPlaceholder() public function testSingleChoiceRequiredWithPlaceholderViaView() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => false, 'expanded' => false, @@ -493,9 +505,10 @@ public function testSingleChoiceGrouped() { $form = $this->factory->createNamed('name', 'choice', '&a', array( 'choices' => array( - 'Group&1' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), - 'Group&2' => array('&c' => 'Choice&C'), + 'Group&1' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'Group&2' => array('Choice&C' => '&c'), ), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, )); @@ -523,7 +536,8 @@ public function testSingleChoiceGrouped() public function testMultipleChoice() { $form = $this->factory->createNamed('name', 'choice', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => true, 'expanded' => false, @@ -547,7 +561,8 @@ public function testMultipleChoice() public function testMultipleChoiceAttributes() { $form = $this->factory->createNamed('name', 'choice', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'required' => true, 'multiple' => true, @@ -574,7 +589,8 @@ public function testMultipleChoiceAttributes() public function testMultipleChoiceSkipsPlaceholder() { $form = $this->factory->createNamed('name', 'choice', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => false, 'placeholder' => 'Test&Me', @@ -597,7 +613,8 @@ public function testMultipleChoiceSkipsPlaceholder() public function testMultipleChoiceNonRequired() { $form = $this->factory->createNamed('name', 'choice', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => true, 'expanded' => false, @@ -620,7 +637,8 @@ public function testMultipleChoiceNonRequired() public function testSingleChoiceExpanded() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, )); @@ -655,7 +673,8 @@ public function testSingleChoiceExpanded() public function testSingleChoiceExpandedWithoutTranslation() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, 'choice_translation_domain' => false, @@ -691,7 +710,8 @@ public function testSingleChoiceExpandedWithoutTranslation() public function testSingleChoiceExpandedAttributes() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => true, @@ -729,7 +749,8 @@ public function testSingleChoiceExpandedAttributes() public function testSingleChoiceExpandedWithPlaceholder() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, 'placeholder' => 'Test&Me', @@ -774,7 +795,8 @@ public function testSingleChoiceExpandedWithPlaceholder() public function testSingleChoiceExpandedWithBooleanValue() { $form = $this->factory->createNamed('name', 'choice', true, array( - 'choices' => array('1' => 'Choice&A', '0' => 'Choice&B'), + 'choices' => array('Choice&A' => '1', 'Choice&B' => '0'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, )); @@ -809,7 +831,8 @@ public function testSingleChoiceExpandedWithBooleanValue() public function testMultipleChoiceExpanded() { $form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => true, 'required' => true, @@ -854,7 +877,8 @@ public function testMultipleChoiceExpanded() public function testMultipleChoiceExpandedWithoutTranslation() { $form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => true, 'required' => true, @@ -900,7 +924,8 @@ public function testMultipleChoiceExpandedWithoutTranslation() public function testMultipleChoiceExpandedAttributes() { $form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => true, 'expanded' => true, diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index 45b2f311c2942..7eeeac0a029cc 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -692,7 +692,8 @@ public function testCollectionRowWithCustomBlock() public function testChoiceRowWithCustomBlock() { $form = $this->factory->createNamedBuilder('name_c', 'choice', 'a', array( - 'choices' => array('a' => 'ChoiceA', 'b' => 'ChoiceB'), + 'choices' => array('ChoiceA' => 'a', 'ChoiceB' => 'b'), + 'choices_as_values' => true, 'expanded' => true, )) ->getForm(); diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 64ecec9b49dfa..0939a2b003a9c 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -497,7 +497,8 @@ public function testCheckboxWithValue() public function testSingleChoice() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, )); @@ -530,7 +531,8 @@ public function testSingleChoice() public function testSingleChoiceWithoutTranslation() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, 'choice_translation_domain' => false, @@ -552,7 +554,8 @@ public function testSingleChoiceWithoutTranslation() public function testSingleChoiceAttributes() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => false, @@ -576,7 +579,8 @@ public function testSingleChoiceAttributes() public function testSingleChoiceWithPreferred() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -599,7 +603,8 @@ public function testSingleChoiceWithPreferred() public function testSingleChoiceWithPreferredAndNoSeparator() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -621,7 +626,8 @@ public function testSingleChoiceWithPreferredAndNoSeparator() public function testSingleChoiceWithPreferredAndBlankSeparator() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&b'), 'multiple' => false, 'expanded' => false, @@ -644,7 +650,8 @@ public function testSingleChoiceWithPreferredAndBlankSeparator() public function testChoiceWithOnlyPreferred() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'preferred_choices' => array('&a', '&b'), 'multiple' => false, 'expanded' => false, @@ -660,7 +667,8 @@ public function testChoiceWithOnlyPreferred() public function testSingleChoiceNonRequired() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => false, 'expanded' => false, @@ -683,7 +691,8 @@ public function testSingleChoiceNonRequired() public function testSingleChoiceNonRequiredNoneSelected() { $form = $this->factory->createNamed('name', 'choice', null, array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => false, 'expanded' => false, @@ -706,7 +715,8 @@ public function testSingleChoiceNonRequiredNoneSelected() public function testSingleChoiceNonRequiredWithPlaceholder() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, 'required' => false, @@ -730,7 +740,8 @@ public function testSingleChoiceNonRequiredWithPlaceholder() public function testSingleChoiceRequiredWithPlaceholder() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => false, 'expanded' => false, @@ -757,7 +768,8 @@ public function testSingleChoiceRequiredWithPlaceholder() public function testSingleChoiceRequiredWithPlaceholderViaView() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => false, 'expanded' => false, @@ -784,9 +796,10 @@ public function testSingleChoiceGrouped() { $form = $this->factory->createNamed('name', 'choice', '&a', array( 'choices' => array( - 'Group&1' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), - 'Group&2' => array('&c' => 'Choice&C'), + 'Group&1' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'Group&2' => array('Choice&C' => '&c'), ), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => false, )); @@ -813,7 +826,8 @@ public function testSingleChoiceGrouped() public function testMultipleChoice() { $form = $this->factory->createNamed('name', 'choice', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => true, 'multiple' => true, 'expanded' => false, @@ -836,7 +850,8 @@ public function testMultipleChoice() public function testMultipleChoiceAttributes() { $form = $this->factory->createNamed('name', 'choice', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'required' => true, 'multiple' => true, @@ -862,7 +877,8 @@ public function testMultipleChoiceAttributes() public function testMultipleChoiceSkipsPlaceholder() { $form = $this->factory->createNamed('name', 'choice', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => false, 'placeholder' => 'Test&Me', @@ -884,7 +900,8 @@ public function testMultipleChoiceSkipsPlaceholder() public function testMultipleChoiceNonRequired() { $form = $this->factory->createNamed('name', 'choice', array('&a'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'required' => false, 'multiple' => true, 'expanded' => false, @@ -906,7 +923,8 @@ public function testMultipleChoiceNonRequired() public function testSingleChoiceExpanded() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, )); @@ -928,7 +946,8 @@ public function testSingleChoiceExpanded() public function testSingleChoiceExpandedWithoutTranslation() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, 'choice_translation_domain' => false, @@ -951,7 +970,8 @@ public function testSingleChoiceExpandedWithoutTranslation() public function testSingleChoiceExpandedAttributes() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => false, 'expanded' => true, @@ -976,7 +996,8 @@ public function testSingleChoiceExpandedAttributes() public function testSingleChoiceExpandedWithPlaceholder() { $form = $this->factory->createNamed('name', 'choice', '&a', array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, 'placeholder' => 'Test&Me', @@ -1001,7 +1022,8 @@ public function testSingleChoiceExpandedWithPlaceholder() public function testSingleChoiceExpandedWithBooleanValue() { $form = $this->factory->createNamed('name', 'choice', true, array( - 'choices' => array('1' => 'Choice&A', '0' => 'Choice&B'), + 'choices' => array('Choice&A' => '1', 'Choice&B' => '0'), + 'choices_as_values' => true, 'multiple' => false, 'expanded' => true, )); @@ -1023,7 +1045,8 @@ public function testSingleChoiceExpandedWithBooleanValue() public function testMultipleChoiceExpanded() { $form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => true, 'required' => true, @@ -1048,7 +1071,8 @@ public function testMultipleChoiceExpanded() public function testMultipleChoiceExpandedWithoutTranslation() { $form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'multiple' => true, 'expanded' => true, 'required' => true, @@ -1074,7 +1098,8 @@ public function testMultipleChoiceExpandedWithoutTranslation() public function testMultipleChoiceExpandedAttributes() { $form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array( - 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'), + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c'), + 'choices_as_values' => true, 'choice_attr' => array('Choice&B' => array('class' => 'foo&bar')), 'multiple' => true, 'expanded' => true, diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php index 50d4df8a9b7cb..03cb7fce5705a 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php @@ -65,6 +65,40 @@ public function testCreateChoiceListWithValueCallback() $this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz'))); } + public function testCreateChoiceListWithoutValueCallbackAndDuplicateFreeToStringChoices() + { + $choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 123)); + + $this->assertSame(array('foo', 'bar', '123'), $choiceList->getValues()); + $this->assertSame(array('foo' => 'foo', 'bar' => 'bar', '123' => 123), $choiceList->getChoices()); + $this->assertSame(array('foo' => 2, 'bar' => 7, '123' => 10), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'foo', 2 => 123), $choiceList->getChoicesForValues(array(1 => 'foo', 2 => '123'))); + $this->assertSame(array(1 => 'foo', 2 => '123'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 123))); + } + + public function testCreateChoiceListWithoutValueCallbackAndToStringDuplicates() + { + $choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => '123', 10 => 123)); + + $this->assertSame(array('0', '1', '2'), $choiceList->getValues()); + $this->assertSame(array('0' => 'foo', '1' => '123', '2' => 123), $choiceList->getChoices()); + $this->assertSame(array('0' => 2, '1' => 7, '2' => 10), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'foo', 2 => 123), $choiceList->getChoicesForValues(array(1 => '0', 2 => '2'))); + $this->assertSame(array(1 => '0', 2 => '2'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 123))); + } + + public function testCreateChoiceListWithoutValueCallbackAndMixedChoices() + { + $object = new \stdClass(); + $choiceList = new ArrayChoiceList(array(2 => 'foo', 5 => array(7 => '123'), 10 => $object)); + + $this->assertSame(array('0', '1', '2'), $choiceList->getValues()); + $this->assertSame(array('0' => 'foo', '1' => '123', '2' => $object), $choiceList->getChoices()); + $this->assertSame(array('0' => 2, '1' => 7, '2' => 10), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'foo', 2 => $object), $choiceList->getChoicesForValues(array(1 => '0', 2 => '2'))); + $this->assertSame(array(1 => '0', 2 => '2'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => $object))); + } + public function testCreateChoiceListWithGroupedChoices() { $choiceList = new ArrayChoiceList(array( @@ -72,15 +106,15 @@ public function testCreateChoiceListWithGroupedChoices() 'Group 2' => array('C' => 'c', 'D' => 'd'), )); - $this->assertSame(array('0', '1', '2', '3'), $choiceList->getValues()); + $this->assertSame(array('a', 'b', 'c', 'd'), $choiceList->getValues()); $this->assertSame(array( - 'Group 1' => array('A' => '0', 'B' => '1'), - 'Group 2' => array('C' => '2', 'D' => '3'), + 'Group 1' => array('A' => 'a', 'B' => 'b'), + 'Group 2' => array('C' => 'c', 'D' => 'd'), ), $choiceList->getStructuredValues()); - $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $choiceList->getChoices()); - $this->assertSame(array(0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D'), $choiceList->getOriginalKeys()); - $this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getChoicesForValues(array(1 => '0', 2 => '1'))); - $this->assertSame(array(1 => '0', 2 => '1'), $choiceList->getValuesForChoices(array(1 => 'a', 2 => 'b'))); + $this->assertSame(array('a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => 'd'), $choiceList->getChoices()); + $this->assertSame(array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getChoicesForValues(array(1 => 'a', 2 => 'b'))); + $this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getValuesForChoices(array(1 => 'a', 2 => 'b'))); } public function testCompareChoicesByIdentityByDefault() diff --git a/src/Symfony/Component/Form/Tests/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 215ddac936a54..b404405d191d4 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -18,14 +18,14 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase { private $choices = array( - 'a' => 'Bernhard', - 'b' => 'Fabien', - 'c' => 'Kris', - 'd' => 'Jon', - 'e' => 'Roman', + 'Bernhard' => 'a', + 'Fabien' => 'b', + 'Kris' => 'c', + 'Jon' => 'd', + 'Roman' => 'e', ); - private $numericChoices = array( + private $numericChoicesFlipped = array( 0 => 'Bernhard', 1 => 'Fabien', 2 => 'Kris', @@ -36,6 +36,18 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase private $objectChoices; protected $groupedChoices = array( + 'Symfony' => array( + 'Bernhard' => 'a', + 'Fabien' => 'b', + 'Kris' => 'c', + ), + 'Doctrine' => array( + 'Jon' => 'd', + 'Roman' => 'e', + ), + ); + + protected $groupedChoicesFlipped = array( 'Symfony' => array( 'a' => 'Bernhard', 'b' => 'Fabien', @@ -107,6 +119,20 @@ public function testExpandedChoicesOptionsTurnIntoChildren() $form = $this->factory->create('choice', null, array( 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, + )); + + $this->assertCount(count($this->choices), $form, 'Each choice should become a new field'); + } + + /** + * @group legacy + */ + public function testExpandedFlippedChoicesOptionsTurnIntoChildren() + { + $form = $this->factory->create('choice', null, array( + 'expanded' => true, + 'choices' => array_flip($this->choices), )); $this->assertCount(count($this->choices), $form, 'Each choice should become a new field'); @@ -119,6 +145,7 @@ public function testPlaceholderPresentOnNonRequiredExpandedSingleChoice() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $this->assertTrue(isset($form['placeholder'])); @@ -132,6 +159,7 @@ public function testPlaceholderNotPresentIfRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $this->assertFalse(isset($form['placeholder'])); @@ -145,6 +173,7 @@ public function testPlaceholderNotPresentIfMultiple() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $this->assertFalse(isset($form['placeholder'])); @@ -158,9 +187,10 @@ public function testPlaceholderNotPresentIfEmptyChoice() 'expanded' => true, 'required' => false, 'choices' => array( - '' => 'Empty', - 1 => 'Not empty', + 'Empty' => '', + 'Not empty' => 1, ), + 'choices_as_values' => true, )); $this->assertFalse(isset($form['placeholder'])); @@ -172,6 +202,29 @@ public function testExpandedChoicesOptionsAreFlattened() $form = $this->factory->create('choice', null, array( 'expanded' => true, 'choices' => $this->groupedChoices, + 'choices_as_values' => true, + )); + + $flattened = array(); + foreach ($this->groupedChoices as $choices) { + $flattened = array_merge($flattened, array_keys($choices)); + } + + $this->assertCount($form->count(), $flattened, 'Each nested choice should become a new field, not the groups'); + + foreach ($flattened as $value => $choice) { + $this->assertTrue($form->has($value), 'Flattened choice is named after it\'s value'); + } + } + + /** + * @group legacy + */ + public function testExpandedChoicesFlippedOptionsAreFlattened() + { + $form = $this->factory->create('choice', null, array( + 'expanded' => true, + 'choices' => $this->groupedChoicesFlipped, )); $flattened = array(); @@ -219,6 +272,7 @@ public function testExpandedCheckboxesAreNeverRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); foreach ($form as $child) { @@ -233,6 +287,7 @@ public function testExpandedRadiosAreRequiredIfChoiceChildIsRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); foreach ($form as $child) { @@ -247,6 +302,7 @@ public function testExpandedRadiosAreNotRequiredIfChoiceChildIsNotRequired() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); foreach ($form as $child) { @@ -260,6 +316,7 @@ public function testSubmitSingleNonExpanded() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('b'); @@ -275,6 +332,7 @@ public function testSubmitSingleNonExpandedInvalidChoice() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -290,6 +348,7 @@ public function testSubmitSingleNonExpandedNull() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(null); @@ -308,6 +367,7 @@ public function testSubmitSingleNonExpandedNullNoChoices() 'multiple' => false, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(null); @@ -323,6 +383,7 @@ public function testSubmitSingleNonExpandedEmpty() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(''); @@ -338,8 +399,9 @@ public function testSubmitSingleNonExpandedEmptyExplicitEmptyChoice() 'multiple' => false, 'expanded' => false, 'choices' => array( - 'EMPTY_CHOICE' => 'Empty', + 'Empty' => 'EMPTY_CHOICE', ), + 'choices_as_values' => true, 'choice_value' => function () { return ''; }, @@ -361,6 +423,7 @@ public function testSubmitSingleNonExpandedEmptyNoChoices() 'multiple' => false, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(''); @@ -376,6 +439,7 @@ public function testSubmitSingleNonExpandedFalse() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(false); @@ -394,6 +458,7 @@ public function testSubmitSingleNonExpandedFalseNoChoices() 'multiple' => false, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(false); @@ -455,6 +520,7 @@ public function testSubmitMultipleNonExpanded() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'b')); @@ -470,6 +536,7 @@ public function testSubmitMultipleNonExpandedEmpty() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array()); @@ -488,6 +555,7 @@ public function testSubmitMultipleNonExpandedEmptyNoChoices() 'multiple' => true, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(array()); @@ -503,6 +571,7 @@ public function testSubmitMultipleNonExpandedInvalidScalarChoice() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -518,6 +587,7 @@ public function testSubmitMultipleNonExpandedInvalidArrayChoice() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'foobar')); @@ -578,6 +648,7 @@ public function testSubmitSingleExpandedRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('b'); @@ -606,6 +677,7 @@ public function testSubmitSingleExpandedRequiredInvalidChoice() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -634,6 +706,7 @@ public function testSubmitSingleExpandedNonRequired() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('b'); @@ -664,6 +737,7 @@ public function testSubmitSingleExpandedNonRequiredInvalidChoice() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -692,6 +766,7 @@ public function testSubmitSingleExpandedRequiredNull() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(null); @@ -723,6 +798,7 @@ public function testSubmitSingleExpandedRequiredNullNoChoices() 'expanded' => true, 'required' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(null); @@ -740,6 +816,7 @@ public function testSubmitSingleExpandedRequiredEmpty() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(''); @@ -771,6 +848,7 @@ public function testSubmitSingleExpandedRequiredEmptyNoChoices() 'expanded' => true, 'required' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(''); @@ -788,6 +866,7 @@ public function testSubmitSingleExpandedRequiredFalse() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(false); @@ -819,6 +898,7 @@ public function testSubmitSingleExpandedRequiredFalseNoChoices() 'expanded' => true, 'required' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(false); @@ -836,6 +916,7 @@ public function testSubmitSingleExpandedNonRequiredNull() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(null); @@ -869,6 +950,7 @@ public function testSubmitSingleExpandedNonRequiredNullNoChoices() 'expanded' => true, 'required' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(null); @@ -886,6 +968,7 @@ public function testSubmitSingleExpandedNonRequiredEmpty() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(''); @@ -919,6 +1002,7 @@ public function testSubmitSingleExpandedNonRequiredEmptyNoChoices() 'expanded' => true, 'required' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(''); @@ -936,6 +1020,7 @@ public function testSubmitSingleExpandedNonRequiredFalse() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(false); @@ -969,6 +1054,7 @@ public function testSubmitSingleExpandedNonRequiredFalseNoChoices() 'expanded' => true, 'required' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(false); @@ -985,9 +1071,10 @@ public function testSubmitSingleExpandedWithEmptyChild() 'multiple' => false, 'expanded' => true, 'choices' => array( - '' => 'Empty', - 1 => 'Not empty', + 'Empty' => '', + 'Not empty' => 1, ), + 'choices_as_values' => true, )); $form->submit(''); @@ -1065,12 +1152,15 @@ public function testLegacySubmitSingleExpandedObjectChoices() $this->assertNull($form[4]->getViewData()); } - public function testSubmitSingleExpandedNumericChoices() + /** + * @group legacy + */ + public function testSubmitSingleExpandedNumericChoicesFlipped() { $form = $this->factory->create('choice', null, array( 'multiple' => false, 'expanded' => true, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->submit('1'); @@ -1096,6 +1186,7 @@ public function testSubmitMultipleExpanded() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'c')); @@ -1123,6 +1214,7 @@ public function testSubmitMultipleExpandedInvalidScalarChoice() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -1150,6 +1242,7 @@ public function testSubmitMultipleExpandedInvalidArrayChoice() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'foobar')); @@ -1177,6 +1270,7 @@ public function testSubmitMultipleExpandedEmpty() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array()); @@ -1205,6 +1299,7 @@ public function testSubmitMultipleExpandedEmptyNoChoices() 'multiple' => true, 'expanded' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(array()); @@ -1219,10 +1314,11 @@ public function testSubmitMultipleExpandedWithEmptyChild() 'multiple' => true, 'expanded' => true, 'choices' => array( - '' => 'Empty', - 1 => 'Not Empty', - 2 => 'Not Empty 2', + 'Empty' => '', + 'Not Empty' => 1, + 'Not Empty 2' => 2, ), + 'choices_as_values' => true, )); $form->submit(array('', '2')); @@ -1302,12 +1398,15 @@ public function testLegacySubmitMultipleExpandedObjectChoices() $this->assertNull($form[4]->getViewData()); } + /** + * @group legacy + */ public function testSubmitMultipleExpandedNumericChoices() { $form = $this->factory->create('choice', null, array( 'multiple' => true, 'expanded' => true, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->submit(array('1', '2')); @@ -1363,16 +1462,18 @@ public function testMultipleSelectedObjectChoices() $this->assertFalse($selectedChecker($view->vars['choices'][1]->value, $view->vars['value'])); } - /* + /** * We need this functionality to create choice fields for Boolean types, - * e.g. false => 'No', true => 'Yes' + * e.g. false => 'No', true => 'Yes'. + * + * @group legacy */ public function testSetDataSingleNonExpandedAcceptsBoolean() { $form = $this->factory->create('choice', null, array( 'multiple' => false, 'expanded' => false, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->setData(false); @@ -1382,12 +1483,15 @@ public function testSetDataSingleNonExpandedAcceptsBoolean() $this->assertTrue($form->isSynchronized()); } + /** + * @group legacy + */ public function testSetDataMultipleNonExpandedAcceptsBoolean() { $form = $this->factory->create('choice', null, array( 'multiple' => true, 'expanded' => false, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->setData(array(false, true)); @@ -1401,6 +1505,7 @@ public function testPassRequiredToView() { $form = $this->factory->create('choice', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1412,6 +1517,7 @@ public function testPassNonRequiredToView() $form = $this->factory->create('choice', null, array( 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1423,6 +1529,7 @@ public function testPassMultipleToView() $form = $this->factory->create('choice', null, array( 'multiple' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1434,6 +1541,7 @@ public function testPassExpandedToView() $form = $this->factory->create('choice', null, array( 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1444,6 +1552,7 @@ public function testPassChoiceTranslationDomainToView() { $form = $this->factory->create('choice', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1454,6 +1563,7 @@ public function testChoiceTranslationDomainWithTrueValueToView() { $form = $this->factory->create('choice', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, 'choice_translation_domain' => true, )); $view = $form->createView(); @@ -1465,6 +1575,7 @@ public function testDefaultChoiceTranslationDomainIsSameAsTranslationDomainToVie { $form = $this->factory->create('choice', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, 'translation_domain' => 'foo', )); $view = $form->createView(); @@ -1491,6 +1602,7 @@ public function testPlaceholderIsNullByDefaultIfRequired() 'multiple' => false, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1503,6 +1615,7 @@ public function testPlaceholderIsEmptyStringByDefaultIfNotRequired() 'multiple' => false, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1520,6 +1633,7 @@ public function testPassPlaceholderToView($multiple, $expanded, $required, $plac 'required' => $required, 'placeholder' => $placeholder, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1539,6 +1653,7 @@ public function testPassEmptyValueBC($multiple, $expanded, $required, $placehold 'required' => $required, 'empty_value' => $placeholder, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1558,7 +1673,8 @@ public function testDontPassPlaceholderIfContainedInChoices($multiple, $expanded 'expanded' => $expanded, 'required' => $required, 'placeholder' => $placeholder, - 'choices' => array('a' => 'A', '' => 'Empty'), + 'choices' => array('A' => 'a', 'Empty' => ''), + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1612,9 +1728,10 @@ public function getOptionsWithPlaceholder() public function testPassChoicesToView() { - $choices = array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'); + $choices = array('A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd'); $form = $this->factory->create('choice', null, array( 'choices' => $choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1628,9 +1745,10 @@ public function testPassChoicesToView() public function testPassPreferredChoicesToView() { - $choices = array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'); + $choices = array('A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd'); $form = $this->factory->create('choice', null, array( 'choices' => $choices, + 'choices_as_values' => true, 'preferred_choices' => array('b', 'd'), )); $view = $form->createView(); @@ -1649,6 +1767,7 @@ public function testPassHierarchicalChoicesToView() { $form = $this->factory->create('choice', null, array( 'choices' => $this->groupedChoices, + 'choices_as_values' => true, 'preferred_choices' => array('b', 'd'), )); $view = $form->createView(); @@ -1694,12 +1813,30 @@ public function testPassChoiceDataToView() ), $view->vars['choices']); } + /** + * @group legacy + */ + public function testDuplicateChoiceLabels() + { + $form = $this->factory->create('choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'A'), + )); + $view = $form->createView(); + + $this->assertEquals(array( + new ChoiceView('a', 'a', 'A'), + new ChoiceView('b', 'b', 'B'), + new ChoiceView('c', 'c', 'A'), + ), $view->vars['choices']); + } + public function testAdjustFullNameForMultipleNonExpanded() { $form = $this->factory->createNamed('name', 'choice', null, array( 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1711,6 +1848,7 @@ public function testInitializeWithEmptyChoices() { $this->factory->createNamed('name', 'choice', null, array( 'choices' => array(), + 'choices_as_values' => true, )); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php index 589ccdbb7ac21..efe241d84d4c9 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php @@ -274,4 +274,30 @@ public function testPrototypeDefaultLabel() $this->assertSame('__test__label__', $form->createView()->vars['prototype']->vars['label']); } + + public function testPrototypeDefaultRequired() + { + $form = $this->factory->create('collection', array(), array( + 'type' => 'file', + 'allow_add' => true, + 'prototype' => true, + 'prototype_name' => '__test__', + )); + + $this->assertTrue($form->createView()->vars['prototype']->vars['required']); + } + + public function testPrototypeSetNotRequired() + { + $form = $this->factory->create('collection', array(), array( + 'type' => 'file', + 'allow_add' => true, + 'prototype' => true, + 'prototype_name' => '__test__', + 'required' => false, + )); + + $this->assertFalse($form->createView()->vars['required'], 'collection is not required'); + $this->assertFalse($form->createView()->vars['prototype']->vars['required'], '"prototype" should not be required'); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/CsrfProvider/LegacyDefaultCsrfProviderTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/CsrfProvider/LegacyDefaultCsrfProviderTest.php index 629d8e4f327a5..29e370df096fd 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/CsrfProvider/LegacyDefaultCsrfProviderTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/CsrfProvider/LegacyDefaultCsrfProviderTest.php @@ -26,7 +26,6 @@ public static function setUpBeforeClass() { ini_set('session.save_handler', 'files'); ini_set('session.save_path', sys_get_temp_dir()); - ini_set('error_reporting', -1 & ~E_USER_DEPRECATED); } protected function setUp() diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php index 6fb24a8cdc9c7..ae174f045d773 100644 --- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php +++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php @@ -840,19 +840,19 @@ public function testGetPropertyPathDefaultsToIndexedNameIfDataClassOfFirstParent $this->assertEquals(new PropertyPath('[name]'), $form->getPropertyPath()); } - /** - * @expectedException \Symfony\Component\Form\Exception\LogicException - */ - public function testViewDataMustNotBeObjectIfDataClassIsNull() + public function testViewDataMayBeObjectIfDataClassIsNull() { + $object = new \stdClass(); $config = new FormConfigBuilder('name', null, $this->dispatcher); $config->addViewTransformer(new FixedDataTransformer(array( '' => '', - 'foo' => new \stdClass(), + 'foo' => $object, ))); $form = new Form($config); $form->setData('foo'); + + $this->assertSame($object, $form->getViewData()); } public function testViewDataMayBeArrayAccessIfDataClassIsNull() diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index ca407b4d93452..108f0402e8f95 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -193,6 +193,10 @@ public function prepare(Request $request) // Use X-Sendfile, do not send any content. $type = $request->headers->get('X-Sendfile-Type'); $path = $this->file->getRealPath(); + // Fall back to scheme://path for stream wrapped locations. + if (false === $path) { + $path = $this->file->getPathname(); + } if (strtolower($type) == 'x-accel-redirect') { // Do X-Accel-Mapping substitutions. // @link http://wiki.nginx.org/X-accel#X-Accel-Redirect diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index 690535157135a..97e11516d9aaa 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -329,16 +329,16 @@ public function sendHeaders() return $this; } - // 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) { - header($name.': '.$value, false, $this->statusCode); + header($name.': '.$value, false); } } + // 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()); @@ -972,7 +972,7 @@ public function getVary() * Sets the Vary header. * * @param string|array $headers - * @param bool $replace Whether to replace the actual value of not (true by default) + * @param bool $replace Whether to replace the actual value or not (true by default) * * @return Response */ diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php index 1036818277e1f..463677b55acfe 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php @@ -52,7 +52,7 @@ public function getSaveHandlerName() */ public function isSessionHandlerInterface() { - return ($this instanceof \SessionHandlerInterface); + return $this instanceof \SessionHandlerInterface; } /** diff --git a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php index 550014dc73f38..d7744ffd6ddf1 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php @@ -153,13 +153,16 @@ public function provideInvalidRanges() ); } - public function testXSendfile() + /** + * @dataProvider provideXSendfileFiles + */ + public function testXSendfile($file) { $request = Request::create('/'); $request->headers->set('X-Sendfile-Type', 'X-Sendfile'); BinaryFileResponse::trustXSendfileTypeHeader(); - $response = BinaryFileResponse::create(__DIR__.'/../README.md', 200, array('Content-Type' => 'application/octet-stream')); + $response = BinaryFileResponse::create($file, 200, array('Content-Type' => 'application/octet-stream')); $response->prepare($request); $this->expectOutputString(''); @@ -168,6 +171,14 @@ public function testXSendfile() $this->assertContains('README.md', $response->headers->get('X-Sendfile')); } + public function provideXSendfileFiles() + { + return array( + array(__DIR__.'/../README.md'), + array('file://'.__DIR__.'/../README.md'), + ); + } + /** * @dataProvider getSampleXAccelMappings */ diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index f13f4791d93c2..f4ddb902b5786 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -340,7 +340,7 @@ public function getFormatToMimeTypeMapProvider() array('json', array('application/json', 'application/x-json')), array('xml', array('text/xml', 'application/xml', 'application/x-xml')), array('rdf', array('application/rdf+xml')), - array('atom',array('application/atom+xml')), + array('atom', array('application/atom+xml')), ); } diff --git a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php index ebec7ff4e7971..bea27c387a705 100644 --- a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php +++ b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php @@ -166,6 +166,10 @@ public function registerCommands(Application $application) return; } + if (!class_exists('Symfony\Component\Finder\Finder')) { + throw new \RuntimeException('You need the symfony/finder component to register bundle commands.'); + } + $finder = new Finder(); $finder->files()->name('*Command.php')->in($dir); diff --git a/src/Symfony/Component/HttpKernel/Client.php b/src/Symfony/Component/HttpKernel/Client.php index b344fa151a34e..80b1bd6cd34e7 100644 --- a/src/Symfony/Component/HttpKernel/Client.php +++ b/src/Symfony/Component/HttpKernel/Client.php @@ -105,7 +105,7 @@ protected function getScript($request) $code = <<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 c29f9ac93d8fd..eeb63491559e8 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -58,11 +58,11 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '2.7.7'; - const VERSION_ID = 20707; + const VERSION = '2.7.8'; + const VERSION_ID = 20708; const MAJOR_VERSION = 2; const MINOR_VERSION = 7; - const RELEASE_VERSION = 7; + const RELEASE_VERSION = 8; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '05/2018'; diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php index 32db4e47adc5a..c298c84ec0059 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php @@ -224,7 +224,7 @@ protected function controllerMethod2($foo, $bar = null) { } - protected function controllerMethod3($foo, $bar = null, $foobar) + protected function controllerMethod3($foo, $bar, $foobar) { } diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php index 3c3410e879a9a..84d5179e576be 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/FullTransformer.php @@ -214,7 +214,7 @@ public function getReverseMatchingRegExp($pattern) */ public function isQuoteMatch($quoteMatch) { - return ("'" === $quoteMatch[0]); + return "'" === $quoteMatch[0]; } /** diff --git a/src/Symfony/Component/Process/PhpProcess.php b/src/Symfony/Component/Process/PhpProcess.php index 4a2a2625ffae0..42ecb66f2bd4d 100644 --- a/src/Symfony/Component/Process/PhpProcess.php +++ b/src/Symfony/Component/Process/PhpProcess.php @@ -46,6 +46,12 @@ public function __construct($script, $cwd = null, array $env = null, $timeout = $php .= ' '.ProcessUtils::escapeArgument($file); $script = null; } + if ('\\' !== DIRECTORY_SEPARATOR && null !== $php) { + // exec is mandatory to deal with sending a signal to the process + // see https://github.com/symfony/symfony/issues/5030 about prepending + // command with exec + $php = 'exec '.$php; + } 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 78a0195f7aa07..b63651b9b5e49 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -54,7 +54,7 @@ class Process private $idleTimeout; private $options; private $exitcode; - private $fallbackExitcode; + private $fallbackStatus = array(); private $processInformation; private $outputDisabled = false; private $stdout; @@ -169,8 +169,7 @@ public function __construct($commandline, $cwd = null, array $env = null, $input public function __destruct() { - // stop() will check if we have a process running. - $this->stop(); + $this->stop(0); } public function __clone() @@ -219,7 +218,7 @@ public function run($callback = null) */ public function mustRun($callback = null) { - if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); } @@ -278,11 +277,14 @@ public function start($callback = null) if (!isset($this->options['bypass_shell'])) { $this->options['bypass_shell'] = true; } - } + } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors[3] = array('pipe', 'w'); - $ptsWorkaround = null; + // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input + $commandline = '{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; + $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code'; - 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'); @@ -290,15 +292,15 @@ public function start($callback = null) $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.'); } $this->status = self::STATUS_STARTED; + if (isset($descriptors[3])) { + $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); + } + if ($this->tty) { return; } @@ -380,17 +382,9 @@ public function wait($callback = null) * Returns the Pid (process identifier), if applicable. * * @return int|null The process id if running, null otherwise - * - * @throws RuntimeException In case --enable-sigchild is activated */ public function getPid() { - if ($this->isSigchildEnabled()) { - throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); - } - - $this->updateStatus(false); - return $this->isRunning() ? $this->processInformation['pid'] : null; } @@ -402,7 +396,7 @@ public function getPid() * @return Process * * @throws LogicException In case the process is not running - * @throws RuntimeException In case --enable-sigchild is activated + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed * @throws RuntimeException In case of failure */ public function signal($signal) @@ -596,7 +590,7 @@ public function clearErrorOutput() */ public function getExitCode() { - if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); } @@ -613,8 +607,6 @@ public function getExitCode() * * @return null|string A string representation for the exit status code, null if the Process is not terminated. * - * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled - * * @see http://tldp.org/LDP/abs/html/exitcodes.html * @see http://en.wikipedia.org/wiki/Unix_signal */ @@ -651,12 +643,10 @@ public function hasBeenSignaled() { $this->requireProcessIsTerminated(__FUNCTION__); - if ($this->isSigchildEnabled()) { + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); } - $this->updateStatus(false); - return $this->processInformation['signaled']; } @@ -674,12 +664,10 @@ public function getTermSignal() { $this->requireProcessIsTerminated(__FUNCTION__); - if ($this->isSigchildEnabled()) { + if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) { throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); } - $this->updateStatus(false); - return $this->processInformation['termsig']; } @@ -696,8 +684,6 @@ public function hasBeenStopped() { $this->requireProcessIsTerminated(__FUNCTION__); - $this->updateStatus(false); - return $this->processInformation['stopped']; } @@ -714,8 +700,6 @@ public function getStopSignal() { $this->requireProcessIsTerminated(__FUNCTION__); - $this->updateStatus(false); - return $this->processInformation['stopsig']; } @@ -775,36 +759,24 @@ 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 { usleep(1000); } 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); - } + if ($this->isRunning()) { + // 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); } } @@ -1240,14 +1212,7 @@ public static function isPtySupported() return $result = false; } - $proc = @proc_open('echo 1', array(array('pty'), array('pty'), array('pty')), $pipes); - if (is_resource($proc)) { - proc_close($proc); - - return $result = true; - } - - return $result = false; + return $result = (bool) @proc_open('echo 1', array(array('pty'), array('pty'), array('pty')), $pipes); } /** @@ -1262,16 +1227,8 @@ private function getDescriptors() } else { $this->processPipes = UnixPipes::create($this, $this->input); } - $descriptors = $this->processPipes->getDescriptors($this->outputDisabled); - - if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { - // last exit code is output on the fourth pipe and caught to work around --enable-sigchild - $descriptors = array_merge($descriptors, array(array('pipe', 'w'))); - - $this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code'; - } - return $descriptors; + return $this->processPipes->getDescriptors($this->outputDisabled); } /** @@ -1315,10 +1272,13 @@ protected function updateStatus($blocking) } $this->processInformation = proc_get_status($this->process); - $this->captureExitCode(); $this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + $this->processInformation = $this->fallbackStatus + $this->processInformation; + } + if (!$this->processInformation['running']) { $this->close(); } @@ -1335,7 +1295,7 @@ protected function isSigchildEnabled() return self::$sigchild; } - if (!function_exists('phpinfo')) { + if (!function_exists('phpinfo') || defined('HHVM_VERSION')) { return self::$sigchild = false; } @@ -1379,24 +1339,17 @@ private function readPipes($blocking, $close) $callback = $this->callback; foreach ($result as $type => $data) { - if (3 == $type) { - $this->fallbackExitcode = (int) $data; + if (3 === $type) { + $this->fallbackStatus['running'] = false; + if (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } } else { $callback($type === self::STDOUT ? self::OUT : self::ERR, $data); } } } - /** - * Captures the exitcode if mentioned in the process information. - */ - private function captureExitCode() - { - if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { - $this->exitcode = $this->processInformation['exitcode']; - } - } - /** * Closes process resource, closes file handles, sets the exitcode. * @@ -1406,21 +1359,26 @@ private function close() { $this->processPipes->close(); if (is_resource($this->process)) { - $exitcode = proc_close($this->process); - } else { - $exitcode = -1; + proc_close($this->process); } - - $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1); + $this->exitcode = $this->processInformation['exitcode']; $this->status = self::STATUS_TERMINATED; - if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { - $this->exitcode = $this->fallbackExitcode; - } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { - // if process has been signaled, no exitcode but a valid termsig, apply Unix convention - $this->exitcode = 128 + $this->processInformation['termsig']; + if (-1 === $this->exitcode) { + if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply Unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + $this->processInformation['signaled'] = true; + $this->processInformation['termsig'] = -1; + } } + // Free memory from self-reference callback created by buildCallback + // Doing so in other contexts like __destruct or by garbage collector is ineffective + // Now pipes are closed, so the callback is no longer necessary + $this->callback = null; + return $this->exitcode; } @@ -1432,7 +1390,7 @@ private function resetProcessData() $this->starttime = null; $this->callback = null; $this->exitcode = null; - $this->fallbackExitcode = null; + $this->fallbackStatus = array(); $this->processInformation = null; $this->stdout = null; $this->stderr = null; @@ -1452,7 +1410,7 @@ private function resetProcessData() * @return bool True if the signal was sent successfully, false otherwise * * @throws LogicException In case the process is not running - * @throws RuntimeException In case --enable-sigchild is activated + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed * @throws RuntimeException In case of failure */ private function doSignal($signal, $throwException) @@ -1465,23 +1423,36 @@ private function doSignal($signal, $throwException) return false; } - if ($this->isSigchildEnabled()) { - if ($throwException) { - throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - } - - return false; - } + if ('\\' === DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); + if ($exitCode && $this->isRunning()) { + if ($throwException) { + throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); + } - if (true !== @proc_terminate($this->process, $signal)) { - if ($throwException) { - throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + return false; + } + } else { + if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) { + $ok = @proc_terminate($this->process, $signal); + } elseif (function_exists('posix_kill')) { + $ok = @posix_kill($this->getPid(), $signal); + } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $this->getPid()), array(2 => array('pipe', 'w')), $pipes)) { + $ok = false === fgets($pipes[2]); } + if (!$ok) { + if ($throwException) { + throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + } - return false; + return false; + } } - $this->latestSignal = $signal; + $this->latestSignal = (int) $signal; + $this->fallbackStatus['signaled'] = true; + $this->fallbackStatus['exitcode'] = -1; + $this->fallbackStatus['termsig'] = $this->latestSignal; return true; } diff --git a/src/Symfony/Component/Process/Tests/NonStopableProcess.php b/src/Symfony/Component/Process/Tests/NonStopableProcess.php index 54510c16a3755..5643259657d50 100644 --- a/src/Symfony/Component/Process/Tests/NonStopableProcess.php +++ b/src/Symfony/Component/Process/Tests/NonStopableProcess.php @@ -30,16 +30,18 @@ function handleSignal($signal) break; } - echo "received signal $name\n"; + echo "signal $name\n"; } -declare (ticks = 1); pcntl_signal(SIGTERM, 'handleSignal'); pcntl_signal(SIGINT, 'handleSignal'); +echo 'received '; + $duration = isset($argv[1]) ? (int) $argv[1] : 3; $start = microtime(true); while ($duration > (microtime(true) - $start)) { - usleep(1000); + usleep(10000); + pcntl_signal_dispatch(); } diff --git a/src/Symfony/Component/Process/Tests/PhpProcessTest.php b/src/Symfony/Component/Process/Tests/PhpProcessTest.php index 2cf79aa1a6d15..9c0f04d3d2843 100644 --- a/src/Symfony/Component/Process/Tests/PhpProcessTest.php +++ b/src/Symfony/Component/Process/Tests/PhpProcessTest.php @@ -30,24 +30,20 @@ public function testNonBlockingWorks() public function testCommandLine() { - if ('phpdbg' === PHP_SAPI) { - $this->markTestSkipped('phpdbg SAPI is not supported by this test.'); - } - $process = new PhpProcess(<<find(); + $commandLine = $process->getCommandLine(); - $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP before start'); + $f = new PhpExecutableFinder(); + $this->assertContains($f->find(), $commandLine, '::getCommandLine() returns the command line of PHP before start'); $process->start(); - $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start'); + $this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start'); $process->wait(); - $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait'); + $this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait'); } } diff --git a/src/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php b/src/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php deleted file mode 100644 index 3977bcdcf1e84..0000000000000 --- a/src/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Process\Tests; - -use Symfony\Component\Process\Process; - -class ProcessInSigchildEnvironment extends Process -{ - protected function isSigchildEnabled() - { - return true; - } -} diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php similarity index 74% rename from src/Symfony/Component/Process/Tests/AbstractProcessTest.php rename to src/Symfony/Component/Process/Tests/ProcessTest.php index fca3729be8e20..4a5a38cd85bab 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessTest.php @@ -21,14 +21,26 @@ /** * @author Robert Schönthal */ -abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase +class ProcessTest extends \PHPUnit_Framework_TestCase { - protected static $phpBin; + private static $phpBin; + private static $sigchild; + private static $notEnhancedSigchild = false; public static function setUpBeforeClass() { $phpBin = new PhpExecutableFinder(); self::$phpBin = 'phpdbg' === PHP_SAPI ? 'php' : $phpBin->find(); + if ('\\' !== DIRECTORY_SEPARATOR) { + // exec is mandatory to deal with sending a signal to the process + // see https://github.com/symfony/symfony/issues/5030 about prepending + // command with exec + self::$phpBin = 'exec '.self::$phpBin; + } + + ob_start(); + phpinfo(INFO_GENERAL); + self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); } public function testThatProcessDoesNotThrowWarningDuringRun() @@ -72,26 +84,23 @@ public function testFloatAndNullTimeout() $this->assertNull($p->getTimeout()); } + /** + * @requires extension pcntl + */ public function testStopWithTimeoutIsActuallyWorking() { - if (!extension_loaded('pcntl')) { - $this->markTestSkipped('Extension pcntl is required.'); - } - - // 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 '.self::$phpBin.' '.__DIR__.'/NonStopableProcess.php 3'); + $p = $this->getProcess(self::$phpBin.' '.__DIR__.'/NonStopableProcess.php 30'); $p->start(); - usleep(100000); - $start = microtime(true); - $p->stop(1.1, SIGKILL); - while ($p->isRunning()) { + + while (false === strpos($p->getOutput(), 'received')) { usleep(1000); } - $duration = microtime(true) - $start; + $start = microtime(true); + $p->stop(0.1); + + $p->wait(); - $this->assertLessThan(4, $duration); + $this->assertLessThan(15, microtime(true) - $start); } public function testAllOutputIsActuallyReadOnTermination() @@ -109,12 +118,16 @@ public function testAllOutputIsActuallyReadOnTermination() $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code))); $p->start(); - // Let's wait enough time for process to finish... - // Here we don't call Process::run or Process::wait to avoid any read of pipes - usleep(500000); - if ($p->isRunning()) { - $this->markTestSkipped('Process execution did not complete in the required time frame'); + // Don't call Process::run nor Process::wait to avoid any read of pipes + $h = new \ReflectionProperty($p, 'process'); + $h->setAccessible(true); + $h = $h->getValue($p); + $s = proc_get_status($h); + + while ($s['running']) { + usleep(1000); + $s = proc_get_status($h); } $o = $p->getOutput(); @@ -124,18 +137,14 @@ public function testAllOutputIsActuallyReadOnTermination() public function testCallbacksAreExecutedWithStart() { - $data = ''; - - $process = $this->getProcess('echo foo && php -r "sleep(1);" && echo foo'); + $process = $this->getProcess('echo foo'); $process->start(function ($type, $buffer) use (&$data) { $data .= $buffer; }); - while ($process->isRunning()) { - usleep(10000); - } + $process->wait(); - $this->assertEquals(2, preg_match_all('/foo/', $data, $matches)); + $this->assertSame('foo'.PHP_EOL, $data); } /** @@ -194,18 +203,23 @@ public function testSetStreamAsInput($code, $size) $this->assertEquals($expectedLength, strlen($p->getErrorOutput())); } + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Input can not be set while the process is running. + */ public function testSetInputWhileRunningThrowsAnException() { - $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $process = $this->getProcess(self::$phpBin.' -r "sleep(30);"'); $process->start(); try { $process->setInput('foobar'); $process->stop(); $this->fail('A LogicException should have been raised.'); } catch (LogicException $e) { - $this->assertEquals('Input can not be set while the process is running.', $e->getMessage()); } $process->stop(); + + throw $e; } /** @@ -215,7 +229,7 @@ public function testSetInputWhileRunningThrowsAnException() */ public function testInvalidInput($value) { - $process = $this->getProcess(self::$phpBin.' -v'); + $process = $this->getProcess('foo'); $process->setInput($value); } @@ -232,7 +246,7 @@ public function provideInvalidInputValues() */ public function testValidInput($expected, $value) { - $process = $this->getProcess(self::$phpBin.' -v'); + $process = $this->getProcess('foo'); $process->setInput($value); $this->assertSame($expected, $process->getInput()); } @@ -456,6 +470,7 @@ public function testExitCodeCommandFailed() if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does not support POSIX exit code'); } + $this->skipIfNotEnhancedSigchild(); // such command run in bash return an exitcode 127 $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'); @@ -467,7 +482,7 @@ public function testExitCodeCommandFailed() public function testTTYCommand() { if ('\\' === DIRECTORY_SEPARATOR) { - $this->markTestSkipped('Windows does have /dev/tty support'); + $this->markTestSkipped('Windows does not have /dev/tty support'); } $process = $this->getProcess('echo "foo" >> /dev/null && '.self::$phpBin.' -r "usleep(100000);"'); @@ -484,6 +499,7 @@ public function testTTYCommandExitCode() if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does have /dev/tty support'); } + $this->skipIfNotEnhancedSigchild(); $process = $this->getProcess('echo "foo" >> /dev/null'); $process->setTty(true); @@ -492,6 +508,10 @@ public function testTTYCommandExitCode() $this->assertTrue($process->isSuccessful()); } + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage TTY mode is not supported on Windows platform. + */ public function testTTYInWindowsEnvironment() { if ('\\' !== DIRECTORY_SEPARATOR) { @@ -500,12 +520,13 @@ public function testTTYInWindowsEnvironment() $process = $this->getProcess('echo "foo" >> /dev/null'); $process->setTty(false); - $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'TTY mode is not supported on Windows platform.'); $process->setTty(true); } public function testExitCodeTextIsNullWhenExitCodeIsNull() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess(''); $this->assertNull($process->getExitCodeText()); } @@ -526,6 +547,8 @@ public function testPTYCommand() public function testMustRun() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess('echo foo'); $this->assertSame($process, $process->mustRun()); @@ -534,6 +557,8 @@ public function testMustRun() public function testSuccessfulMustRunHasCorrectExitCode() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess('echo foo')->mustRun(); $this->assertEquals(0, $process->getExitCode()); } @@ -543,12 +568,16 @@ public function testSuccessfulMustRunHasCorrectExitCode() */ public function testMustRunThrowsException() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess('exit 1'); $process->mustRun(); } public function testExitCodeText() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess(''); $r = new \ReflectionObject($process); $p = $r->getProperty('exitcode'); @@ -565,19 +594,21 @@ public function testStartIsNonBlocking() $process->start(); $end = microtime(true); $this->assertLessThan(0.4, $end - $start); - $process->wait(); + $process->stop(); } public function testUpdateStatus() { - $process = $this->getProcess(self::$phpBin.' -v'); + $process = $this->getProcess('echo foo'); $process->run(); $this->assertTrue(strlen($process->getOutput()) > 0); } public function testGetExitCodeIsNullOnStart() { - $process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"'); + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"'); $this->assertNull($process->getExitCode()); $process->start(); $this->assertNull($process->getExitCode()); @@ -587,7 +618,9 @@ public function testGetExitCodeIsNullOnStart() public function testGetExitCodeIsNullOnWhenStartingAgain() { - $process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"'); + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"'); $process->run(); $this->assertEquals(0, $process->getExitCode()); $process->start(); @@ -598,14 +631,16 @@ public function testGetExitCodeIsNullOnWhenStartingAgain() public function testGetExitCode() { - $process = $this->getProcess(self::$phpBin.' -v'); + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('echo foo'); $process->run(); $this->assertSame(0, $process->getExitCode()); } public function testStatus() { - $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"'); $this->assertFalse($process->isRunning()); $this->assertFalse($process->isStarted()); $this->assertFalse($process->isTerminated()); @@ -624,7 +659,7 @@ public function testStatus() public function testStop() { - $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"'); + $process = $this->getProcess(self::$phpBin.' -r "sleep(31);"'); $process->start(); $this->assertTrue($process->isRunning()); $process->stop(); @@ -633,52 +668,44 @@ public function testStop() public function testIsSuccessful() { - $process = $this->getProcess(self::$phpBin.' -v'); + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('echo foo'); $process->run(); $this->assertTrue($process->isSuccessful()); } public function testIsSuccessfulOnlyAfterTerminated() { - $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"'); + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"'); $process->start(); $this->assertFalse($process->isSuccessful()); - while ($process->isRunning()) { - usleep(300000); - } + $process->wait(); $this->assertTrue($process->isSuccessful()); } public function testIsNotSuccessful() { - $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);throw new \Exception(\'BOUM\');"'); - $process->start(); - $this->assertTrue($process->isRunning()); - $process->wait(); - $this->assertFalse($process->isSuccessful()); - } - - public function testProcessIsNotSignaled() - { - if ('\\' === DIRECTORY_SEPARATOR) { - $this->markTestSkipped('Windows does not support POSIX signals'); - } + $this->skipIfNotEnhancedSigchild(); - $process = $this->getProcess(self::$phpBin.' -v'); + $process = $this->getProcess(self::$phpBin.' -r "throw new \Exception(\'BOUM\');"'); $process->run(); - $this->assertFalse($process->hasBeenSignaled()); + $this->assertFalse($process->isSuccessful()); } - public function testProcessWithoutTermSignalIsNotSignaled() + public function testProcessIsNotSignaled() { if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does not support POSIX signals'); } + $this->skipIfNotEnhancedSigchild(); - $process = $this->getProcess(self::$phpBin.' -v'); + $process = $this->getProcess('echo foo'); $process->run(); $this->assertFalse($process->hasBeenSignaled()); } @@ -688,8 +715,9 @@ public function testProcessWithoutTermSignal() if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does not support POSIX signals'); } + $this->skipIfNotEnhancedSigchild(); - $process = $this->getProcess(self::$phpBin.' -v'); + $process = $this->getProcess('echo foo'); $process->run(); $this->assertEquals(0, $process->getTermSignal()); } @@ -699,42 +727,30 @@ public function testProcessIsSignaledIfStopped() if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does not support POSIX signals'); } + $this->skipIfNotEnhancedSigchild(); - $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"'); + $process = $this->getProcess(self::$phpBin.' -r "sleep(32);"'); $process->start(); $process->stop(); $this->assertTrue($process->hasBeenSignaled()); + $this->assertEquals(15, $process->getTermSignal()); // SIGTERM } - public function testProcessWithTermSignal() - { - if ('\\' === DIRECTORY_SEPARATOR) { - $this->markTestSkipped('Windows does not support POSIX signals'); - } - - // SIGTERM is only defined if pcntl extension is present - $termSignal = defined('SIGTERM') ? SIGTERM : 15; - - $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"'); - $process->start(); - $process->stop(); - - $this->assertEquals($termSignal, $process->getTermSignal()); - } - + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage The process has been signaled + */ public function testProcessThrowsExceptionWhenExternallySignaled() { if (!function_exists('posix_kill')) { $this->markTestSkipped('Function posix_kill is required.'); } + $this->skipIfNotEnhancedSigchild(false); - $termSignal = defined('SIGKILL') ? SIGKILL : 9; - - $process = $this->getProcess('exec '.self::$phpBin.' -r "while (true) {}"'); + $process = $this->getProcess(self::$phpBin.' -r "sleep(32.1)"'); $process->start(); - posix_kill($process->getPid(), $termSignal); + posix_kill($process->getPid(), 9); // SIGKILL - $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'The process has been signaled with signal "9".'); $process->wait(); } @@ -756,10 +772,14 @@ public function testRestart() $this->assertNotEquals($process1->getOutput(), $process2->getOutput()); } + /** + * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException + * @expectedExceptionMessage exceeded the timeout of 0.1 seconds. + */ public function testRunProcessWithTimeout() { - $timeout = 0.5; - $process = $this->getProcess(self::$phpBin.' -r "usleep(600000);"'); + $timeout = 0.1; + $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"'); $process->setTimeout($timeout); $start = microtime(true); try { @@ -769,67 +789,68 @@ public function testRunProcessWithTimeout() } $duration = microtime(true) - $start; - if ('\\' === DIRECTORY_SEPARATOR) { - // Windows is a bit slower as it read file handles, then allow twice the precision + if ('\\' !== DIRECTORY_SEPARATOR) { + // On Windows, timers are too transient $maxDuration = $timeout + 2 * Process::TIMEOUT_PRECISION; - } else { - $maxDuration = $timeout + Process::TIMEOUT_PRECISION; + $this->assertLessThan($maxDuration, $duration); } - $this->assertLessThan($maxDuration, $duration); + throw $e; } public function testCheckTimeoutOnNonStartedProcess() { - $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"'); - $process->checkTimeout(); + $process = $this->getProcess('echo foo'); + $this->assertNull($process->checkTimeout()); } public function testCheckTimeoutOnTerminatedProcess() { - $process = $this->getProcess(self::$phpBin.' -v'); + $process = $this->getProcess('echo foo'); $process->run(); - $process->checkTimeout(); + $this->assertNull($process->checkTimeout()); } + /** + * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException + * @expectedExceptionMessage exceeded the timeout of 0.1 seconds. + */ public function testCheckTimeoutOnStartedProcess() { - $timeout = 0.5; - $precision = 100000; - $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"'); - $process->setTimeout($timeout); - $start = microtime(true); + $process = $this->getProcess(self::$phpBin.' -r "sleep(33);"'); + $process->setTimeout(0.1); $process->start(); + $start = microtime(true); try { while ($process->isRunning()) { $process->checkTimeout(); - usleep($precision); + usleep(100000); } - $this->fail('A RuntimeException should have been raised'); - } catch (RuntimeException $e) { + $this->fail('A ProcessTimedOutException should have been raised'); + } catch (ProcessTimedOutException $e) { } - $duration = microtime(true) - $start; - $this->assertLessThan($timeout + $precision, $duration); - $this->assertFalse($process->isSuccessful()); + $this->assertLessThan(15, microtime(true) - $start); + + throw $e; } public function testIdleTimeout() { - $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"'); - $process->setTimeout(10); - $process->setIdleTimeout(0.5); + $process = $this->getProcess(self::$phpBin.' -r "sleep(34);"'); + $process->setTimeout(60); + $process->setIdleTimeout(0.1); try { $process->run(); $this->fail('A timeout exception was expected.'); - } catch (ProcessTimedOutException $ex) { - $this->assertTrue($ex->isIdleTimeout()); - $this->assertFalse($ex->isGeneralTimeout()); - $this->assertEquals(0.5, $ex->getExceededTimeout()); + } catch (ProcessTimedOutException $e) { + $this->assertTrue($e->isIdleTimeout()); + $this->assertFalse($e->isGeneralTimeout()); + $this->assertEquals(0.1, $e->getExceededTimeout()); } } @@ -838,79 +859,92 @@ public function testIdleTimeoutNotExceededWhenOutputIsSent() if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestIncomplete('This test fails with a timeout on Windows, can someone investigate please?'); } - $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 30; while ($n--) {echo "foo\n"; usleep(100000); }'))); - $process->setTimeout(2); - $process->setIdleTimeout(1); + $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('while (true) {echo "foo\n"; usleep(10000);}'))); + $process->setTimeout(1); + $process->start(); + + while (false === strpos($process->getOutput(), 'foo')) { + usleep(1000); + } + + $process->setIdleTimeout(0.1); try { - $process->run(); + $process->wait(); $this->fail('A timeout exception was expected.'); } catch (ProcessTimedOutException $ex) { $this->assertTrue($ex->isGeneralTimeout(), 'A general timeout is expected.'); $this->assertFalse($ex->isIdleTimeout(), 'No idle timeout is expected.'); - $this->assertEquals(2, $ex->getExceededTimeout()); + $this->assertEquals(1, $ex->getExceededTimeout()); } } + /** + * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException + * @expectedExceptionMessage exceeded the timeout of 0.1 seconds. + */ public function testStartAfterATimeout() { - $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 1000; while ($n--) {echo \'\'; usleep(1000); }'))); + $process = $this->getProcess(self::$phpBin.' -r "sleep(35);"'); $process->setTimeout(0.1); try { $process->run(); - $this->fail('A RuntimeException should have been raised.'); - } catch (RuntimeException $e) { + $this->fail('A ProcessTimedOutException should have been raised.'); + } catch (ProcessTimedOutException $e) { } + $this->assertFalse($process->isRunning()); $process->start(); - usleep(1000); - $process->stop(); + $this->assertTrue($process->isRunning()); + $process->stop(0); + + throw $e; } public function testGetPid() { - $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $process = $this->getProcess(self::$phpBin.' -r "sleep(36);"'); $process->start(); $this->assertGreaterThan(0, $process->getPid()); - $process->wait(); + $process->stop(0); } public function testGetPidIsNullBeforeStart() { - $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"'); + $process = $this->getProcess('foo'); $this->assertNull($process->getPid()); } public function testGetPidIsNullAfterRun() { - $process = $this->getProcess(self::$phpBin.' -v'); + $process = $this->getProcess('echo foo'); $process->run(); $this->assertNull($process->getPid()); } + /** + * @requires extension pcntl + */ public function testSignal() { - if (!extension_loaded('pcntl')) { - $this->markTestSkipped('Extension pcntl is required.'); - } - - $process = $this->getProcess('exec php -f '.__DIR__.'/SignalListener.php'); + $process = $this->getProcess(self::$phpBin.' '.__DIR__.'/SignalListener.php'); $process->start(); - usleep(500000); - $process->signal(SIGUSR1); - while ($process->isRunning() && false === strpos($process->getOutput(), 'Caught SIGUSR1')) { - usleep(10000); + while (false === strpos($process->getOutput(), 'Caught')) { + usleep(1000); } + $process->signal(SIGUSR1); + $process->wait(); $this->assertEquals('Caught SIGUSR1', $process->getOutput()); } + /** + * @requires extension pcntl + */ public function testExitCodeIsAvailableAfterSignal() { - if (!extension_loaded('pcntl')) { - $this->markTestSkipped('Extension pcntl is required.'); - } + $this->skipIfNotEnhancedSigchild(); $process = $this->getProcess('sleep 4'); $process->start(); @@ -928,15 +962,12 @@ public function testExitCodeIsAvailableAfterSignal() /** * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Can not send signal on a non running process. */ public function testSignalProcessNotRunning() { - if (!extension_loaded('pcntl')) { - $this->markTestSkipped('Extension pcntl is required.'); - } - - $process = $this->getProcess(self::$phpBin.' -v'); - $process->signal(SIGHUP); + $process = $this->getProcess('foo'); + $process->signal(1); // SIGHUP } /** @@ -944,7 +975,7 @@ public function testSignalProcessNotRunning() */ public function testMethodsThatNeedARunningProcess($method) { - $process = $this->getProcess(self::$phpBin.' -v'); + $process = $this->getProcess('foo'); $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method)); $process->{$method}(); } @@ -962,20 +993,22 @@ public function provideMethodsThatNeedARunningProcess() /** * @dataProvider provideMethodsThatNeedATerminatedProcess + * @expectedException Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Process must be terminated before calling */ public function testMethodsThatNeedATerminatedProcess($method) { - $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"'); + $process = $this->getProcess(self::$phpBin.' -r "sleep(37);"'); $process->start(); try { $process->{$method}(); $process->stop(0); $this->fail('A LogicException must have been thrown'); } catch (\Exception $e) { - $this->assertInstanceOf('Symfony\Component\Process\Exception\LogicException', $e); - $this->assertEquals(sprintf('Process must be terminated before calling %s.', $method), $e->getMessage()); } $process->stop(0); + + throw $e; } public function provideMethodsThatNeedATerminatedProcess() @@ -989,36 +1022,38 @@ public function provideMethodsThatNeedATerminatedProcess() } /** + * @dataProvider provideWrongSignal * @expectedException \Symfony\Component\Process\Exception\RuntimeException */ - public function testSignalWithWrongIntSignal() + public function testWrongSignal($signal) { if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('POSIX signals do not work on Windows'); } - $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"'); + $process = $this->getProcess(self::$phpBin.' -r "sleep(38);"'); $process->start(); - $process->signal(-4); + try { + $process->signal($signal); + $this->fail('A RuntimeException must have been thrown'); + } catch (RuntimeException $e) { + $process->stop(0); + } + + throw $e; } - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - */ - public function testSignalWithWrongNonIntSignal() + public function provideWrongSignal() { - if ('\\' === DIRECTORY_SEPARATOR) { - $this->markTestSkipped('POSIX signals do not work on Windows'); - } - - $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"'); - $process->start(); - $process->signal('Céphalopodes'); + return array( + array(-4), + array('Céphalopodes'), + ); } public function testDisableOutputDisablesTheOutput() { - $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p = $this->getProcess('foo'); $this->assertFalse($p->isOutputDisabled()); $p->disableOutput(); $this->assertTrue($p->isOutputDisabled()); @@ -1026,54 +1061,66 @@ public function testDisableOutputDisablesTheOutput() $this->assertFalse($p->isOutputDisabled()); } + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage Disabling output while the process is running is not possible. + */ public function testDisableOutputWhileRunningThrowsException() { - $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p = $this->getProcess(self::$phpBin.' -r "sleep(39);"'); $p->start(); - $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Disabling output while the process is running is not possible.'); $p->disableOutput(); } + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage Enabling output while the process is running is not possible. + */ public function testEnableOutputWhileRunningThrowsException() { - $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p = $this->getProcess(self::$phpBin.' -r "sleep(40);"'); $p->disableOutput(); $p->start(); - $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Enabling output while the process is running is not possible.'); $p->enableOutput(); } public function testEnableOrDisableOutputAfterRunDoesNotThrowException() { - $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p = $this->getProcess('echo foo'); $p->disableOutput(); - $p->start(); - $p->wait(); + $p->run(); $p->enableOutput(); $p->disableOutput(); + $this->assertTrue($p->isOutputDisabled()); } + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Output can not be disabled while an idle timeout is set. + */ public function testDisableOutputWhileIdleTimeoutIsSet() { - $process = $this->getProcess('sleep 3'); + $process = $this->getProcess('foo'); $process->setIdleTimeout(1); - $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output can not be disabled while an idle timeout is set.'); $process->disableOutput(); } + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage timeout can not be set while the output is disabled. + */ public function testSetIdleTimeoutWhileOutputIsDisabled() { - $process = $this->getProcess('sleep 3'); + $process = $this->getProcess('foo'); $process->disableOutput(); - $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Idle timeout can not be set while the output is disabled.'); $process->setIdleTimeout(1); } public function testSetNullIdleTimeoutWhileOutputIsDisabled() { - $process = $this->getProcess('sleep 3'); + $process = $this->getProcess('foo'); $process->disableOutput(); - $process->setIdleTimeout(null); + $this->assertSame($process, $process->setIdleTimeout(null)); } /** @@ -1081,9 +1128,12 @@ public function testSetNullIdleTimeoutWhileOutputIsDisabled() */ public function testStartWithACallbackAndDisabledOutput($startMethod, $exception, $exceptionMessage) { - $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p = $this->getProcess('foo'); $p->disableOutput(); $this->setExpectedException($exception, $exceptionMessage); + if ('mustRun' === $startMethod) { + $this->skipIfNotEnhancedSigchild(); + } $p->{$startMethod}(function () {}); } @@ -1098,13 +1148,14 @@ public function provideStartMethods() /** * @dataProvider provideOutputFetchingMethods + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Output has been disabled. */ public function testGetOutputWhileDisabled($fetchMethod) { - $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"'); + $p = $this->getProcess(self::$phpBin.' -r "sleep(41);"'); $p->disableOutput(); $p->start(); - $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output has been disabled.'); $p->{$fetchMethod}(); } @@ -1118,6 +1169,33 @@ public function provideOutputFetchingMethods() ); } + public function testStopTerminatesProcessCleanly() + { + $process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(42);"'); + $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() + { + $process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(43);"'); + $process->run(function () use ($process) { + $process->signal(9); // SIGKILL + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + + public function testTermSignalTerminatesProcessCleanly() + { + $process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(44);"'); + $process->run(function () use ($process) { + $process->signal(15); // SIGTERM + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + public function responsesCodeProvider() { return array( @@ -1180,7 +1258,38 @@ public function methodProvider() * * @return Process */ - abstract protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()); + private function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()) + { + $process = new Process($commandline, $cwd, $env, $input, $timeout, $options); + + if (false !== $enhance = getenv('ENHANCE_SIGCHLD')) { + try { + $process->setEnhanceSigchildCompatibility(false); + $process->getExitCode(); + $this->fail('ENHANCE_SIGCHLD must be used together with a sigchild-enabled PHP.'); + } catch (RuntimeException $e) { + $this->assertSame('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.', $e->getMessage()); + if ($enhance) { + $process->setEnhanceSigChildCompatibility(true); + } else { + self::$notEnhancedSigchild = true; + } + } + } + + return $process; + } + + private function skipIfNotEnhancedSigchild($expectException = true) + { + if (self::$sigchild) { + if (!$expectException) { + $this->markTestSkipped('PHP is compiled with --enable-sigchild.'); + } elseif (self::$notEnhancedSigchild) { + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild.'); + } + } + } } class Stringifiable diff --git a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php deleted file mode 100644 index fdae5ec25e41d..0000000000000 --- a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php +++ /dev/null @@ -1,263 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Process\Tests; - -class SigchildDisabledProcessTest extends AbstractProcessTest -{ - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testGetExitCode() - { - parent::testGetExitCode(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testGetExitCodeIsNullOnStart() - { - parent::testGetExitCodeIsNullOnStart(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testGetExitCodeIsNullOnWhenStartingAgain() - { - parent::testGetExitCodeIsNullOnWhenStartingAgain(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testExitCodeCommandFailed() - { - parent::testExitCodeCommandFailed(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testMustRun() - { - parent::testMustRun(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testSuccessfulMustRunHasCorrectExitCode() - { - parent::testSuccessfulMustRunHasCorrectExitCode(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - */ - public function testMustRunThrowsException() - { - parent::testMustRunThrowsException(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - */ - public function testProcessIsSignaledIfStopped() - { - parent::testProcessIsSignaledIfStopped(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithTermSignal() - { - parent::testProcessWithTermSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessIsNotSignaled() - { - parent::testProcessIsNotSignaled(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithoutTermSignal() - { - parent::testProcessWithoutTermSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testCheckTimeoutOnStartedProcess() - { - parent::testCheckTimeoutOnStartedProcess(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPid() - { - parent::testGetPid(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPidIsNullBeforeStart() - { - parent::testGetPidIsNullBeforeStart(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPidIsNullAfterRun() - { - parent::testGetPidIsNullAfterRun(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testExitCodeText() - { - $process = $this->getProcess('qdfsmfkqsdfmqmsd'); - $process->run(); - - $process->getExitCodeText(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testExitCodeTextIsNullWhenExitCodeIsNull() - { - parent::testExitCodeTextIsNullWhenExitCodeIsNull(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testIsSuccessful() - { - parent::testIsSuccessful(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testIsSuccessfulOnlyAfterTerminated() - { - parent::testIsSuccessfulOnlyAfterTerminated(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testIsNotSuccessful() - { - parent::testIsNotSuccessful(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testTTYCommandExitCode() - { - parent::testTTYCommandExitCode(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process can not be signaled. - */ - public function testSignal() - { - parent::testSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithoutTermSignalIsNotSignaled() - { - parent::testProcessWithoutTermSignalIsNotSignaled(); - } - - public function testStopWithTimeoutIsActuallyWorking() - { - $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); - } - - public function testProcessThrowsExceptionWhenExternallySignaled() - { - $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment'); - } - - public function testExitCodeIsAvailableAfterSignal() - { - $this->markTestSkipped('Signal is not supported in sigchild environment'); - } - - public function testRunProcessWithTimeout() - { - $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); - } - - public function provideStartMethods() - { - return array( - array('start', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'), - array('run', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'), - array('mustRun', 'Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'), - ); - } - - /** - * {@inheritdoc} - */ - protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()) - { - $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $input, $timeout, $options); - $process->setEnhanceSigchildCompatibility(false); - - return $process; - } -} diff --git a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php deleted file mode 100644 index 2668a9b4bf65b..0000000000000 --- a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php +++ /dev/null @@ -1,148 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Process\Tests; - -class SigchildEnabledProcessTest extends AbstractProcessTest -{ - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessIsSignaledIfStopped() - { - parent::testProcessIsSignaledIfStopped(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithTermSignal() - { - parent::testProcessWithTermSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessIsNotSignaled() - { - parent::testProcessIsNotSignaled(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithoutTermSignal() - { - parent::testProcessWithoutTermSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPid() - { - parent::testGetPid(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPidIsNullBeforeStart() - { - parent::testGetPidIsNullBeforeStart(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPidIsNullAfterRun() - { - parent::testGetPidIsNullAfterRun(); - } - - public function testExitCodeText() - { - $process = $this->getProcess('qdfsmfkqsdfmqmsd'); - $process->run(); - - $this->assertInternalType('string', $process->getExitCodeText()); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process can not be signaled. - */ - public function testSignal() - { - parent::testSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithoutTermSignalIsNotSignaled() - { - parent::testProcessWithoutTermSignalIsNotSignaled(); - } - - public function testProcessThrowsExceptionWhenExternallySignaled() - { - $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment'); - } - - public function testExitCodeIsAvailableAfterSignal() - { - $this->markTestSkipped('Signal is not supported in sigchild environment'); - } - - public function testStartAfterATimeout() - { - if ('\\' === DIRECTORY_SEPARATOR) { - $this->markTestSkipped('Restarting a timed-out process on Windows is not supported in sigchild environment'); - } - parent::testStartAfterATimeout(); - } - - public function testStopWithTimeoutIsActuallyWorking() - { - $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); - } - - public function testRunProcessWithTimeout() - { - $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); - } - - public function testCheckTimeoutOnStartedProcess() - { - $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); - } - - /** - * {@inheritdoc} - */ - protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()) - { - $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $input, $timeout, $options); - $process->setEnhanceSigchildCompatibility(true); - - return $process; - } -} diff --git a/src/Symfony/Component/Process/Tests/SignalListener.php b/src/Symfony/Component/Process/Tests/SignalListener.php index 4206550f5b8b7..03536577c40f2 100644 --- a/src/Symfony/Component/Process/Tests/SignalListener.php +++ b/src/Symfony/Component/Process/Tests/SignalListener.php @@ -9,17 +9,13 @@ * file that was distributed with this source code. */ -// required for signal handling -declare (ticks = 1); +pcntl_signal(SIGUSR1, function () {echo 'SIGUSR1'; exit;}); -pcntl_signal(SIGUSR1, function () {echo 'Caught SIGUSR1'; exit;}); +echo 'Caught '; $n = 0; -// ticks require activity to work - sleep(4); does not work -while ($n < 400) { +while ($n++ < 400) { usleep(10000); - ++$n; + pcntl_signal_dispatch(); } - -return; diff --git a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php deleted file mode 100644 index b3ee5c00f8894..0000000000000 --- a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php +++ /dev/null @@ -1,225 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Process\Tests; - -use Symfony\Component\Process\Process; - -class SimpleProcessTest extends AbstractProcessTest -{ - private $enabledSigchild = false; - - protected function setUp() - { - ob_start(); - phpinfo(INFO_GENERAL); - - $this->enabledSigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); - } - - public function testGetExitCode() - { - $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case - parent::testGetExitCode(); - } - - public function testExitCodeCommandFailed() - { - $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case - parent::testExitCodeCommandFailed(); - } - - public function testProcessIsSignaledIfStopped() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); - parent::testProcessIsSignaledIfStopped(); - } - - public function testProcessWithTermSignal() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); - parent::testProcessWithTermSignal(); - } - - public function testProcessIsNotSignaled() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); - parent::testProcessIsNotSignaled(); - } - - public function testProcessWithoutTermSignal() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); - parent::testProcessWithoutTermSignal(); - } - - public function testExitCodeText() - { - $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case - parent::testExitCodeText(); - } - - public function testIsSuccessful() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testIsSuccessful(); - } - - public function testIsNotSuccessful() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testIsNotSuccessful(); - } - - public function testGetPid() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testGetPid(); - } - - public function testGetPidIsNullBeforeStart() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testGetPidIsNullBeforeStart(); - } - - public function testGetPidIsNullAfterRun() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testGetPidIsNullAfterRun(); - } - - public function testSignal() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - parent::testSignal(); - } - - public function testProcessWithoutTermSignalIsNotSignaled() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); - parent::testProcessWithoutTermSignalIsNotSignaled(); - } - - public function testProcessThrowsExceptionWhenExternallySignaled() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testProcessThrowsExceptionWhenExternallySignaled(); - } - - public function testExitCodeIsAvailableAfterSignal() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - parent::testExitCodeIsAvailableAfterSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\LogicException - * @expectedExceptionMessage Can not send signal on a non running process. - */ - public function testSignalProcessNotRunning() - { - parent::testSignalProcessNotRunning(); - } - - public function testSignalWithWrongIntSignal() - { - if ($this->enabledSigchild) { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - } else { - $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Error while sending signal `-4`.'); - } - parent::testSignalWithWrongIntSignal(); - } - - public function testSignalWithWrongNonIntSignal() - { - if ($this->enabledSigchild) { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - } else { - $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Error while sending signal `Céphalopodes`.'); - } - parent::testSignalWithWrongNonIntSignal(); - } - - public function testStopTerminatesProcessCleanly() - { - try { - $process = $this->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'); - } - } - - 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'); - } - } - - 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'); - } - } - - public function testStopWithTimeoutIsActuallyWorking() - { - $this->skipIfPHPSigchild(); - - parent::testStopWithTimeoutIsActuallyWorking(); - } - - /** - * {@inheritdoc} - */ - protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array()) - { - return new Process($commandline, $cwd, $env, $input, $timeout, $options); - } - - private function skipIfPHPSigchild() - { - if ($this->enabledSigchild) { - $this->markTestSkipped('Your PHP has been compiled with --enable-sigchild, this test can not be executed'); - } - } - - private function expectExceptionIfPHPSigchild($classname, $message) - { - if ($this->enabledSigchild) { - $this->setExpectedException($classname, $message); - } - } -} diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 008f55001f20e..87e9c71266ef7 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -24,19 +24,74 @@ */ class PropertyAccessor implements PropertyAccessorInterface { + /** + * @internal + */ const VALUE = 0; + + /** + * @internal + */ const IS_REF = 1; + + /** + * @internal + */ const IS_REF_CHAINED = 2; + + /** + * @internal + */ const ACCESS_HAS_PROPERTY = 0; + + /** + * @internal + */ const ACCESS_TYPE = 1; + + /** + * @internal + */ const ACCESS_NAME = 2; + + /** + * @internal + */ const ACCESS_REF = 3; + + /** + * @internal + */ const ACCESS_ADDER = 4; + + /** + * @internal + */ const ACCESS_REMOVER = 5; + + /** + * @internal + */ const ACCESS_TYPE_METHOD = 0; + + /** + * @internal + */ const ACCESS_TYPE_PROPERTY = 1; + + /** + * @internal + */ const ACCESS_TYPE_MAGIC = 2; + + /** + * @internal + */ const ACCESS_TYPE_ADDER_AND_REMOVER = 3; + + /** + * @internal + */ const ACCESS_TYPE_NOT_FOUND = 4; /** @@ -62,6 +117,9 @@ class PropertyAccessor implements PropertyAccessorInterface /** * Should not be used by application code. Use * {@link PropertyAccess::createPropertyAccessor()} instead. + * + * @param bool $magicCall + * @param bool $throwExceptionOnInvalidIndex */ public function __construct($magicCall = false, $throwExceptionOnInvalidIndex = false) { @@ -365,7 +423,7 @@ private function &readProperty(&$object, $property) } } elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) { // Needed to support \stdClass instances. We need to explicitly - // exclude $classHasProperty, otherwise if in the previous clause + // exclude $access[self::ACCESS_HAS_PROPERTY], otherwise if // a *protected* property was found on the class, property_exists() // returns true, consequently the following line will result in a // fatal error. @@ -411,7 +469,6 @@ private function getReadAccessInfo($object, $property) $getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item) $isser = 'is'.$camelProp; $hasser = 'has'.$camelProp; - $classHasProperty = $reflClass->hasProperty($property); if ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) { $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD; @@ -429,13 +486,10 @@ private function getReadAccessInfo($object, $property) $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY; $access[self::ACCESS_NAME] = $property; $access[self::ACCESS_REF] = false; - } elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) { + } elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) { $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY; $access[self::ACCESS_NAME] = $property; $access[self::ACCESS_REF] = true; - - $result[self::VALUE] = &$object->$property; - $result[self::IS_REF] = true; } elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) { // we call the getter and hope the __call do the job $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC; @@ -506,7 +560,7 @@ private function writeProperty(&$object, $property, $value) $this->writeCollection($object, $property, $value, $access[self::ACCESS_ADDER], $access[self::ACCESS_REMOVER]); } elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) { // Needed to support \stdClass instances. We need to explicitly - // exclude $classHasProperty, otherwise if in the previous clause + // exclude $access[self::ACCESS_HAS_PROPERTY], otherwise if // a *protected* property was found on the class, property_exists() // returns true, consequently the following line will result in a // fatal error. @@ -579,7 +633,6 @@ private function writeCollection($object, $property, $collection, $addMethod, $r private function getWriteAccessInfo($object, $property, $value) { $key = get_class($object).'::'.$property; - $guessedAdders = ''; if (isset($this->writePropertyCache[$key])) { $access = $this->writePropertyCache[$key]; @@ -594,13 +647,7 @@ private function getWriteAccessInfo($object, $property, $value) if (is_array($value) || $value instanceof \Traversable) { $methods = $this->findAdderAndRemover($reflClass, $singulars); - if (null === $methods) { - // It is sufficient to include only the adders in the error - // message. If the user implements the adder but not the remover, - // an exception will be thrown in findAdderAndRemover() that - // the remover has to be implemented as well. - $guessedAdders = '"add'.implode('()", "add', $singulars).'()", '; - } else { + if (null !== $methods) { $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER; $access[self::ACCESS_ADDER] = $methods[0]; $access[self::ACCESS_REMOVER] = $methods[1]; @@ -608,11 +655,9 @@ private function getWriteAccessInfo($object, $property, $value) } if (!isset($access[self::ACCESS_TYPE])) { - $setter = 'set'.$this->camelize($property); + $setter = 'set'.$camelized; $getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item) - $classHasProperty = $reflClass->hasProperty($property); - if ($this->isMethodAccessible($reflClass, $setter, 1)) { $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD; $access[self::ACCESS_NAME] = $setter; @@ -622,7 +667,7 @@ private function getWriteAccessInfo($object, $property, $value) } elseif ($this->isMethodAccessible($reflClass, '__set', 2)) { $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY; $access[self::ACCESS_NAME] = $property; - } elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) { + } elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) { $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY; $access[self::ACCESS_NAME] = $property; } elseif ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2)) { diff --git a/src/Symfony/Component/PropertyAccess/PropertyPathBuilder.php b/src/Symfony/Component/PropertyAccess/PropertyPathBuilder.php index 5d7fdac6fd661..39bf2da2e443b 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyPathBuilder.php +++ b/src/Symfony/Component/PropertyAccess/PropertyPathBuilder.php @@ -142,6 +142,7 @@ public function replace($offset, $length, $path, $pathOffset = 0, $pathLength = $this->elements[$offset + $i] = $path->getElement($pathOffset + $i); $this->isIndex[$offset + $i] = $path->isIndex($pathOffset + $i); } + ksort($this->elements); } /** diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyPathBuilderTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyPathBuilderTest.php index 3767e08c82f7d..6b4fdd8db9824 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyPathBuilderTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyPathBuilderTest.php @@ -252,6 +252,17 @@ public function testReplaceWithLongerPath() $this->assertEquals($path, $builder->getPropertyPath()); } + public function testReplaceWithLongerPathKeepsOrder() + { + $path = new PropertyPath('new1.new2.new3'); + $expected = new PropertyPath('new1.new2.new3.old2'); + + $builder = new PropertyPathBuilder(new PropertyPath('old1.old2')); + $builder->replace(0, 1, $path); + + $this->assertEquals($expected, $builder->getPropertyPath()); + } + public function testRemove() { $this->builder->remove(3); 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/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/src/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php index 91dbd555f9681..8433711195f17 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,37 @@ public function testDumpWithRoutes() $this->assertEquals($relativeUrlWithoutParameter, '/app.php/testing2'); } + public function testDumpWithTooManyRoutes() + { + if (defined('HHVM_VERSION_ID')) { + $this->markTestSkipped('HHVM consumes too much memory on this test.'); + } + + $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')); + + file_put_contents($this->largeTestTmpFilepath, $this->generatorDumper->dump(array( + 'class' => 'ProjectLargeUrlGenerator', + ))); + $this->routeCollection = $this->generatorDumper = null; + 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/Security/Acl/Dbal/AclProvider.php b/src/Symfony/Component/Security/Acl/Dbal/AclProvider.php index 989e40b4a776b..64102d22c6752 100644 --- a/src/Symfony/Component/Security/Acl/Dbal/AclProvider.php +++ b/src/Symfony/Component/Security/Acl/Dbal/AclProvider.php @@ -573,7 +573,7 @@ private function hydrateObjectIdentities(Statement $stmt, array $oidLookup, arra $oidCache[$oidLookupKey] = new ObjectIdentity($objectIdentifier, $classType); } - $acl = new Acl((int) $aclId, $oidCache[$oidLookupKey], $permissionGrantingStrategy, $emptyArray, !!$entriesInheriting); + $acl = new Acl((int) $aclId, $oidCache[$oidLookupKey], $permissionGrantingStrategy, $emptyArray, (bool) $entriesInheriting); // keep a local, and global reference to this ACL $loadedAcls[$classType][$objectIdentifier] = $acl; @@ -615,9 +615,9 @@ private function hydrateObjectIdentities(Statement $stmt, array $oidLookup, arra } if (null === $fieldName) { - $loadedAces[$aceId] = new Entry((int) $aceId, $acl, $sids[$key], $grantingStrategy, (int) $mask, !!$granting, !!$auditFailure, !!$auditSuccess); + $loadedAces[$aceId] = new Entry((int) $aceId, $acl, $sids[$key], $grantingStrategy, (int) $mask, (bool) $granting, (bool) $auditFailure, (bool) $auditSuccess); } else { - $loadedAces[$aceId] = new FieldEntry((int) $aceId, $acl, $fieldName, $sids[$key], $grantingStrategy, (int) $mask, !!$granting, !!$auditFailure, !!$auditSuccess); + $loadedAces[$aceId] = new FieldEntry((int) $aceId, $acl, $fieldName, $sids[$key], $grantingStrategy, (int) $mask, (bool) $granting, (bool) $auditFailure, (bool) $auditSuccess); } } $ace = $loadedAces[$aceId]; diff --git a/src/Symfony/Component/Security/Acl/Domain/Acl.php b/src/Symfony/Component/Security/Acl/Domain/Acl.php index f417c8fda0475..fb70738ae7f65 100644 --- a/src/Symfony/Component/Security/Acl/Domain/Acl.php +++ b/src/Symfony/Component/Security/Acl/Domain/Acl.php @@ -56,7 +56,7 @@ class Acl implements AuditableAclInterface, NotifyPropertyChanged * @param array $loadedSids * @param bool $entriesInheriting */ - public function __construct($id, ObjectIdentityInterface $objectIdentity, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $loadedSids = array(), $entriesInheriting) + public function __construct($id, ObjectIdentityInterface $objectIdentity, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $loadedSids, $entriesInheriting) { $this->id = $id; $this->objectIdentity = $objectIdentity; diff --git a/src/Symfony/Component/Security/Core/Encoder/BCryptPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/BCryptPasswordEncoder.php index d2b031999cbde..83ae33466e13d 100644 --- a/src/Symfony/Component/Security/Core/Encoder/BCryptPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/BCryptPasswordEncoder.php @@ -19,6 +19,8 @@ */ class BCryptPasswordEncoder extends BasePasswordEncoder { + const MAX_PASSWORD_LENGTH = 72; + /** * @var string */ diff --git a/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php index 1c9ada1e64f53..fcf2e47088bf3 100644 --- a/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php @@ -95,6 +95,6 @@ protected function comparePasswords($password1, $password2) */ protected function isPasswordTooLong($password) { - return strlen($password) > self::MAX_PASSWORD_LENGTH; + return strlen($password) > static::MAX_PASSWORD_LENGTH; } } diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/BCryptPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/BCryptPasswordEncoderTest.php index ebd845de285d4..40de8af218c3a 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/BCryptPasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/BCryptPasswordEncoderTest.php @@ -67,13 +67,15 @@ public function testEncodePasswordLength() { $encoder = new BCryptPasswordEncoder(self::VALID_COST); - $encoder->encodePassword(str_repeat('a', 5000), 'salt'); + $encoder->encodePassword(str_repeat('a', 73), 'salt'); } public function testCheckPasswordLength() { $encoder = new BCryptPasswordEncoder(self::VALID_COST); + $result = $encoder->encodePassword(str_repeat('a', 72), null); - $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), 'salt')); + $this->assertFalse($encoder->isPasswordValid($result, str_repeat('a', 73), 'salt')); + $this->assertTrue($encoder->isPasswordValid($result, str_repeat('a', 72), 'salt')); } } diff --git a/src/Symfony/Component/Security/Core/User/InMemoryUserProvider.php b/src/Symfony/Component/Security/Core/User/InMemoryUserProvider.php index 9aa39cad4849a..c1981deb96df3 100644 --- a/src/Symfony/Component/Security/Core/User/InMemoryUserProvider.php +++ b/src/Symfony/Component/Security/Core/User/InMemoryUserProvider.php @@ -97,7 +97,7 @@ public function supportsClass($class) /** * Returns the user by given username. * - * @param string $username The username. + * @param string $username The username. * * @return User * diff --git a/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php b/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php index 0d7595d40720e..c8e43e535f558 100644 --- a/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php +++ b/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php @@ -24,7 +24,17 @@ interface AuthenticationEntryPointInterface { /** - * Starts the authentication scheme. + * Returns a response that directs the user to authenticate. + * + * This is called when an anonymous request accesses a resource that + * requires authentication. The job of this method is to return some + * response that "helps" the user start into the authentication process. + * + * Examples: + * A) For a form login, you might redirect to the login page + * return new RedirectResponse('/login'); + * B) For an API token authentication system, you return a 401 response + * return new Response('Auth header required', 401); * * @param Request $request The request that resulted in an AuthenticationException * @param AuthenticationException $authException The exception that started the authentication process diff --git a/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php b/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php index 298c22445c542..4ad63cc3b1103 100644 --- a/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php +++ b/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php @@ -121,7 +121,7 @@ private function generateLogoutUrl($key, $referenceType) $request = $this->requestStack->getCurrentRequest(); - $url = UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($logoutPath) : $request->getBasePath().$logoutPath; + $url = UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($logoutPath) : $request->getBaseUrl().$logoutPath; if (!empty($parameters)) { $url .= '?'.http_build_query($parameters); diff --git a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php index 5f131cf8a5f21..cbbbb235192d9 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php @@ -45,7 +45,7 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices * @param LoggerInterface $logger * @param SecureRandomInterface $secureRandom */ - public function __construct(array $userProviders, $key, $providerKey, array $options = array(), LoggerInterface $logger = null, SecureRandomInterface $secureRandom) + public function __construct(array $userProviders, $key, $providerKey, array $options, LoggerInterface $logger = null, SecureRandomInterface $secureRandom) { parent::__construct($userProviders, $key, $providerKey, $options, $logger); diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php index 0701a58b56257..6bb30274e3428 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php @@ -16,6 +16,8 @@ * * Primarily, the metadata stores serialization groups. * + * @internal + * * @author Kévin Dunglas */ interface AttributeMetadataInterface diff --git a/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php index c967666bd7e6e..5889f0da11886 100644 --- a/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php @@ -14,7 +14,11 @@ /** * Stores metadata needed for serializing and deserializing objects of specific class. * - * Primarily, the metadata stores the list of attributes to serialize or deserialize. + * Primarily, the metadata stores the set of attributes to serialize or deserialize. + * + * There may only exist one metadata for each attribute according to its name. + * + * @internal * * @author Kévin Dunglas */ diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 55c60c0f950b3..554f36c585809 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -259,7 +259,7 @@ protected function getAllowedAttributes($classOrObject, array $context, $attribu } } - return array_unique($allowedAttributes); + return $allowedAttributes; } /** diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php index a62de61485416..72e2e6a23bfa8 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php @@ -74,7 +74,7 @@ public function testAttributes() '@Type' => 'test', ), 'föo_bär' => 'a', - 'Bar' => array(1,2,3), + 'Bar' => array(1, 2, 3), 'a' => 'b', ); $expected = ''."\n". @@ -384,7 +384,7 @@ public function testDecodeWithoutItemHash() '@Type' => 'test', ), 'föo_bär' => 'a', - 'Bar' => array(1,2,3), + 'Bar' => array(1, 2, 3), 'a' => 'b', ); $expected = array( @@ -397,7 +397,7 @@ public function testDecodeWithoutItemHash() '@Type' => 'test', ), 'föo_bär' => 'a', - 'Bar' => array(1,2,3), + 'Bar' => array(1, 2, 3), 'a' => 'b', ); $xml = $this->encoder->encode($obj, 'xml'); diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractNormalizerDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractNormalizerDummy.php new file mode 100644 index 0000000000000..27b2f24f53fb1 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractNormalizerDummy.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; + +/** + * Provides a dummy Normalizer which extends the AbstractNormalizer. + * + * @author Konstantin S. M. Möllers + */ +class AbstractNormalizerDummy extends AbstractNormalizer +{ + /** + * {@inheritdoc} + */ + public function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false) + { + return parent::getAllowedAttributes($classOrObject, $context, $attributesAsString); + } + + /** + * {@inheritdoc} + */ + public function normalize($object, $format = null, array $context = array()) + { + } + + /** + * {@inheritdoc} + */ + public function supportsNormalization($data, $format = null) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function denormalize($data, $class, $format = null, array $context = array()) + { + } + + /** + * {@inheritdoc} + */ + public function supportsDenormalization($data, $type, $format = null) + { + return true; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php new file mode 100644 index 0000000000000..d27c8250aa095 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php @@ -0,0 +1,91 @@ + + */ +class AbstractNormalizerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var AbstractNormalizerDummy + */ + private $normalizer; + + /** + * @var ClassMetadataFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $classMetadata; + + protected function setUp() + { + $loader = $this->getMock('Symfony\Component\Serializer\Mapping\Loader\LoaderChain', array(), array(array())); + $this->classMetadata = $this->getMock('Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory', array(), array($loader)); + $this->normalizer = new AbstractNormalizerDummy($this->classMetadata); + } + + public function testGetAllowedAttributesAsString() + { + $classMetadata = new ClassMetadata('c'); + + $a1 = new AttributeMetadata('a1'); + $classMetadata->addAttributeMetadata($a1); + + $a2 = new AttributeMetadata('a2'); + $a2->addGroup('test'); + $classMetadata->addAttributeMetadata($a2); + + $a3 = new AttributeMetadata('a3'); + $a3->addGroup('other'); + $classMetadata->addAttributeMetadata($a3); + + $a4 = new AttributeMetadata('a4'); + $a4->addGroup('test'); + $a4->addGroup('other'); + $classMetadata->addAttributeMetadata($a4); + + $this->classMetadata->method('getMetadataFor')->willReturn($classMetadata); + + $result = $this->normalizer->getAllowedAttributes('c', array('groups' => array('test')), true); + $this->assertEquals(array('a2', 'a4'), $result); + + $result = $this->normalizer->getAllowedAttributes('c', array('groups' => array('other')), true); + $this->assertEquals(array('a3', 'a4'), $result); + } + + public function testGetAllowedAttributesAsObjects() + { + $classMetadata = new ClassMetadata('c'); + + $a1 = new AttributeMetadata('a1'); + $classMetadata->addAttributeMetadata($a1); + + $a2 = new AttributeMetadata('a2'); + $a2->addGroup('test'); + $classMetadata->addAttributeMetadata($a2); + + $a3 = new AttributeMetadata('a3'); + $a3->addGroup('other'); + $classMetadata->addAttributeMetadata($a3); + + $a4 = new AttributeMetadata('a4'); + $a4->addGroup('test'); + $a4->addGroup('other'); + $classMetadata->addAttributeMetadata($a4); + + $this->classMetadata->method('getMetadataFor')->willReturn($classMetadata); + + $result = $this->normalizer->getAllowedAttributes('c', array('groups' => array('test')), false); + $this->assertEquals(array($a2, $a4), $result); + + $result = $this->normalizer->getAllowedAttributes('c', array('groups' => array('other')), false); + $this->assertEquals(array($a3, $a4), $result); + } +} diff --git a/src/Symfony/Component/Translation/Tests/PluralizationRulesTest.php b/src/Symfony/Component/Translation/Tests/PluralizationRulesTest.php index 43c31672c2ce5..5de544e0c1b1c 100644 --- a/src/Symfony/Component/Translation/Tests/PluralizationRulesTest.php +++ b/src/Symfony/Component/Translation/Tests/PluralizationRulesTest.php @@ -60,10 +60,10 @@ public function testLangcodes($nplural, $langCodes) public function successLangcodes() { return array( - array('1', array('ay','bo', 'cgg','dz','id', 'ja', 'jbo', 'ka','kk','km','ko','ky')), + array('1', array('ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky')), 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('3', array('be', 'bs', 'cs', 'hr')), + array('4', array('cy', 'mt', 'sl')), array('5', array()), array('6', array('ar')), ); @@ -83,7 +83,7 @@ public function failingLangcodes() array('1', array('fa')), array('2', array('jbo')), array('3', array('cbs')), - array('4', array('gd','kw')), + array('4', array('gd', 'kw')), array('5', array('ga')), array('6', array()), ); diff --git a/src/Symfony/Component/Validator/README.md b/src/Symfony/Component/Validator/README.md index 4555b9af5ece9..7a62399f751a9 100644 --- a/src/Symfony/Component/Validator/README.md +++ b/src/Symfony/Component/Validator/README.md @@ -18,7 +18,7 @@ use Symfony\Component\Validator\Constraints\Length; $validator = Validation::createValidator(); -$violations = $validator->validateValue('Bernhard', new Length(array('min' => 10))); +$violations = $validator->validate('Bernhard', new Length(array('min' => 10))); ``` This validation will fail because the given string is shorter than ten @@ -46,7 +46,7 @@ $constraint = new Assert\Collection(array( 'password' => new Assert\Length(array('min' => 60)), )); -$violations = $validator->validateValue($input, $constraint); +$violations = $validator->validate($input, $constraint); ``` Again, the validator returns the list of violations. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf index 7c5da55bcf9da..a33eb8239153b 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf @@ -278,6 +278,42 @@ This value should not be identical to {{ compared_value_type }} {{ compared_value }}. Стойността не трябва да бъде идентична с {{ compared_value_type }} {{ compared_value }}. + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Изображението е с твърде голяма пропорция ({{ ratio }}). Максималната пропорция трябва да е {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Изображението е с твърде малка пропорция ({{ ratio }}). Минималната пропорция трябва да е {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Изображението е квадрат ({{ width }}x{{ height }}px). Такива изображения не са разрешени. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Изображението е с пейзажна ориентация ({{ width }}x{{ height }}px). Изображения с такава ориентация не са разрешени. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Изображението е с портретна ориентация ({{ width }}x{{ height }}px). Изображения с такава ориентация не са разрешени. + + + An empty file is not allowed. + Празни файлове не са разрешени. + + + The host could not be resolved. + Хостът е недостъпен. + + + This value does not match the expected {{ charset }} charset. + Стойността не съвпада с {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Невалиден бизнес идентификационен код (BIC). + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.hy.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.hy.xlf index 664c085ae71af..bc0daced86de2 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.hy.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.hy.xlf @@ -1,186 +1,318 @@ - + This value should be false. - Արժեքը պետք է լինի կեղծ. + Արժեքը պետք է լինի սխալ։ This value should be true. - Արժեքը պետք է լինի ճշմարիտ. + Արժեքը պետք է լինի ճիշտ։ This value should be of type {{ type }}. - Արժեքը պետք է լինի {{ type }} տեսակի. + Արժեքը պետք է լինի {{ type }} տեսակի։ This value should be blank. - Արժեքը պետք է լինի դատարկ. + Արժեքը պետք է լինի դատարկ։ The value you selected is not a valid choice. - Ձեր ընտրած արժեքը անթույլատրելի է. + Ձեր ընտրած արժեքը անվավեր ընտրություն է։ You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. - Դուք պետք է ընտրեք ամենաքիչը {{ limit }} տարբերակներ. + Դուք պետք է ընտրեք ամենաքիչը {{ limit }} տարբերակներ։ You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. - Դուք պետք է ընտրեք ոչ ավելի քան {{ limit }} տարբերակներ. + Դուք պետք է ընտրեք ոչ ավելի քան {{ limit }} տարբերակներ։ One or more of the given values is invalid. - Մեկ կամ ավելի տրված արժեքները անթույլատրելի են. + Մեկ կամ ավելի տրված արժեքները անվավեր են։ This field was not expected. - Այս դաշտը չի սպասվում. + Այս դաշտը չի սպասվում։ This field is missing. - Այս դաշտը բացակայում է. + Այս դաշտը բացակայում է։ This value is not a valid date. - Արժեքը սխալ ամսաթիվ է. + Արժեքը սխալ ամսաթիվ է։ This value is not a valid datetime. - Ամսաթվի և ժամանակի արժեքը անթույլատրելի է. + Ամսաթվի և ժամանակի արժեքը անվավեր է։ This value is not a valid email address. - Էլ-փոստի արժեքը անթույլատրելի է. + Անվավեր էլ֊փոստի արժեք։ The file could not be found. - Ֆայլը չի գտնվել. + Նիշքը չի գտնվել։ The file is not readable. - Ֆայլը անընթեռնելի է. + Նիշքը անընթեռնելի է։ The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. - Ֆայլը չափազանց մեծ է ({{ size }} {{ suffix }}): Մաքսիմալ թույլատրելի չափսը՝ {{ limit }} {{ suffix }}. + Նիշքը չափազանց մեծ է ({{ size }} {{ suffix }}): Մաքսիմալ թույլատրելի չափսը՝ {{ limit }} {{ suffix }}։ The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. - MIME-տեսակը անթույլատրելի է({{ type }}): Ֆայլերի թույլատրելի MIME-տեսակներն են: {{ types }}. + MIME-տեսակը անվավեր է է({{ type }}): Նիշքերի թույլատրելի MIME-տեսակներն են: {{ types }}։ This value should be {{ limit }} or less. - Արժեքը պետք է լինի {{ limit }} կամ փոքր. + Արժեքը պետք է լինի {{ limit }} կամ փոքր։ This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. - Արժեքը չափազանց երկար է: Պետք է լինի {{ limit }} կամ ավել սիմվոլներ. + Արժեքը չափազանց երկար է: Պետք է լինի {{ limit }} կամ ավել սիմվոլներ։ This value should be {{ limit }} or more. - Արժեքը պետ է լինի {{ limit }} կամ շատ. + Արժեքը պետ է լինի {{ limit }} կամ շատ։ This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. - Արժեքը չափազանց կարճ է: Պետք է լինի {{ limit }} կամ ավելի սիմվոլներ. + Արժեքը չափազանց կարճ է: Պետք է լինի {{ limit }} կամ ավելի սիմվոլներ։ This value should not be blank. - Արժեքը չպետք է դատարկ լինի. + Արժեքը չպետք է դատարկ լինի։ This value should not be null. - Արժեքը չպետք է լինի null. + Արժեքը չպետք է լինի null։ This value should be null. - Արժեքը պետք է լինի null. + Արժեքը պետք է լինի null։ This value is not valid. - Անթույլատրելի արժեք. + Անվավեր արժեք։ This value is not a valid time. - Ժամանակի արժեքը անթույլատրելի է. + Ժամանակի արժեքը անվավեր է։ This value is not a valid URL. - Արժեքը URL չէ. + Արժեքը URL չէ։ The two values should be equal. - Երկու արժեքները պետք է նույնը լինեն. + Երկու արժեքները պետք է նույնը լինեն։ The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. - Ֆայլը չափազանց մեծ է: Մաքսիմալ թույլատրելի չափսը {{ limit }} {{ suffix }} է. + Նիշքը չափազանց մեծ է: Մաքսիմալ թույլատրելի չափսը {{ limit }} {{ suffix }} է։ The file is too large. - Ֆայլը չափազանց մեծ է. + Նիշքը չափազանց մեծ է։ The file could not be uploaded. - Ֆայլը չի կարող բեռնվել. + Նիշքը չի կարող բեռնվել։ This value should be a valid number. - Արժեքը պետք է լինի թիվ. + Արժեքը պետք է լինի թիվ։ This value is not a valid country. - Արժեքը պետք է լինի երկիր. + Արժեքը պետք է լինի երկիր։ This file is not a valid image. - Ֆայլը նկարի թույլատրելի ֆորմատ չէ. + Նիշքը նկարի վավեր ֆորմատ չէ։ This is not a valid IP address. - Արժեքը թույլատրելի IP հասցե չէ. + Արժեքը վավեր IP հասցե չէ։ This value is not a valid language. - Արժեքը թույլատրելի լեզու չէ. + Արժեքը վավեր լեզու չէ։ This value is not a valid locale. - Արժեքը չի հանդիսանում թույլատրելի տեղայնացում. + Արժեքը չի հանդիսանում վավեր տեղայնացում։ This value is already used. - Այդ արժեքը արդեն օգտագործվում է. + Այդ արժեքն արդեն օգտագործվում է։ The size of the image could not be detected. - Նկարի չափսերը չստացվեց որոշել. + Նկարի չափսերը չստացվեց որոշել։ The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. - Նկարի լայնությունը չափազանց մեծ է({{ width }}px). Մաքսիմալ չափն է {{ max_width }}px. + Նկարի լայնությունը չափազանց մեծ է({{ width }}px). Մաքսիմալ չափն է {{ max_width }}px։ The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. - Նկարի լայնությունը չափազանց փոքր է ({{ width }}px). Մինիմալ չափն է {{ min_ width }}px. + Նկարի լայնությունը չափազանց փոքր է ({{ width }}px). Մինիմալ չափն է {{ min_ width }}px։ The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. - Նկարի բարձրությունը չափազանց մեծ է ({{ height }}px). Մաքսիմալ չափն է {{ max_height }}px. + Նկարի բարձրությունը չափազանց մեծ է ({{ height }}px). Մաքսիմալ չափն է {{ max_height }}px։ The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. - Նկարի բարձրությունը չափազանց փոքր է ({{ height }}px). Մինիմալ չափն է {{ min_height }}px. + Նկարի բարձրությունը չափազանց փոքր է ({{ height }}px). Մինիմալ չափն է {{ min_height }}px։ This value should be the user's current password. - Այս արժեքը պետք է լինի օգտագործողի ներկա ծածկագիրը. + Այս արժեքը պետք է լինի օգտագործողի ներկա ծածկագիրը։ This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. - Այս արժեքը պետք է ունենա ճիշտ {{ limit }} սիմվոլներ. + Այս արժեքը պետք է ունենա ճիշտ {{ limit }} սիմվոլներ։ + + + The file was only partially uploaded. + Նիշքի մասնակի բեռնման սխալ։ + + + No file was uploaded. + Նիշքը չի բեռնվել։ + + + No temporary folder was configured in php.ini. + php.ini նիշքում ժամանակավոր պանակ նշված չէ։ + + + Cannot write temporary file to disk. + Ժամանակավոր նիշքը հնարավոր չէ գրել սկավառակի վրա։ + + + A PHP extension caused the upload to fail. + PHP ֆորմատը դարձել է բեռնման չհաջողման պատճառ։ + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Այս հավաքածուն պետք է պաուրակի {{ limit }} կամ ավելի տարրեր։|Այս հավելվածը պետք է պարունակի limit }} տարր կամ ավելին։|Այս հավաքածուն պետք է պարունակի {{ limit }} տարրերին կամ ավելի։ + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Այս հավաքածուն պետք է պաուրակի {{ limit }} տարրեր կամ քիչ։|Այս հավաքածուն պետք է պաուրակի {{ limit }} տարր կամ քիչ։|Այս հավաքածուն պետք է պաուրակի {{ limit }} տարրեր կամ քիչ։ + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Այս հավաքածուն պետք է պաուրակի ուղիղ {{ limit }} տարր։|Այս հավաքածուն պետք է պաուրակի ուղիղ {{ limit }} տարրեր։|Այս հավաքածուն պետք է պաուրակի {{ limit }} տարրեր։ + + + Invalid card number. + Քարտի սխալ համար: + + + Unsupported card type or invalid card number. + Չսպասարկվող կամ սխալ քարտի համար: + + + This is not a valid International Bank Account Number (IBAN). + Արժեքը վավեր միջազային բանկային հաշվի համար չէ (IBAN)։ + + + This value is not a valid ISBN-10. + Արժեքը ունի անվավեր ISBN-10 ձևաչափ։ + + + This value is not a valid ISBN-13. + Արժեքը ունի անվավեր ISBN-13 ձևաչափ։ + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Արժեքը չի համապատասխանում ISBN-10 և ISBN-13 ձևաչափերին։ + + + This value is not a valid ISSN. + Արժեքը չի համապաստասխանում ISSN ձևաչափին։ + + + This value is not a valid currency. + Արժեքը վավեր տարադրամ չէ։ + + + This value should be equal to {{ compared_value }}. + Արժեքը պետք է լինի {{ compared_value }}։ + + + This value should be greater than {{ compared_value }}. + Արժեքը պետք է մեծ լինի, քան {{ compared_value }}։ + + + This value should be greater than or equal to {{ compared_value }}. + Արժեքը պետք է լինի հավասար կամ մեծ քան {{ compared_value }}։ + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Արժեքը պետք է լինի ինչպես {{ compared_value_type }} {{ compared_value }}։ + + + This value should be less than {{ compared_value }}. + Արժեքը պետք է լինի փոքր քան {{ compared_value }}։ + + + This value should be less than or equal to {{ compared_value }}. + Արժեքը պետք է լինի փոքր կամ հավասար {{ compared_value }}։ + + + This value should not be equal to {{ compared_value }}. + Արժեքը պետք է լինի հավասար {{ compared_value }}։ + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Արժեքը պետք է լինի նունը {{ compared_value_type }} {{ compared_value }}: + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Պատկերի կողմերի հարաբերակցությունը խիստ մեծ է ({{ ratio }}). Մաքսիմալ հարաբերակցությունը՝ {{ max_ratio }}։ + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + Պատկերի կողմերի հարաբերակցությունը խիստ փոքր է ({{ ratio }}). Մինիմալ հարաբերակցությունը՝ {{ min_ratio }}։ + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Պատկերը քառակուսի է({{ width }}x{{ height }}px)։ Քառակուսի նկարներ չեն թույլատրվում։ + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Պատկերը ալբոմային ուղղվածության է({{ width }}x{{ height }}px)․ դա չի թույլատրվում։ + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Պատկերը պորտրետային ուղղվածության է ({{ width }}x{{ height }}px)․ դա չի թույլատրվում։ + + + An empty file is not allowed. + Դատարկ նիշք չի թույլատրվում։ + + + The host could not be resolved. + Հոսթի անունը հնարավոր չի պարզել: + + + This value does not match the expected {{ charset }} charset. + Արժեքը չի համընկնում {{ charset }} կոդավորման հետ: + + + This is not a valid Business Identifier Code (BIC). + Սա վավեր Business Identifier Code (BIC) չէ։ diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.lb.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.lb.xlf index 8281c7c249a05..d631797018bb8 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.lb.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.lb.xlf @@ -24,11 +24,11 @@ You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. - Dir sollt mindestens {{ limit }} Méiglechkeete wielen. + Et muss mindestens {{ limit }} Méiglechkeet ausgewielt ginn.|Et musse mindestens {{ limit }} Méiglechkeeten ausgewielt ginn. You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. - Dir sollt héchstens {{ limit }} Méiglechkeete wielen. + Et dierf héchstens {{ limit }} Méiglechkeet ausgewielt ginn.|Et dierfen héchstens {{ limit }} Méiglechkeeten ausgewielt ginn. One or more of the given values is invalid. @@ -298,6 +298,22 @@ The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. D'Bild ass am Héichformat ({{ width }}x{{ height }}px). Biller am Héichformat sinn net erlaabt. + + An empty file is not allowed. + En eidele Fichier ass net erlaabt. + + + The host could not be resolved. + Den Domain-Numm konnt net opgeléist ginn. + + + This value does not match the expected {{ charset }} charset. + Dëse Wäert entsprécht net dem erwaarten Zeechesaz {{ charset }}. + + + This is not a valid Business Identifier Code (BIC). + Dëst ass kee gëltege "Business Identifier Code" (BIC). + diff --git a/src/Symfony/Component/VarDumper/Caster/AmqpCaster.php b/src/Symfony/Component/VarDumper/Caster/AmqpCaster.php index 4e9b351c181c9..98eede22d55d4 100644 --- a/src/Symfony/Component/VarDumper/Caster/AmqpCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/AmqpCaster.php @@ -48,7 +48,7 @@ public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, { $prefix = Caster::PREFIX_VIRTUAL; - // BC layer in the ampq lib + // BC layer in the amqp lib if (method_exists($c, 'getReadTimeout')) { $timeout = $c->getReadTimeout(); } else { diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index 0ae752166a940..1de180157282f 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -31,7 +31,7 @@ class HtmlDumper extends CliDumper protected $headerIsDumped = false; protected $lastDepth = -1; protected $styles = array( - 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:100000; word-break: normal', + 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal', 'num' => 'font-weight:bold; color:#1299DA', 'const' => 'font-weight:bold', 'str' => 'font-weight:bold; color:#56DB3A', diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 04f2237be27ed..88cc0ff4b0522 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -240,6 +240,10 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = if ($isRef) { $this->refs[$isRef] = $data[$key]; } + + if ($objectForMap && !is_object($data)) { + $data = (object) $data; + } } else { // multiple documents are not supported if ('---' === $this->currentLine) { @@ -339,6 +343,7 @@ private function getCurrentLineIndentation() private function getNextEmbedBlock($indentation = null, $inSequence = false) { $oldLineIndentation = $this->getCurrentLineIndentation(); + $insideBlockScalar = $this->isBlockScalarHeader(); if (!$this->moveToNextLine()) { return; @@ -347,7 +352,7 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false) if (null === $indentation) { $newIndent = $this->getCurrentLineIndentation(); - $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem($this->currentLine); + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); @@ -373,20 +378,24 @@ 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.'$~'; - $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); + if (!$insideBlockScalar) { + $insideBlockScalar = $this->isBlockScalarHeader(); + } + + $previousLineIndentation = $this->getCurrentLineIndentation(); while ($this->moveToNextLine()) { $indent = $this->getCurrentLineIndentation(); - if ($indent === $newIndent) { - $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); + if (!$insideBlockScalar && $indent === $previousLineIndentation) { + $insideBlockScalar = $this->isBlockScalarHeader(); } - if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem($this->currentLine) && $newIndent === $indent) { + $previousLineIndentation = $indent; + + if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { $this->moveToPreviousLine(); break; } @@ -396,7 +405,8 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false) continue; } - if ($removeComments && $this->isCurrentLineComment()) { + // we ignore "comment" lines only when we are not inside a scalar block + if (!$insideBlockScalar && $this->isCurrentLineComment()) { continue; } @@ -499,13 +509,13 @@ private function parseBlockScalar($style, $chomping = '', $indentation = 0) } $isCurrentLineBlank = $this->isCurrentLineBlank(); - $text = ''; + $blockLines = array(); // leading blank lines are consumed before determining indentation while ($notEOF && $isCurrentLineBlank) { // newline only if not EOF if ($notEOF = $this->moveToNextLine()) { - $text .= "\n"; + $blockLines[] = ''; $isCurrentLineBlank = $this->isCurrentLineBlank(); } } @@ -526,37 +536,59 @@ private function parseBlockScalar($style, $chomping = '', $indentation = 0) preg_match($pattern, $this->currentLine, $matches) ) ) { - if ($isCurrentLineBlank) { - $text .= substr($this->currentLine, $indentation); + if ($isCurrentLineBlank && strlen($this->currentLine) > $indentation) { + $blockLines[] = substr($this->currentLine, $indentation); + } elseif ($isCurrentLineBlank) { + $blockLines[] = ''; } else { - $text .= $matches[1]; + $blockLines[] = $matches[1]; } // newline only if not EOF if ($notEOF = $this->moveToNextLine()) { - $text .= "\n"; $isCurrentLineBlank = $this->isCurrentLineBlank(); } } } elseif ($notEOF) { - $text .= "\n"; + $blockLines[] = ''; } if ($notEOF) { + $blockLines[] = ''; $this->moveToPreviousLine(); } // folded style if ('>' === $style) { - // folded lines - // replace all non-leading/non-trailing single newlines with spaces - preg_match('/(\n*)$/', $text, $matches); - $text = preg_replace('/(?getCurrentLineIndentation() == $currentIndentation && - $this->isStringUnIndentedCollectionItem($this->currentLine) + $this->isStringUnIndentedCollectionItem() ) { $ret = true; } @@ -707,6 +739,16 @@ private function isNextLineUnIndentedCollection() */ private function isStringUnIndentedCollectionItem() { - return (0 === strpos($this->currentLine, '- ')); + return 0 === strpos($this->currentLine, '- '); + } + + /** + * Tests whether or not the current line is the header of a block scalar. + * + * @return bool + */ + private function isBlockScalarHeader() + { + return (bool) preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine); } } diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 19078220bbafd..0aaddc7f18496 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -438,6 +438,28 @@ public function testObjectSupportDisabledButNoExceptions() $this->assertEquals(array('foo' => null, 'bar' => 1), $this->parser->parse($input), '->parse() does not parse objects'); } + public function testObjectForMapEnabledWithMapping() + { + $yaml = <<parser->parse($yaml, false, false, true); + + $this->assertInstanceOf('stdClass', $result); + $this->assertInstanceOf('stdClass', $result->foo); + $this->assertEquals(array('cat'), $result->foo->fiz); + } + + public function testObjectForMapEnabledWithInlineMapping() + { + $result = $this->parser->parse('{ "foo": "bar", "fiz": "cat" }', false, false, true); + + $this->assertInstanceOf('stdClass', $result); + $this->assertEquals('bar', $result->foo); + $this->assertEquals('cat', $result->fiz); + } + /** * @expectedException \Symfony\Component\Yaml\Exception\ParseException */ @@ -783,6 +805,151 @@ public function testFloatKeys() $this->assertEquals($expected, $this->parser->parse($yaml)); } + + /** + * @dataProvider getCommentLikeStringInScalarBlockData + */ + public function testCommentLikeStringsAreNotStrippedInBlockScalars($yaml, $expectedParserResult) + { + $this->assertSame($expectedParserResult, $this->parser->parse($yaml)); + } + + public function getCommentLikeStringInScalarBlockData() + { + $yaml1 = << +

title

+ + + footer # comment3 +EOT; + $expected1 = array( + 'pages' => array( + array( + 'title' => 'some title', + 'content' => << +

title

+ + +footer # comment3 +EOT + , + ), + ), + ); + + $yaml2 = << << array( + array( + 'one' => << << +

A heading

+ +
    +
  • a list
  • +
  • may be a good example
  • +
+EOT; + + $this->assertSame( + array( + 'test' => <<A heading +
  • a list
  • may be a good example
+EOT + , + ), + $this->parser->parse($yaml) + ); + } + + public function testAdditionallyIndentedLinesAreParsedAsNewLinesInFoldedBlocks() + { + $yaml = << +

A heading

+ +
    +
  • a list
  • +
  • may be a good example
  • +
+EOT; + + $this->assertSame( + array( + 'test' => <<A heading +
    +
  • a list
  • +
  • may be a good example
  • +
+EOT + , + ), + $this->parser->parse($yaml) + ); + } } class B