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 d4c5156a6d2a5..54b8a101f255d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ env: matrix: include: - - php: 7.1.3 + - php: 7.1 - php: 7.1 env: deps=high - php: 7.2 @@ -41,21 +41,30 @@ services: - mongodb - redis-server - rabbitmq + - 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() { + nanoseconds () { local cmd="date" local format="+%s%N" local os=$(uname) @@ -70,7 +79,7 @@ before_install: # tfold is a helper to create folded reports tfold () { - local title=$1 + local title="🐘 $PHP $1" local fold=$(echo $title | sed -r 's/[^-_A-Za-z0-9]+/./g') shift local id=$(printf %08x $(( RANDOM * RANDOM ))) @@ -90,17 +99,6 @@ before_install: } export -f tfold - # php.ini configuration - INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - phpenv config-rm xdebug.ini || echo "xdebug not available" - echo date.timezone = Europe/Paris >> $INI - echo memory_limit = -1 >> $INI - echo session.gc_probability = 0 >> $INI - echo opcache.enable_cli = 1 >> $INI - echo apc.enable_cli = 1 >> $INI - echo extension = redis.so >> $INI - echo extension = memcached.so >> $INI - # tpecl is a helper to compile and cache php extensions tpecl () { local ext_name=$1 @@ -112,6 +110,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 @@ -119,24 +118,34 @@ before_install: } export -f tpecl - # Matrix lines for intermediate PHP versions are skipped for pull requests - if [[ ! $deps && ! $PHP = $MIN_PHP && $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://php.net/get/php-$MIN_PHP.tar.bz2/from/this/mirror -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 + 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 + echo date.timezone = Europe/Paris >> $INI + echo memory_limit = -1 >> $INI + echo session.gc_probability = 0 >> $INI + echo opcache.enable_cli = 1 >> $INI + echo apc.enable_cli = 1 >> $INI + echo extension = redis.so >> $INI + echo extension = memcached.so >> $INI + done + - | # Install extra PHP extensions - if [[ ! $skip ]]; then + for PHP in $TRAVIS_PHP_VERSION $php_extra; do + export PHP=$PHP + phpenv global $PHP + INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini + # Install librabbitmq wget http://ftp.debian.org/debian/pool/main/libr/librabbitmq/librabbitmq-dev_0.5.2-2_amd64.deb wget http://ftp.debian.org/debian/pool/main/libr/librabbitmq/librabbitmq1_0.5.2-2_amd64.deb @@ -151,7 +160,7 @@ before_install: tfold ext.libsodium tpecl libsodium sodium.so $INI tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI tfold ext.amqp tpecl amqp-1.9.3 amqp.so $INI - fi + done - | # Load fixtures @@ -165,7 +174,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 && @@ -181,42 +190,57 @@ 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 [[ $deps ]]; then mv composer.json.phpunit composer.json; fi - if [[ ! $skip ]]; then - ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer require --dev --no-update mongodb/mongodb) - fi - - - if [[ ! $skip ]]; then $COMPOSER_UP; fi - - if [[ ! $skip ]]; then ./phpunit install; fi - php -i - | 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 + ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer require --dev --no-update mongodb/mongodb) + tfold 'composer update' $COMPOSER_UP + tfold 'phpunit install' ./phpunit install + 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 else echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}" - tfold tty-group $PHPUNIT --group tty - if [[ $PHP = $MIN_PHP ]]; then + tfold src/Symfony/Component/Console.tty $PHPUNIT src/Symfony/Component/Console --group tty + if [[ $PHP = ${MIN_PHP%.*} ]]; then + export PHP=$MIN_PHP tfold src/Symfony/Component/Process.sigchild SYMFONY_DEPRECATIONS_HELPER=weak php-$MIN_PHP/sapi/cli/php ./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-4.0.md b/CHANGELOG-4.0.md index 2afe811abce75..cba2e57482bbb 100644 --- a/CHANGELOG-4.0.md +++ b/CHANGELOG-4.0.md @@ -7,6 +7,16 @@ in 4.0 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.0.0...v4.0.1 +* 4.0.14 (2018-08-01) + + * security #cve-2018-14774 [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer (nicolas-grekas) + * security #cve-2018-14773 [HttpFoundation] Remove support for legacy and risky HTTP headers (nicolas-grekas) + * bug #28003 [HttpKernel] Fixes invalid REMOTE_ADDR in inline subrequest when configuring trusted proxy with subnet (netiul) + * bug #28007 [FrameworkBundle] fixed guard event names for transitions (destillat) + * bug #28045 [HttpFoundation] Fix Cookie::isCleared (ro0NL) + * bug #28080 [HttpFoundation] fixed using _method parameter with invalid type (Phobetor) + * bug #28052 [HttpKernel] Fix merging bindings for controllers' locators (nicolas-grekas) + * 4.0.13 (2018-07-23) * bug #28005 [HttpKernel] Fixed templateExists on parse error of the template name (yceruto) diff --git a/CHANGELOG-4.1.md b/CHANGELOG-4.1.md index fbde322ed215c..98aa4aca6a284 100644 --- a/CHANGELOG-4.1.md +++ b/CHANGELOG-4.1.md @@ -7,6 +7,30 @@ in 4.1 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/v4.1.0...v4.1.1 +* 4.1.4 (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 #28251 [HttpFoundation] Allow RedisCluster class for RedisSessionHandler (michaelperrin) + * 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 #28216 [FrameworkBundle] `message_bus` alias public (sroze) + * bug #28113 [Form] Add help texts for checkboxes in horizontal bootstrap 4 forms (apfelbox) + * bug #28100 [Security] Call AccessListener after LogoutListener (chalasr) + * bug #28174 Remove the HTML5 validation from the profiler URL search form (Soullivaneuh) + * bug #28159 [DI] Fix autowire inner service (hason) + * 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 #28152 [Translation] fix perf of lint:xliff command (nicolas-grekas) + * bug #28115 [Form] Remove extra .form-group wrapper around file widget in bootstrap 4 (MrMitch) + * bug #28120 [Routing] Fixed scheme redirecting for root path (twoleds) + * bug #28112 Fix CSS property typo (AhmedAbdulrahman) + * 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) + * 4.1.3 (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 a901ad79397c2..55b41de6b1a10 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=>=4.1" - SET ANSICON=121x90 (121x90) - REG ADD "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v DelayedExpansion /t REG_DWORD /d 1 /f @@ -40,9 +41,10 @@ install: - echo extension=php_curl.dll >> php.ini-max - copy /Y php.ini-max 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 update --no-progress --no-suggest --ansi diff --git a/composer.json b/composer.json index 67065a8d919ad..f861cbca31349 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^7.1.3", "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", @@ -90,7 +90,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 5de8d728db102..f36c2d647c563 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "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 134eefe940180..b7ba8b0d3d9b0 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php @@ -12,7 +12,7 @@ public function test() $this->markTestSkipped('This test cannot be run on Windows.'); } - 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 5e6bd1ab67983..81b2e52d5a940 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -130,7 +130,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/Bridge/Twig/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig index ca40981ec8524..7fcea4b0ecd25 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig @@ -81,6 +81,7 @@ col-sm-10
{#--#}
{{- form_widget(form) -}} + {{- form_help(form) -}} {{- form_errors(form) -}}
{#--#} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig index 8f790fdd19450..5030264c127bb 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig @@ -115,17 +115,15 @@ {%- endblock percent_widget %} {% block file_widget -%} -
- <{{ element|default('div') }} class="custom-file"> - {%- set type = type|default('file') -%} - {{- block('form_widget_simple') -}} - - -
+ <{{ element|default('div') }} class="custom-file"> + {%- set type = type|default('file') -%} + {{- block('form_widget_simple') -}} + + {% endblock %} {% block form_widget_simple -%} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php index 9d216e38194f2..588c9a422bc5c 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4HorizontalLayoutTest.php @@ -214,4 +214,24 @@ public function testCheckboxRow() $this->assertMatchesXpath($html, '/div[@class="form-group row"]/div[@class="col-sm-2" or @class="col-sm-10"]', 2); } + + public function testCheckboxRowWithHelp() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType'); + $view = $form->createView(); + $html = $this->renderRow($view, array('label' => 'foo', 'help' => 'really helpful text')); + + $this->assertMatchesXpath($html, +'/div + [@class="form-group row"] + [ + ./div[@class="col-sm-2" or @class="col-sm-10"] + /following-sibling::div[@class="col-sm-2" or @class="col-sm-10"] + [ + ./small[text() = "[trans]really helpful text[/trans]"] + ] + ] +' + ); + } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php index 1336405bdd30e..5710a2d486c00 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap4LayoutTest.php @@ -942,17 +942,13 @@ public function testFile() $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'n/a', 'attr' => array('class' => 'my&class form-control-file')), '/div - [@class="form-group"] + [@class="custom-file"] [ - ./div - [@class="custom-file"] - [ - ./input - [@type="file"] - [@name="name"] - /following-sibling::label - [@for="name"] - ] + ./input + [@type="file"] + [@name="name"] + /following-sibling::label + [@for="name"] ] ' ); @@ -964,17 +960,13 @@ public function testFileWithPlaceholder() $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'n/a', 'attr' => array('class' => 'my&class form-control-file', 'placeholder' => 'Custom Placeholder')), '/div - [@class="form-group"] + [@class="custom-file"] [ - ./div - [@class="custom-file"] - [ - ./input - [@type="file"] - [@name="name"] - /following-sibling::label - [@for="name" and text() = "[trans]Custom Placeholder[/trans]"] - ] + ./input + [@type="file"] + [@name="name"] + /following-sibling::label + [@for="name" and text() = "[trans]Custom Placeholder[/trans]"] ] ' ); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index b08f1bbde2068..19f62d8dc74e2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1488,7 +1488,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $container->register($busId, MessageBus::class)->addArgument(array())->addTag('messenger.bus'); if ($busId === $config['default_bus']) { - $container->setAlias('message_bus', $busId); + $container->setAlias('message_bus', $busId)->setPublic(true); $container->setAlias(MessageBusInterface::class, $busId); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 47a496ad247b6..b3dc2cc163f1e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -528,6 +528,7 @@ public function testMessenger() { $container = $this->createContainerFromFile('messenger'); $this->assertTrue($container->hasAlias('message_bus')); + $this->assertTrue($container->getAlias('message_bus')->isPublic()); $this->assertFalse($container->hasDefinition('messenger.transport.amqp.factory')); $this->assertTrue($container->hasDefinition('messenger.transport_factory')); $this->assertSame(TransportFactory::class, $container->getDefinition('messenger.transport_factory')->getClass()); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php index 1c4d1a7e7f512..bd3692bf2c7c1 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::$container->get('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 77d778a9de616..a0ce2da42a35b 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^7.1.3", "ext-xml": "*", - "symfony/security": "^4.1.1", + "symfony/security": "^4.1.4", "symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/http-kernel": "^4.1" }, diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/search.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/search.html.twig index 71059ed2352c7..7494b4ec7f279 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/search.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/search.html.twig @@ -22,7 +22,7 @@
- +
diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 426d6e8a5743a..ac6a56095a27a 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -52,7 +52,7 @@ function ($key, $value, $isHit) use ($defaultLifetime) { null, CacheItem::class ); - $getId = function ($key) { return $this->getId((string) $key); }; + $getId = \Closure::fromCallable(array($this, 'getId')); $this->mergeByLifetime = \Closure::bind( function ($deferred, $namespace, &$expiredIds) use ($getId) { $byLifetime = array(); @@ -60,6 +60,7 @@ function ($deferred, $namespace, &$expiredIds) use ($getId) { $expiredIds = array(); foreach ($deferred as $key => $item) { + $key = (string) $key; if (null === $item->expiry) { $byLifetime[0 < $item->defaultLifetime ? $item->defaultLifetime : 0][$getId($key)] = $item->value; } elseif ($item->expiry > $now) { diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index 62f815e0171ad..9110511b9aa7a 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -36,7 +36,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R private $knownTagVersions = array(); private $knownTagVersionsTtl; - public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, $knownTagVersionsTtl = 0.15) + public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15) { $this->pool = $itemsPool; $this->tags = $tagsPool ?: $itemsPool; 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/Config/Definition/BaseNode.php b/src/Symfony/Component/Config/Definition/BaseNode.php index f9cf829e4e7bd..61e75eb5895ba 100644 --- a/src/Symfony/Component/Config/Definition/BaseNode.php +++ b/src/Symfony/Component/Config/Definition/BaseNode.php @@ -499,7 +499,7 @@ private static function resolvePlaceholderValue($value) return self::$placeholders[$value]; } - if (0 === strpos($value, self::$placeholderUniquePrefix)) { + if (self::$placeholderUniquePrefix && 0 === strpos($value, self::$placeholderUniquePrefix)) { return array(); } } 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 22976bbb12694..27f2c21adb3ce 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(bool $onlyConstructorArguments = false) + public function __construct(bool $onlyConstructorArguments = false, bool $hasProxyDumper = true) { $this->onlyConstructorArguments = $onlyConstructorArguments; + $this->hasProxyDumper = $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/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 2d937f2840835..e858d14fa1c5a 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -144,11 +144,12 @@ private function doProcessValue($value, $isRoot = false) */ private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot): array { + $this->decoratedId = null; + $this->decoratedClass = null; + $this->getPreviousValue = null; + if ($isRoot && ($definition = $this->container->getDefinition($this->currentId)) && $this->container->has($this->decoratedId = $definition->innerServiceId)) { $this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass(); - } else { - $this->decoratedId = null; - $this->decoratedClass = null; } foreach ($this->methodCalls as $i => $call) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php index 31a7a2789e1a4..73a12026ece4b 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php @@ -58,7 +58,7 @@ private function getDefinitionId(string $id, ContainerBuilder $container): strin $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 = (string) $container->getAlias($id); diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 98da2596c51d2..9b012361a4b44 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -230,7 +230,7 @@ public function get($id, $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERE private function make(string $id, int $invalidBehavior) { 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 7255036809232..080b5de005f57 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -121,7 +121,6 @@ class ContainerBuilder extends Container implements TaggedContainerInterface private $autoconfiguredInstanceof = array(); private $removedIds = array(); - private $alreadyLoading = array(); private static $internalTypes = array( 'int' => true, @@ -555,20 +554,30 @@ 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) { 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 { @@ -585,16 +594,17 @@ private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_ throw new RuntimeException(reset($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; } /** @@ -1050,7 +1060,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]; @@ -1068,16 +1078,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; @@ -1089,19 +1097,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])) { @@ -1169,11 +1179,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]; @@ -1216,9 +1226,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) { @@ -1511,18 +1521,6 @@ protected function getEnv($name) } } - /** - * Retrieves the currently set proxy instantiator or instantiates one. - */ - private function getProxyInstantiator(): InstantiatorInterface - { - if (!$this->proxyInstantiator) { - $this->proxyInstantiator = new RealServiceInstantiator(); - } - - return $this->proxyInstantiator; - } - private function callMethod($service, $call, array &$inlineServices) { foreach (self::getServiceConditionals($call[1]) as $s) { @@ -1552,7 +1550,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 f9f9613f602cf..f37641030a4da 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; @@ -139,7 +140,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) { @@ -287,21 +301,18 @@ private function getProxyDumper(): ProxyDumper return $this->proxyDumper; } - private function addServiceLocalTempVariables(string $cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, \SplObjectStorage $allInlinedDefinitions): string + private function addServiceLocalTempVariables(string $cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, array $serviceCalls, bool $preInstance = false): string { - $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])) { @@ -310,23 +321,23 @@ private function addServiceLocalTempVariables(string $cId, Definition $definitio } $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 .= sprintf(<<<'EOTXT' if (isset($this->%s['%s'])) { @@ -422,23 +433,21 @@ private function generateProxyClasses() } } - private function addServiceInclude(string $cId, Definition $definition, \SplObjectStorage $inlinedDefinitions): string + private function addServiceInclude(string $cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, array $serviceCalls): string { $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()) @@ -471,24 +480,24 @@ private function addServiceInclude(string $cId, Definition $definition, \SplObje * @throws RuntimeException When the factory definition is incomplete * @throws ServiceCircularReferenceException When a circular reference is detected */ - private function addServiceInlinedDefinitions(string $id, Definition $definition, \SplObjectStorage $inlinedDefinitions, bool &$isSimpleInstance): string + private function addServiceInlinedDefinitions(string $id, Definition $definition, \SplObjectStorage $inlinedDefinitions, bool &$isSimpleInstance, bool $preInstance = false): string { $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 @@ -497,7 +506,7 @@ private function addServiceInlinedDefinitions(string $id, Definition $definition // $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); @@ -545,6 +554,7 @@ private function addServiceInstance(string $id, Definition $definition, string $ } $code = $this->addNewInstance($definition, $return, $instantiation, $id); + $this->referenceVariables[$id] = new Variable('instance'); if (!$isSimpleInstance) { $code .= "\n"; @@ -625,8 +635,6 @@ private function addServiceProperties(Definition $definition, $variableName = 'i */ private function addServiceInlinedDefinitionsSetup(string $id, Definition $definition, \SplObjectStorage $inlinedDefinitions, bool $isSimpleInstance): string { - $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)) { @@ -636,7 +644,7 @@ private function addServiceInlinedDefinitionsSetup(string $id, Definition $defin // 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]; @@ -753,6 +761,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])) { @@ -760,11 +769,14 @@ 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); + $code .= $this->addServiceInclude($id, $definition, $inlinedDefinitions, $serviceCalls); if ($this->getProxyDumper()->isProxyCandidate($definition)) { $factoryCode = $asFile ? "\$this->load('%s.php', false)" : '$this->%s(false)'; @@ -776,10 +788,12 @@ protected function {$methodName}($lazyInitialization) } $code .= - $this->addServiceLocalTempVariables($id, $definition, $constructorDefinitions, $inlinedDefinitions). - $this->addServiceInlinedDefinitions($id, $definition, $constructorDefinitions, $isSimpleInstance). + $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). @@ -1359,29 +1373,26 @@ private function getServiceConditionals($value): string return implode(' && ', $conditions); } - private function getServiceCallsFromArguments(array $arguments, array &$calls, bool $isPreInstance, string $callerId, array &$behavior = array(), int $step = 1) + private function getServiceCallsFromArguments(array $arguments, array &$calls, bool $preInstance, string $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 = (string) $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): \SplObjectStorage + private function getDefinitionsFromArguments(array $arguments, bool $isConstructorArgument = true, \SplObjectStorage $definitions = null): \SplObjectStorage { if (null === $definitions) { $definitions = new \SplObjectStorage(); @@ -1389,22 +1400,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; } } @@ -1430,7 +1442,7 @@ private function hasReference(string $id, array $arguments, bool $deep = false, 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/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index cbbd8867865fc..366583aba10d8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -834,6 +834,29 @@ public function testAutowireDecorator() $this->assertSame(Decorator::class.'.inner', (string) $definition->getArgument(1)); } + public function testAutowireDecoratorChain() + { + $container = new ContainerBuilder(); + $container->register(LoggerInterface::class, NullLogger::class); + $container->register(Decorated::class, Decorated::class); + $container + ->register(Decorator::class, Decorator::class) + ->setDecoratedService(Decorated::class) + ->setAutowired(true) + ; + $container + ->register(DecoratedDecorator::class, DecoratedDecorator::class) + ->setDecoratedService(Decorated::class) + ->setAutowired(true) + ; + + (new DecoratorServicePass())->process($container); + (new AutowirePass())->process($container); + + $definition = $container->getDefinition(DecoratedDecorator::class); + $this->assertSame(DecoratedDecorator::class.'.inner', (string) $definition->getArgument(0)); + } + public function testAutowireDecoratorRenamedId() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 7ac3a4bb40507..f6fa62fc31ecd 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1353,6 +1353,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 cf5a4b579b1fc..8d6d0781b1672 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -22,6 +22,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; @@ -570,50 +571,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() @@ -891,6 +864,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/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php index d806f294ddaa9..93eab8c50f9a7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -373,6 +373,13 @@ public function __construct(LoggerInterface $logger, DecoratorInterface $decorat } } +class DecoratedDecorator implements DecoratorInterface +{ + public function __construct(DecoratorInterface $decorator) + { + } +} + class NonAutowirableDecorator implements DecoratorInterface { public function __construct(LoggerInterface $logger, DecoratorInterface $decorated1, DecoratorInterface $decorated2) 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 4de6bfc233193..d6f1d6b118416 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 @@ -30,10 +30,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(); @@ -62,10 +68,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, ); } @@ -99,6 +111,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 = ($this->services['subscriber'] ?? $this->getSubscriberService()); + $b->logger = ($this->services['logger'] ?? $this->getLoggerService()); + + 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 = ($this->services['manager2'] ?? $this->getManager2Service()); + + $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. * @@ -140,7 +195,7 @@ protected function getFoo5Service() { $this->services['foo5'] = $instance = new \stdClass(); - $a = new \stdClass(($this->services['foo5'] ?? $this->getFoo5Service())); + $a = new \stdClass($instance); $a->foo = $instance; @@ -164,4 +219,72 @@ protected function getFoobar4Service() return $instance; } + + /** + * Gets the public 'logger' shared service. + * + * @return \stdClass + */ + protected function getLoggerService() + { + $a = ($this->services['connection'] ?? $this->getConnectionService()); + + if (isset($this->services['logger'])) { + return $this->services['logger']; + } + + $this->services['logger'] = $instance = new \stdClass($a); + + $instance->handler = new \stdClass(($this->services['manager'] ?? $this->getManagerService())); + + return $instance; + } + + /** + * Gets the public 'manager' shared service. + * + * @return \stdClass + */ + protected function getManagerService() + { + $a = ($this->services['connection'] ?? $this->getConnectionService()); + + 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 = ($this->services['connection2'] ?? $this->getConnection2Service()); + + 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 = ($this->services['manager'] ?? $this->getManagerService()); + + 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 79a0c11cc15c3..42c09d33f5328 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 @@ -31,6 +31,10 @@ public function __construct() 'bar' => 'getBarService', 'bar3' => 'getBar3Service', 'bar5' => 'getBar5Service', + 'connection' => 'getConnectionService', + 'connection2' => 'getConnection2Service', + 'dispatcher' => 'getDispatcherService', + 'dispatcher2' => 'getDispatcher2Service', 'foo' => 'getFooService', 'foo2' => 'getFoo2Service', 'foo4' => 'getFoo4Service', @@ -39,6 +43,10 @@ public function __construct() 'foobar2' => 'getFoobar2Service', 'foobar3' => 'getFoobar3Service', 'foobar4' => 'getFoobar4Service', + 'logger' => 'getLoggerService', + 'manager' => 'getManagerService', + 'manager2' => 'getManager2Service', + 'subscriber' => 'getSubscriberService', ); $this->aliases = array(); @@ -66,6 +74,10 @@ public function getRemovedIds() 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'bar2' => true, + 'config' => true, + 'config2' => true, + 'logger2' => true, + 'subscriber2' => true, ); } @@ -119,6 +131,81 @@ protected function getBar5Service() return $instance; } + /** + * Gets the public 'connection' shared service. + * + * @return \stdClass + */ + protected function getConnectionService() + { + $a = ($this->services['dispatcher'] ?? $this->getDispatcherService()); + + if (isset($this->services['connection'])) { + return $this->services['connection']; + } + + $b = new \stdClass(); + + $this->services['connection'] = $instance = new \stdClass($a, $b); + + $b->logger = ($this->services['logger'] ?? $this->getLoggerService()); + + return $instance; + } + + /** + * Gets the public 'connection2' shared service. + * + * @return \stdClass + */ + protected function getConnection2Service() + { + $a = ($this->services['dispatcher2'] ?? $this->getDispatcher2Service()); + + 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(($this->services['manager2'] ?? $this->getManager2Service())); + $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 = ($this->services['subscriber'] ?? $this->getSubscriberService()); + + 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(($this->services['manager2'] ?? $this->getManager2Service())); + + return $instance; + } + /** * Gets the public 'foo' shared service. * @@ -236,4 +323,72 @@ protected function getFoobar4Service() return $instance; } + + /** + * Gets the public 'logger' shared service. + * + * @return \stdClass + */ + protected function getLoggerService() + { + $a = ($this->services['connection'] ?? $this->getConnectionService()); + + if (isset($this->services['logger'])) { + return $this->services['logger']; + } + + $this->services['logger'] = $instance = new \stdClass($a); + + $instance->handler = new \stdClass(($this->services['manager'] ?? $this->getManagerService())); + + return $instance; + } + + /** + * Gets the public 'manager' shared service. + * + * @return \stdClass + */ + protected function getManagerService() + { + $a = ($this->services['connection'] ?? $this->getConnectionService()); + + 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 = ($this->services['connection2'] ?? $this->getConnection2Service()); + + 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 = ($this->services['manager'] ?? $this->getManagerService()); + + 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 0c069c351a7bb..be2b5f8562f2d 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -1467,6 +1467,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 ae0f69a0ae36d..34a415fcbe90f 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 4a0b5f6ffe3d5..ab5241737614a 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1941,10 +1941,16 @@ private function getTrustedValues($type, $ip = null) $forwardedValues = array(); $param = self::$forwardedParams[$type]; foreach ($parts as $subParts) { - $assoc = HeaderUtils::combine($subParts); - if (isset($assoc[$param])) { - $forwardedValues[] = self::HEADER_X_FORWARDED_PORT === $type ? substr_replace($assoc[$param], '0.0.0.0', 0, strrpos($assoc[$param], ':')) : $assoc[$param]; + if (null === $v = HeaderUtils::combine($subParts)[$param] ?? null) { + continue; } + if (self::HEADER_X_FORWARDED_PORT === $type) { + if (']' === substr($v, -1) || false === $v = strrchr($v, ':')) { + $v = $this->isSecure() ? ':443' : ':80'; + } + $v = '0.0.0.0'.$v; + } + $forwardedValues[] = $v; } } @@ -1985,7 +1991,7 @@ private function normalizeAndFilterClientIps(array $clientIps, $ip) if ($i) { $clientIps[$key] = $clientIp = substr($clientIp, 0, $i); } - } elseif ('[' == $clientIp[0]) { + } elseif (0 === strpos($clientIp, '[')) { // Strip brackets and :port from IPv6 addresses. $i = strpos($clientIp, ']', 1); $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1); diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index 07ef41339ca00..7f6ae7cd7a0f3 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -689,6 +689,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/Session/Storage/Handler/RedisSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php index 05983623955cd..36adf2424d618 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php @@ -33,14 +33,20 @@ class RedisSessionHandler extends AbstractSessionHandler * List of available options: * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server. * - * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redis - * @param array $options An associative array of options + * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client|RedisProxy $redis + * @param array $options An associative array of options * * @throws \InvalidArgumentException When unsupported client or options are passed */ public function __construct($redis, array $options = array()) { - if (!$redis instanceof \Redis && !$redis instanceof \RedisArray && !$redis instanceof \Predis\Client && !$redis instanceof RedisProxy) { + if ( + !$redis instanceof \Redis && + !$redis instanceof \RedisArray && + !$redis instanceof \RedisCluster && + !$redis instanceof \Predis\Client && + !$redis instanceof RedisProxy + ) { throw new \InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, \is_object($redis) ? \get_class($redis) : \gettype($redis))); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php index ce47157fe10dc..a3253d18ebdb8 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 88c4021039477..52392adde40e0 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -906,7 +906,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), @@ -920,8 +920,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 @@ -1025,7 +1025,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); @@ -2153,6 +2153,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 29cf71240dc04..f9b5ef4f2f0f7 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/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php index bcf622e6db17a..a2bf168de444f 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php @@ -32,11 +32,6 @@ abstract class AbstractRedisSessionHandlerTestCase extends TestCase */ protected $redisClient; - /** - * @var \Redis - */ - protected $validator; - /** * @return \Redis|\RedisArray|\RedisCluster|\Predis\Client */ @@ -52,9 +47,6 @@ protected function setUp() $host = getenv('REDIS_HOST') ?: 'localhost'; - $this->validator = new \Redis(); - $this->validator->connect($host); - $this->redisClient = $this->createRedisClient($host); $this->storage = new RedisSessionHandler( $this->redisClient, @@ -82,8 +74,8 @@ public function testCloseSession() public function testReadSession() { - $this->setFixture(self::PREFIX.'id1', null); - $this->setFixture(self::PREFIX.'id2', 'abc123'); + $this->redisClient->set(self::PREFIX.'id1', null); + $this->redisClient->set(self::PREFIX.'id2', 'abc123'); $this->assertEquals('', $this->storage->read('id1')); $this->assertEquals('abc123', $this->storage->read('id2')); @@ -93,14 +85,14 @@ public function testWriteSession() { $this->assertTrue($this->storage->write('id', 'data')); - $this->assertTrue($this->hasFixture(self::PREFIX.'id')); - $this->assertEquals('data', $this->getFixture(self::PREFIX.'id')); + $this->assertTrue((bool) $this->redisClient->exists(self::PREFIX.'id')); + $this->assertEquals('data', $this->redisClient->get(self::PREFIX.'id')); } public function testUseSessionGcMaxLifetimeAsTimeToLive() { $this->storage->write('id', 'data'); - $ttl = $this->fixtureTtl(self::PREFIX.'id'); + $ttl = $this->redisClient->ttl(self::PREFIX.'id'); $this->assertLessThanOrEqual(ini_get('session.gc_maxlifetime'), $ttl); $this->assertGreaterThanOrEqual(0, $ttl); @@ -108,11 +100,11 @@ public function testUseSessionGcMaxLifetimeAsTimeToLive() public function testDestroySession() { - $this->setFixture(self::PREFIX.'id', 'foo'); + $this->redisClient->set(self::PREFIX.'id', 'foo'); - $this->assertTrue($this->hasFixture(self::PREFIX.'id')); + $this->assertTrue((bool) $this->redisClient->exists(self::PREFIX.'id')); $this->assertTrue($this->storage->destroy('id')); - $this->assertFalse($this->hasFixture(self::PREFIX.'id')); + $this->assertFalse((bool) $this->redisClient->exists(self::PREFIX.'id')); } public function testGcSession() @@ -122,12 +114,12 @@ public function testGcSession() public function testUpdateTimestamp() { - $lowTTL = 10; + $lowTtl = 10; - $this->setFixture(self::PREFIX.'id', 'foo', $lowTTL); + $this->redisClient->setex(self::PREFIX.'id', $lowTtl, 'foo'); $this->storage->updateTimestamp('id', array()); - $this->assertGreaterThan($lowTTL, $this->fixtureTtl(self::PREFIX.'id')); + $this->assertGreaterThan($lowTtl, $this->redisClient->ttl(self::PREFIX.'id')); } /** @@ -150,28 +142,4 @@ public function getOptionFixtures(): array array(array('prefix' => 'sfs', 'foo' => 'bar'), false), ); } - - protected function setFixture($key, $value, $ttl = null) - { - if (null !== $ttl) { - $this->validator->setex($key, $ttl, $value); - } else { - $this->validator->set($key, $value); - } - } - - protected function getFixture($key) - { - return $this->validator->get($key); - } - - protected function hasFixture($key): bool - { - return $this->validator->exists($key); - } - - protected function fixtureTtl($key): int - { - return $this->validator->ttl($key); - } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php index ffb2d41a536ce..458100101c4ab 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php @@ -17,6 +17,6 @@ class PredisClusterSessionHandlerTest extends AbstractRedisSessionHandlerTestCas { protected function createRedisClient(string $host): Client { - return new Client(array(array('host' => $host))); + return new Client(array(array('host' => $host))); } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php new file mode 100644 index 0000000000000..7d85a59ee7739 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/RedisClusterSessionHandlerTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +class RedisClusterSessionHandlerTest extends AbstractRedisSessionHandlerTestCase +{ + 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.'); + } + } + + protected function createRedisClient(string $host): \RedisCluster + { + return new \RedisCluster(null, explode(' ', getenv('REDIS_CLUSTER_HOSTS'))); + } +} diff --git a/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php b/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php index 8c57356ac3d2f..84d7a84e7995f 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/SubRequestHandler.php @@ -41,6 +41,7 @@ public static function handle(HttpKernelInterface $kernel, Request $request, $ty ); foreach (array_filter($trustedHeaders) as $name => $key) { $request->headers->remove($name); + $request->server->remove('HTTP_'.$name); } } @@ -59,13 +60,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('Forwarded', implode(', ', $trustedValues)); + $request->headers->set('Forwarded', $v = implode(', ', $trustedValues)); + $request->server->set('HTTP_FORWARDED', $v); } if (Request::HEADER_X_FORWARDED_FOR & $trustedHeaderSet) { - $request->headers->set('X-Forwarded-For', implode(', ', $trustedIps)); + $request->headers->set('X-Forwarded-For', $v = implode(', ', $trustedIps)); + $request->server->set('HTTP_X_FORWARDED_FOR', $v); } elseif (!(Request::HEADER_FORWARDED & $trustedHeaderSet)) { Request::setTrustedProxies($trustedProxies, $trustedHeaderSet | Request::HEADER_X_FORWARDED_FOR); - $request->headers->set('X-Forwarded-For', implode(', ', $trustedIps)); + $request->headers->set('X-Forwarded-For', $v = implode(', ', $trustedIps)); + $request->server->set('HTTP_X_FORWARDED_FOR', $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 21ea5b06b1deb..9404cc0fba7d9 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -63,11 +63,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '4.1.3'; - const VERSION_ID = 40103; + const VERSION = '4.1.4'; + const VERSION_ID = 40104; const MAJOR_VERSION = 4; const MINOR_VERSION = 1; - const RELEASE_VERSION = 3; + const RELEASE_VERSION = 4; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '01/2019'; diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php index 91552a1b54c8d..f633a4bf777c0 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -45,6 +45,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)); @@ -57,6 +59,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()); @@ -149,8 +152,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)); @@ -173,6 +178,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' => '*')); @@ -188,6 +195,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)); @@ -205,6 +214,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 6d7958beefb92..2aeab40fb307c 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 '; @@ -140,7 +142,7 @@ private function evaluate(string $script, string $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/Messenger/Tests/Asynchronous/Transport/Serialization/SerializerConfigurationTest.php b/src/Symfony/Component/Messenger/Tests/Asynchronous/Transport/Serialization/SerializerConfigurationTest.php index 6ebcc8ddfa0ca..a5faf45b13f61 100644 --- a/src/Symfony/Component/Messenger/Tests/Asynchronous/Transport/Serialization/SerializerConfigurationTest.php +++ b/src/Symfony/Component/Messenger/Tests/Asynchronous/Transport/Serialization/SerializerConfigurationTest.php @@ -20,7 +20,7 @@ */ class SerializerConfigurationTest extends TestCase { - public function testSerialiazable() + public function testSerializable() { $config = new SerializerConfiguration(array(ObjectNormalizer::GROUPS => array('Default', 'Extra'))); diff --git a/src/Symfony/Component/Messenger/Tests/Middleware/Configuration/ValidationConfigurationTest.php b/src/Symfony/Component/Messenger/Tests/Middleware/Configuration/ValidationConfigurationTest.php index 6fd6962994f4f..b4ccf614b23b6 100644 --- a/src/Symfony/Component/Messenger/Tests/Middleware/Configuration/ValidationConfigurationTest.php +++ b/src/Symfony/Component/Messenger/Tests/Middleware/Configuration/ValidationConfigurationTest.php @@ -29,7 +29,7 @@ public function testConfig() $this->assertSame($groups, $config->getGroups()); } - public function testSerialiazable() + public function testSerializable() { $this->assertTrue(is_subclass_of(ValidationConfiguration::class, \Serializable::class, true)); $this->assertEquals($config = new ValidationConfiguration(array('Default', 'Extra')), unserialize(serialize($config))); diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php index 868cbd4d5c98e..0ed63fd3900fe 100644 --- a/src/Symfony/Component/Process/Tests/ProcessTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessTest.php @@ -450,9 +450,6 @@ public function testExitCodeCommandFailed() $this->assertGreaterThan(0, $process->getExitCode()); } - /** - * @group tty - */ public function testTTYCommand() { if ('\\' === \DIRECTORY_SEPARATOR) { @@ -468,9 +465,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 5987a36383f85..08d79743cdd07 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -160,7 +160,12 @@ public function setValue(&$objectOrArray, $propertyPath, $value) 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 new file mode 100644 index 0000000000000..71c4a574c0ecf --- /dev/null +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ReturnTyped.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Tests\Fixtures; + +/** + * @author Kévin Dunglas + */ +class ReturnTyped +{ + public function getFoos(): array + { + return 'It doesn\'t respect the return type on purpose'; + } + + 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/Fixtures/TestClassTypeErrorInsideCall.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassTypeErrorInsideCall.php new file mode 100644 index 0000000000000..44a93900fae34 --- /dev/null +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassTypeErrorInsideCall.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Tests\Fixtures; + +class TestClassTypeErrorInsideCall +{ + public function expectsDateTime(\DateTime $date) + { + } + + public function getProperty() + { + } + + public function setProperty($property) + { + $this->expectsDateTime(null); // throws TypeError + } +} diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index 5ac744d645b39..15932147c2891 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -15,11 +15,13 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassSetValue; +use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassTypeErrorInsideCall; use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object; use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted; @@ -538,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(); @@ -632,4 +645,34 @@ public function setFoo($foo) return $obj; } + + /** + * @expectedException \TypeError + */ + public function testThrowTypeErrorInsideSetterCall() + { + $object = new TestClassTypeErrorInsideCall(); + + $this->propertyAccessor->setValue($object, 'property', 'foo'); + } + + /** + * @expectedException \TypeError + */ + public function testDoNotDiscardReturnTypeError() + { + $object = new ReturnTyped(); + + $this->propertyAccessor->setValue($object, 'foos', array(new \DateTime())); + } + + /** + * @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 4d188cb4bf265..f75ae90b9fd50 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 afddf47395063..5b73c9772638c 100644 --- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php +++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php @@ -87,27 +87,25 @@ private function createType(string $docType, bool $nullable): ?Type 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/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index a1052195a0cc8..14e5cc341e807 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -194,7 +194,7 @@ private function compileRoutes(RouteCollection $routes, bool $matchHost): string } // used to display the Welcome Page in apps that don't define a homepage - $code .= " if ('/' === \$pathinfo && !\$allow) {\n"; + $code .= " if ('/' === \$pathinfo && !\$allow && !\$allowSchemes) {\n"; $code .= " throw new Symfony\Component\Routing\Exception\NoConfigurationException();\n"; $code .= " }\n"; diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php index 0a56bf1735a13..e7c0765003b2f 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php @@ -26,7 +26,7 @@ public function match($rawPathinfo) $canonicalMethod = 'GET'; } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php index c91066a46d281..68e7741b52189 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -238,7 +238,7 @@ public function match($rawPathinfo) $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php index 81ded3417cdd3..2f5cc3fc5aa5a 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php @@ -2821,7 +2821,7 @@ public function match($rawPathinfo) $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php index c0bbc582d4dd2..173cc15279f2b 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php @@ -142,7 +142,7 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php index 02f6ac949eb20..eba4c8ace32a2 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php @@ -91,7 +91,7 @@ public function match($rawPathinfo) $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php index b4457d7cdc7e7..5cda7753dac61 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php @@ -60,7 +60,7 @@ public function match($rawPathinfo) $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php index a711dbaf61263..5aec5db0179ad 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -275,7 +275,7 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php index df4e2208c6392..6f1c45aa9e8d1 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php @@ -103,7 +103,7 @@ public function match($rawPathinfo) $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php index 795feaf531bbb..418f8e0b14ab5 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php @@ -75,7 +75,7 @@ public function match($rawPathinfo) return $ret; } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php index f3f1fc167142b..976a25fbee621 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php @@ -145,7 +145,7 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php index 784c0fcafa6a4..9cebf2d929d00 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php @@ -122,7 +122,7 @@ public function match($rawPathinfo) $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php index caf04cc3776e7..b2b707213555d 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php @@ -157,7 +157,7 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php index 0f2ce53fed0b9..38bbfa2249379 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php @@ -79,7 +79,7 @@ public function match($rawPathinfo) $offset += strlen($m); } } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php index d9035392b272f..cfcd1d440c9a7 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php @@ -44,7 +44,7 @@ public function match($rawPathinfo) break; } - if ('/' === $pathinfo && !$allow) { + if ('/' === $pathinfo && !$allow && !$allowSchemes) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php index 0ae5ff1be9cd9..e36796224b451 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -93,6 +93,20 @@ public function testSchemeRedirectWithParams() $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'redirect' => 'value'), $matcher->match('/foo/baz')); } + public function testSchemeRedirectForRoot() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/', array(), array(), array(), '', array('https'))); + + $matcher = $this->getUrlMatcher($coll); + $matcher + ->expects($this->once()) + ->method('redirect') + ->with('/', 'foo', 'https') + ->will($this->returnValue(array('redirect' => 'value'))); + $this->assertEquals(array('_route' => 'foo', 'redirect' => 'value'), $matcher->match('/')); + } + public function testSlashRedirectWithParams() { $coll = new RouteCollection(); 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/Translation/Command/XliffLintCommand.php b/src/Symfony/Component/Translation/Command/XliffLintCommand.php index 7979e51da1a4d..378788f5b8b9c 100644 --- a/src/Symfony/Component/Translation/Command/XliffLintCommand.php +++ b/src/Symfony/Component/Translation/Command/XliffLintCommand.php @@ -130,10 +130,10 @@ private function validate($content, $file = null) $document->schemaValidate(__DIR__.'/../Resources/schemas/xliff-core-1.2-strict.xsd'); foreach (libxml_get_errors() as $xmlError) { $errors[] = array( - 'line' => $xmlError->line, - 'column' => $xmlError->column, - 'message' => trim($xmlError->message), - ); + 'line' => $xmlError->line, + 'column' => $xmlError->column, + 'message' => trim($xmlError->message), + ); } libxml_clear_errors(); diff --git a/src/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd b/src/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd index dface628ce3e4..4102d6472d1f6 100644 --- a/src/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd +++ b/src/Symfony/Component/Translation/Resources/schemas/xliff-core-1.2-strict.xsd @@ -30,7 +30,7 @@ Jan-10-2006 --> - + 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/VarDumper/Resources/css/htmlDescriptor.css b/src/Symfony/Component/VarDumper/Resources/css/htmlDescriptor.css index 4277c6809bc32..65da8137fc156 100644 --- a/src/Symfony/Component/VarDumper/Resources/css/htmlDescriptor.css +++ b/src/Symfony/Component/VarDumper/Resources/css/htmlDescriptor.css @@ -102,7 +102,7 @@ pre.sf-dump { margin-bottom: 0; } .hidden { - display: none; !important + display: none !important; } .dumped-tag > .sf-dump { display: inline-block; diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 35e7443d13163..75ba138b2b737 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -499,11 +499,6 @@ private function getCurrentLineIndentation(): int private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): ?string { $oldLineIndentation = $this->getCurrentLineIndentation(); - $blockScalarIndentations = array(); - - if ($this->isBlockScalarHeader()) { - $blockScalarIndentations[] = $oldLineIndentation; - } if (!$this->moveToNextLine()) { return null; @@ -562,30 +557,9 @@ private function getNextEmbedBlock(int $indentation = null, bool $inSequence = f $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; @@ -1002,16 +976,6 @@ private function isStringUnIndentedCollectionItem(): bool 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(): bool - { - 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.