diff --git a/.github/rm-invalid-lowest-lock-files.php b/.github/rm-invalid-lowest-lock-files.php new file mode 100644 index 0000000000000..c71463262171a --- /dev/null +++ b/.github/rm-invalid-lowest-lock-files.php @@ -0,0 +1,159 @@ + array(), 'packages-dev' => array()); + $composerJsons[$composerJson['name']] = array($dir, $composerLock['packages'] + $composerLock['packages-dev'], getRelevantContent($composerJson)); +} + +$referencedCommits = array(); + +foreach ($composerJsons as list($dir, $lockedPackages)) { + foreach ($lockedPackages as $lockedJson) { + if (0 !== strpos($version = $lockedJson['version'], 'dev-') && '-dev' !== substr($version, -4)) { + continue; + } + + if (!isset($composerJsons[$name = $lockedJson['name']])) { + echo "$dir/composer.lock references missing $name.\n"; + @unlink($dir.'/composer.lock'); + continue 2; + } + + if (isset($composerJsons[$name][2]['repositories']) && !isset($lockedJson[$key]['repositories'])) { + // the locked package has been patched locally but the lock references a commit, + // which means the referencing package itself is not modified + continue; + } + + foreach (array('minimum-stability', 'prefer-stable') as $key) { + if (array_key_exists($key, $composerJsons[$name][2])) { + $lockedJson[$key] = $composerJsons[$name][2][$key]; + } + } + + // use weak comparison to ignore ordering + if (getRelevantContent($lockedJson) != $composerJsons[$name][2]) { + echo "$dir/composer.lock is not in sync with $name.\n"; + @unlink($dir.'/composer.lock'); + continue 2; + } + + if ($lockedJson['dist']['reference']) { + $referencedCommits[$name][$lockedJson['dist']['reference']][] = $dir; + } + } +} + +if (!$referencedCommits || (isset($_SERVER['TRAVIS_PULL_REQUEST']) && 'false' !== $_SERVER['TRAVIS_PULL_REQUEST'])) { + // cached commits cannot be stale for PRs + return; +} + +@mkdir($_SERVER['HOME'].'/.cache/composer/repo/https---repo.packagist.org', 0777, true); + +$ch = null; +$mh = curl_multi_init(); +$sh = curl_share_init(); +curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); +curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); +curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); +$chs = array(); + +foreach ($referencedCommits as $name => $dirsByCommit) { + $chs[] = $ch = array(curl_init(), fopen($_SERVER['HOME'].'/.cache/composer/repo/https---repo.packagist.org/provider-'.strtr($name, '/', '$').'.json', 'wb')); + curl_setopt($ch[0], CURLOPT_URL, 'https://repo.packagist.org/p/'.$name.'.json'); + curl_setopt($ch[0], CURLOPT_FILE, $ch[1]); + curl_setopt($ch[0], CURLOPT_SHARE, $sh); + curl_multi_add_handle($mh, $ch[0]); +} + +do { + curl_multi_exec($mh, $active); + curl_multi_select($mh); +} while ($active); + +foreach ($chs as list($ch, $fd)) { + curl_multi_remove_handle($mh, $ch); + curl_close($ch); + fclose($fd); +} + +foreach ($referencedCommits as $name => $dirsByCommit) { + $repo = file_get_contents($_SERVER['HOME'].'/.cache/composer/repo/https---repo.packagist.org/provider-'.strtr($name, '/', '$').'.json'); + $repo = json_decode($repo, true); + + foreach ($repo['packages'][$name] as $version) { + unset($referencedCommits[$name][$version['source']['reference']]); + } +} + +foreach ($referencedCommits as $name => $dirsByCommit) { + foreach ($dirsByCommit as $dirs) { + foreach ($dirs as $dir) { + if (file_exists($dir.'/composer.lock')) { + echo "$dir/composer.lock references old commit for $name.\n"; + @unlink($dir.'/composer.lock'); + } + } + } +} diff --git a/.travis.yml b/.travis.yml index f3f3660ef8dfe..719785fc18af3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,8 +24,7 @@ matrix: sudo: required group: edge - php: 5.5 - - php: 5.6 - - php: 7.0 + env: php_extra="5.6 7.0" - php: 7.1 env: deps=high - php: 7.2 @@ -42,21 +41,39 @@ services: - memcached - mongodb - redis-server + - docker before_install: + - | + # Start Redis cluster + docker pull grokzen/redis-cluster:4.0.8 + docker run -d -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 --name redis-cluster grokzen/redis-cluster:4.0.8 + export REDIS_CLUSTER_HOSTS='localhost:7000 localhost:7001 localhost:7002 localhost:7003 localhost:7004 localhost:7005' + - | # General configuration + set -e stty cols 120 mkdir /tmp/slapd slapd -f src/Symfony/Component/Ldap/Tests/Fixtures/conf/slapd.conf -h ldap://localhost:3389 & - PHP=$TRAVIS_PHP_VERSION [ -d ~/.composer ] || mkdir ~/.composer cp .composer/* ~/.composer/ export PHPUNIT=$(readlink -f ./phpunit) export PHPUNIT_X="$PHPUNIT --exclude-group tty,benchmark,intl-data" export COMPOSER_UP='composer update --no-progress --no-suggest --ansi' + export COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n') + find ~/.phpenv -name xdebug.ini -delete - nanoseconds() { + if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then + composer () { + $HOME/.phpenv/versions/7.1/bin/php $HOME/.phpenv/versions/7.1/bin/composer config platform.php $(echo ' > $INI - echo memory_limit = -1 >> $INI - echo session.gc_probability = 0 >> $INI - echo opcache.enable_cli = 1 >> $INI - echo hhvm.jit = 0 >> $INI - echo apc.enable_cli = 1 >> $INI - echo extension = redis.so >> $INI - echo extension = memcached.so >> $INI - [[ $PHP = 5.* ]] && echo extension = memcache.so >> $INI - if [[ $PHP = 5.* ]]; then - echo extension = mongo.so >> $INI - fi - # tpecl is a helper to compile and cache php extensions tpecl () { local ext_name=$1 @@ -122,6 +119,7 @@ before_install: if [[ -e $ext_cache/$ext_so ]]; then echo extension = $ext_cache/$ext_so >> $INI else + rm ~/.pearrc /tmp/pear 2>/dev/null || true mkdir -p $ext_cache echo yes | pecl install -f $ext_name && cp $ext_dir/$ext_so $ext_cache @@ -129,36 +127,62 @@ before_install: } export -f tpecl - # Matrix lines for intermediate PHP versions are skipped for pull requests - if [[ ! $deps && ! $PHP = ${MIN_PHP%.*} && ! $PHP = hhvm* && $TRAVIS_PULL_REQUEST != false ]]; then - deps=skip - skip=1 - else - COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n') - fi - - | # Install sigchild-enabled PHP to test the Process component on the lowest PHP matrix line - if [[ ! $deps && $PHP = ${MIN_PHP%.*} && ! -d php-$MIN_PHP/sapi ]]; then + 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 + - | + # php.ini configuration + for PHP in $TRAVIS_PHP_VERSION $php_extra; do + if [[ $PHP = hhvm* ]]; then + INI=/etc/hhvm/php.ini + else + phpenv global $PHP 2>/dev/null || (cd / && wget https://s3.amazonaws.com/travis-php-archives/binaries/ubuntu/14.04/x86_64/php-$PHP.tar.bz2 -O - | tar -xj) + INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini + fi + echo date.timezone = Europe/Paris >> $INI + echo memory_limit = -1 >> $INI + echo session.gc_probability = 0 >> $INI + echo opcache.enable_cli = 1 >> $INI + echo hhvm.jit = 0 >> $INI + echo apc.enable_cli = 1 >> $INI + echo extension = redis.so >> $INI + echo extension = memcached.so >> $INI + if [[ $PHP = 5.* ]]; then + echo extension = memcache.so >> $INI + echo extension = mongo.so >> $INI + fi + done + - | # Install extra PHP extensions - if [[ ! $skip && $PHP = 5.* ]]; then - ([[ $deps ]] || tfold ext.symfony_debug 'cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo extension = $(pwd)/modules/symfony_debug.so >> '"$INI") - tfold ext.apcu tpecl apcu-4.0.11 apcu.so $INI - elif [[ ! $skip && $PHP = 7.* ]]; then - # install libsodium - sudo add-apt-repository ppa:ondrej/php -y - sudo apt-get update -q - sudo apt-get install libsodium-dev -y - - tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI - tfold ext.libsodium tpecl libsodium sodium.so $INI - tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI - fi + for PHP in $TRAVIS_PHP_VERSION $php_extra; do + if [[ $PHP = hhvm* ]]; then + continue + fi + export PHP=$PHP + phpenv global $PHP + INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini + if [[ $PHP = 5.* ]]; then + tfold ext.apcu tpecl apcu-4.0.11 apcu.so $INI + [[ $deps ]] && continue + ext_cache=~/php-ext/$(php -r "echo basename(ini_get('extension_dir'));")/symfony_debug.so + [[ -e $ext_cache ]] || (tfold ext.symfony_debug "cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && mv modules/symfony_debug.so $ext_cache && phpize --clean") + echo extension = $ext_cache >> $INI + elif [[ $PHP = 7.* ]]; then + # install libsodium + sudo add-apt-repository ppa:ondrej/php -y + sudo apt-get update -q + sudo apt-get install libsodium-dev -y + + tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI + tfold ext.libsodium tpecl libsodium sodium.so $INI + tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI + fi + done - | # Load fixtures @@ -172,7 +196,7 @@ install: # Create local composer packages for each patched components and reference them in composer.json files when cross-testing components if [[ ! $deps ]]; then php .github/build-packages.php HEAD^ src/Symfony/Bridge/PhpUnit - elif [[ ! $skip ]]; then + else export SYMFONY_DEPRECATIONS_HELPER=weak && cp composer.json composer.json.orig && echo -e '{\n"require":{'"$(grep phpunit-bridge composer.json)"'"php":"*"},"minimum-stability":"dev"}' > composer.json && @@ -188,46 +212,67 @@ install: 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') - elif [[ ! $skip ]]; then + else SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*') fi + - | + # Install symfony/flex + if [[ $deps = low ]]; then + export SYMFONY_REQUIRE='>=2.3' + else + export SYMFONY_REQUIRE=">=$SYMFONY_VERSION" + fi + composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-master + - | # Legacy tests are skipped when deps=high and when the current branch version has not the same major version number than the next one [[ $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) ]] && LEGACY=,legacy export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev - if [[ ! $skip && $deps ]]; then mv composer.json.phpunit composer.json; fi - - if [[ ! $skip && $PHP = 7.* ]]; then - ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer require --dev --no-update mongodb/mongodb) - fi + if [[ $deps ]]; then mv composer.json.phpunit composer.json; fi - - if [[ ! $skip ]]; then $COMPOSER_UP; fi - - if [[ ! $skip ]]; then ./phpunit install; fi - | # phpinfo - if [[ ! $PHP = hhvm* ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi + if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi - | run_tests () { set -e - if [[ $skip ]]; then + export PHP=$1 + if [[ $PHP != $TRAVIS_PHP_VERSION && $TRAVIS_PULL_REQUEST != false ]]; then echo -e "\\n\\e[1;34mIntermediate PHP version $PHP is skipped for pull requests.\\e[0m" - elif [[ $deps = high ]]; then + break + fi + phpenv global ${PHP/hhvm*/hhvm} + if [[ $PHP = 7.* ]]; then + ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.5.0; composer require --dev --no-update mongodb/mongodb) + fi + tfold 'composer update' $COMPOSER_UP + if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then + tfold 'phpunit install' 'composer global remove symfony/flex && ./phpunit install && composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-master' + else + tfold 'phpunit install' ./phpunit install + fi + if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && $COMPOSER_UP && $PHPUNIT_X$LEGACY'" elif [[ $deps = low ]]; then - echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && $COMPOSER_UP --prefer-lowest --prefer-stable && $PHPUNIT_X'" + [[ -e ~/php-ext/composer-lowest.lock.tar ]] && tar -xf ~/php-ext/composer-lowest.lock.tar + tar -cf ~/php-ext/composer-lowest.lock.tar --files-from /dev/null + php .github/rm-invalid-lowest-lock-files.php $COMPONENTS + echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && ([ -e composer.lock ] && ${COMPOSER_UP/update/install} || $COMPOSER_UP --prefer-lowest --prefer-stable) && $PHPUNIT_X'" + echo "$COMPONENTS" | xargs -n1 -I{} tar --append -f ~/php-ext/composer-lowest.lock.tar {}/composer.lock elif [[ $PHP = hhvm* ]]; then $PHPUNIT --exclude-group no-hhvm,benchmark,intl-data else echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}" - tfold tty-group $PHPUNIT --group tty + tfold src/Symfony/Component/Console.tty $PHPUNIT src/Symfony/Component/Console --group tty if [[ $PHP = ${MIN_PHP%.*} ]]; then + export PHP=$MIN_PHP echo -e "1\\n0" | xargs -I{} bash -c "tfold src/Symfony/Component/Process.sigchild{} SYMFONY_DEPRECATIONS_HELPER=weak ENHANCE_SIGCHLD={} php-$MIN_PHP/sapi/cli/php .phpunit/phpunit-4.8/phpunit --colors=always src/Symfony/Component/Process/" fi fi } script: - - (run_tests) + - for PHP in $TRAVIS_PHP_VERSION $php_extra; do (run_tests $PHP); done diff --git a/CHANGELOG-3.4.md b/CHANGELOG-3.4.md index f0cde9e267b62..567f31a8c12c3 100644 --- a/CHANGELOG-3.4.md +++ b/CHANGELOG-3.4.md @@ -7,6 +7,21 @@ in 3.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.4.0...v3.4.1 +* 3.4.15 (2018-08-28) + + * bug #28278 [HttpFoundation] Fix unprepared BinaryFileResponse sends empty file (wackymole) + * bug #28284 [PhpUnitBridge] keep compat with composer 1.0 (nicolas-grekas) + * bug #28241 [HttpKernel] fix forwarding trusted headers as server parameters (nicolas-grekas) + * bug #28220 [PropertyAccess] fix type error handling when writing values (xabbuh) + * bug #28249 [Cache] enable Memcached::OPT_TCP_NODELAY to fix perf of misses (nicolas-grekas) + * bug #28252 [DoctrineBridge] support __toString as documented for UniqueEntityValidator (dmaicher) + * bug #28100 [Security] Call AccessListener after LogoutListener (chalasr) + * bug #28060 [DI] Fix false-positive circular ref leading to wrong exceptions or infinite loops at runtime (nicolas-grekas) + * bug #28144 [HttpFoundation] fix false-positive ConflictingHeadersException (nicolas-grekas) + * bug #28012 [PropertyInfo] Allow nested collections (jderusse) + * bug #28055 [PropertyInfo] Allow nested collections (jderusse) + * bug #28083 Remove the Expires header when calling Response::expire() (javiereguiluz) + * 3.4.14 (2018-08-01) * security #cve-2018-14774 [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer (nicolas-grekas) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b6eef0b63198c..629d8b80c98a3 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -24,18 +24,18 @@ Symfony is the result of the work of many people who made the code better - Hugo Hamon (hhamon) - Abdellatif Ait boudad (aitboudad) - Romain Neutron (romain) - - Pascal Borreli (pborreli) - Roland Franssen (ro0) + - Pascal Borreli (pborreli) - Wouter De Jong (wouterj) - Joseph Bielawski (stloyd) - Karma Dordrak (drak) - Lukas Kahwe Smith (lsmith) - Martin Hasoň (hason) - Jeremy Mikola (jmikola) + - Samuel ROZE (sroze) - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) - Igor Wiedler (igorw) - - Samuel ROZE (sroze) - Jules Pietri (heah) - Eriksen Costa (eriksencosta) - Guilhem Niot (energetick) @@ -52,9 +52,9 @@ Symfony is the result of the work of many people who made the code better - stealth35 ‏ (stealth35) - Alexander Mols (asm89) - Bulat Shakirzyanov (avalanche123) + - Dany Maillard (maidmaid) - Peter Rehm (rpet) - Matthias Pigulla (mpdude) - - Dany Maillard (maidmaid) - Saša Stamenković (umpirsky) - Kevin Bond (kbond) - Tobias Nyholm (tobias) @@ -63,21 +63,21 @@ Symfony is the result of the work of many people who made the code better - Miha Vrhovnik - Diego Saint Esteben (dii3g0) - Alexander M. Turek (derrabus) + - Jérémy DERUSSÉ (jderusse) - Konstantin Kudryashov (everzet) - Bilal Amarni (bamarni) - - Jérémy DERUSSÉ (jderusse) - Florin Patan (florinpatan) - - Mathieu Piot (mpiot) - Gábor Egyed (1ed) + - Mathieu Piot (mpiot) - Michel Weimerskirch (mweimerskirch) - Titouan Galopin (tgalopin) - Andrej Hudec (pulzarraider) - Eric Clemmons (ericclemmons) - Jáchym Toušek (enumag) - Charles Sarrazin (csarrazi) + - David Maicher (dmaicher) - Konstantin Myakshin (koc) - Christian Raue - - David Maicher (dmaicher) - Arnout Boks (aboks) - Deni - Henrik Westphal (snc) @@ -99,11 +99,11 @@ Symfony is the result of the work of many people who made the code better - Fran Moreno (franmomu) - Antoine Hérault (herzult) - Paráda József (paradajozsef) + - Grégoire Paris (greg0ire) - Arnaud Le Blanc (arnaud-lb) - Maxime STEINHAUSSER - Michal Piotrowski (eventhorizon) - Tim Nagel (merk) - - Grégoire Paris (greg0ire) - Brice BERNARD (brikou) - Valentin Udaltsov (vudaltsov) - gadelat (gadelat) @@ -124,6 +124,7 @@ Symfony is the result of the work of many people who made the code better - Fabien Pennequin (fabienpennequin) - Gordon Franke (gimler) - Eric GELOEN (gelo) + - Sebastiaan Stok (sstok) - Lars Strojny (lstrojny) - Daniel Wehner (dawehner) - Tugdual Saunier (tucksaun) @@ -131,7 +132,6 @@ Symfony is the result of the work of many people who made the code better - Théo FIDRY (theofidry) - Robert Schönthal (digitalkaoz) - Florian Lonqueu-Brochard (florianlb) - - Sebastiaan Stok (sstok) - Stefano Sala (stefano.sala) - Jérôme Vasseur (jvasseur) - Evgeniy (ewgraf) @@ -144,6 +144,7 @@ Symfony is the result of the work of many people who made the code better - Hidenori Goto (hidenorigoto) - Chris Wilkinson (thewilkybarkid) - Arnaud Kleinpeter (nanocom) + - Jannik Zschiesche (apfelbox) - Guilherme Blanco (guilhermeblanco) - Pablo Godel (pgodel) - Jérémie Augustin (jaugustin) @@ -170,7 +171,6 @@ Symfony is the result of the work of many people who made the code better - Amal Raghav (kertz) - Jonathan Ingram (jonathaningram) - Artur Kotyrba - - Jannik Zschiesche (apfelbox) - GDIBass - jeremyFreeAgent (Jérémy Romey) (jeremyfreeagent) - James Halsall (jaitsu) @@ -192,6 +192,7 @@ Symfony is the result of the work of many people who made the code better - SpacePossum - Benjamin Dulau (dbenjamin) - Mathieu Lemoine (lemoinem) + - Thomas Calvet (fancyweb) - Christian Schmidt - Andreas Hucks (meandmymonkey) - Noel Guilbert (noel) @@ -200,6 +201,7 @@ Symfony is the result of the work of many people who made the code better - bronze1man - sun (sun) - Larry Garfield (crell) + - Michaël Perrin (michael.perrin) - Martin Schuhfuß (usefulthink) - apetitpa - Matthieu Bontemps (mbontemps) @@ -221,7 +223,6 @@ Symfony is the result of the work of many people who made the code better - Tom Van Looy (tvlooy) - Sven Paulus (subsven) - Yanick Witschi (toflar) - - Thomas Calvet (fancyweb) - Rui Marinho (ruimarinho) - Alessandro Chitolina - Eugene Wissner @@ -230,7 +231,6 @@ Symfony is the result of the work of many people who made the code better - Leo Feyer - Tristan Darricau (nicofuma) - Nikolay Labinskiy (e-moe) - - Michaël Perrin (michael.perrin) - Marcel Beerta (mazen) - Albert Casademont (acasademont) - Loïc Faugeron @@ -253,6 +253,7 @@ Symfony is the result of the work of many people who made the code better - Ruben Gonzalez (rubenrua) - Adam Prager (padam87) - Benoît Burnichon (bburnichon) + - Florent Mata (fmata) - Roman Marintšenko (inori) - Xavier Montaña Carreras (xmontana) - Mickaël Andrieu (mickaelandrieu) @@ -284,6 +285,7 @@ Symfony is the result of the work of many people who made the code better - Andreas Schempp (aschempp) - jdhoek - Pavel Batanov (scaytrase) + - Massimiliano Arione (garak) - Bob den Otter (bopp) - Nikita Konstantinov - Wodor Wodorski @@ -296,7 +298,6 @@ Symfony is the result of the work of many people who made the code better - Roumen Damianoff (roumen) - Antonio J. García Lagar (ajgarlag) - Kim Hemsø Rasmussen (kimhemsoe) - - Florent Mata (fmata) - Wouter Van Hecke - Jérôme Parmentier (lctrs) - Michael Babker (mbabker) @@ -309,6 +310,7 @@ Symfony is the result of the work of many people who made the code better - Chad Sikorra (chadsikorra) - Chris Smith (cs278) - Florian Klein (docteurklein) + - Gary PEGEOT (gary-p) - Manuel Kiessling (manuelkiessling) - Atsuhiro KUBO (iteman) - Andrew Moore (finewolf) @@ -332,7 +334,6 @@ Symfony is the result of the work of many people who made the code better - Adrian Rudnik (kreischweide) - Francesc Rosàs (frosas) - Romain Pierre (romain-pierre) - - Massimiliano Arione (garak) - Julien Galenski (ruian) - Bongiraud Dominique - janschoenherr @@ -343,6 +344,7 @@ Symfony is the result of the work of many people who made the code better - Thierry Thuon (lepiaf) - Ricard Clau (ricardclau) - Mark Challoner (markchalloner) + - Colin O'Dell (colinodell) - Gennady Telegin (gtelegin) - Ben Davies (bendavies) - Erin Millard @@ -380,7 +382,7 @@ Symfony is the result of the work of many people who made the code better - Grzegorz (Greg) Zdanowski (kiler129) - Kirill chEbba Chebunin (chebba) - Greg Thornton (xdissent) - - Gary PEGEOT (gary-p) + - Sullivan SENECHAL (soullivaneuh) - Costin Bereveanu (schniper) - Loïc Chardonnet (gnusat) - Marek Kalnik (marekkalnik) @@ -407,6 +409,7 @@ Symfony is the result of the work of many people who made the code better - Karoly Negyesi (chx) - Ivan Kurnosov - Xavier HAUSHERR + - David Prévot - Albert Jessurum (ajessu) - Laszlo Korte - Miha Vrhovnik @@ -431,13 +434,13 @@ Symfony is the result of the work of many people who made the code better - Christopher Davis (chrisguitarguy) - Jan Schumann - Niklas Fiekas - - Colin O'Dell (colinodell) - Markus Bachmann (baachi) - lancergr - Zan Baldwin - Mihai Stancu - Olivier Dolbeau (odolbeau) - Jan Rosier (rosier) + - Alessandro Lai (jean85) - Arturs Vonda - Josip Kruslin - Asmir Mustafic (goetas) @@ -484,7 +487,6 @@ Symfony is the result of the work of many people who made the code better - Haralan Dobrev (hkdobrev) - Sebastian Bergmann - Miroslav Sustek - - Sullivan SENECHAL (soullivaneuh) - Pablo Díez (pablodip) - Martin Hujer (martinhujer) - Kevin McBride @@ -508,6 +510,7 @@ Symfony is the result of the work of many people who made the code better - Markus Lanthaler (lanthaler) - Remi Collet - Vicent Soria Durá (vicentgodella) + - Michael Moravec - Anthony Ferrara - Ioan Negulescu - Jakub Škvára (jskvara) @@ -565,7 +568,6 @@ Symfony is the result of the work of many people who made the code better - Disquedur - Michiel Boeckaert (milio) - Geoffrey Tran (geoff) - - David Prévot - Jan Behrens - Mantas Var (mvar) - Sebastian Krebs @@ -584,7 +586,6 @@ Symfony is the result of the work of many people who made the code better - Max Rath (drak3) - Stéphane Escandell (sescandell) - Konstantin S. M. Möllers (ksmmoellers) - - Alessandro Lai (jean85) - James Johnston - Sinan Eldem - Alexandre Dupuy (satchette) @@ -652,6 +653,7 @@ Symfony is the result of the work of many people who made the code better - Szijarto Tamas - Robin Lehrmann (robinlehrmann) - Catalin Dan + - Jaroslav Kuba - Stephan Vock - Benjamin Zikarsky (bzikarsky) - Simon Schick (simonsimcity) @@ -713,6 +715,7 @@ Symfony is the result of the work of many people who made the code better - Yuen-Chi Lian - Besnik Br - Jose Gonzalez + - Oleksii Zhurbytskyi - Dariusz Ruminski - Joshua Nye - Claudio Zizza @@ -737,7 +740,6 @@ Symfony is the result of the work of many people who made the code better - Marc Morera (mmoreram) - Saif Eddin Gmati (azjezz) - Smaine Milianni (ismail1432) - - Michael Moravec - Andrew Hilobok (hilobok) - Noah Heck (myesain) - Christian Soronellas (theunic) @@ -787,6 +789,7 @@ Symfony is the result of the work of many people who made the code better - Sofiane HADDAG (sofhad) - frost-nzcr4 - Bozhidar Hristov + - Ivan Nikolaev (destillat) - andrey1s - Abhoryo - Fabian Vogler (fabian) @@ -882,6 +885,7 @@ Symfony is the result of the work of many people who made the code better - datibbaw - Erik Saunier (snickers) - Rootie + - Kyle - Raul Fraile (raulfraile) - sensio - Sebastien Morel (plopix) @@ -911,6 +915,7 @@ Symfony is the result of the work of many people who made the code better - Michael Tibben - Billie Thompson - Sander Marechal + - ProgMiner - Oleg Golovakhin (doc_tr) - Icode4Food (icode4food) - Radosław Benkel @@ -999,6 +1004,7 @@ Symfony is the result of the work of many people who made the code better - Denis Kop - Jean-Guilhem Rouel (jean-gui) - jfcixmedia + - Dominic Tubach - Nikita Konstantinov - Martijn Evers - Benjamin Paap (benjaminpaap) @@ -1041,6 +1047,7 @@ Symfony is the result of the work of many people who made the code better - Alexander Cheprasov - Rodrigo Díez Villamuera (rodrigodiez) - e-ivanov + - Einenlum - Jochen Bayer (jocl) - Alex Bowers - Jeremy Bush @@ -1060,7 +1067,6 @@ Symfony is the result of the work of many people who made the code better - David Otton - Will Donohoe - peter - - Jaroslav Kuba - Jérémy Jourdin (jjk801) - BRAMILLE Sébastien (oktapodia) - Artem Kolesnikov (tyomo4ka) @@ -1166,6 +1172,7 @@ Symfony is the result of the work of many people who made the code better - Saem Ghani - Clément LEFEBVRE - Conrad Kleinespel + - Zacharias Luiten - Sebastian Utz - Adrien Gallou (agallou) - Maks Rafalko (bornfree) @@ -1231,6 +1238,7 @@ Symfony is the result of the work of many people who made the code better - WedgeSama - Felds Liscia - Chihiro Adachi (chihiro-adachi) + - Emanuele Panzeri (thepanz) - Tadcka - Beth Binkovitz - Gonzalo Míguez @@ -1303,6 +1311,7 @@ Symfony is the result of the work of many people who made the code better - Benjamin Bender - Jared Farrish - karl.rixon + - raplider - Konrad Mohrfeldt - Lance Chen - Ciaran McNulty (ciaranmcnulty) @@ -1371,18 +1380,20 @@ Symfony is the result of the work of many people who made the code better - Harold Iedema - Arnau González (arnaugm) - Simon Bouland (bouland) - - Ivan Nikolaev (destillat) - Matthew Foster (mfoster) - Paul Seiffert (seiffert) - Vasily Khayrulin (sirian) - Stefan Koopmanschap (skoop) - Stefan Hüsges (tronsha) + - Vlad Gregurco (vgregurco) - Jake Bishop (yakobeyak) - Dan Blows - Matt Wells - Nicolas Appriou - stloyd + - Andreas - Chris Tickner + - BoShurik - Andrew Coulton - Jeremy Benoist - Michal Gebauer @@ -1396,7 +1407,9 @@ Symfony is the result of the work of many people who made the code better - Luis Muñoz - Matthew Donadio - Houziaux mike + - Phobetor - Andreas + - Markus - Thomas Chmielowiec - shdev - Andrey Ryaguzov @@ -1404,6 +1417,7 @@ Symfony is the result of the work of many people who made the code better - Peter Bex - Manatsawin Hanmongkolchai - Gunther Konig + - Mickael GOETZ - Maciej Schmidt - Greg ORIOL - Dennis Væversted @@ -1415,6 +1429,7 @@ Symfony is the result of the work of many people who made the code better - Mike Francis - Christoph Nissle (derstoffel) - Ionel Scutelnicu (ionelscutelnicu) + - Grenier Kévin (mcsky_biig) - Nicolas Tallefourtané (nicolab) - Botond Dani (picur) - Thierry Marianne (thierrymarianne) @@ -1432,7 +1447,6 @@ Symfony is the result of the work of many people who made the code better - Nicolas Bastien (nicolas_bastien) - Denis (yethee) - Andrew Zhilin (zhil) - - Oleksii Zhurbytskyi - Andy Stanberry - Felix Marezki - Normunds @@ -1813,6 +1827,7 @@ Symfony is the result of the work of many people who made the code better - jspee - David Soria Parra - Sergiy Sokolenko + - Ahmed Abdulrahman - dinitrol - Penny Leach - Yurii K @@ -1973,7 +1988,6 @@ Symfony is the result of the work of many people who made the code better - fh-github@fholzhauer.de - AbdElKader Bouadjadja - DSeemiller - - Kyle - Jan Emrich - Mark Topper - Xavier REN @@ -2001,3 +2015,4 @@ Symfony is the result of the work of many people who made the code better - Matej Žilák (teo_sk) - Vladislav Vlastovskiy (vlastv) - RENAUDIN Xavier (xorrox) + - Yannick Vanhaeren (yvh) diff --git a/appveyor.yml b/appveyor.yml index bd35af195d13e..45373a93583f9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,7 @@ init: - SET PATH=c:\php;%PATH% - SET COMPOSER_NO_INTERACTION=1 - SET SYMFONY_DEPRECATIONS_HELPER=strict + - SET "SYMFONY_REQUIRE=>=3.4" - SET ANSICON=121x90 (121x90) - SET SYMFONY_PHPUNIT_VERSION=4.8 - REG ADD "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v DelayedExpansion /t REG_DWORD /d 1 /f @@ -48,9 +49,10 @@ install: - copy /Y php.ini-min php.ini - echo extension=php_openssl.dll >> php.ini - cd c:\projects\symfony - - IF NOT EXIST composer.phar (appveyor DownloadFile https://getcomposer.org/download/1.3.0/composer.phar) + - IF NOT EXIST composer.phar (appveyor DownloadFile https://github.com/composer/composer/releases/download/1.7.1/composer.phar) - php composer.phar self-update - copy /Y .composer\* %APPDATA%\Composer\ + - php composer.phar global require --no-progress --no-scripts --no-plugins symfony/flex dev-master - php .github/build-packages.php "HEAD^" src\Symfony\Bridge\PhpUnit - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) - php composer.phar config platform.php 5.5.9 diff --git a/composer.json b/composer.json index e0a1c53c4f158..6d5065d4d3084 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^5.5.9|>=7.0.8", "ext-xml": "*", - "doctrine/common": "~2.4@stable", + "doctrine/common": "~2.4", "fig/link-util": "^1.0", "twig/twig": "^1.35|^2.4.4", "psr/cache": "~1.0", @@ -92,7 +92,7 @@ "doctrine/cache": "~1.6", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", - "doctrine/orm": "~2.4,>=2.4.5,<=2.7.0", + "doctrine/orm": "~2.4,>=2.4.5", "doctrine/doctrine-bundle": "~1.4", "monolog/monolog": "~1.11", "ocramius/proxy-manager": "~0.4|~1.0|~2.0", diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index a26ce98107f60..d8b55eb808fc2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -482,7 +482,7 @@ public function testAssociatedEntity() $this->buildViolation('myMessage') ->atPath('property.path.single') - ->setParameter('{{ value }}', 'object("Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity") identified by (id => 1)') + ->setParameter('{{ value }}', 'foo') ->setInvalidValue($entity1) ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->setCause(array($associated, $associated2)) diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 2015e7fb3acc6..161a187ff98ab 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -186,6 +186,10 @@ private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, return $this->formatValue($value, self::PRETTY_DATE); } + if (\method_exists($value, '__toString')) { + return (string) $value; + } + if ($class->getName() !== $idClass = \get_class($value)) { // non unique value might be a composite PK that consists of other entity objects if ($em->getMetadataFactory()->hasMetadataFor($idClass)) { diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 1535f8f42b8f8..420535b7516fd 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^5.5.9|>=7.0.8", - "doctrine/common": "~2.4@stable", + "doctrine/common": "~2.4", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, diff --git a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php index ce8f62d2c53c0..6674feb94a88e 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php @@ -16,7 +16,7 @@ public function test() $this->markTestSkipped('This test cannot be run on HHVM.'); } - exec('type phpdbg', $output, $returnCode); + exec('type phpdbg 2> /dev/null', $output, $returnCode); if (\PHP_VERSION_ID >= 70000 && 0 === $returnCode) { $php = 'phpdbg -qrr'; diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index 197650915fe91..bcfc432f8a139 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -101,7 +101,8 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ } $prevRoot = getenv('COMPOSER_ROOT_VERSION'); putenv("COMPOSER_ROOT_VERSION=$PHPUNIT_VERSION.99"); - $exit = proc_close(proc_open("$COMPOSER install --no-dev --prefer-dist --no-suggest --no-progress --ansi", array(), $p, getcwd(), null, array('bypass_shell' => true))); + // --no-suggest is not in the list to keep compat with composer 1.0, which is shipped with Ubuntu 16.04LTS + $exit = proc_close(proc_open("$COMPOSER install --no-dev --prefer-dist --no-progress --ansi", array(), $p, getcwd(), null, array('bypass_shell' => true))); putenv('COMPOSER_ROOT_VERSION'.(false !== $prevRoot ? '='.$prevRoot : '')); if ($exit) { exit($exit); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php index ddea9dc1742fe..a3f73cfc35fa7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php @@ -49,4 +49,14 @@ public function testCsrfTokensAreClearedOnLogout() $this->assertFalse(static::$kernel->getContainer()->get('test.security.csrf.token_storage')->hasToken('foo')); } + + public function testAccessControlDoesNotApplyOnLogout() + { + $client = $this->createClient(array('test_case' => 'LogoutAccess', 'root_config' => 'config.yml')); + + $client->request('POST', '/login', array('_username' => 'johannes', '_password' => 'test')); + $client->request('GET', '/logout'); + + $this->assertRedirect($client->getResponse(), '/'); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/bundles.php new file mode 100644 index 0000000000000..c934b52aee7c0 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/bundles.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\SecurityBundle\SecurityBundle; + +return array( + new FrameworkBundle(), + new SecurityBundle(), +); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/config.yml new file mode 100644 index 0000000000000..2e20735b80236 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/config.yml @@ -0,0 +1,26 @@ +imports: +- { resource: ./../config/framework.yml } + +security: + encoders: + Symfony\Component\Security\Core\User\User: plaintext + + providers: + in_memory: + memory: + users: + johannes: { password: test, roles: [ROLE_USER] } + + firewalls: + default: + form_login: + check_path: login + remember_me: true + require_previous_session: false + logout: ~ + anonymous: ~ + stateless: true + + access_control: + - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY } + - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/routing.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/routing.yml new file mode 100644 index 0000000000000..1dddfca2f8154 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/LogoutAccess/routing.yml @@ -0,0 +1,5 @@ +login: + path: /login + +logout: + path: /logout diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index fa304be517a22..57d313d87142c 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^5.5.9|>=7.0.8", "ext-xml": "*", - "symfony/security": "~3.4.12|~4.0.12|^4.1.1", + "symfony/security": "~3.4.15|~4.0.15|^4.1.4", "symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/http-kernel": "~3.4|~4.0", "symfony/polyfill-php70": "~1.0" diff --git a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php index 76e0608006173..d1f87903406fe 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php @@ -89,6 +89,7 @@ public function testDefaultOptions() $this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION)); $this->assertSame(1, $client->getOption(\Memcached::OPT_BINARY_PROTOCOL)); + $this->assertSame(1, $client->getOption(\Memcached::OPT_TCP_NODELAY)); $this->assertSame(1, $client->getOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE)); } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php new file mode 100644 index 0000000000000..852079c00ce79 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Adapter; + +class RedisClusterAdapterTest extends AbstractRedisAdapterTest +{ + public static function setupBeforeClass() + { + if (!class_exists('RedisCluster')) { + self::markTestSkipped('The RedisCluster class is required.'); + } + if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) { + self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.'); + } + + self::$redis = new \RedisCluster(null, explode(' ', $hosts)); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/RedisClusterCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/RedisClusterCacheTest.php new file mode 100644 index 0000000000000..99d4e518fb409 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/RedisClusterCacheTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Simple; + +class RedisClusterCacheTest extends AbstractRedisCacheTest +{ + public static function setupBeforeClass() + { + if (!class_exists('RedisCluster')) { + self::markTestSkipped('The RedisCluster class is required.'); + } + if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) { + self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.'); + } + + self::$redis = new \RedisCluster(null, explode(' ', $hosts)); + } +} diff --git a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php index 7e36164293842..5983d9ebd1da5 100644 --- a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php +++ b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php @@ -134,6 +134,7 @@ public static function createConnection($servers, array $options = array()) $options = array_change_key_case($options, CASE_UPPER); $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); $client->setOption(\Memcached::OPT_NO_BLOCK, true); + $client->setOption(\Memcached::OPT_TCP_NODELAY, true); if (!array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) { $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true); } diff --git a/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt b/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt index 74cf2e9171001..7211afbdf85ea 100644 --- a/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt +++ b/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt @@ -38,8 +38,7 @@ Did you forget a "use" statement for another namespace?" ["line":protected]=> int(%d) ["trace":"Exception":private]=> - array(0) { - } + array(%d) {%A} ["previous":"Exception":private]=> NULL ["severity":protected]=> diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index 19f01b8536cb4..aeebbf0bf18c4 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -34,15 +34,17 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe private $graph; private $currentDefinition; private $onlyConstructorArguments; + private $hasProxyDumper; private $lazy; private $expressionLanguage; /** * @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls */ - public function __construct($onlyConstructorArguments = false) + public function __construct($onlyConstructorArguments = false, $hasProxyDumper = true) { $this->onlyConstructorArguments = (bool) $onlyConstructorArguments; + $this->hasProxyDumper = (bool) $hasProxyDumper; } /** @@ -97,7 +99,7 @@ protected function processValue($value, $isRoot = false) $targetId, $targetDefinition, $value, - $this->lazy || ($targetDefinition && $targetDefinition->isLazy()), + $this->lazy || ($this->hasProxyDumper && $targetDefinition && $targetDefinition->isLazy()), ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior() ); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php index 8ffb245c12f2c..839af1ae232d3 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php @@ -66,7 +66,7 @@ private function getDefinitionId($id, ContainerBuilder $container) $seen = array(); while ($container->hasAlias($id)) { if (isset($seen[$id])) { - throw new ServiceCircularReferenceException($id, array_keys($seen)); + throw new ServiceCircularReferenceException($id, array_merge(array_keys($seen), array($id))); } $seen[$id] = true; $id = $container->normalizeId($container->getAlias($id)); diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 5dfa1da59dc50..4d3e3883d428a 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -294,7 +294,7 @@ public function get($id, $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERE } if (isset($this->loading[$id])) { - throw new ServiceCircularReferenceException($id, array_keys($this->loading)); + throw new ServiceCircularReferenceException($id, array_merge(array_keys($this->loading), array($id))); } $this->loading[$id] = true; diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index c2b73e70c84dc..7a66382f6058b 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -122,7 +122,6 @@ class ContainerBuilder extends Container implements TaggedContainerInterface private $autoconfiguredInstanceof = array(); private $removedIds = array(); - private $alreadyLoading = array(); private static $internalTypes = array( 'int' => true, @@ -588,22 +587,32 @@ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INV return $this->doGet($id, $invalidBehavior); } - private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = array()) + private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = null, $isConstructorArgument = false) { $id = $this->normalizeId($id); if (isset($inlineServices[$id])) { return $inlineServices[$id]; } - if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) { - return parent::get($id, $invalidBehavior); + if (null === $inlineServices) { + $isConstructorArgument = true; + $inlineServices = array(); } - if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { - return $service; + try { + if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) { + return parent::get($id, $invalidBehavior); + } + if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { + return $service; + } + } catch (ServiceCircularReferenceException $e) { + if ($isConstructorArgument) { + throw $e; + } } if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) { - return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior, $inlineServices); + return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior, $inlineServices, $isConstructorArgument); } try { @@ -616,16 +625,17 @@ private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_ throw $e; } - $loading = isset($this->alreadyLoading[$id]) ? 'loading' : 'alreadyLoading'; - $this->{$loading}[$id] = true; + if ($isConstructorArgument) { + $this->loading[$id] = true; + } try { - $service = $this->createService($definition, $inlineServices, $id); + return $this->createService($definition, $inlineServices, $isConstructorArgument, $id); } finally { - unset($this->{$loading}[$id]); + if ($isConstructorArgument) { + unset($this->loading[$id]); + } } - - return $service; } /** @@ -1092,7 +1102,7 @@ public function findDefinition($id) * @throws RuntimeException When the service is a synthetic service * @throws InvalidArgumentException When configure callable is not callable */ - private function createService(Definition $definition, array &$inlineServices, $id = null, $tryProxy = true) + private function createService(Definition $definition, array &$inlineServices, $isConstructorArgument = false, $id = null, $tryProxy = true) { if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) { return $inlineServices[$h]; @@ -1110,16 +1120,14 @@ private function createService(Definition $definition, array &$inlineServices, $ @trigger_error($definition->getDeprecationMessage($id), E_USER_DEPRECATED); } - if ($tryProxy && $definition->isLazy()) { - $proxy = $this - ->getProxyInstantiator() - ->instantiateProxy( - $this, - $definition, - $id, function () use ($definition, &$inlineServices, $id) { - return $this->createService($definition, $inlineServices, $id, false); - } - ); + if ($tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) { + $proxy = $proxy->instantiateProxy( + $this, + $definition, + $id, function () use ($definition, &$inlineServices, $id) { + return $this->createService($definition, $inlineServices, true, $id, false); + } + ); $this->shareService($definition, $proxy, $id, $inlineServices); return $proxy; @@ -1131,19 +1139,21 @@ private function createService(Definition $definition, array &$inlineServices, $ require_once $parameterBag->resolveValue($definition->getFile()); } - $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlineServices); - - if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) { - return $this->services[$id]; - } + $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlineServices, $isConstructorArgument); if (null !== $factory = $definition->getFactory()) { if (\is_array($factory)) { - $factory = array($this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices), $factory[1]); + $factory = array($this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices, $isConstructorArgument), $factory[1]); } elseif (!\is_string($factory)) { throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id)); } + } + if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) { + return $this->services[$id]; + } + + if (null !== $factory) { $service = \call_user_func_array($factory, $arguments); if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) { @@ -1214,11 +1224,11 @@ public function resolveServices($value) return $this->doResolveServices($value); } - private function doResolveServices($value, array &$inlineServices = array()) + private function doResolveServices($value, array &$inlineServices = array(), $isConstructorArgument = false) { if (\is_array($value)) { foreach ($value as $k => $v) { - $value[$k] = $this->doResolveServices($v, $inlineServices); + $value[$k] = $this->doResolveServices($v, $inlineServices, $isConstructorArgument); } } elseif ($value instanceof ServiceClosureArgument) { $reference = $value->getValues()[0]; @@ -1261,9 +1271,9 @@ private function doResolveServices($value, array &$inlineServices = array()) return $count; }); } elseif ($value instanceof Reference) { - $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices); + $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices, $isConstructorArgument); } elseif ($value instanceof Definition) { - $value = $this->createService($value, $inlineServices); + $value = $this->createService($value, $inlineServices, $isConstructorArgument); } elseif ($value instanceof Parameter) { $value = $this->getParameter((string) $value); } elseif ($value instanceof Expression) { @@ -1584,20 +1594,6 @@ protected function getEnv($name) } } - /** - * Retrieves the currently set proxy instantiator or instantiates one. - * - * @return InstantiatorInterface - */ - private function getProxyInstantiator() - { - if (!$this->proxyInstantiator) { - $this->proxyInstantiator = new RealServiceInstantiator(); - } - - return $this->proxyInstantiator; - } - private function callMethod($service, $call, array &$inlineServices) { foreach (self::getServiceConditionals($call[1]) as $s) { @@ -1627,7 +1623,7 @@ private function shareService(Definition $definition, $service, $id, array &$inl if (null !== $id && $definition->isShared()) { $this->services[$id] = $service; - unset($this->loading[$id], $this->alreadyLoading[$id]); + unset($this->loading[$id]); } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 71e776310b270..5530912c90a9e 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; +use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -138,7 +139,20 @@ public function dump(array $options = array()) $this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass); - (new AnalyzeServiceReferencesPass())->process($this->container); + if ($this->getProxyDumper() instanceof NullDumper) { + (new AnalyzeServiceReferencesPass(true, false))->process($this->container); + try { + (new CheckCircularReferencesPass())->process($this->container); + } catch (ServiceCircularReferenceException $e) { + $path = $e->getPath(); + end($path); + $path[key($path)] .= '". Try running "composer require symfony/proxy-manager-bridge'; + + throw new ServiceCircularReferenceException($e->getServiceId(), $path); + } + } + + (new AnalyzeServiceReferencesPass(false, !$this->getProxyDumper() instanceof NullDumper))->process($this->container); $this->circularReferences = array(); $checkedNodes = array(); foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) { @@ -286,21 +300,18 @@ private function getProxyDumper() * * @return string */ - private function addServiceLocalTempVariables($cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, \SplObjectStorage $allInlinedDefinitions) + private function addServiceLocalTempVariables($cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, array $serviceCalls, $preInstance = false) { - $allCalls = $calls = $behavior = array(); + $calls = array(); - foreach ($allInlinedDefinitions as $def) { - $arguments = array($def->getArguments(), $def->getFactory(), $def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()); - $this->getServiceCallsFromArguments($arguments, $allCalls, false, $cId, $behavior, $allInlinedDefinitions[$def]); - } - - $isPreInstance = isset($inlinedDefinitions[$definition]) && isset($this->circularReferences[$cId]) && !$this->getProxyDumper()->isProxyCandidate($definition) && $definition->isShared(); foreach ($inlinedDefinitions as $def) { - $this->getServiceCallsFromArguments(array($def->getArguments(), $def->getFactory()), $calls, $isPreInstance, $cId); + if ($preInstance && !$inlinedDefinitions[$def][1]) { + continue; + } + $this->getServiceCallsFromArguments(array($def->getArguments(), $def->getFactory()), $calls, $preInstance, $cId); if ($def !== $definition) { $arguments = array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()); - $this->getServiceCallsFromArguments($arguments, $calls, $isPreInstance && !$this->hasReference($cId, $arguments, true), $cId); + $this->getServiceCallsFromArguments($arguments, $calls, $preInstance && !$this->hasReference($cId, $arguments, true), $cId); } } if (!isset($inlinedDefinitions[$definition])) { @@ -309,23 +320,23 @@ private function addServiceLocalTempVariables($cId, Definition $definition, \Spl } $code = ''; - foreach ($calls as $id => $callCount) { + foreach ($calls as $id => list($callCount)) { if ('service_container' === $id || $id === $cId || isset($this->referenceVariables[$id])) { continue; } - if ($callCount <= 1 && $allCalls[$id] <= 1) { + if ($callCount <= 1 && $serviceCalls[$id][0] <= 1) { continue; } $name = $this->getNextVariableName(); $this->referenceVariables[$id] = new Variable($name); - $reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id] ? new Reference($id, $behavior[$id]) : null; + $reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $serviceCalls[$id][1] ? new Reference($id, $serviceCalls[$id][1]) : null; $code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($id, $reference)); } if ('' !== $code) { - if ($isPreInstance) { + if ($preInstance) { $code .= <<services['$cId'])) { @@ -425,23 +436,21 @@ private function generateProxyClasses() * * @return string */ - private function addServiceInclude($cId, Definition $definition, \SplObjectStorage $inlinedDefinitions) + private function addServiceInclude($cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, array $serviceCalls) { $code = ''; if ($this->inlineRequires && !$this->isHotPath($definition)) { - $lineage = $calls = $behavior = array(); + $lineage = array(); foreach ($inlinedDefinitions as $def) { if (!$def->isDeprecated() && \is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass())) { $this->collectLineage($class, $lineage); } - $arguments = array($def->getArguments(), $def->getFactory(), $def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()); - $this->getServiceCallsFromArguments($arguments, $calls, false, $cId, $behavior, $inlinedDefinitions[$def]); } - foreach ($calls as $id => $callCount) { + foreach ($serviceCalls as $id => list($callCount, $behavior)) { if ('service_container' !== $id && $id !== $cId - && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior[$id] + && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior && $this->container->has($id) && $this->isTrivialInstance($def = $this->container->findDefinition($id)) && \is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass()) @@ -476,24 +485,24 @@ private function addServiceInclude($cId, Definition $definition, \SplObjectStora * @throws RuntimeException When the factory definition is incomplete * @throws ServiceCircularReferenceException When a circular reference is detected */ - private function addServiceInlinedDefinitions($id, Definition $definition, \SplObjectStorage $inlinedDefinitions, &$isSimpleInstance) + private function addServiceInlinedDefinitions($id, Definition $definition, \SplObjectStorage $inlinedDefinitions, &$isSimpleInstance, $preInstance = false) { $code = ''; foreach ($inlinedDefinitions as $def) { - if ($definition === $def) { + if ($definition === $def || isset($this->definitionVariables[$def])) { continue; } - if ($inlinedDefinitions[$def] <= 1 && !$def->getMethodCalls() && !$def->getProperties() && !$def->getConfigurator() && false === strpos($this->dumpValue($def->getClass()), '$')) { + if ($inlinedDefinitions[$def][0] <= 1 && !$def->getMethodCalls() && !$def->getProperties() && !$def->getConfigurator() && false === strpos($this->dumpValue($def->getClass()), '$')) { continue; } - if (isset($this->definitionVariables[$def])) { - $name = $this->definitionVariables[$def]; - } else { - $name = $this->getNextVariableName(); - $this->definitionVariables[$def] = new Variable($name); + if ($preInstance && !$inlinedDefinitions[$def][1]) { + continue; } + $name = $this->getNextVariableName(); + $this->definitionVariables[$def] = new Variable($name); + // a construct like: // $a = new ServiceA(ServiceB $b); $b = new ServiceB(ServiceA $a); // this is an indication for a wrong implementation, you can circumvent this problem @@ -502,7 +511,7 @@ private function addServiceInlinedDefinitions($id, Definition $definition, \SplO // $a = new ServiceA(ServiceB $b); // $b->setServiceA(ServiceA $a); if (isset($inlinedDefinitions[$definition]) && $this->hasReference($id, array($def->getArguments(), $def->getFactory()))) { - throw new ServiceCircularReferenceException($id, array($id)); + throw new ServiceCircularReferenceException($id, array($id, '...', $id)); } $code .= $this->addNewInstance($def, '$'.$name, ' = ', $id); @@ -558,6 +567,7 @@ private function addServiceInstance($id, Definition $definition, $isSimpleInstan } $code = $this->addNewInstance($definition, $return, $instantiation, $id); + $this->referenceVariables[$id] = new Variable('instance'); if (!$isSimpleInstance) { $code .= "\n"; @@ -657,8 +667,6 @@ private function addServiceProperties(Definition $definition, $variableName = 'i */ private function addServiceInlinedDefinitionsSetup($id, Definition $definition, \SplObjectStorage $inlinedDefinitions, $isSimpleInstance) { - $this->referenceVariables[$id] = new Variable('instance'); - $code = ''; foreach ($inlinedDefinitions as $def) { if ($definition === $def || !$this->hasReference($id, array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()), true)) { @@ -668,7 +676,7 @@ private function addServiceInlinedDefinitionsSetup($id, Definition $definition, // if the instance is simple, the return statement has already been generated // so, the only possible way to get there is because of a circular reference if ($isSimpleInstance) { - throw new ServiceCircularReferenceException($id, array($id)); + throw new ServiceCircularReferenceException($id, array($id, '...', $id)); } $name = (string) $this->definitionVariables[$def]; @@ -805,6 +813,7 @@ protected function {$methodName}($lazyInitialization) $inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition)); $constructorDefinitions = $this->getDefinitionsFromArguments(array($definition->getArguments(), $definition->getFactory())); $otherDefinitions = new \SplObjectStorage(); + $serviceCalls = array(); foreach ($inlinedDefinitions as $def) { if ($def === $definition || isset($constructorDefinitions[$def])) { @@ -812,16 +821,21 @@ protected function {$methodName}($lazyInitialization) } else { $otherDefinitions[$def] = $inlinedDefinitions[$def]; } + $arguments = array($def->getArguments(), $def->getFactory(), $def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()); + $this->getServiceCallsFromArguments($arguments, $serviceCalls, false, $id); } $isSimpleInstance = !$definition->getProperties() && !$definition->getMethodCalls() && !$definition->getConfigurator(); + $preInstance = isset($this->circularReferences[$id]) && !$this->getProxyDumper()->isProxyCandidate($definition) && $definition->isShared(); $code .= - $this->addServiceInclude($id, $definition, $inlinedDefinitions). - $this->addServiceLocalTempVariables($id, $definition, $constructorDefinitions, $inlinedDefinitions). - $this->addServiceInlinedDefinitions($id, $definition, $constructorDefinitions, $isSimpleInstance). + $this->addServiceInclude($id, $definition, $inlinedDefinitions, $serviceCalls). + $this->addServiceLocalTempVariables($id, $definition, $constructorDefinitions, $serviceCalls, $preInstance). + $this->addServiceInlinedDefinitions($id, $definition, $constructorDefinitions, $isSimpleInstance, $preInstance). $this->addServiceInstance($id, $definition, $isSimpleInstance). - $this->addServiceLocalTempVariables($id, $definition, $otherDefinitions, $inlinedDefinitions). + $this->addServiceLocalTempVariables($id, $definition, $constructorDefinitions->offsetUnset($definition) ?: $constructorDefinitions, $serviceCalls). + $this->addServiceLocalTempVariables($id, $definition, $otherDefinitions, $serviceCalls). + $this->addServiceInlinedDefinitions($id, $definition, $constructorDefinitions, $isSimpleInstance). $this->addServiceInlinedDefinitions($id, $definition, $otherDefinitions, $isSimpleInstance). $this->addServiceInlinedDefinitionsSetup($id, $definition, $inlinedDefinitions, $isSimpleInstance). $this->addServiceProperties($definition). @@ -1558,30 +1572,29 @@ private function getServiceConditionals($value) /** * Builds service calls from arguments. + * + * Populates $calls with "referenced id" => ["reference count", "invalid behavior"] pairs. */ - private function getServiceCallsFromArguments(array $arguments, array &$calls, $isPreInstance, $callerId, array &$behavior = array(), $step = 1) + private function getServiceCallsFromArguments(array $arguments, array &$calls, $preInstance, $callerId) { foreach ($arguments as $argument) { if (\is_array($argument)) { - $this->getServiceCallsFromArguments($argument, $calls, $isPreInstance, $callerId, $behavior, $step); + $this->getServiceCallsFromArguments($argument, $calls, $preInstance, $callerId); } elseif ($argument instanceof Reference) { $id = $this->container->normalizeId($argument); if (!isset($calls[$id])) { - $calls[$id] = (int) ($isPreInstance && isset($this->circularReferences[$callerId][$id])); - } - if (!isset($behavior[$id])) { - $behavior[$id] = $argument->getInvalidBehavior(); + $calls[$id] = array((int) ($preInstance && isset($this->circularReferences[$callerId][$id])), $argument->getInvalidBehavior()); } else { - $behavior[$id] = min($behavior[$id], $argument->getInvalidBehavior()); + $calls[$id][1] = min($calls[$id][1], $argument->getInvalidBehavior()); } - $calls[$id] += $step; + ++$calls[$id][0]; } } } - private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null) + private function getDefinitionsFromArguments(array $arguments, $isConstructorArgument = true, \SplObjectStorage $definitions = null) { if (null === $definitions) { $definitions = new \SplObjectStorage(); @@ -1589,22 +1602,23 @@ private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage foreach ($arguments as $argument) { if (\is_array($argument)) { - $this->getDefinitionsFromArguments($argument, $definitions); + $this->getDefinitionsFromArguments($argument, $isConstructorArgument, $definitions); } elseif (!$argument instanceof Definition) { // no-op } elseif (isset($definitions[$argument])) { - $definitions[$argument] = 1 + $definitions[$argument]; + $def = $definitions[$argument]; + $definitions[$argument] = array(1 + $def[0], $isConstructorArgument || $def[1]); } else { - $definitions[$argument] = 1; - $this->getDefinitionsFromArguments($argument->getArguments(), $definitions); - $this->getDefinitionsFromArguments(array($argument->getFactory()), $definitions); - $this->getDefinitionsFromArguments($argument->getProperties(), $definitions); - $this->getDefinitionsFromArguments($argument->getMethodCalls(), $definitions); - $this->getDefinitionsFromArguments(array($argument->getConfigurator()), $definitions); + $definitions[$argument] = array(1, $isConstructorArgument); + $this->getDefinitionsFromArguments($argument->getArguments(), $isConstructorArgument, $definitions); + $this->getDefinitionsFromArguments(array($argument->getFactory()), $isConstructorArgument, $definitions); + $this->getDefinitionsFromArguments($argument->getProperties(), false, $definitions); + $this->getDefinitionsFromArguments($argument->getMethodCalls(), false, $definitions); + $this->getDefinitionsFromArguments(array($argument->getConfigurator()), false, $definitions); // move current definition last in the list - $nbOccurences = $definitions[$argument]; + $def = $definitions[$argument]; unset($definitions[$argument]); - $definitions[$argument] = $nbOccurences; + $definitions[$argument] = $def; } } @@ -1640,7 +1654,7 @@ private function hasReference($id, array $arguments, $deep = false, array &$visi return true; } - if (!$deep || isset($visited[$argumentId]) || !isset($this->circularReferences[$id][$argumentId])) { + if (!$deep || isset($visited[$argumentId]) || !isset($this->circularReferences[$argumentId])) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index e370157d41fa6..249dfc8787b21 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1379,6 +1379,12 @@ public function testAlmostCircular($visibility) $foo5 = $container->get('foo5'); $this->assertSame($foo5, $foo5->bar->foo); + + $manager = $container->get('manager'); + $this->assertEquals(new \stdClass(), $manager); + + $manager = $container->get('manager2'); + $this->assertEquals(new \stdClass(), $manager); } public function provideAlmostCircular() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index e90edd50283b4..9c1b80cdedc9b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -23,6 +23,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -530,50 +531,22 @@ public function testCircularReferenceAllowanceForLazyServices() $container->compile(); $dumper = new PhpDumper($container); + $dumper->setProxyDumper(new \DummyProxyDumper()); $dumper->dump(); $this->addToAssertionCount(1); - } - - public function testCircularReferenceAllowanceForInlinedDefinitionsForLazyServices() - { - /* - * test graph: - * [connection] -> [event_manager] --> [entity_manager](lazy) - * | - * --(call)- addEventListener ("@lazy_service") - * - * [lazy_service](lazy) -> [entity_manager](lazy) - * - */ - - $container = new ContainerBuilder(); - - $eventManagerDefinition = new Definition('stdClass'); - - $connectionDefinition = $container->register('connection', 'stdClass')->setPublic(true); - $connectionDefinition->addArgument($eventManagerDefinition); - - $container->register('entity_manager', 'stdClass') - ->setPublic(true) - ->setLazy(true) - ->addArgument(new Reference('connection')); - - $lazyServiceDefinition = $container->register('lazy_service', 'stdClass'); - $lazyServiceDefinition->setPublic(true); - $lazyServiceDefinition->setLazy(true); - $lazyServiceDefinition->addArgument(new Reference('entity_manager')); - - $eventManagerDefinition->addMethodCall('addEventListener', array(new Reference('lazy_service'))); - - $container->compile(); $dumper = new PhpDumper($container); - $dumper->setProxyDumper(new \DummyProxyDumper()); - $dumper->dump(); + $message = 'Circular reference detected for service "foo", path: "foo -> bar -> foo". Try running "composer require symfony/proxy-manager-bridge".'; + if (method_exists($this, 'expectException')) { + $this->expectException(ServiceCircularReferenceException::class); + $this->expectExceptionMessage($message); + } else { + $this->setExpectedException(ServiceCircularReferenceException::class, $message); + } - $this->addToAssertionCount(1); + $dumper->dump(); } public function testDedupLazyProxy() @@ -851,6 +824,12 @@ public function testAlmostCircular($visibility) $foo5 = $container->get('foo5'); $this->assertSame($foo5, $foo5->bar->foo); + + $manager = $container->get('manager'); + $this->assertEquals(new \stdClass(), $manager); + + $manager = $container->get('manager2'); + $this->assertEquals(new \stdClass(), $manager); } public function provideAlmostCircular() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php index dff937ccdbb7f..2079e136b74b3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php @@ -55,4 +55,50 @@ ->addArgument(new Reference('foo5')) ->setProperty('foo', new Reference('foo5')); +// doctrine-like event system + some extra + +$container->register('manager', 'stdClass')->setPublic(true) + ->addArgument(new Reference('connection')); + +$container->register('logger', 'stdClass')->setPublic(true) + ->addArgument(new Reference('connection')) + ->setProperty('handler', (new Definition('stdClass'))->addArgument(new Reference('manager'))) +; +$container->register('connection', 'stdClass')->setPublic(true) + ->addArgument(new Reference('dispatcher')) + ->addArgument(new Reference('config')); + +$container->register('config', 'stdClass')->setPublic(false) + ->setProperty('logger', new Reference('logger')); + +$container->register('dispatcher', 'stdClass')->setPublic($public) + ->setLazy($public) + ->setProperty('subscriber', new Reference('subscriber')); + +$container->register('subscriber', 'stdClass')->setPublic(true) + ->addArgument(new Reference('manager')); + +// doctrine-like event system + some extra (bis) + +$container->register('manager2', 'stdClass')->setPublic(true) + ->addArgument(new Reference('connection2')); + +$container->register('logger2', 'stdClass')->setPublic(false) + ->addArgument(new Reference('connection2')) + ->setProperty('handler2', (new Definition('stdClass'))->addArgument(new Reference('manager2'))) +; +$container->register('connection2', 'stdClass')->setPublic(true) + ->addArgument(new Reference('dispatcher2')) + ->addArgument(new Reference('config2')); + +$container->register('config2', 'stdClass')->setPublic(false) + ->setProperty('logger2', new Reference('logger2')); + +$container->register('dispatcher2', 'stdClass')->setPublic($public) + ->setLazy($public) + ->setProperty('subscriber2', new Reference('subscriber2')); + +$container->register('subscriber2', 'stdClass')->setPublic(false) + ->addArgument(new Reference('manager2')); + return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index 45fb2432c33c9..f0ad8ef31c00a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -25,10 +25,16 @@ public function __construct() $this->methodMap = array( 'bar2' => 'getBar2Service', 'bar3' => 'getBar3Service', + 'connection' => 'getConnectionService', + 'connection2' => 'getConnection2Service', 'foo' => 'getFooService', 'foo2' => 'getFoo2Service', 'foo5' => 'getFoo5Service', 'foobar4' => 'getFoobar4Service', + 'logger' => 'getLoggerService', + 'manager' => 'getManagerService', + 'manager2' => 'getManager2Service', + 'subscriber' => 'getSubscriberService', ); $this->aliases = array(); @@ -41,10 +47,16 @@ public function getRemovedIds() 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'bar' => true, 'bar5' => true, + 'config' => true, + 'config2' => true, + 'dispatcher' => true, + 'dispatcher2' => true, 'foo4' => true, 'foobar' => true, 'foobar2' => true, 'foobar3' => true, + 'logger2' => true, + 'subscriber2' => true, ); } @@ -95,6 +107,49 @@ protected function getBar3Service() return $instance; } + /** + * Gets the public 'connection' shared service. + * + * @return \stdClass + */ + protected function getConnectionService() + { + $a = new \stdClass(); + + $b = new \stdClass(); + + $this->services['connection'] = $instance = new \stdClass($a, $b); + + $a->subscriber = ${($_ = isset($this->services['subscriber']) ? $this->services['subscriber'] : $this->getSubscriberService()) && false ?: '_'}; + $b->logger = ${($_ = isset($this->services['logger']) ? $this->services['logger'] : $this->getLoggerService()) && false ?: '_'}; + + return $instance; + } + + /** + * Gets the public 'connection2' shared service. + * + * @return \stdClass + */ + protected function getConnection2Service() + { + $a = new \stdClass(); + + $b = new \stdClass(); + + $this->services['connection2'] = $instance = new \stdClass($a, $b); + + $c = ${($_ = isset($this->services['manager2']) ? $this->services['manager2'] : $this->getManager2Service()) && false ?: '_'}; + + $d = new \stdClass($instance); + + $a->subscriber2 = new \stdClass($c); + $d->handler2 = new \stdClass($c); + $b->logger2 = $d; + + return $instance; + } + /** * Gets the public 'foo' shared service. * @@ -136,7 +191,7 @@ protected function getFoo5Service() { $this->services['foo5'] = $instance = new \stdClass(); - $a = new \stdClass(${($_ = isset($this->services['foo5']) ? $this->services['foo5'] : $this->getFoo5Service()) && false ?: '_'}); + $a = new \stdClass($instance); $a->foo = $instance; @@ -160,4 +215,72 @@ protected function getFoobar4Service() return $instance; } + + /** + * Gets the public 'logger' shared service. + * + * @return \stdClass + */ + protected function getLoggerService() + { + $a = ${($_ = isset($this->services['connection']) ? $this->services['connection'] : $this->getConnectionService()) && false ?: '_'}; + + if (isset($this->services['logger'])) { + return $this->services['logger']; + } + + $this->services['logger'] = $instance = new \stdClass($a); + + $instance->handler = new \stdClass(${($_ = isset($this->services['manager']) ? $this->services['manager'] : $this->getManagerService()) && false ?: '_'}); + + return $instance; + } + + /** + * Gets the public 'manager' shared service. + * + * @return \stdClass + */ + protected function getManagerService() + { + $a = ${($_ = isset($this->services['connection']) ? $this->services['connection'] : $this->getConnectionService()) && false ?: '_'}; + + if (isset($this->services['manager'])) { + return $this->services['manager']; + } + + return $this->services['manager'] = new \stdClass($a); + } + + /** + * Gets the public 'manager2' shared service. + * + * @return \stdClass + */ + protected function getManager2Service() + { + $a = ${($_ = isset($this->services['connection2']) ? $this->services['connection2'] : $this->getConnection2Service()) && false ?: '_'}; + + if (isset($this->services['manager2'])) { + return $this->services['manager2']; + } + + return $this->services['manager2'] = new \stdClass($a); + } + + /** + * Gets the public 'subscriber' shared service. + * + * @return \stdClass + */ + protected function getSubscriberService() + { + $a = ${($_ = isset($this->services['manager']) ? $this->services['manager'] : $this->getManagerService()) && false ?: '_'}; + + if (isset($this->services['subscriber'])) { + return $this->services['subscriber']; + } + + return $this->services['subscriber'] = new \stdClass($a); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index be18eb183abd0..784346b9242fa 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -26,6 +26,10 @@ public function __construct() 'bar' => 'getBarService', 'bar3' => 'getBar3Service', 'bar5' => 'getBar5Service', + 'connection' => 'getConnectionService', + 'connection2' => 'getConnection2Service', + 'dispatcher' => 'getDispatcherService', + 'dispatcher2' => 'getDispatcher2Service', 'foo' => 'getFooService', 'foo2' => 'getFoo2Service', 'foo4' => 'getFoo4Service', @@ -34,6 +38,10 @@ public function __construct() 'foobar2' => 'getFoobar2Service', 'foobar3' => 'getFoobar3Service', 'foobar4' => 'getFoobar4Service', + 'logger' => 'getLoggerService', + 'manager' => 'getManagerService', + 'manager2' => 'getManager2Service', + 'subscriber' => 'getSubscriberService', ); $this->aliases = array(); @@ -45,6 +53,10 @@ public function getRemovedIds() 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'bar2' => true, + 'config' => true, + 'config2' => true, + 'logger2' => true, + 'subscriber2' => true, ); } @@ -115,6 +127,81 @@ protected function getBar5Service() return $instance; } + /** + * Gets the public 'connection' shared service. + * + * @return \stdClass + */ + protected function getConnectionService() + { + $a = ${($_ = isset($this->services['dispatcher']) ? $this->services['dispatcher'] : $this->getDispatcherService()) && false ?: '_'}; + + if (isset($this->services['connection'])) { + return $this->services['connection']; + } + + $b = new \stdClass(); + + $this->services['connection'] = $instance = new \stdClass($a, $b); + + $b->logger = ${($_ = isset($this->services['logger']) ? $this->services['logger'] : $this->getLoggerService()) && false ?: '_'}; + + return $instance; + } + + /** + * Gets the public 'connection2' shared service. + * + * @return \stdClass + */ + protected function getConnection2Service() + { + $a = ${($_ = isset($this->services['dispatcher2']) ? $this->services['dispatcher2'] : $this->getDispatcher2Service()) && false ?: '_'}; + + if (isset($this->services['connection2'])) { + return $this->services['connection2']; + } + + $b = new \stdClass(); + + $this->services['connection2'] = $instance = new \stdClass($a, $b); + + $c = new \stdClass($instance); + + $c->handler2 = new \stdClass(${($_ = isset($this->services['manager2']) ? $this->services['manager2'] : $this->getManager2Service()) && false ?: '_'}); + $b->logger2 = $c; + + return $instance; + } + + /** + * Gets the public 'dispatcher' shared service. + * + * @return \stdClass + */ + protected function getDispatcherService($lazyLoad = true) + { + $this->services['dispatcher'] = $instance = new \stdClass(); + + $instance->subscriber = ${($_ = isset($this->services['subscriber']) ? $this->services['subscriber'] : $this->getSubscriberService()) && false ?: '_'}; + + return $instance; + } + + /** + * Gets the public 'dispatcher2' shared service. + * + * @return \stdClass + */ + protected function getDispatcher2Service($lazyLoad = true) + { + $this->services['dispatcher2'] = $instance = new \stdClass(); + + $instance->subscriber2 = new \stdClass(${($_ = isset($this->services['manager2']) ? $this->services['manager2'] : $this->getManager2Service()) && false ?: '_'}); + + return $instance; + } + /** * Gets the public 'foo' shared service. * @@ -232,4 +319,72 @@ protected function getFoobar4Service() return $instance; } + + /** + * Gets the public 'logger' shared service. + * + * @return \stdClass + */ + protected function getLoggerService() + { + $a = ${($_ = isset($this->services['connection']) ? $this->services['connection'] : $this->getConnectionService()) && false ?: '_'}; + + if (isset($this->services['logger'])) { + return $this->services['logger']; + } + + $this->services['logger'] = $instance = new \stdClass($a); + + $instance->handler = new \stdClass(${($_ = isset($this->services['manager']) ? $this->services['manager'] : $this->getManagerService()) && false ?: '_'}); + + return $instance; + } + + /** + * Gets the public 'manager' shared service. + * + * @return \stdClass + */ + protected function getManagerService() + { + $a = ${($_ = isset($this->services['connection']) ? $this->services['connection'] : $this->getConnectionService()) && false ?: '_'}; + + if (isset($this->services['manager'])) { + return $this->services['manager']; + } + + return $this->services['manager'] = new \stdClass($a); + } + + /** + * Gets the public 'manager2' shared service. + * + * @return \stdClass + */ + protected function getManager2Service() + { + $a = ${($_ = isset($this->services['connection2']) ? $this->services['connection2'] : $this->getConnection2Service()) && false ?: '_'}; + + if (isset($this->services['manager2'])) { + return $this->services['manager2']; + } + + return $this->services['manager2'] = new \stdClass($a); + } + + /** + * Gets the public 'subscriber' shared service. + * + * @return \stdClass + */ + protected function getSubscriberService() + { + $a = ${($_ = isset($this->services['manager']) ? $this->services['manager'] : $this->getManagerService()) && false ?: '_'}; + + if (isset($this->services['subscriber'])) { + return $this->services['subscriber']; + } + + return $this->services['subscriber'] = new \stdClass($a); + } } diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index e4ff91ca80bbc..c42cc939dc275 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -1483,6 +1483,31 @@ public function testDumpFile() } } + public function testDumpFileWithArray() + { + $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt'; + + $this->filesystem->dumpFile($filename, array('bar')); + + $this->assertFileExists($filename); + $this->assertStringEqualsFile($filename, 'bar'); + } + + public function testDumpFileWithResource() + { + $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo'.\DIRECTORY_SEPARATOR.'baz.txt'; + + $resource = fopen('php://memory', 'rw'); + fwrite($resource, 'bar'); + fseek($resource, 0); + + $this->filesystem->dumpFile($filename, $resource); + + fclose($resource); + $this->assertFileExists($filename); + $this->assertStringEqualsFile($filename, 'bar'); + } + public function testDumpFileOverwritesAnExistingFile() { $filename = $this->workspace.\DIRECTORY_SEPARATOR.'foo.txt'; diff --git a/src/Symfony/Component/Form/FormConfigInterface.php b/src/Symfony/Component/Form/FormConfigInterface.php index e66570cf5f1e6..b7083544a436b 100644 --- a/src/Symfony/Component/Form/FormConfigInterface.php +++ b/src/Symfony/Component/Form/FormConfigInterface.php @@ -99,7 +99,7 @@ public function getModelTransformers(); /** * Returns the data mapper of the form. * - * @return DataMapperInterface The data mapper + * @return DataMapperInterface|null The data mapper */ public function getDataMapper(); diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index 01740f176646c..ac620b28c09e1 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -31,8 +31,8 @@ class BinaryFileResponse extends Response * @var File */ protected $file; - protected $offset; - protected $maxlen; + protected $offset = 0; + protected $maxlen = -1; protected $deleteFileAfterSend = false; /** diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 4208f9a62951b..6ac23eed4d164 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -2086,10 +2086,13 @@ private function getTrustedValues($type, $ip = null) if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) { $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]); - $forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array(); + $forwardedValues = preg_match_all(sprintf('{(?:%s)="?([a-zA-Z0-9\.:_\-/\[\]]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array(); if (self::HEADER_CLIENT_PORT === $type) { foreach ($forwardedValues as $k => $v) { - $forwardedValues[$k] = substr_replace($v, '0.0.0.0', 0, strrpos($v, ':')); + if (']' === substr($v, -1) || false === $v = strrchr($v, ':')) { + $v = $this->isSecure() ? ':443' : ':80'; + } + $forwardedValues[$k] = '0.0.0.0'.$v; } } } @@ -2124,9 +2127,17 @@ private function normalizeAndFilterClientIps(array $clientIps, $ip) $firstTrustedIp = null; foreach ($clientIps as $key => $clientIp) { - // Remove port (unfortunately, it does happen) - if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) { - $clientIps[$key] = $clientIp = $match[1]; + if (strpos($clientIp, '.')) { + // Strip :port from IPv4 addresses. This is allowed in Forwarded + // and may occur in X-Forwarded-For. + $i = strpos($clientIp, ':'); + if ($i) { + $clientIps[$key] = $clientIp = substr($clientIp, 0, $i); + } + } elseif (0 === strpos($clientIp, '[')) { + // Strip brackets and :port from IPv6 addresses. + $i = strpos($clientIp, ']', 1); + $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1); } if (!filter_var($clientIp, FILTER_VALIDATE_IP)) { diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index 4e9fb8723c96b..3070aed610532 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -718,6 +718,7 @@ public function expire() { if ($this->isFresh()) { $this->headers->set('Age', $this->getMaxAge()); + $this->headers->remove('Expires'); } return $this; diff --git a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php index 1b9e58991cc6d..d21791f000512 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php @@ -208,6 +208,19 @@ public function provideFullFileRanges() ); } + public function testUnpreparedResponseSendsFullFile() + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200); + + $data = file_get_contents(__DIR__.'/File/Fixtures/test.gif'); + + $this->expectOutputString($data); + $response = clone $response; + $response->sendContent(); + + $this->assertEquals(200, $response->getStatusCode()); + } + /** * @dataProvider provideInvalidRanges */ diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 426ae43e41036..ff4dd67b30e8c 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -900,7 +900,7 @@ public function getClientIpsForwardedProvider() public function getClientIpsProvider() { - // $expected $remoteAddr $httpForwardedFor $trustedProxies + // $expected $remoteAddr $httpForwardedFor $trustedProxies return array( // simple IPv4 array(array('88.88.88.88'), '88.88.88.88', null, null), @@ -914,8 +914,8 @@ public function getClientIpsProvider() // forwarded for with remote IPv4 addr not trusted array(array('127.0.0.1'), '127.0.0.1', '88.88.88.88', null), - // forwarded for with remote IPv4 addr trusted - array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88', array('127.0.0.1')), + // forwarded for with remote IPv4 addr trusted + comma + array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88,', array('127.0.0.1')), // forwarded for with remote IPv4 and all FF addrs trusted array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88', array('127.0.0.1', '88.88.88.88')), // forwarded for with remote IPv4 range trusted @@ -1019,7 +1019,7 @@ public function testGetClientIpsWithAgreeingHeaders($httpForwarded, $httpXForwar 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor, ); - Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_ALL); + Request::setTrustedProxies(array('88.88.88.88'), -1); $request->initialize(array(), array(), array(), array(), array(), $server); @@ -2277,6 +2277,55 @@ public function testNonstandardRequests($requestUri, $queryString, $expectedPath $this->assertEquals($expectedBaseUrl, $request->getBaseUrl()); $this->assertEquals($expectedBasePath, $request->getBasePath()); } + + public function testTrustedHost() + { + Request::setTrustedProxies(array('1.1.1.1'), -1); + + $request = Request::create('/'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('Forwarded', 'host=localhost:8080'); + $request->headers->set('X-Forwarded-Host', 'localhost:8080'); + + $this->assertSame('localhost:8080', $request->getHttpHost()); + $this->assertSame(8080, $request->getPort()); + + $request = Request::create('/'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('Forwarded', 'host="[::1]:443"'); + $request->headers->set('X-Forwarded-Host', '[::1]:443'); + $request->headers->set('X-Forwarded-Port', 443); + + $this->assertSame('[::1]:443', $request->getHttpHost()); + $this->assertSame(443, $request->getPort()); + } + + public function testTrustedPort() + { + Request::setTrustedProxies(array('1.1.1.1'), -1); + + $request = Request::create('/'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('Forwarded', 'host=localhost:8080'); + $request->headers->set('X-Forwarded-Port', 8080); + + $this->assertSame(8080, $request->getPort()); + + $request = Request::create('/'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('Forwarded', 'host=localhost'); + $request->headers->set('X-Forwarded-Port', 80); + + $this->assertSame(80, $request->getPort()); + + $request = Request::create('/'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('Forwarded', 'host="[::1]"'); + $request->headers->set('X-Forwarded-Proto', 'https'); + $request->headers->set('X-Forwarded-Port', 443); + + $this->assertSame(443, $request->getPort()); + } } class RequestContentProxy extends Request diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php index 9444d54c37831..43fa9b70aa1e3 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php @@ -362,6 +362,11 @@ public function testExpire() $response->headers->set('Expires', -1); $response->expire(); $this->assertNull($response->headers->get('Age'), '->expire() does not set the Age when the response is expired'); + + $response = new Response(); + $response->headers->set('Expires', date(DATE_RFC2822, time() + 600)); + $response->expire(); + $this->assertNull($response->headers->get('Expires'), '->expire() removes the Expires header when the response is fresh'); } public function testGetTtl() diff --git a/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php b/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php index 789cb6ea6b328..9649072c16c6d 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php @@ -57,6 +57,7 @@ public static function handle(HttpKernelInterface $kernel, Request $request, $ty foreach ($trustedHeaders as $key => $name) { if ($trustedHeaderSet & $key) { $request->headers->remove($name); + $request->server->remove('HTTP_'.strtoupper(str_replace('-', '_', $name))); } } } @@ -76,13 +77,16 @@ public static function handle(HttpKernelInterface $kernel, Request $request, $ty // set trusted values, reusing as much as possible the global trusted settings if (Request::HEADER_FORWARDED & $trustedHeaderSet) { $trustedValues[0] .= sprintf(';host="%s";proto=%s', $request->getHttpHost(), $request->getScheme()); - $request->headers->set($trustedHeaders[Request::HEADER_FORWARDED], implode(', ', $trustedValues)); + $request->headers->set($name = $trustedHeaders[Request::HEADER_FORWARDED], $v = implode(', ', $trustedValues)); + $request->server->set('HTTP_'.strtoupper(str_replace('-', '_', $name)), $v); } if (Request::HEADER_X_FORWARDED_FOR & $trustedHeaderSet) { - $request->headers->set($trustedHeaders[Request::HEADER_X_FORWARDED_FOR], implode(', ', $trustedIps)); + $request->headers->set($name = $trustedHeaders[Request::HEADER_X_FORWARDED_FOR], $v = implode(', ', $trustedIps)); + $request->server->set('HTTP_'.strtoupper(str_replace('-', '_', $name)), $v); } elseif (!(Request::HEADER_FORWARDED & $trustedHeaderSet)) { Request::setTrustedProxies($trustedProxies, $trustedHeaderSet | Request::HEADER_X_FORWARDED_FOR); - $request->headers->set($trustedHeaders[Request::HEADER_X_FORWARDED_FOR], implode(', ', $trustedIps)); + $request->headers->set($name = $trustedHeaders[Request::HEADER_X_FORWARDED_FOR], $v = implode(', ', $trustedIps)); + $request->server->set('HTTP_'.strtoupper(str_replace('-', '_', $name)), $v); } // fix the client IP address by setting it to 127.0.0.1, diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 0684f22a03232..ac6471a8b93d9 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -67,11 +67,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '3.4.14'; - const VERSION_ID = 30414; + const VERSION = '3.4.15'; + const VERSION_ID = 30415; const MAJOR_VERSION = 3; const MINOR_VERSION = 4; - const RELEASE_VERSION = 14; + const RELEASE_VERSION = 15; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '11/2020'; diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php index c36ea5576001a..b3457b5d7a922 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -47,6 +47,8 @@ public function testRenderWithObjectsAsAttributes() $subRequest->attributes->replace(array('object' => $object, '_format' => 'html', '_controller' => 'main_controller', '_locale' => 'en')); $subRequest->headers->set('x-forwarded-for', array('127.0.0.1')); $subRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http')); + $subRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + $subRequest->server->set('HTTP_FORWARDED', 'for="127.0.0.1";host="localhost";proto=http'); $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($subRequest)); @@ -101,6 +103,7 @@ public function testRenderWithTrustedHeaderDisabled() $expectedSubRequest = Request::create('/'); $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); $this->assertSame('foo', $strategy->render('/', Request::create('/'))->getContent()); @@ -193,8 +196,10 @@ public function testESIHeaderIsKeptInSubrequest() if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) { $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); } $expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http')); + $expectedSubRequest->server->set('HTTP_FORWARDED', 'for="127.0.0.1";host="localhost";proto=http'); $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); @@ -217,6 +222,8 @@ public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest() $expectedSubRequest = Request::create('/'); $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); $expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + $expectedSubRequest->server->set('HTTP_FORWARDED', 'for="127.0.0.1";host="localhost";proto=http'); $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); $request = Request::create('/', 'GET', array(), array(), array(), array('HTTP_IF_MODIFIED_SINCE' => 'Fri, 01 Jan 2016 00:00:00 GMT', 'HTTP_IF_NONE_MATCH' => '*')); @@ -232,6 +239,8 @@ public function testFirstTrustedProxyIsSetAsRemote() $expectedSubRequest->server->set('REMOTE_ADDR', '127.0.0.1'); $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); $expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + $expectedSubRequest->server->set('HTTP_FORWARDED', 'for="127.0.0.1";host="localhost";proto=http'); $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); @@ -249,6 +258,8 @@ public function testIpAddressOfRangedTrustedProxyIsSetAsRemote() $expectedSubRequest->server->set('REMOTE_ADDR', '127.0.0.1'); $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); $expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + $expectedSubRequest->server->set('HTTP_FORWARDED', 'for="127.0.0.1";host="localhost";proto=http'); Request::setTrustedProxies(array('1.1.1.1/24'), -1); diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php index a65e8cb8f5000..e3b1500532376 100644 --- a/src/Symfony/Component/Lock/Store/RedisStore.php +++ b/src/Symfony/Component/Lock/Store/RedisStore.php @@ -54,8 +54,10 @@ public function save(Key $key) $script = ' if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("PEXPIRE", KEYS[1], ARGV[2]) + elseif redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) then + return 1 else - return redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) + return 0 end '; @@ -144,7 +146,7 @@ private function evaluate($script, $resource, array $args) return \call_user_func_array(array($this->redis, 'eval'), array_merge(array($script, 1, $resource), $args)); } - throw new InvalidArgumentException(sprintf('%s() expects been initialized with a Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, \is_object($this->redis) ? \get_class($this->redis) : \gettype($this->redis))); + throw new InvalidArgumentException(sprintf('%s() expects being initialized with a Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, \is_object($this->redis) ? \get_class($this->redis) : \gettype($this->redis))); } /** diff --git a/src/Symfony/Component/Lock/Tests/Store/RedisClusterStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/RedisClusterStoreTest.php new file mode 100644 index 0000000000000..2ee17888eb6d3 --- /dev/null +++ b/src/Symfony/Component/Lock/Tests/Store/RedisClusterStoreTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Lock\Tests\Store; + +/** + * @author Jérémy Derussé + * + * @requires extension redis + */ +class RedisClusterStoreTest extends AbstractRedisStoreTest +{ + public static function setupBeforeClass() + { + if (!class_exists('RedisCluster')) { + self::markTestSkipped('The RedisCluster class is required.'); + } + if (!getenv('REDIS_CLUSTER_HOSTS')) { + self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.'); + } + } + + protected function getRedisConnection() + { + return new \RedisCluster(null, explode(' ', getenv('REDIS_CLUSTER_HOSTS'))); + } +} diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php index 9174cac3bce0f..56e7fd992a6a1 100644 --- a/src/Symfony/Component/Process/Tests/ProcessTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessTest.php @@ -447,9 +447,6 @@ public function testExitCodeCommandFailed() $this->assertGreaterThan(0, $process->getExitCode()); } - /** - * @group tty - */ public function testTTYCommand() { if ('\\' === \DIRECTORY_SEPARATOR) { @@ -465,9 +462,6 @@ public function testTTYCommand() $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); } - /** - * @group tty - */ public function testTTYCommandExitCode() { if ('\\' === \DIRECTORY_SEPARATOR) { diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index b367d4f9ac2b9..b467d9a05f60a 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -256,7 +256,12 @@ public static function handleError($type, $message, $file, $line, $context = arr private static function throwInvalidArgumentException($message, $trace, $i) { - if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file'] && isset($trace[$i]['args'][0])) { + // the type mismatch is not caused by invalid arguments (but e.g. by an incompatible return type hint of the writer method) + if (0 !== strpos($message, 'Argument ')) { + return; + } + + if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file'] && array_key_exists(0, $trace[$i]['args'])) { $pos = strpos($message, $delim = 'must be of the type ') ?: (strpos($message, $delim = 'must be an instance of ') ?: strpos($message, $delim = 'must implement interface ')); $pos += \strlen($delim); $type = $trace[$i]['args'][0]; diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ReturnTyped.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ReturnTyped.php index b6a9852715d79..71c4a574c0ecf 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ReturnTyped.php +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ReturnTyped.php @@ -28,4 +28,9 @@ public function addFoo(\DateTime $dateTime) public function removeFoo(\DateTime $dateTime) { } + + public function setName($name): self + { + return 'This does not respect the return type on purpose.'; + } } diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index 29ce421851b8e..07e5e2fb52f6b 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -540,6 +540,17 @@ public function testThrowTypeError() $this->propertyAccessor->setValue($object, 'date', 'This is a string, \DateTime expected.'); } + /** + * @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException + * @expectedExceptionMessage Expected argument of type "DateTime", "NULL" given + */ + public function testThrowTypeErrorWithNullArgument() + { + $object = new TypeHinted(); + + $this->propertyAccessor->setValue($object, 'date', null); + } + public function testSetTypeHint() { $date = new \DateTime(); @@ -663,4 +674,16 @@ public function testDoNotDiscardReturnTypeError() $this->propertyAccessor->setValue($object, 'foos', array(new \DateTime())); } + + /** + * @requires PHP 7 + * + * @expectedException \TypeError + */ + public function testDoNotDiscardReturnTypeErrorWhenWriterMethodIsMisconfigured() + { + $object = new ReturnTyped(); + + $this->propertyAccessor->setValue($object, 'name', 'foo'); + } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php index 080df2893a877..6766b249a77d8 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php @@ -87,6 +87,8 @@ public function typesProvider() array('bal', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime')), null, null), array('parent', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')), null, null), array('collection', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))), null, null), + array('nestedCollection', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))), null, null), + array('mixedCollection', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, null, null)), null, null), array('a', array(new Type(Type::BUILTIN_TYPE_INT)), 'A.', null), array('b', array(new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')), 'B.', null), array('c', array(new Type(Type::BUILTIN_TYPE_BOOL, true)), null, null), @@ -126,6 +128,8 @@ public function typesWithCustomPrefixesProvider() array('bal', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime')), null, null), array('parent', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')), null, null), array('collection', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))), null, null), + array('nestedCollection', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))), null, null), + array('mixedCollection', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, null, null)), null, null), array('a', null, 'A.', null), array('b', null, 'B.', null), array('c', array(new Type(Type::BUILTIN_TYPE_BOOL, true)), null, null), @@ -164,6 +168,8 @@ public function typesWithNoPrefixesProvider() array('bal', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime')), null, null), array('parent', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')), null, null), array('collection', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))), null, null), + array('nestedCollection', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING, false)))), null, null), + array('mixedCollection', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, null, null)), null, null), array('a', null, 'A.', null), array('b', null, 'B.', null), array('c', null, null, null), diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index e7fddf473f37b..24ef06239be59 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -38,6 +38,8 @@ public function testGetProperties() 'bal', 'parent', 'collection', + 'nestedCollection', + 'mixedCollection', 'B', 'Guid', 'g', @@ -77,6 +79,8 @@ public function testGetPropertiesWithCustomPrefixes() 'bal', 'parent', 'collection', + 'nestedCollection', + 'mixedCollection', 'B', 'Guid', 'g', @@ -108,6 +112,8 @@ public function testGetPropertiesWithNoPrefixes() 'bal', 'parent', 'collection', + 'nestedCollection', + 'mixedCollection', 'B', 'Guid', 'g', diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php index 76c2e1042c604..5b82209e1d417 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php @@ -46,6 +46,16 @@ class Dummy extends ParentDummy */ public $collection; + /** + * @var string[][] + */ + public $nestedCollection; + + /** + * @var mixed[] + */ + public $mixedCollection; + /** * @var ParentDummy */ diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php index 46d25a67b74b8..5031c12fc3a17 100644 --- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php +++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php @@ -89,30 +89,28 @@ private function createType($docType, $nullable) { // Cannot guess if (!$docType || 'mixed' === $docType) { - return; + return null; } - if ($collection = '[]' === substr($docType, -2)) { - $docType = substr($docType, 0, -2); - } - - $docType = $this->normalizeType($docType); - list($phpType, $class) = $this->getPhpTypeAndClass($docType); - - $array = 'array' === $docType; - - if ($collection || $array) { - if ($array || 'mixed' === $docType) { + if ('[]' === substr($docType, -2)) { + if ('mixed[]' === $docType) { $collectionKeyType = null; $collectionValueType = null; } else { $collectionKeyType = new Type(Type::BUILTIN_TYPE_INT); - $collectionValueType = new Type($phpType, $nullable, $class); + $collectionValueType = $this->createType(substr($docType, 0, -2), $nullable); } return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType); } + $docType = $this->normalizeType($docType); + list($phpType, $class) = $this->getPhpTypeAndClass($docType); + + if ('array' === $docType) { + return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, null, null); + } + return new Type($phpType, $nullable, $class); } diff --git a/src/Symfony/Component/Security/Http/Firewall.php b/src/Symfony/Component/Security/Http/Firewall.php index 831316d9dfe0c..a0a2aa8411ca0 100644 --- a/src/Symfony/Component/Security/Http/Firewall.php +++ b/src/Symfony/Component/Security/Http/Firewall.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Http\Firewall\AccessListener; /** * Firewall uses a FirewallMap to register security listeners for the given @@ -58,11 +59,29 @@ public function onKernelRequest(GetResponseEvent $event) $exceptionListener->register($this->dispatcher); } - $this->handleRequest($event, $authenticationListeners); + $authenticationListeners = function () use ($authenticationListeners, $logoutListener) { + $accessListener = null; - if (null !== $logoutListener) { - $logoutListener->handle($event); - } + foreach ($authenticationListeners as $listener) { + if ($listener instanceof AccessListener) { + $accessListener = $listener; + + continue; + } + + yield $listener; + } + + if (null !== $logoutListener) { + yield $logoutListener; + } + + if (null !== $accessListener) { + yield $accessListener; + } + }; + + $this->handleRequest($event, $authenticationListeners()); } public function onKernelFinishRequest(FinishRequestEvent $event) diff --git a/src/Symfony/Component/Security/Http/Tests/FirewallTest.php b/src/Symfony/Component/Security/Http/Tests/FirewallTest.php index bd475bb4e5b1f..66dad46152c02 100644 --- a/src/Symfony/Component/Security/Http/Tests/FirewallTest.php +++ b/src/Symfony/Component/Security/Http/Tests/FirewallTest.php @@ -79,7 +79,7 @@ public function testOnKernelRequestStopsWhenThereIsAResponse() ->getMock() ; $event - ->expects($this->once()) + ->expects($this->at(0)) ->method('hasResponse') ->will($this->returnValue(true)) ; diff --git a/src/Symfony/Component/Security/README.md b/src/Symfony/Component/Security/README.md index b8bba12f20362..f55f8a10eab98 100644 --- a/src/Symfony/Component/Security/README.md +++ b/src/Symfony/Component/Security/README.md @@ -6,7 +6,7 @@ application. It ships with facilities for authenticating using HTTP basic or digest authentication, interactive form login or X.509 certificate login, but also allows you to implement your own authentication strategies. Furthermore, the component provides ways to authorize authenticated users based on their -roles, and it contains an advanced ACL system. +roles. Resources --------- diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf index 413a97eb17c6f..bee0ab3b55a8f 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf @@ -316,7 +316,7 @@ This is not a valid UUID. - Deze waarde is geen geldige UUID waarde. + Dit is geen geldige UUID. diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 284af1f9f932b..36c9f1d913702 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -549,11 +549,6 @@ private function getCurrentLineIndentation() private function getNextEmbedBlock($indentation = null, $inSequence = false) { $oldLineIndentation = $this->getCurrentLineIndentation(); - $blockScalarIndentations = array(); - - if ($this->isBlockScalarHeader()) { - $blockScalarIndentations[] = $oldLineIndentation; - } if (!$this->moveToNextLine()) { return; @@ -612,30 +607,9 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false) $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); - if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) { - $blockScalarIndentations[] = $this->getCurrentLineIndentation(); - } - - $previousLineIndentation = $this->getCurrentLineIndentation(); - while ($this->moveToNextLine()) { $indent = $this->getCurrentLineIndentation(); - // terminate all block scalars that are more indented than the current line - if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) { - foreach ($blockScalarIndentations as $key => $blockScalarIndentation) { - if ($blockScalarIndentation >= $indent) { - unset($blockScalarIndentations[$key]); - } - } - } - - if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) { - $blockScalarIndentations[] = $indent; - } - - $previousLineIndentation = $indent; - if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { $this->moveToPreviousLine(); break; @@ -1054,16 +1028,6 @@ private function isStringUnIndentedCollectionItem() return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- '); } - /** - * Tests whether or not the current line is the header of a block scalar. - * - * @return bool - */ - private function isBlockScalarHeader() - { - return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine); - } - /** * A local wrapper for `preg_match` which will throw a ParseException if there * is an internal error in the PCRE engine.