diff --git a/.composer/composer.json b/.composer/composer.json index 655ee13805f47..d69e9f7626d55 100644 --- a/.composer/composer.json +++ b/.composer/composer.json @@ -1,6 +1,5 @@ { "require": { - "php": ">=5.3.7", - "hirak/prestissimo": "^0.1.15" + "hirak/prestissimo": "^0.1.18" } } diff --git a/.composer/composer.lock b/.composer/composer.lock index 8e037ebb4af81..7027e21d9ac4b 100644 --- a/.composer/composer.lock +++ b/.composer/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "aa7aa2d143fd85800595242996021ada", - "content-hash": "51e9161b78dda1fe149a9e9c106be90b", + "hash": "e02685ff7d2409d34fa87e306fc86ec5", + "content-hash": "2854c05c76a78113c693dbbdd3f9c518", "packages": [ { "name": "hirak/prestissimo", @@ -65,8 +65,6 @@ "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, - "platform": { - "php": ">=5.3.7" - }, + "platform": [], "platform-dev": [] } diff --git a/.travis.yml b/.travis.yml index d82550017d9ab..4718a1090ac7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,44 +35,46 @@ cache: services: mongodb before_install: + - PHP=$TRAVIS_PHP_VERSION # Matrix lines for intermediate PHP versions are skipped for pull requests - - if [[ ! $deps && ! $TRAVIS_PHP_VERSION = ${MIN_PHP%.*} && $TRAVIS_PHP_VERSION != hhvm && $TRAVIS_PULL_REQUEST != false ]]; then deps=skip; fi; + - if [[ ! $deps && ! $PHP = ${MIN_PHP%.*} && $PHP != hhvm && $TRAVIS_PULL_REQUEST != false ]]; then deps=skip; skip=1; fi # A sigchild-enabled-PHP is used to test the Process component on the lowest PHP matrix line - - if [[ ! $deps && $TRAVIS_PHP_VERSION = ${MIN_PHP%.*} && ! -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); fi; - - if [[ $TRAVIS_PHP_VERSION = 7.* ]]; then (echo yes | pecl install -f apcu-5.1.2 && echo apc.enable_cli = 1 >> $INI_FILE); 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 = 5.* ]]; then pecl install -f memcached-2.1.0; 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 && $TRAVIS_REPO_SLUG = symfony/symfony ]]; then cp .composer/* ~/.composer/; composer global install; fi; - - if [[ $deps != skip ]]; then ./phpunit install; fi; - - export PHPUNIT=$(readlink -f ./phpunit) + - if [[ ! $deps && $PHP = ${MIN_PHP%.*} && ! -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 [[ $PHP != hhvm ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi + - if [[ ! $skip ]]; then echo memory_limit = -1 >> $INI_FILE; fi + - if [[ ! $skip ]]; then echo session.gc_probability = 0 >> $INI_FILE; fi + - if [[ ! $skip && $PHP = 5.* ]]; then echo extension = mongo.so >> $INI_FILE; fi + - if [[ ! $skip && $PHP = 5.* ]]; then echo extension = memcache.so >> $INI_FILE; fi + - if [[ ! $skip && $PHP = 5.* ]]; then (echo yes | pecl install -f apcu-4.0.10 && echo apc.enable_cli = 1 >> $INI_FILE); fi + - if [[ ! $skip && $PHP = 7.* ]]; then (echo yes | pecl install -f apcu-5.1.2 && echo apc.enable_cli = 1 >> $INI_FILE); fi + - if [[ ! $deps && $PHP = 5.* ]]; then (cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo extension = $(pwd)/modules/symfony_debug.so >> $INI_FILE); fi + - if [[ ! $skip && $PHP = 5.* ]]; then pecl install -f memcached-2.1.0; fi + - if [[ ! $skip && $PHP != hhvm ]]; then echo extension = ldap.so >> $INI_FILE; fi + - if [[ ! $skip && $PHP != hhvm ]]; then phpenv config-rm xdebug.ini; fi + - if [[ ! $skip ]]; then composer self-update --stable; fi + - if [[ ! $skip ]]; then cp .composer/* ~/.composer/; composer global install; fi + - if [[ ! $skip ]]; then ./phpunit install; fi + - if [[ ! $skip && $deps ]]; then composer global remove hirak/prestissimo; fi + - if [[ ! $skip ]]; then export PHPUNIT=$(readlink -f ./phpunit); fi install: - - if [[ $deps != skip ]]; then COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi; + - if [[ ! $skip ]]; then COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi # Create local composer packages for each patched components and reference them in composer.json files when cross-testing components - - if [[ $deps != skip && $deps ]]; then php .travis.php $TRAVIS_COMMIT_RANGE $TRAVIS_BRANCH $COMPONENTS; fi; + - if [[ ! $skip && $deps ]]; then php .travis.php $TRAVIS_COMMIT_RANGE $TRAVIS_BRANCH $COMPONENTS; fi # For the master branch when deps=high, the version before master is checked out and tested with the locally patched components - - 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; COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); ./phpunit install; 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; COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); ./phpunit install; fi # Legacy tests are skipped when deps=high and when the current branch version has not the same major version number than the next one - - 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 update; else export SYMFONY_DEPRECATIONS_HELPER=weak; fi; - - if [[ $TRAVIS_BRANCH = master ]]; then export SYMFONY_PHPUNIT_OVERLOAD=1; fi; - - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; 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 update; else export SYMFONY_DEPRECATIONS_HELPER=weak; fi + - if [[ $TRAVIS_BRANCH = master ]]; then export SYMFONY_PHPUNIT_OVERLOAD=1; fi + - if [[ $PHP != hhvm ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi script: - - 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" | xargs -I{} sh -c 'echo "\\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 update; $PHPUNIT --exclude-group tty,benchmark,intl-data'$LEGACY; fi; - - if [[ $deps = low ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --prefer-lowest --prefer-stable; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi; - - if [[ $deps = skip ]]; then echo This matrix line is skipped for pull requests.; fi; + - if [[ $skip ]]; then echo -e "\\n\\e[1;34mIntermediate PHP version $PHP is skipped for pull requests.\\e[0m"; 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 && $PHP = ${MIN_PHP%.*} ]]; then echo -e "1\\n0" | xargs -I{} sh -c 'echo "\\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 update --no-progress --ansi; $PHPUNIT --exclude-group tty,benchmark,intl-data'$LEGACY; fi + - if [[ $deps = low ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --no-progress --ansi --prefer-lowest --prefer-stable; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi diff --git a/CHANGELOG-2.8.md b/CHANGELOG-2.8.md index 38abdc97b27f1..854716d996a75 100644 --- a/CHANGELOG-2.8.md +++ b/CHANGELOG-2.8.md @@ -7,6 +7,102 @@ in 2.8 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.8.0...v2.8.1 +* 2.8.5 (2016-04-29) + + * bug #18180 [Form] fixed BC break with pre selection of choices with `ChoiceType` and its children (HeahDude) + * bug #18645 [Console] Fix wrong exceptions being thrown (JhonnyL) + * bug #18562 [WebProfilerBunde] Give an absolute url in case the request occured from another domain (romainneutron) + * bug #18600 [DI] Fix AutowirePass fatal error with classes that have non-existing parents (hason, nicolas-grekas) + * bug #18603 [PropertyAccess] ->getValue() should be read-only (nicolas-grekas) + * bug #18593 [VarDumper] Fix dumping type hints for non-existing parent classes (nicolas-grekas) + * bug #18596 [DI] Fix internal caching in AutowirePass (nicolas-grekas) + * bug #18581 [Console] [TableHelper] make it work with SymfonyStyle. (aitboudad) + * bug #18280 [Routing] add query param if value is different from default (Tobion) + * bug #18540 Replace iconv_*() uses by mb_*(), add mbstring polyfill when required (nicolas-grekas) + * bug #18496 [Console] use ANSI escape sequences in ProgressBar overwrite method (alekitto) + * bug #18490 [LDAP] Free the search result after a search to free memory (hiddewie) + * bug #18491 [DependencyInjection] anonymous services are always private (xabbuh) + * bug #18515 [Filesystem] Better error handling in remove() (nicolas-grekas) + * bug #18360 [PropertyInfo] Extract nullable and collection key type for Doctrine associations (teohhanhui) + * bug #18449 [PropertyAccess] Fix regression (nicolas-grekas) + * bug #18429 [Console] Correct time formatting. (camporter) + * bug #18457 [WebProfilerBundle] Fixed error from unset twig variable (simonsargeant) + * bug #18467 [DependencyInjection] Resolve aliases before removing abstract services + add tests (nicolas-grekas) + * bug #18469 Force profiler toolbar svg display (pyrech) + * bug #18460 [DomCrawler] Fix select option with empty value (Matt Wells) + * bug #18425 [Security] Fixed SwitchUserListener when exiting an impersonation with AnonymousToken (lyrixx) + * bug #18317 [Form] fix "prototype" not required when parent form is not required (HeahDude) + * bug #18439 [Logging] Add support for Firefox (43+) in ChromePhpHandler (arjenm) + * bug #18385 Detect CLI color support for Windows 10 build 10586 (mlocati) + * bug #18426 [EventDispatcher] Try first if the event is Stopped (lyrixx) + * bug #18407 Fixed the "hover" state of the profiler sidebar menu (javiereguiluz) + * bug #18394 [FrameworkBundle] Return the invokable service if its name is the class name (dunglas) + * bug #18347 Fixed the styles of the Symfony icon in the web debug toolbar (javiereguiluz) + * bug #18265 Optimize ReplaceAliasByActualDefinitionPass (ajb-in) + * bug #18349 [Process] Fix stream_select priority when writing to stdin (nicolas-grekas) + * bug #18358 [Form] NumberToLocalizedStringTransformer should return floats when possible (nicolas-grekas) + * bug #17926 [DependencyInjection] Enable alias for service_container (hason) + * bug #18352 [Debug] Fix case sensitivity checks (nicolas-grekas) + * bug #18336 [Debug] Fix handling of php7 throwables (nicolas-grekas) + * bug #18354 [FrameworkBundle][TwigBridge] fix high deps tests (xabbuh) + * bug #18312 [ClassLoader] Fix storing not-found classes in APC cache (nicolas-grekas) + +* 2.8.4 (2016-03-27) + + * bug #18298 [Validator] do not treat payload as callback (xabbuh) + * bug #18275 [Form] Fix BC break introduced in #14403 (HeahDude) + * bug #18271 [FileSystem] Google app engine filesystem (swordbeta) + * bug #18255 [HttpFoundation] Fix support of custom mime types with parameters (Ener-Getick) + * bug #18272 [Bridge\PhpUnit] Workaround old phpunit bug, no colors in weak mode, add tests (nicolas-grekas) + * bug #18259 [PropertyAccess] Backport fixes from 2.7 (nicolas-grekas) + * bug #18261 [PropertyAccess] Fix isPropertyWritable not using the reflection cache (nicolas-grekas) + * bug #18224 [PropertyAccess] Remove most ref mismatches to improve perf (nicolas-grekas) + * bug #18237 [WebProfilerBundle] Added table-layout property to AJAX toolbar css (kevintweber) + * bug #18209 [PropertyInfo] Support Doctrine custom mapping type in DoctrineExtractor (teohhanhui) + * bug #18210 [PropertyAccess] Throw an UnexpectedTypeException when the type do not match (dunglas, nicolas-grekas) + * bug #18216 [Intl] Fix invalid numeric literal on PHP 7 (nicolas-grekas) + * bug #18147 [Validator] EmailValidator cannot extract hostname if email contains multiple @ symbols (natechicago) + * bug #18023 [Process] getIncrementalOutput should work without calling getOutput (romainneutron) + * bug #18175 [Translation] Add support for fuzzy tags in PoFileLoader (nud) + * bug #18179 [Form] Fix NumberToLocalizedStringTransformer::reverseTransform with big integers (ovrflo, nicolas-grekas) + * bug #18164 [HttpKernel] set s-maxage only if all responses are cacheable (xabbuh) + * bug #18150 [Process] Wait a bit less on Windows (nicolas-grekas) + * bug #18130 [Debug] Replaced logic for detecting filesystem case sensitivity (Dan Blows) + * bug #18137 Autowiring the concrete class too - consistent with behavior of other services (weaverryan) + * bug #18087 [WebProfiler] Sidebar button padding (rvanlaak) + * bug #18080 [HttpFoundation] Set the Content-Range header if the requested Range is unsatisfied (jakzal) + * bug #18084 [HttpFoundation] Avoid warnings when checking malicious IPs (jakzal) + * bug #18066 [Process] Fix pipes handling (nicolas-grekas) + * bug #18078 [Console] Fix an autocompletion question helper issue with non-sequentially indexed choices (jakzal) + * bug #18048 [HttpKernel] Fix mem usage when stripping the prod container (nicolas-grekas) + * bug #18065 [Finder] Partially revert #17134 to fix a regression (jakzal) + * bug #18018 [HttpFoundation] exception when registering bags for started sessions (xabbuh) + * bug #18054 [Filesystem] Fix false positive in ->remove() (nicolas-grekas) + * bug #18049 [Validator] Fix the locale validator so it treats a locale alias as a valid locale (jakzal) + * bug #18019 [Intl] Update ICU to version 55 (jakzal) + * bug #18015 [Process] Fix memory issue when using large input streams (romainneutron) + * bug #16656 [HttpFoundation] automatically generate safe fallback filename (xabbuh) + * bug #15794 [Console] default to stderr in the console helpers (alcohol) + * bug #17984 Allow to normalize \Traversable when serializing xml (Ener-Getick) + * bug #17434 Improved the error message when a template is not found (rvanginneken, javiereguiluz) + * bug #17687 Improved the error message when using "@" in a decorated service (javiereguiluz) + * bug #17744 Improve error reporting in router panel of web profiler (javiereguiluz) + * bug #17894 [FrameworkBundle] Fix a regression in handling absolute template paths (jakzal) + * bug #17990 [DoctrineBridge][Form] Fix performance regression in EntityType (kimlai) + * bug #17595 [HttpKernel] Remove _path from query parameters when fragment is a subrequest (cmenning) + * bug #17986 [DomCrawler] Dont use LIBXML_PARSEHUGE by default (nicolas-grekas) + * bug #17668 add 'guid' to list of exception to filter out (garak) + * bug #17615 Ensure backend slashes for symlinks on Windows systems (cpsitgmbh) + * bug #17626 Try to delete broken symlinks (IchHabRecht) + * bug #17978 [Yaml] ensure dump indentation to be greather than zero (xabbuh) + * bug #16886 [Form] [ChoiceType] Prefer placeholder to empty_value (boite) + * bug #17976 [WebProfilerBundle] fix debug toolbar rendering by removing inadvertently added links (craue) + * bug #17971 Variadic controller params (NiR-, fabpot) + * bug #17876 [DependencyInjection] Fixing autowiring bug when some args are set (weaverryan) + * bug #17568 Improved Bootstrap form theme for hidden fields (javiereguiluz) + * bug #17561 [WebProfilerBundle] Fix design issue in profiler when having errors in forms (Pierstoval) + * bug #17925 [Bridge] The WebProcessor now forwards the client IP (magnetik) + * 2.8.3 (2016-02-28) * bug #17947 Fix - #17676 (backport #17919 to 2.3) (Ocramius) diff --git a/appveyor.yml b/appveyor.yml index b29e117343008..4a64d1494c765 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,11 +21,11 @@ install: - IF %PHP%==1 appveyor DownloadFile https://curl.haxx.se/ca/cacert.pem - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/releases/archives/php-5.3.11-nts-Win32-VC9-x86.zip - IF %PHP%==1 7z x php-5.3.11-nts-Win32-VC9-x86.zip -y >nul - - IF %PHP%==1 appveyor DownloadFile http://nebm.ist.utl.pt/~glopes/misc/intl_win/ICU-51.2-dlls.zip + - IF %PHP%==1 appveyor DownloadFile https://raw.githubusercontent.com/symfony/binary-utils/master/ICU-51.2-dlls.zip - IF %PHP%==1 7z x ICU-51.2-dlls.zip -y >nul - IF %PHP%==1 del /Q *.zip - 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 appveyor DownloadFile https://raw.githubusercontent.com/symfony/binary-utils/master/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.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 @@ -50,12 +50,12 @@ install: - IF %PHP%==1 echo extension=php_ldap.dll >> php.ini-max - IF %PHP%==1 echo extension=php_curl.dll >> php.ini-max - IF %PHP%==1 echo curl.cainfo=c:\php\cacert.pem >> php.ini-max - - appveyor DownloadFile https://getcomposer.org/composer.phar + - IF %PHP%==1 appveyor DownloadFile https://getcomposer.org/download/1.0.2/composer.phar - copy /Y php.ini-max php.ini - cd c:\projects\symfony - mkdir %APPDATA%\Composer - - IF %APPVEYOR_REPO_NAME%==symfony/symfony copy /Y .composer\* %APPDATA%\Composer\ - - IF %APPVEYOR_REPO_NAME%==symfony/symfony composer global install --no-progress --ansi || echo curl.cainfo needs PHP 5.3.7 + - copy /Y .composer\* %APPDATA%\Composer\ + - composer global install --no-progress --ansi - php phpunit install - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) - composer update --no-progress --ansi diff --git a/composer.json b/composer.json index 288b9d9b0dedd..ee035f235b3e0 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "doctrine/common": "~2.4", "twig/twig": "~1.23|~2.0", "psr/log": "~1.0", - "symfony/security-acl": "~2.7", + "symfony/security-acl": "~2.7|~3.0.0", "symfony/polyfill-apcu": "~1.1", "symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-mbstring": "~1.0", @@ -93,7 +93,11 @@ }, "autoload": { "psr-4": { - "Symfony\\Bridge\\": "src/Symfony/Bridge/", + "Symfony\\Bridge\\Doctrine\\": "src/Symfony/Bridge/Doctrine/", + "Symfony\\Bridge\\Monolog\\": "src/Symfony/Bridge/Monolog/", + "Symfony\\Bridge\\ProxyManager\\": "src/Symfony/Bridge/ProxyManager/", + "Symfony\\Bridge\\Swiftmailer\\": "src/Symfony/Bridge/Swiftmailer/", + "Symfony\\Bridge\\Twig\\": "src/Symfony/Bridge/Twig/", "Symfony\\Bundle\\": "src/Symfony/Bundle/", "Symfony\\Component\\": "src/Symfony/Component/" }, @@ -104,6 +108,11 @@ "**/Tests/" ] }, + "autoload-dev": { + "psr-4": { + "Symfony\\Bridge\\PhpUnit\\": "src/Symfony/Bridge/PhpUnit/" + } + }, "minimum-stability": "dev", "extra": { "branch-alias": { diff --git a/phpunit b/phpunit index d2da42616d4eb..6d66f7b2b252a 100755 --- a/phpunit +++ b/phpunit @@ -51,6 +51,7 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ $zip->extractTo(getcwd()); $zip->close(); chdir("phpunit-$PHPUNIT_VERSION"); + passthru("$COMPOSER remove --no-update phpspec/prophecy"); passthru("$COMPOSER remove --no-update symfony/yaml"); passthru("$COMPOSER require --dev --no-update symfony/phpunit-bridge \">=3.1@dev\""); passthru("$COMPOSER install --prefer-dist --no-progress --ansi", $exit); diff --git a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php index 18fb341e5b74c..88b48e1ddb47b 100644 --- a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +++ b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php @@ -95,8 +95,8 @@ private function normalizeParams(array $params) } // detect if the too long string must be shorten - if (self::MAX_STRING_LENGTH < iconv_strlen($params[$index], 'UTF-8')) { - $params[$index] = iconv_substr($params[$index], 0, self::MAX_STRING_LENGTH - 6, 'UTF-8').' [...]'; + if (self::MAX_STRING_LENGTH < mb_strlen($params[$index], 'UTF-8')) { + $params[$index] = mb_substr($params[$index], 0, self::MAX_STRING_LENGTH - 6, 'UTF-8').' [...]'; continue; } } diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index ecd25088be0f4..6e8c29e16b0dd 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -71,7 +71,9 @@ public function getTypes($class, $property, array $context = array()) if ($metadata->isSingleValuedAssociation($property)) { if ($metadata instanceof ClassMetadataInfo) { - $nullable = isset($metadata->discriminatorColumn['nullable']) ? $metadata->discriminatorColumn['nullable'] : false; + $associationMapping = $metadata->getAssociationMapping($property); + + $nullable = $this->isAssociationNullable($associationMapping); } else { $nullable = false; } @@ -79,12 +81,25 @@ public function getTypes($class, $property, array $context = array()) return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $class)); } + $collectionKeyType = Type::BUILTIN_TYPE_INT; + + if ($metadata instanceof ClassMetadataInfo) { + $associationMapping = $metadata->getAssociationMapping($property); + + if (isset($associationMapping['indexBy'])) { + $indexProperty = $associationMapping['indexBy']; + $typeOfField = $metadata->getTypeOfField($indexProperty); + + $collectionKeyType = $this->getPhpType($typeOfField); + } + } + return array(new Type( Type::BUILTIN_TYPE_OBJECT, false, 'Doctrine\Common\Collections\Collection', true, - new Type(Type::BUILTIN_TYPE_INT), + new Type($collectionKeyType), new Type(Type::BUILTIN_TYPE_OBJECT, false, $class) )); } @@ -118,6 +133,35 @@ public function getTypes($class, $property, array $context = array()) } } + /** + * Determines whether an association is nullable. + * + * @param array $associationMapping + * + * @return bool + * + * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246 + */ + private function isAssociationNullable(array $associationMapping) + { + if (isset($associationMapping['id']) && $associationMapping['id']) { + return false; + } + + if (!isset($associationMapping['joinColumns'])) { + return true; + } + + $joinColumns = $associationMapping['joinColumns']; + foreach ($joinColumns as $joinColumn) { + if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) { + return false; + } + } + + return true; + } + /** * Gets the corresponding built-in PHP type. * diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 0f056953063c4..469d8fa3d1d5d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -287,7 +287,7 @@ public function testSubmitSingleExpandedNull() $field->submit(null); $this->assertNull($field->getData()); - $this->assertNull($field->getViewData()); + $this->assertSame('', $field->getViewData(), 'View data is always a string'); } public function testSubmitSingleNonExpandedNull() diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index ca098b6fc8e70..1bc57b1140fae 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -54,6 +54,7 @@ public function testGetProperties() 'customFoo', 'foo', 'bar', + 'indexedBar', ), $this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy') ); @@ -75,7 +76,7 @@ public function typesProvider() array('bool', array(new Type(Type::BUILTIN_TYPE_BOOL))), array('binary', array(new Type(Type::BUILTIN_TYPE_RESOURCE))), array('json', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true))), - array('foo', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation'))), + array('foo', array(new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation'))), array('bar', array(new Type( Type::BUILTIN_TYPE_OBJECT, false, @@ -84,6 +85,14 @@ public function typesProvider() new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') ))), + array('indexedBar', array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Doctrine\Common\Collections\Collection', + true, + new Type(Type::BUILTIN_TYPE_STRING), + new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + ))), array('simpleArray', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))), array('customFoo', null), array('notMapped', null), diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php index 0b6b7bb544039..317d14257b203 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php @@ -40,6 +40,11 @@ class DoctrineDummy */ public $bar; + /** + * @ManyToMany(targetEntity="DoctrineRelation", indexBy="guid") + */ + protected $indexedBar; + /** * @Column(type="guid") */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php index bfb27e9338d99..23f64d829e030 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; /** @@ -26,4 +27,9 @@ class DoctrineRelation * @Column(type="smallint") */ public $id; + + /** + * @Column(type="guid") + */ + protected $guid; } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index f07527f5cb523..f0f79f522bbd1 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -17,12 +17,13 @@ ], "require": { "php": ">=5.3.9", - "doctrine/common": "~2.4" + "doctrine/common": "~2.4", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { "symfony/stopwatch": "~2.2|~3.0.0", "symfony/dependency-injection": "~2.2|~3.0.0", - "symfony/form": "~2.8|~3.0.0", + "symfony/form": "~2.8.5|~3.0.5", "symfony/http-kernel": "~2.2|~3.0.0", "symfony/property-access": "~2.3|~3.0.0", "symfony/property-info": "~2.8|3.0", diff --git a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php index 267b012966901..2636bc3b8980e 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php @@ -41,7 +41,7 @@ public function onKernelResponse(FilterResponseEvent $event) return; } - if (!preg_match('{\bChrome/\d+[\.\d+]*\b}', $event->getRequest()->headers->get('User-Agent'))) { + if (!preg_match('{\b(?:Chrome/\d+(?:\.\d+)*|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}', $event->getRequest()->headers->get('User-Agent'))) { $this->sendHeaders = false; $this->headers = array(); diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index efd17102923a7..330798fa6eb89 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -194,7 +194,11 @@ public static function register($mode = 0) private static function hasColorSupport() { if ('\\' === DIRECTORY_SEPARATOR) { - return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); + return + 0 >= version_compare('10.0.10586', PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); } return defined('STDOUT') && function_exists('posix_isatty') && @posix_isatty(STDOUT); diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 7f9684c0de4df..5e1441126ad52 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -322,7 +322,7 @@ {%- if read_only %} readonly="readonly"{% endif -%} {%- if disabled %} disabled="disabled"{% endif -%} {%- if required %} required="required"{% endif -%} - {%- for attrname, attrvalue in attr if 'readonly' != attrname -%} + {%- for attrname, attrvalue in attr if 'readonly' != attrname or not read_only -%} {{- " " -}} {%- if attrname in ['placeholder', 'title'] -%} {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index 3c8eca370ffec..f6d9877f1f10b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -80,7 +80,7 @@ protected function configure() php %command.full_name% --parameters -Display a specific parameter by specifying his name with the --parameter option: +Display a specific parameter by specifying its name with the --parameter option: php %command.full_name% --parameter=kernel.debug @@ -100,25 +100,21 @@ protected function execute(InputInterface $input, OutputInterface $output) } $this->validateInput($input); + $object = $this->getContainerBuilder(); if ($input->getOption('parameters')) { - $object = $this->getContainerBuilder()->getParameterBag(); + $object = $object->getParameterBag(); $options = array(); } elseif ($parameter = $input->getOption('parameter')) { - $object = $this->getContainerBuilder(); $options = array('parameter' => $parameter); } elseif ($input->getOption('tags')) { - $object = $this->getContainerBuilder(); $options = array('group_by' => 'tags', 'show_private' => $input->getOption('show-private')); } elseif ($tag = $input->getOption('tag')) { - $object = $this->getContainerBuilder(); $options = array('tag' => $tag, 'show_private' => $input->getOption('show-private')); } elseif ($name = $input->getArgument('name')) { - $object = $this->getContainerBuilder(); $name = $this->findProperServiceName($input, $io, $object, $name); $options = array('id' => $name); } else { - $object = $this->getContainerBuilder(); $options = array('show_private' => $input->getOption('show-private')); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php index 6804ded30d0e5..31593b9b80506 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php @@ -78,6 +78,10 @@ protected function createController($controller) */ protected function instantiateController($class) { + if ($this->container->has($class)) { + return $this->container->get($class); + } + $controller = parent::instantiateController($class); if ($controller instanceof ContainerAwareInterface) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php index 8e3e35e8342a3..6e2f74836e6f0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php @@ -2,7 +2,7 @@ disabled="disabled" required="required" $v): ?> - + escape($k), $view->escape(false !== $translation_domain ? $view['translator']->trans($v, array(), $translation_domain) : $v)) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php index 76a3489fe1551..b4dc8ada555b6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php @@ -87,6 +87,8 @@ public function testGetControllerService() public function testGetControllerInvokableService() { + $invokableController = new InvokableController('bar'); + $container = $this->createMockContainer(); $container->expects($this->once()) ->method('has') @@ -96,7 +98,7 @@ public function testGetControllerInvokableService() $container->expects($this->once()) ->method('get') ->with('foo') - ->will($this->returnValue($this)) + ->will($this->returnValue($invokableController)) ; $resolver = $this->createControllerResolver(null, null, $container); @@ -105,7 +107,33 @@ public function testGetControllerInvokableService() $controller = $resolver->getController($request); - $this->assertInstanceOf(get_class($this), $controller); + $this->assertEquals($invokableController, $controller); + } + + public function testGetControllerInvokableServiceWithClassNameAsName() + { + $invokableController = new InvokableController('bar'); + $className = __NAMESPACE__.'\InvokableController'; + + $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('has') + ->with($className) + ->will($this->returnValue(true)) + ; + $container->expects($this->once()) + ->method('get') + ->with($className) + ->will($this->returnValue($invokableController)) + ; + + $resolver = $this->createControllerResolver(null, null, $container); + $request = Request::create('/'); + $request->attributes->set('_controller', $className); + + $controller = $resolver->getController($request); + + $this->assertEquals($invokableController, $controller); } /** @@ -178,3 +206,14 @@ public function __invoke() { } } + +class InvokableController +{ + public function __construct($bar) // mandatory argument to prevent automatic instantiation + { + } + + public function __invoke() + { + } +} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php index 2be420ba8b0b0..d77eb279f4a69 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/RouterController.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\WebProfilerBundle\Controller; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Matcher\UrlMatcherInterface; use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php index 71c5090fc8a53..ee537af4f9889 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php @@ -65,7 +65,7 @@ public function onKernelResponse(FilterResponseEvent $event) try { $response->headers->set( 'X-Debug-Token-Link', - $this->urlGenerator->generate('_profiler', array('token' => $response->headers->get('X-Debug-Token'))) + $this->urlGenerator->generate('_profiler', array('token' => $response->headers->get('X-Debug-Token')), UrlGeneratorInterface::ABSOLUTE_URL) ); } catch (\Exception $e) { $response->headers->set('X-Debug-Error', get_class($e).': '.$e->getMessage()); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig index a2a7dff8b49f0..545d34e48697e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig @@ -50,24 +50,24 @@ - {% if 'n/a' != collector.appname %} + {% if 'n/a' is not same as(collector.appname) %}
Kernel name {{ collector.appname }}
{% endif %} - {% if 'n/a' != collector.env %} + {% if 'n/a' is not same as(collector.env) %}
Environment {{ collector.env }}
{% endif %} - {% if 'n/a' != collector.debug %} + {% if 'n/a' is not same as(collector.debug) %}
Debug - {{ collector.debug ? 'enabled' : 'disabled' }} + {{ collector.debug ? 'enabled' : 'disabled' }}
{% endif %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg index 170e4c0cf941b..30b1c2e9aad51 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Icon/symfony.svg @@ -1,4 +1,4 @@ - + formatLineCount)); + $this->overwrite(''); } /** @@ -513,37 +512,22 @@ private function setMaxSteps($max) */ private function overwrite($message) { - $lines = explode("\n", $message); - - // append whitespace to match the line's length - if (null !== $this->lastMessagesLength) { - foreach ($lines as $i => $line) { - if ($this->lastMessagesLength > Helper::strlenWithoutDecoration($this->output->getFormatter(), $line)) { - $lines[$i] = str_pad($line, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); - } - } - } - if ($this->overwrite) { - // move back to the beginning of the progress bar before redrawing it + // Move the cursor to the beginning of the line $this->output->write("\x0D"); - } elseif ($this->step > 0) { - // move to new line - $this->output->writeln(''); - } - if ($this->formatLineCount) { - $this->output->write(sprintf("\033[%dA", $this->formatLineCount)); - } - $this->output->write(implode("\n", $lines)); + // Erase the line + $this->output->write("\x1B[2K"); - $this->lastMessagesLength = 0; - foreach ($lines as $line) { - $len = Helper::strlenWithoutDecoration($this->output->getFormatter(), $line); - if ($len > $this->lastMessagesLength) { - $this->lastMessagesLength = $len; + // Erase previous lines + if ($this->formatLineCount > 0) { + $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount)); } + } elseif ($this->step > 0) { + $this->output->writeln(''); } + + $this->output->write($message); } private function determineBestFormat() diff --git a/src/Symfony/Component/Console/Helper/ProgressIndicator.php b/src/Symfony/Component/Console/Helper/ProgressIndicator.php index 8c8a49c44b910..ccf9771bcf9e4 100644 --- a/src/Symfony/Component/Console/Helper/ProgressIndicator.php +++ b/src/Symfony/Component/Console/Helper/ProgressIndicator.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Console\Helper; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Output\OutputInterface; /** @@ -53,7 +55,7 @@ public function __construct(OutputInterface $output, $format = null, $indicatorC $indicatorValues = array_values($indicatorValues); if (2 > count($indicatorValues)) { - throw new \InvalidArgumentException('Must have at least 2 indicator value characters.'); + throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); } $this->format = self::getFormatDefinition($format); @@ -118,7 +120,7 @@ public function getCurrentValue() public function start($message) { if ($this->started) { - throw new \LogicException('Progress indicator already started.'); + throw new LogicException('Progress indicator already started.'); } $this->message = $message; @@ -137,7 +139,7 @@ public function start($message) public function advance() { if (!$this->started) { - throw new \LogicException('Progress indicator has not yet been started.'); + throw new LogicException('Progress indicator has not yet been started.'); } if (!$this->output->isDecorated()) { @@ -164,7 +166,7 @@ public function advance() public function finish($message) { if (!$this->started) { - throw new \LogicException('Progress indicator has not yet been started.'); + throw new LogicException('Progress indicator has not yet been started.'); } $this->message = $message; diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php index 1f103ad144ca5..dc038e4707c16 100644 --- a/src/Symfony/Component/Console/Helper/Table.php +++ b/src/Symfony/Component/Console/Helper/Table.php @@ -162,7 +162,7 @@ public function setColumnStyle($columnIndex, $name) } elseif (isset(self::$styles[$name])) { $this->columnStyles[$columnIndex] = self::$styles[$name]; } else { - throw new \InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); } return $this; diff --git a/src/Symfony/Component/Console/Output/StreamOutput.php b/src/Symfony/Component/Console/Output/StreamOutput.php index 62d04c0e0d966..098966286eeb5 100644 --- a/src/Symfony/Component/Console/Output/StreamOutput.php +++ b/src/Symfony/Component/Console/Output/StreamOutput.php @@ -85,7 +85,7 @@ protected function doWrite($message, $newline) * * Colorization is disabled if not supported by the stream: * - * - Windows without Ansicon, ConEmu or Mintty + * - Windows before 10.0.10586 without Ansicon, ConEmu or Mintty * - non tty consoles * * @return bool true if the stream supports colorization, false otherwise @@ -93,7 +93,11 @@ protected function doWrite($message, $newline) protected function hasColorSupport() { if (DIRECTORY_SEPARATOR === '\\') { - return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); + return + 0 >= version_compare('10.0.10586', PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); } return function_exists('posix_isatty') && @posix_isatty($this->stream); diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php index 47d7d1249d128..6086918ac7840 100644 --- a/src/Symfony/Component/Console/Style/SymfonyStyle.php +++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php @@ -18,6 +18,7 @@ use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Helper\SymfonyQuestionHelper; use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Helper\TableCell; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; @@ -213,7 +214,16 @@ public function caution($message) */ public function table(array $headers, array $rows) { - $headers = array_map(function ($value) { return sprintf('%s', $value); }, $headers); + array_walk_recursive($headers, function (&$value) { + if ($value instanceof TableCell) { + $value = new TableCell(sprintf('%s', $value), array( + 'colspan' => $value->getColspan(), + 'rowspan' => $value->getRowspan(), + )); + } else { + $value = sprintf('%s', $value); + } + }); $table = new Table($this); $table->setHeaders($headers); diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_8.php b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_8.php new file mode 100644 index 0000000000000..0244fd27256f2 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_8.php @@ -0,0 +1,26 @@ + 3))), + array('ISBN', 'Title', 'Author'), + ); + + $rows = array( + array( + '978-0521567817', + 'De Monarchia', + new TableCell("Dante Alighieri\nspans multiple rows", array('rowspan' => 2)), + ), + array('978-0804169127', 'Divine Comedy'), + ); + + $output = new SymfonyStyleWithForcedLineLength($input, $output); + $output->table($headers, $rows); +}; diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_8.txt b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_8.txt new file mode 100644 index 0000000000000..005b846eae491 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_8.txt @@ -0,0 +1,9 @@ + ---------------- --------------- --------------------- + Main table title + ---------------- --------------- --------------------- + ISBN Title Author + ---------------- --------------- --------------------- + 978-0521567817 De Monarchia Dante Alighieri + 978-0804169127 Divine Comedy spans multiple rows + ---------------- --------------- --------------------- + diff --git a/src/Symfony/Component/Console/Tests/Helper/HelperTest.php b/src/Symfony/Component/Console/Tests/Helper/HelperTest.php new file mode 100644 index 0000000000000..33fa22051a05d --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Helper/HelperTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use Symfony\Component\Console\Helper\Helper; + +class HelperTest extends \PHPUnit_Framework_TestCase +{ + public function formatTimeProvider() + { + return array( + array(0, '< 1 sec'), + array(1, '1 sec'), + array(2, '2 secs'), + array(59, '59 secs'), + array(60, '1 min'), + array(61, '1 min'), + array(119, '1 min'), + array(120, '2 mins'), + array(121, '2 mins'), + array(3599, '59 mins'), + array(3600, '1 hr'), + array(7199, '1 hr'), + array(7200, '2 hrs'), + array(7201, '2 hrs'), + array(86399, '23 hrs'), + array(86400, '1 day'), + array(86401, '1 day'), + array(172799, '1 day'), + array(172800, '2 days'), + array(172801, '2 days'), + ); + } + + /** + * @dataProvider formatTimeProvider + * + * @param int $secs + * @param string $expectedFormat + */ + public function testFormatTime($secs, $expectedFormat) + { + $this->assertEquals($expectedFormat, Helper::formatTime($secs)); + } +} diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index 4b25d1b72a46b..261908b542307 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -233,7 +233,7 @@ public function testOverwriteWithShorterLine() $this->generateOutput(' 0/50 [>---------------------------] 0%'). $this->generateOutput(' 0/50 [>---------------------------] 0%'). $this->generateOutput(' 1/50 [>---------------------------] 2%'). - $this->generateOutput(' 2/50 [=>--------------------------] '), + $this->generateOutput(' 2/50 [=>--------------------------]'), stream_get_contents($output->getStream()) ); } @@ -353,7 +353,7 @@ public function testClear() $this->assertEquals( $this->generateOutput(' 0/50 [>---------------------------] 0%'). $this->generateOutput(' 25/50 [==============>-------------] 50%'). - $this->generateOutput(' '), + $this->generateOutput(''), stream_get_contents($output->getStream()) ); } @@ -551,9 +551,9 @@ public function testMultilineFormat() rewind($output->getStream()); $this->assertEquals( $this->generateOutput(">---------------------------\nfoobar"). - $this->generateOutput("=========>------------------\nfoobar "). - $this->generateOutput(" \n "). - $this->generateOutput("============================\nfoobar "), + $this->generateOutput("=========>------------------\nfoobar"). + "\x0D\x1B[2K\x1B[1A\x1B[2K". + $this->generateOutput("============================\nfoobar"), stream_get_contents($output->getStream()) ); } @@ -585,17 +585,17 @@ public function testAnsiColorsAndEmojis() $this->generateOutput( " \033[44;37m Starting the demo... fingers crossed \033[0m\n". ' 0/15 '.$progress.str_repeat($empty, 26)." 0%\n". - " \xf0\x9f\x8f\x81 1 sec \033[44;37m 0 B \033[0m" + " \xf0\x9f\x8f\x81 < 1 sec \033[44;37m 0 B \033[0m" ). $this->generateOutput( " \033[44;37m Looks good to me... \033[0m\n". ' 4/15 '.str_repeat($done, 7).$progress.str_repeat($empty, 19)." 26%\n". - " \xf0\x9f\x8f\x81 1 sec \033[41;37m 97 KiB \033[0m" + " \xf0\x9f\x8f\x81 < 1 sec \033[41;37m 97 KiB \033[0m" ). $this->generateOutput( " \033[44;37m Thanks, bye \033[0m\n". ' 15/15 '.str_repeat($done, 28)." 100%\n". - " \xf0\x9f\x8f\x81 1 sec \033[41;37m 195 KiB \033[0m" + " \xf0\x9f\x8f\x81 < 1 sec \033[41;37m 195 KiB \033[0m" ), stream_get_contents($output->getStream()) ); @@ -659,6 +659,6 @@ protected function generateOutput($expected) { $count = substr_count($expected, "\n"); - return "\x0D".($count ? sprintf("\033[%dA", $count) : '').$expected; + return "\x0D\x1B[2K".($count ? str_repeat("\x1B[1A\x1B[2K", $count) : '').$expected; } } diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php index 93b0ff5e4e20c..636b89c381ac2 100644 --- a/src/Symfony/Component/Debug/DebugClassLoader.php +++ b/src/Symfony/Component/Debug/DebugClassLoader.php @@ -51,15 +51,25 @@ public function __construct($classLoader) } if (!isset(self::$caseCheck)) { - if(!file_exists(strtolower(__FILE__))) { + $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR); + $i = strrpos($file, DIRECTORY_SEPARATOR); + $dir = substr($file, 0, 1 + $i); + $file = substr($file, 1 + $i); + $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); + $test = realpath($dir.$test); + + if (false === $test || false === $i) { // filesystem is case sensitive self::$caseCheck = 0; - } elseif(realpath(strtolower(__FILE__)) === __FILE__) { - // filesystem is not case sensitive + } elseif (substr($test, -strlen($file)) === $file) { + // filesystem is case insensitive and realpath() normalizes the case of characters self::$caseCheck = 1; - } else { - // filesystem is not case sensitive AND realpath() fails to normalize case + } elseif (false !== stripos(PHP_OS, 'darwin')) { + // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters self::$caseCheck = 2; + } else { + // filesystem case checks failed, fallback to disabling them + self::$caseCheck = 0; } } } diff --git a/src/Symfony/Component/DependencyInjection/Alias.php b/src/Symfony/Component/DependencyInjection/Alias.php index 5a6c2feda7f4e..eaf7f00ccd446 100644 --- a/src/Symfony/Component/DependencyInjection/Alias.php +++ b/src/Symfony/Component/DependencyInjection/Alias.php @@ -17,8 +17,6 @@ class Alias private $public; /** - * Constructor. - * * @param string $id Alias identifier * @param bool $public If this alias is public */ diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index 5120eb6215c1a..42b491c7c70e8 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -34,8 +34,6 @@ class AnalyzeServiceReferencesPass implements RepeatablePassInterface private $onlyConstructorArguments; /** - * Constructor. - * * @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls */ public function __construct($onlyConstructorArguments = false) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index b46635dca615a..4cef7df71401a 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -34,19 +34,32 @@ class AutowirePass implements CompilerPassInterface */ public function process(ContainerBuilder $container) { - $this->container = $container; - foreach ($container->getDefinitions() as $id => $definition) { - if ($definition->isAutowired()) { - $this->completeDefinition($id, $definition); + $throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); }; + spl_autoload_register($throwingAutoloader); + + try { + $this->container = $container; + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isAutowired()) { + $this->completeDefinition($id, $definition); + } } + } catch (\Error $e) { + } catch (\Exception $e) { } + spl_autoload_unregister($throwingAutoloader); + // Free memory and remove circular reference to container $this->container = null; $this->reflectionClasses = array(); $this->definedTypes = array(); $this->types = null; $this->notGuessableTypes = array(); + + if (isset($e)) { + throw $e; + } } /** @@ -107,11 +120,11 @@ private function completeDefinition($id, Definition $definition) } } } - } catch (\ReflectionException $reflectionException) { + } catch (\ReflectionException $e) { // Typehint against a non-existing class if (!$parameter->isDefaultValueAvailable()) { - throw new RuntimeException(sprintf('Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).', $index + 1, $definition->getClass(), $reflectionException->getMessage()), 0, $reflectionException); + throw new RuntimeException(sprintf('Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).', $index + 1, $definition->getClass(), $e->getMessage()), 0, $e); } $value = $parameter->getDefaultValue(); @@ -228,7 +241,7 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint, $id) * @param string $id * @param Definition $definition * - * @return \ReflectionClass|null + * @return \ReflectionClass|false */ private function getReflectionClass($id, Definition $definition) { @@ -238,15 +251,17 @@ private function getReflectionClass($id, Definition $definition) // Cannot use reflection if the class isn't set if (!$class = $definition->getClass()) { - return; + return false; } $class = $this->container->getParameterBag()->resolveValue($class); try { - return $this->reflectionClasses[$id] = new \ReflectionClass($class); - } catch (\ReflectionException $reflectionException) { - // return null + $reflector = new \ReflectionClass($class); + } catch (\ReflectionException $e) { + $reflector = false; } + + return $this->reflectionClasses[$id] = $reflector; } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php b/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php index 4e4c2cdaba5b9..1f6304ee82e13 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php @@ -25,9 +25,6 @@ class Compiler private $loggingFormatter; private $serviceReferenceGraph; - /** - * Constructor. - */ public function __construct() { $this->passConfig = new PassConfig(); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 246529d865cd3..ffb8c01f0f762 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -35,9 +35,6 @@ class PassConfig private $optimizationPasses; private $removingPasses; - /** - * Constructor. - */ public function __construct() { $this->mergePass = new MergeExtensionConfigurationPass(); @@ -58,8 +55,8 @@ public function __construct() $this->removingPasses = array( new RemovePrivateAliasesPass(), - new RemoveAbstractDefinitionsPass(), new ReplaceAliasByActualDefinitionPass(), + new RemoveAbstractDefinitionsPass(), new RepeatedPass(array( new AnalyzeServiceReferencesPass(), new InlineServiceDefinitionsPass(), @@ -102,8 +99,7 @@ public function addPass(CompilerPassInterface $pass, $type = self::TYPE_BEFORE_O throw new InvalidArgumentException(sprintf('Invalid type "%s".', $type)); } - $passes = &$this->$property; - $passes[] = $pass; + $this->{$property}[] = $pass; } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php index e34b0681e5389..508a8978ea68b 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php @@ -32,8 +32,6 @@ class RepeatedPass implements CompilerPassInterface private $passes; /** - * Constructor. - * * @param RepeatablePassInterface[] $passes An array of RepeatablePassInterface objects * * @throws InvalidArgumentException when the passes don't implement RepeatablePassInterface diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php index c5332fcd8d987..e80d3ccbf7b53 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ReplaceAliasByActualDefinitionPass.php @@ -25,7 +25,6 @@ class ReplaceAliasByActualDefinitionPass implements CompilerPassInterface { private $compiler; private $formatter; - private $sourceId; /** * Process the Container to replace aliases with service definitions. @@ -36,114 +35,110 @@ class ReplaceAliasByActualDefinitionPass implements CompilerPassInterface */ public function process(ContainerBuilder $container) { + // Setup $this->compiler = $container->getCompiler(); $this->formatter = $this->compiler->getLoggingFormatter(); - - foreach ($container->getAliases() as $id => $alias) { - $aliasId = (string) $alias; - + // First collect all alias targets that need to be replaced + $seenAliasTargets = array(); + $replacements = array(); + foreach ($container->getAliases() as $definitionId => $target) { + $targetId = (string) $target; + // Special case: leave this target alone + if ('service_container' === $targetId) { + continue; + } + // Check if target needs to be replaces + if (isset($replacements[$targetId])) { + $container->setAlias($definitionId, $replacements[$targetId]); + } + // No neeed to process the same target twice + if (isset($seenAliasTargets[$targetId])) { + continue; + } + // Process new target + $seenAliasTargets[$targetId] = true; try { - $definition = $container->getDefinition($aliasId); + $definition = $container->getDefinition($targetId); } catch (InvalidArgumentException $e) { - throw new InvalidArgumentException(sprintf('Unable to replace alias "%s" with actual definition "%s".', $id, $alias), null, $e); + throw new InvalidArgumentException(sprintf('Unable to replace alias "%s" with actual definition "%s".', $definitionId, $targetId), null, $e); } - if ($definition->isPublic()) { continue; } - + // Remove private definition and schedule for replacement $definition->setPublic(true); - $container->setDefinition($id, $definition); - $container->removeDefinition($aliasId); - - $this->updateReferences($container, $aliasId, $id); - - // we have to restart the process due to concurrent modification of - // the container - $this->process($container); - - break; - } - } - - /** - * Updates references to remove aliases. - * - * @param ContainerBuilder $container The container - * @param string $currentId The alias identifier being replaced - * @param string $newId The id of the service the alias points to - */ - private function updateReferences($container, $currentId, $newId) - { - foreach ($container->getAliases() as $id => $alias) { - if ($currentId === (string) $alias) { - $container->setAlias($id, $newId); - } + $container->setDefinition($definitionId, $definition); + $container->removeDefinition($targetId); + $replacements[$targetId] = $definitionId; } - foreach ($container->getDefinitions() as $id => $definition) { - $this->sourceId = $id; - - $definition->setArguments( - $this->updateArgumentReferences($definition->getArguments(), $currentId, $newId) - ); - - $definition->setMethodCalls( - $this->updateArgumentReferences($definition->getMethodCalls(), $currentId, $newId) - ); - - $definition->setProperties( - $this->updateArgumentReferences($definition->getProperties(), $currentId, $newId) - ); - - $definition->setFactoryService($this->updateFactoryServiceReference($definition->getFactoryService(false), $currentId, $newId), false); - $definition->setFactory($this->updateFactoryReference($definition->getFactory(), $currentId, $newId)); + // Now replace target instances in all definitions + foreach ($container->getDefinitions() as $definitionId => $definition) { + $definition->setArguments($this->updateArgumentReferences($replacements, $definitionId, $definition->getArguments())); + $definition->setMethodCalls($this->updateArgumentReferences($replacements, $definitionId, $definition->getMethodCalls())); + $definition->setProperties($this->updateArgumentReferences($replacements, $definitionId, $definition->getProperties())); + $definition->setFactoryService($this->updateFactoryReferenceId($replacements, $definition->getFactoryService(false)), false); + $definition->setFactory($this->updateFactoryReference($replacements, $definition->getFactory())); } } /** - * Updates argument references. + * Recursively updates references in an array. * - * @param array $arguments An array of Arguments - * @param string $currentId The alias identifier - * @param string $newId The identifier the alias points to + * @param array $replacements Table of aliases to replace + * @param string $definitionId Identifier of this definition + * @param array $arguments Where to replace the aliases * * @return array */ - private function updateArgumentReferences(array $arguments, $currentId, $newId) + private function updateArgumentReferences(array $replacements, $definitionId, array $arguments) { foreach ($arguments as $k => $argument) { + // Handle recursion step if (is_array($argument)) { - $arguments[$k] = $this->updateArgumentReferences($argument, $currentId, $newId); - } elseif ($argument instanceof Reference) { - if ($currentId === (string) $argument) { - $arguments[$k] = new Reference($newId, $argument->getInvalidBehavior()); - $this->compiler->addLogMessage($this->formatter->formatUpdateReference($this, $this->sourceId, $currentId, $newId)); - } + $arguments[$k] = $this->updateArgumentReferences($replacements, $definitionId, $argument); + continue; } + // Skip arguments that don't need replacement + if (!$argument instanceof Reference) { + continue; + } + $referenceId = (string) $argument; + if (!isset($replacements[$referenceId])) { + continue; + } + // Perform the replacement + $newId = $replacements[$referenceId]; + $arguments[$k] = new Reference($newId, $argument->getInvalidBehavior()); + $this->compiler->addLogMessage($this->formatter->formatUpdateReference($this, $definitionId, $referenceId, $newId)); } return $arguments; } - private function updateFactoryServiceReference($factoryService, $currentId, $newId) + /** + * Returns the updated reference for the factory service. + * + * @param array $replacements Table of aliases to replace + * @param string|null $referenceId Factory service reference identifier + * + * @return string|null + */ + private function updateFactoryReferenceId(array $replacements, $referenceId) { - if (null === $factoryService) { + if (null === $referenceId) { return; } - return $currentId === $factoryService ? $newId : $factoryService; + return isset($replacements[$referenceId]) ? $replacements[$referenceId] : $referenceId; } - private function updateFactoryReference($factory, $currentId, $newId) + private function updateFactoryReference(array $replacements, $factory) { - if (null === $factory || !is_array($factory) || !$factory[0] instanceof Reference) { - return $factory; + if (is_array($factory) && $factory[0] instanceof Reference && isset($replacements[$referenceId = (string) $factory[0]])) { + $factory[0] = new Reference($replacements[$referenceId], $factory[0]->getInvalidBehavior()); } - if ($currentId === (string) $factory[0]) { - $factory[0] = new Reference($newId, $factory[0]->getInvalidBehavior()); - } return $factory; } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php index 6a3e2ea5697d3..056be7fa3f5d4 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php @@ -25,8 +25,6 @@ class ServiceReferenceGraphEdge private $value; /** - * Constructor. - * * @param ServiceReferenceGraphNode $sourceNode * @param ServiceReferenceGraphNode $destNode * @param string $value diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php index c49c932575f03..e5718b2b6d59a 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php @@ -29,8 +29,6 @@ class ServiceReferenceGraphNode private $value; /** - * Constructor. - * * @param string $id The node identifier * @param mixed $value The node value */ diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 3305fc98c6dd3..51ea1bc2a3130 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -78,8 +78,6 @@ class Container implements IntrospectableContainerInterface, ResettableContainer private $underscoreMap = array('_' => '', '.' => '_', '\\' => '_'); /** - * Constructor. - * * @param ParameterBagInterface $parameterBag A ParameterBagInterface instance */ public function __construct(ParameterBagInterface $parameterBag = null) diff --git a/src/Symfony/Component/DependencyInjection/ContainerAware.php b/src/Symfony/Component/DependencyInjection/ContainerAware.php index 5686bf81d2c02..f3f2a5065c311 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerAware.php +++ b/src/Symfony/Component/DependencyInjection/ContainerAware.php @@ -26,9 +26,7 @@ abstract class ContainerAware implements ContainerAwareInterface protected $container; /** - * Sets the container. - * - * @param ContainerInterface|null $container A ContainerInterface instance or null + * {@inheritdoc} */ public function setContainer(ContainerInterface $container = null) { diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index bda42963219a2..895b37459cd27 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -47,8 +47,6 @@ class Definition protected $arguments; /** - * Constructor. - * * @param string|null $class The service class * @param array $arguments An array of arguments to pass to the service constructor */ diff --git a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php index a434b562da3ba..83ee0a0e7d303 100644 --- a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php +++ b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php @@ -25,8 +25,6 @@ class DefinitionDecorator extends Definition private $changes = array(); /** - * Constructor. - * * @param string $parent The id of Definition instance to decorate. */ public function __construct($parent) diff --git a/src/Symfony/Component/DependencyInjection/Dumper/Dumper.php b/src/Symfony/Component/DependencyInjection/Dumper/Dumper.php index 4b9d586f2c934..a39a5c744ba78 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/Dumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/Dumper.php @@ -23,8 +23,6 @@ abstract class Dumper implements DumperInterface protected $container; /** - * Constructor. - * * @param ContainerBuilder $container The service container to dump */ public function __construct(ContainerBuilder $container) diff --git a/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php b/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php index b529f0fe83d22..ab7b86d5acc90 100644 --- a/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php +++ b/src/Symfony/Component/DependencyInjection/Exception/ParameterNotFoundException.php @@ -24,8 +24,6 @@ class ParameterNotFoundException extends InvalidArgumentException private $alternatives; /** - * Constructor. - * * @param string $key The requested parameter key * @param string $sourceId The service id that references the non-existent parameter * @param string $sourceKey The parameter key that references the non-existent parameter diff --git a/src/Symfony/Component/DependencyInjection/Extension/Extension.php b/src/Symfony/Component/DependencyInjection/Extension/Extension.php index 20ea1002cb82c..ced39f7281b6c 100644 --- a/src/Symfony/Component/DependencyInjection/Extension/Extension.php +++ b/src/Symfony/Component/DependencyInjection/Extension/Extension.php @@ -27,9 +27,7 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionInterface { /** - * Returns the base path for the XSD files. - * - * @return string The XSD base path + * {@inheritdoc} */ public function getXsdValidationBasePath() { @@ -37,9 +35,7 @@ public function getXsdValidationBasePath() } /** - * Returns the namespace to be used for this extension (XML namespace). - * - * @return string The XML namespace + * {@inheritdoc} */ public function getNamespace() { diff --git a/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php b/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php index a5b4e5ad240e5..df70cdf44f283 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php @@ -26,8 +26,6 @@ class ClosureLoader extends Loader private $container; /** - * Constructor. - * * @param ContainerBuilder $container A ContainerBuilder instance */ public function __construct(ContainerBuilder $container) diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index d71eecf74156f..90cd6bcfafa4d 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -25,8 +25,6 @@ abstract class FileLoader extends BaseFileLoader protected $container; /** - * Constructor. - * * @param ContainerBuilder $container A ContainerBuilder instance * @param FileLocatorInterface $locator A FileLocator instance */ diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index c19b86d5660c1..8da4bcd8e2b2a 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -325,11 +325,7 @@ private function processAnonymousServices(\DOMDocument $xml, $file) // give it a unique name $id = sprintf('%s_%d', hash('sha256', $file), ++$count); $node->setAttribute('id', $id); - - if ($services = $this->getChildren($node, 'service')) { - $definitions[$id] = array($node, $file, true); - $services[0]->setAttribute('id', $id); - } + $definitions[$id] = array($node, $file, true); } } diff --git a/src/Symfony/Component/DependencyInjection/Parameter.php b/src/Symfony/Component/DependencyInjection/Parameter.php index 5431ed8221cf4..cac6f6c4c2264 100644 --- a/src/Symfony/Component/DependencyInjection/Parameter.php +++ b/src/Symfony/Component/DependencyInjection/Parameter.php @@ -21,8 +21,6 @@ class Parameter private $id; /** - * Constructor. - * * @param string $id The parameter key */ public function __construct($id) @@ -31,8 +29,6 @@ public function __construct($id) } /** - * __toString. - * * @return string The parameter key */ public function __toString() diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php b/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php index d9fe9eceb69b9..ad65ad960f527 100644 --- a/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php +++ b/src/Symfony/Component/DependencyInjection/ParameterBag/FrozenParameterBag.php @@ -21,8 +21,6 @@ class FrozenParameterBag extends ParameterBag { /** - * Constructor. - * * For performance reasons, the constructor assumes that * all keys are already lowercased. * diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php b/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php index 64a7789d1a859..0611b1f69e322 100644 --- a/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php +++ b/src/Symfony/Component/DependencyInjection/ParameterBag/ParameterBag.php @@ -26,8 +26,6 @@ class ParameterBag implements ParameterBagInterface protected $resolved = false; /** - * Constructor. - * * @param array $parameters An array of parameters */ public function __construct(array $parameters = array()) @@ -56,9 +54,7 @@ public function add(array $parameters) } /** - * Gets the service container parameters. - * - * @return array An array of parameters + * {@inheritdoc} */ public function all() { @@ -66,13 +62,7 @@ public function all() } /** - * Gets a service container parameter. - * - * @param string $name The parameter name - * - * @return mixed The parameter value - * - * @throws ParameterNotFoundException if the parameter is not defined + * {@inheritdoc} */ public function get($name) { @@ -109,11 +99,7 @@ public function set($name, $value) } /** - * Returns true if a parameter name is defined. - * - * @param string $name The parameter name - * - * @return bool true if the parameter name is defined, false otherwise + * {@inheritdoc} */ public function has($name) { @@ -131,7 +117,7 @@ public function remove($name) } /** - * Replaces parameter placeholders (%name%) by their values for all parameters. + * {@inheritdoc} */ public function resolve() { @@ -266,6 +252,9 @@ public function escapeValue($value) return $value; } + /** + * {@inheritdoc} + */ public function unescapeValue($value) { if (is_string($value)) { diff --git a/src/Symfony/Component/DependencyInjection/Reference.php b/src/Symfony/Component/DependencyInjection/Reference.php index 572062be9cccf..cb2445023da01 100644 --- a/src/Symfony/Component/DependencyInjection/Reference.php +++ b/src/Symfony/Component/DependencyInjection/Reference.php @@ -23,8 +23,6 @@ class Reference private $strict; /** - * Constructor. - * * Note: The $strict parameter is deprecated since version 2.8 and will be removed in 3.0. * * @param string $id The service identifier @@ -41,8 +39,6 @@ public function __construct($id, $invalidBehavior = ContainerInterface::EXCEPTIO } /** - * __toString. - * * @return string The service identifier */ public function __toString() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 4a97456f99ba1..1c61f05b0b7b6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -268,6 +268,21 @@ public function testClassNotFoundThrowsException() $pass->process($container); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException + * @expectedExceptionMessage Cannot autowire argument 2 for Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument because the type-hinted class does not exist (Class Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass does not exist). + */ + public function testParentClassNotFoundThrowsException() + { + $container = new ContainerBuilder(); + + $aDefinition = $container->register('a', __NAMESPACE__.'\BadParentTypeHintedArgument'); + $aDefinition->setAutowired(true); + + $pass = new AutowirePass(); + $pass->process($container); + } + public function testDontUseAbstractServices() { $container = new ContainerBuilder(); @@ -397,6 +412,21 @@ public function testOptionalScalarArgsNotPassedIfLast() $definition->getArguments() ); } + + public function testIgnoreServiceWithClassNotExisting() + { + $container = new ContainerBuilder(); + + $container->register('class_not_exist', __NAMESPACE__.'\OptionalServiceClass'); + + $barDefinition = $container->register('bar', __NAMESPACE__.'\Bar'); + $barDefinition->setAutowired(true); + + $pass = new AutowirePass(); + $pass->process($container); + + $this->assertTrue($container->hasDefinition('bar')); + } } class Foo @@ -509,6 +539,12 @@ public function __construct(Dunglas $k, NotARealClass $r) { } } +class BadParentTypeHintedArgument +{ + public function __construct(Dunglas $k, OptionalServiceClass $r) + { + } +} class NotGuessableArgument { public function __construct(Foo $k) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php new file mode 100644 index 0000000000000..7e9238f22301b --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Compiler; + +use Symfony\Bug\NotExistClass; + +class OptionalServiceClass extends NotExistClass +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ReplaceAliasByActualDefinitionPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ReplaceAliasByActualDefinitionPassTest.php index be58a1a175967..7cb63c6613663 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ReplaceAliasByActualDefinitionPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ReplaceAliasByActualDefinitionPassTest.php @@ -36,6 +36,8 @@ public function testProcess() $container->setAlias('a_alias', 'a'); $container->setAlias('b_alias', 'b'); + $container->setAlias('container', 'service_container'); + $this->process($container); $this->assertTrue($container->has('a'), '->process() does nothing to public definitions.'); @@ -47,6 +49,7 @@ public function testProcess() ); $this->assertSame('b_alias', $aDefinition->getFactoryService(false)); + $this->assertTrue($container->has('container')); $resolvedFactory = $aDefinition->getFactory(false); $this->assertSame('b_alias', (string) $resolvedFactory[0]); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 18f7b67f46a5b..8e80c47205908 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -61,24 +61,27 @@ public function testDefinitions() public function testCreateDeprecatedService() { - $definition = new Definition('stdClass'); - $definition->setDeprecated(true); + $deprecations = array(); + set_error_handler(function ($type, $msg) use (&$deprecations) { + if (E_USER_DEPRECATED !== $type) { + restore_error_handler(); - $that = $this; - $wasTriggered = false; + return call_user_func_array('PHPUnit_Util_ErrorHandler::handleError', func_get_args()); + } - set_error_handler(function ($errno, $errstr) use ($that, &$wasTriggered) { - $that->assertSame(E_USER_DEPRECATED, $errno); - $that->assertSame('The "deprecated_foo" service is deprecated. You should stop using it, as it will soon be removed.', $errstr); - $wasTriggered = true; + $deprecations[] = $msg; }); + $definition = new Definition('stdClass'); + $definition->setDeprecated(true); + $builder = new ContainerBuilder(); $builder->createService($definition, 'deprecated_foo'); restore_error_handler(); - $this->assertTrue($wasTriggered); + $this->assertCount(1, $deprecations); + $this->assertContains('The "deprecated_foo" service is deprecated. You should stop using it, as it will soon be removed.', $deprecations[0]); } public function testRegister() @@ -786,6 +789,21 @@ public function testExtensionConfig() $this->assertEquals(array($second, $first), $configs); } + public function testAbstractAlias() + { + $container = new ContainerBuilder(); + + $abstract = new Definition('AbstractClass'); + $abstract->setAbstract(true); + + $container->setDefinition('abstract_service', $abstract); + $container->setAlias('abstract_alias', 'abstract_service'); + + $container->compile(); + + $this->assertSame('abstract_service', (string) $container->getAlias('abstract_alias')); + } + public function testLazyLoadedService() { $loader = new ClosureLoader($container = new ContainerBuilder()); diff --git a/src/Symfony/Component/DependencyInjection/Tests/CrossCheckTest.php b/src/Symfony/Component/DependencyInjection/Tests/CrossCheckTest.php index 692d73dbcdc2e..7905e7f3c6b9c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/CrossCheckTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/CrossCheckTest.php @@ -73,24 +73,17 @@ public function testCrossCheck($fixture, $type) public function crossCheckLoadersDumpers() { - $tests = array( + return array( array('services1.xml', 'xml'), array('services2.xml', 'xml'), array('services6.xml', 'xml'), array('services8.xml', 'xml'), array('services9.xml', 'xml'), + array('services1.yml', 'yaml'), + array('services2.yml', 'yaml'), + array('services6.yml', 'yaml'), + array('services8.yml', 'yaml'), + array('services9.yml', 'yaml'), ); - - if (class_exists('Symfony\Component\Yaml\Yaml')) { - $tests = array_merge($tests, array( - array('services1.yml', 'yaml'), - array('services2.yml', 'yaml'), - array('services6.yml', 'yaml'), - array('services8.yml', 'yaml'), - array('services9.yml', 'yaml'), - )); - } - - return $tests; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 05d846b353654..6f600166ff1d9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -16,6 +16,8 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Variable; +use Symfony\Component\ExpressionLanguage\Expression; class PhpDumperTest extends \PHPUnit_Framework_TestCase { @@ -85,14 +87,25 @@ public function testDumpRelativeDir() } /** + * @dataProvider provideInvalidParameters * @expectedException \InvalidArgumentException */ - public function testExportParameters() + public function testExportParameters($parameters) { - $dumper = new PhpDumper(new ContainerBuilder(new ParameterBag(array('foo' => new Reference('foo'))))); + $dumper = new PhpDumper(new ContainerBuilder(new ParameterBag($parameters))); $dumper->dump(); } + public function provideInvalidParameters() + { + return array( + array(array('foo' => new Definition('stdClass'))), + array(array('foo' => new Expression('service("foo").foo() ~ (container.hasparameter("foo") ? parameter("foo") : "default")'))), + array(array('foo' => new Reference('foo'))), + array(array('foo' => new Variable('foo'))), + ); + } + public function testAddParameters() { $container = include self::$fixturesPath.'/containers/container8.php'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services5.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services5.xml index acb93e748e483..347df977dd26b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services5.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services5.xml @@ -14,8 +14,12 @@ - + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index a49925f9298ea..b0f3535308fdd 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -163,7 +163,7 @@ public function testLoadAnonymousServices() $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); $loader->load('services5.xml'); $services = $container->getDefinitions(); - $this->assertCount(4, $services, '->load() attributes unique ids to anonymous services'); + $this->assertCount(6, $services, '->load() attributes unique ids to anonymous services'); // anonymous service as an argument $args = $services['foo']->getArguments(); @@ -172,6 +172,7 @@ public function testLoadAnonymousServices() $this->assertTrue(isset($services[(string) $args[0]]), '->load() makes a reference to the created ones'); $inner = $services[(string) $args[0]]; $this->assertEquals('BarClass', $inner->getClass(), '->load() uses the same configuration as for the anonymous ones'); + $this->assertFalse($inner->isPublic()); // inner anonymous services $args = $inner->getArguments(); @@ -188,7 +189,25 @@ public function testLoadAnonymousServices() $this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Reference', $property, '->load() converts anonymous services to references to "normal" services'); $this->assertTrue(isset($services[(string) $property]), '->load() makes a reference to the created ones'); $inner = $services[(string) $property]; - $this->assertEquals('BazClass', $inner->getClass(), '->load() uses the same configuration as for the anonymous ones'); + $this->assertEquals('BuzClass', $inner->getClass(), '->load() uses the same configuration as for the anonymous ones'); + $this->assertFalse($inner->isPublic()); + + // "wild" service + $service = $container->findTaggedServiceIds('biz_tag'); + $this->assertCount(1, $service); + + foreach ($service as $id => $tag) { + $service = $container->getDefinition($id); + } + $this->assertEquals('BizClass', $service->getClass(), '->load() uses the same configuration as for the anonymous ones'); + $this->assertFalse($service->isPublic()); + + // anonymous services are shared when using decoration definitions + $container->compile(); + $services = $container->getDefinitions(); + $fooArgs = $services['foo']->getArguments(); + $barArgs = $services['bar']->getArguments(); + $this->assertSame($fooArgs[0], $barArgs[0]); } /** diff --git a/src/Symfony/Component/DependencyInjection/Variable.php b/src/Symfony/Component/DependencyInjection/Variable.php index e50235607ec8f..ddd43743826f6 100644 --- a/src/Symfony/Component/DependencyInjection/Variable.php +++ b/src/Symfony/Component/DependencyInjection/Variable.php @@ -29,8 +29,6 @@ class Variable private $name; /** - * Constructor. - * * @param string $name */ public function __construct($name) diff --git a/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php b/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php index 3e05015873588..c429417c25f80 100644 --- a/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php +++ b/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php @@ -263,7 +263,8 @@ private function buildOptionValue(\DOMElement $node) { $option = array(); - $defaultValue = (isset($node->nodeValue) && !empty($node->nodeValue)) ? $node->nodeValue : 'on'; + $defaultDefaultValue = 'select' === $this->node->nodeName ? '' : 'on'; + $defaultValue = (isset($node->nodeValue) && !empty($node->nodeValue)) ? $node->nodeValue : $defaultDefaultValue; $option['value'] = $node->hasAttribute('value') ? $node->getAttribute('value') : $defaultValue; $option['disabled'] = $node->hasAttribute('disabled'); diff --git a/src/Symfony/Component/DomCrawler/Tests/Field/ChoiceFormFieldTest.php b/src/Symfony/Component/DomCrawler/Tests/Field/ChoiceFormFieldTest.php index 8b35cc9ac5ba0..9592286305651 100644 --- a/src/Symfony/Component/DomCrawler/Tests/Field/ChoiceFormFieldTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/Field/ChoiceFormFieldTest.php @@ -351,6 +351,14 @@ public function testDisableValidation() $this->assertEquals(array('foobar'), $field->getValue(), '->disableValidation() allows to set a value which is not in the selected options.'); } + public function testSelectWithEmptyValue() + { + $node = $this->createSelectNodeWithEmptyOption(array('' => true, 'Female' => false, 'Male' => false)); + $field = new ChoiceFormField($node); + + $this->assertSame('', $field->getValue()); + } + protected function createSelectNode($options, $attributes = array(), $selectedAttrText = 'selected') { $document = new \DOMDocument(); diff --git a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php index 75ba2a760f45a..e729eeb103715 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php @@ -124,6 +124,10 @@ public function dispatch($eventName, Event $event = null) $event = new Event(); } + if (null !== $this->logger && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); + } + $this->preProcess($eventName); $this->preDispatch($eventName, $event); diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcher.php b/src/Symfony/Component/EventDispatcher/EventDispatcher.php index 65c5cb6f6ee89..d4c5cdf58c652 100644 --- a/src/Symfony/Component/EventDispatcher/EventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/EventDispatcher.php @@ -178,10 +178,10 @@ public function removeSubscriber(EventSubscriberInterface $subscriber) protected function doDispatch($listeners, $eventName, Event $event) { foreach ($listeners as $listener) { - call_user_func($listener, $event, $eventName, $this); if ($event->isPropagationStopped()) { break; } + call_user_func($listener, $event, $eventName, $this); } } diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 8e12aef07b4a3..f7e3b96b462cc 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -155,24 +155,27 @@ public function touch($files, $time = null, $atime = null) */ public function remove($files) { - $files = iterator_to_array($this->toIterator($files)); + if ($files instanceof \Traversable) { + $files = iterator_to_array($files, false); + } elseif (!is_array($files)) { + $files = array($files); + } $files = array_reverse($files); foreach ($files as $file) { - if (@(unlink($file) || rmdir($file))) { - continue; - } if (is_link($file)) { // See https://bugs.php.net/52176 - $error = error_get_last(); - throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, $error['message'])); + if (!@(unlink($file) || '\\' !== DIRECTORY_SEPARATOR || rmdir($file)) && file_exists($file)) { + $error = error_get_last(); + throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, $error['message'])); + } } elseif (is_dir($file)) { - $this->remove(new \FilesystemIterator($file)); + $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); - if (!@rmdir($file)) { + if (!@rmdir($file) && file_exists($file)) { $error = error_get_last(); throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, $error['message'])); } - } elseif (file_exists($file)) { + } elseif (!@unlink($file) && file_exists($file)) { $error = error_get_last(); throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, $error['message'])); } diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php index b1169ceafa8c3..b85fb0025fe61 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php @@ -11,9 +11,8 @@ namespace Symfony\Component\Form\Extension\Core\DataMapper; -use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\DataMapperInterface; -use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; /** * Maps choices to/from checkbox forms. @@ -26,16 +25,6 @@ */ class CheckboxListMapper implements DataMapperInterface { - /** - * @var ChoiceListInterface - */ - private $choiceList; - - public function __construct(ChoiceListInterface $choiceList) - { - $this->choiceList = $choiceList; - } - /** * {@inheritdoc} */ @@ -46,22 +35,12 @@ public function mapDataToForms($choices, $checkboxes) } if (!is_array($choices)) { - throw new TransformationFailedException('Expected an array.'); - } - - try { - $valueMap = array_flip($this->choiceList->getValuesForChoices($choices)); - } catch (\Exception $e) { - throw new TransformationFailedException( - 'Can not read the choices from the choice list.', - $e->getCode(), - $e - ); + throw new UnexpectedTypeException($choices, 'array'); } foreach ($checkboxes as $checkbox) { $value = $checkbox->getConfig()->getOption('value'); - $checkbox->setData(isset($valueMap[$value]) ? true : false); + $checkbox->setData(in_array($value, $choices, true)); } } @@ -70,6 +49,10 @@ public function mapDataToForms($choices, $checkboxes) */ public function mapFormsToData($checkboxes, &$choices) { + if (!is_array($choices)) { + throw new UnexpectedTypeException($choices, 'array'); + } + $values = array(); foreach ($checkboxes as $checkbox) { @@ -79,14 +62,6 @@ public function mapFormsToData($checkboxes, &$choices) } } - try { - $choices = $this->choiceList->getChoicesForValues($values); - } catch (\Exception $e) { - throw new TransformationFailedException( - 'Can not read the values from the choice list.', - $e->getCode(), - $e - ); - } + $choices = $values; } } diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php index d08a603b5c90e..6f757c601f9b4 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php +++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/RadioListMapper.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Form\Extension\Core\DataMapper; -use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\DataMapperInterface; +use Symfony\Component\Form\Exception\UnexpectedTypeException; /** * Maps choices to/from radio forms. @@ -25,24 +25,18 @@ */ class RadioListMapper implements DataMapperInterface { - /** - * @var ChoiceListInterface - */ - private $choiceList; - - public function __construct(ChoiceListInterface $choiceList) - { - $this->choiceList = $choiceList; - } - /** * {@inheritdoc} */ - public function mapDataToForms($data, $radios) + public function mapDataToForms($choice, $radios) { + if (!is_string($choice)) { + throw new UnexpectedTypeException($choice, 'string'); + } + foreach ($radios as $radio) { $value = $radio->getConfig()->getOption('value'); - $radio->setData($value === $data ? true : false); + $radio->setData($choice === $value); } } @@ -51,6 +45,10 @@ public function mapDataToForms($data, $radios) */ public function mapFormsToData($radios, &$choice) { + if (null !== $choice && !is_string($choice)) { + throw new UnexpectedTypeException($choice, 'null or string'); + } + $choice = null; foreach ($radios as $radio) { @@ -59,8 +57,7 @@ public function mapFormsToData($radios, &$choice) return; } - $value = $radio->getConfig()->getOption('value'); - $choice = current($this->choiceList->getChoicesForValues(array($value))); + $choice = $radio->getConfig()->getOption('value'); return; } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php index 3bddd608982f9..6e336d4ba2dbd 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php @@ -205,6 +205,10 @@ public function reverseTransform($value) throw new TransformationFailedException('I don\'t have a clear idea what infinity looks like'); } + if (is_int($result) && $result === (int) $float = (float) $result) { + $result = $float; + } + if (false !== $encoding = mb_detect_encoding($value, null, true)) { $length = mb_strlen($value, $encoding); $remainder = mb_substr($value, $position, $length, $encoding); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 34566432f1599..3080ede669cd9 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -60,9 +60,7 @@ public function __construct(ChoiceListFactoryInterface $choiceListFactory = null public function buildForm(FormBuilderInterface $builder, array $options) { if ($options['expanded']) { - $builder->setDataMapper($options['multiple'] - ? new CheckboxListMapper($options['choice_list']) - : new RadioListMapper($options['choice_list'])); + $builder->setDataMapper($options['multiple'] ? new CheckboxListMapper() : new RadioListMapper()); // Initialize all choices before doing the index check below. // This helps in cases where index checks are optimized for non @@ -133,30 +131,13 @@ public function buildForm(FormBuilderInterface $builder, array $options) $event->setData($data); }); + } - if (!$options['multiple']) { - // For radio lists, transform empty arrays to null - // This is kind of a hack necessary because the RadioListMapper - // is not invoked for forms without choices - $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) { - if (array() === $event->getData()) { - $event->setData(null); - } - }); - // For radio lists, pre selection of the choice needs to pre set data - // with the string value so it can be matched in - // {@link \Symfony\Component\Form\Extension\Core\DataMapper\RadioListMapper::mapDataToForms()} - $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { - $choiceList = $event->getForm()->getConfig()->getOption('choice_list'); - $value = current($choiceList->getValuesForChoices(array($event->getData()))); - $event->setData((string) $value); - }); - } - } elseif ($options['multiple']) { - //