diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index cb9b8a69c1638..1bad363eca810 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ | Q | A | ------------- | --- -| Branch? | 3.4 or master / 2.7, 2.8, 3.2 or 3.3 +| Branch? | 3.4 or master / 2.7, 2.8 or 3.3 | Bug fix? | yes/no | New feature? | yes/no | BC breaks? | yes/no diff --git a/.travis.yml b/.travis.yml index af674057da4b8..b7ce58bd8ffbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,15 +51,38 @@ before_install: export PHPUNIT_X="$PHPUNIT --exclude-group tty,benchmark,intl-data" export COMPOSER_UP='composer update --no-progress --no-suggest --ansi' + nanoseconds() { + local cmd="date" + local format="+%s%N" + local os=$(uname) + if hash gdate > /dev/null 2>&1; then + cmd="gdate" + elif [[ "$os" = Darwin ]]; then + format="+%s000000000" + fi + $cmd -u $format + } + export -f nanoseconds + # tfold is a helper to create folded reports tfold () { - title=$1 - fold=$(echo $title | sed -r 's/[^-_A-Za-z\d]+/./g') + local title=$1 + local fold=$(echo $title | sed -r 's/[^-_A-Za-z0-9]+/./g') shift - echo -e "travis_fold:start:$fold\\n\\e[1;34m$title\\e[0m" - bash -xc "$*" 2>&1 && + local id=$(printf %08x $(( RANDOM * RANDOM ))) + local start=$(nanoseconds) + echo -e "travis_fold:start:$fold" + echo -e "travis_time:start:$id" + echo -e "\\e[1;34m$title\\e[0m" + + bash -xc "$*" 2>&1 + local ok=$? + local end=$(nanoseconds) + echo -e "\\ntravis_time:end:$id:start=$start,finish=$end,duration=$(($end-$start))" + (exit $ok) && echo -e "\\e[32mOK\\e[0m $title\\n\\ntravis_fold:end:$fold" || - ( echo -e "\\e[41mKO\\e[0m $title\\n" && exit 1 ) + echo -e "\\e[41mKO\\e[0m $title\\n" + (exit $ok) } export -f tfold diff --git a/CHANGELOG-2.7.md b/CHANGELOG-2.7.md index d08766327bc5e..f742ca8ea0385 100644 --- a/CHANGELOG-2.7.md +++ b/CHANGELOG-2.7.md @@ -7,6 +7,19 @@ in 2.7 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.7.0...v2.7.1 +* 2.7.34 (2017-08-28) + + * bug #23989 [Debug] Remove false-positive check in DebugClassLoader (nicolas-grekas) + * bug #23982 [VarDumper] Strengthen dumped JS (nicolas-grekas) + * bug #23925 [Validator] Fix use of GroupSequenceProvider in child classes (linniksa) + * bug #23945 [Validator] Fix Greek translation (azhurb) + * bug #23909 [Console] Initialize lazily to render exceptions properly (nicolas-grekas) + * bug #23856 [DI] Fix dumping abstract with YamlDumper (nicolas-grekas) + * bug #23752 Ignore memcached missing key error on session destroy (jderusse) + * bug #23658 [HttpFoundation] Generate safe fallback filename for wrongly encoded filename (xelaris) + * bug #23783 Avoid infinite loops when profiler data is malformed (javiereguiluz) + * bug #23729 [Bridge\ProxyManager] Dont call __destruct() on non-instantiated services (nicolas-grekas) + * 2.7.33 (2017-08-01) * bug #22244 [Console] Fix passing options with defaultCommand (Jakub Sacha) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 1b059f5012cb7..ced9b18126294 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -7,8 +7,8 @@ Symfony is the result of the work of many people who made the code better - Fabien Potencier (fabpot) - Nicolas Grekas (nicolas-grekas) - Bernhard Schussek (bschussek) - - Tobias Schultze (tobion) - Christian Flothmann (xabbuh) + - Tobias Schultze (tobion) - Christophe Coevoet (stof) - Jordi Boggiano (seldaek) - Victor Berchet (victor) @@ -37,8 +37,8 @@ Symfony is the result of the work of many people who made the code better - Roland Franssen (ro0) - Eriksen Costa (eriksencosta) - Jules Pietri (heah) - - Sarah Khalil (saro0h) - Guilhem Niot (energetick) + - Sarah Khalil (saro0h) - Jonathan Wage (jwage) - Diego Saint Esteben (dosten) - Alexandre Salomé (alexandresalome) @@ -65,20 +65,20 @@ Symfony is the result of the work of many people who made the code better - Michel Weimerskirch (mweimerskirch) - Eric Clemmons (ericclemmons) - Charles Sarrazin (csarrazi) - - Christian Raue - Konstantin Myakshin (koc) + - Christian Raue + - Dany Maillard (maidmaid) - Arnout Boks (aboks) + - Jérémy DERUSSÉ (jderusse) - Deni - Henrik Westphal (snc) - Dariusz Górecki (canni) - Jáchym Toušek (enumag) - Titouan Galopin (tgalopin) - Douglas Greenshields (shieldo) - - Dany Maillard (maidmaid) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) - - Jérémy DERUSSÉ (jderusse) - Graham Campbell (graham) - Daniel Holmes (dholmes) - Toni Uebernickel (havvg) @@ -87,24 +87,24 @@ Symfony is the result of the work of many people who made the code better - Jérôme Tamarelle (gromnan) - John Wards (johnwards) - Dariusz Ruminski + - Alexander M. Turek (derrabus) - Fran Moreno (franmomu) - Antoine Hérault (herzult) + - Tobias Nyholm (tobias) - Paráda József (paradajozsef) - Issei Murasawa (issei_m) - Arnaud Le Blanc (arnaud-lb) - Maxime STEINHAUSSER - - Alexander M. Turek (derrabus) - Michal Piotrowski (eventhorizon) - - Tim Nagel (merk) - Yonel Ceruto González (yonelceruto) + - Tim Nagel (merk) - Brice BERNARD (brikou) - Baptiste Clavié (talus) - Vladimir Reznichenko (kalessil) - marc.weistroff - lenar - - Tobias Nyholm (tobias) - - Włodzimierz Gajda (gajdaw) - Alexander Schwenn (xelaris) + - Włodzimierz Gajda (gajdaw) - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) - Colin Frei @@ -152,7 +152,9 @@ Symfony is the result of the work of many people who made the code better - Rouven Weßling (realityking) - Teoh Han Hui (teohhanhui) - Clemens Tolboom + - Oleg Voronkovich - Helmer Aaviksoo + - Lars Strojny (lstrojny) - Hiromi Hishida (77web) - Matthieu Ouellette-Vachon (maoueh) - Michał Pipa (michal.pipa) @@ -166,7 +168,6 @@ Symfony is the result of the work of many people who made the code better - Warnar Boekkooi (boekkooi) - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) - - Lars Strojny (lstrojny) - Possum - Dorian Villet (gnutix) - Richard Miller (mr_r_miller) @@ -183,7 +184,6 @@ Symfony is the result of the work of many people who made the code better - Daniel Espendiller - sun (sun) - Larry Garfield (crell) - - Oleg Voronkovich - Martin Schuhfuß (usefulthink) - apetitpa - Matthieu Bontemps (mbontemps) @@ -334,9 +334,11 @@ Symfony is the result of the work of many people who made the code better - Damien Alexandre (damienalexandre) - Felix Labrecque - Yaroslav Kiliba + - Amrouche Hamza - Terje Bråten - Robbert Klarenbeek (robbertkl) - Thomas Calvet (fancyweb) + - Valentin Udaltsov (vudaltsov) - Niels Keurentjes (curry684) - JhonnyL - David Badura (davidbadura) @@ -387,6 +389,7 @@ Symfony is the result of the work of many people who made the code better - Karel Souffriau - Christophe L. (christophelau) - Anthon Pang (robocoder) + - Jérôme Parmentier (lctrs) - Emanuele Gaspari (inmarelibero) - Sébastien Santoro (dereckson) - Brian King @@ -421,7 +424,6 @@ Symfony is the result of the work of many people who made the code better - Dirk Pahl (dirkaholic) - cedric lombardot (cedriclombardot) - Jonas Flodén (flojon) - - Amrouche Hamza - Marcin Sikoń (marphi) - Dominik Zogg (dominik.zogg) - Marek Pietrzak @@ -432,6 +434,7 @@ Symfony is the result of the work of many people who made the code better - Zander Baldwin - Adam Harvey - Maxime Veber (nek-) + - Sanpi - Alex Bakhturin - Alexander Obuhovich (aik099) - boombatower @@ -512,6 +515,7 @@ Symfony is the result of the work of many people who made the code better - Dave Hulbert (dave1010) - Ivan Rey (ivanrey) - Marcin Chyłek (songoq) + - Ben Scott - Ned Schwartz - Ziumin - Jeremy Benoist @@ -520,7 +524,6 @@ Symfony is the result of the work of many people who made the code better - Benjamin Laugueux (yzalis) - Zach Badgett (zachbadgett) - Aurélien Fredouelle - - Jérôme Parmentier (lctrs) - Pavel Campr (pcampr) - Johnny Robeson (johnny) - Disquedur @@ -592,7 +595,6 @@ Symfony is the result of the work of many people who made the code better - Ulumuddin Yunus (joenoez) - Luc Vieillescazes (iamluc) - Johann Saunier (prophet777) - - Valentin Udaltsov (vudaltsov) - Michael Devery (mickadoo) - Antoine Corcy - Artur Eshenbrener @@ -671,9 +673,11 @@ Symfony is the result of the work of many people who made the code better - Andrew Hilobok (hilobok) - Noah Heck (myesain) - Christian Soronellas (theunic) + - Adam Szaraniec (mimol) - Yosmany Garcia (yosmanyga) - Wouter de Wild - Degory Valentine + - izzyp - Benoit Lévêque (benoit_leveque) - Jeroen Fiege (fieg) - Krzysiek Łabuś @@ -709,7 +713,6 @@ Symfony is the result of the work of many people who made the code better - Pierre Vanliefland (pvanliefland) - Sofiane HADDAG (sofhad) - frost-nzcr4 - - Sanpi - Abhoryo - Fabian Vogler (fabian) - Korvin Szanto @@ -742,7 +745,6 @@ Symfony is the result of the work of many people who made the code better - Omar Yepez (oyepez003) - mwsaz - Jelle Kapitein - - Ben Scott - Benoît Bourgeois - mantulo - corphi @@ -877,6 +879,7 @@ Symfony is the result of the work of many people who made the code better - Boris Vujicic (boris.vujicic) - Max Beutel - Antanas Arvasevicius + - Maximilian Berghoff (electricmaxxx) - nacho - Piotr Antosik (antek88) - Artem Lopata @@ -910,6 +913,7 @@ Symfony is the result of the work of many people who made the code better - Matteo Giachino (matteosister) - Alex Demchenko (pilot) - Tadas Gliaubicas (tadcka) + - Thanos Polymeneas (thanos) - Benoit Garret - Jakub Sacha - DerManoMann @@ -1098,7 +1102,6 @@ Symfony is the result of the work of many people who made the code better - Tomaz Ahlin - Marcus Stöhr (dafish) - Emmanuel Vella (emmanuel.vella) - - Adam Szaraniec (mimol) - Carsten Nielsen (phreaknerd) - Mathieu Rochette - Jay Severson @@ -1171,6 +1174,7 @@ Symfony is the result of the work of many people who made the code better - César Suárez (csuarez) - Nicolas Badey (nico-b) - Shane Preece (shane) + - Johannes Goslar - Geoff - georaldc - Malte Wunsch @@ -1204,6 +1208,7 @@ Symfony is the result of the work of many people who made the code better - catch - Alexandre Segura - Josef Cech + - Harold Iedema - Arnau González (arnaugm) - Simon Bouland (bouland) - Matthew Foster (mfoster) @@ -1236,7 +1241,6 @@ Symfony is the result of the work of many people who made the code better - Dennis Væversted - nuncanada - flack - - izzyp - František Bereň - Mike Francis - Christoph Nissle (derstoffel) @@ -1477,6 +1481,7 @@ Symfony is the result of the work of many people who made the code better - Ismail Asci (ismailasci) - Simon CONSTANS (kosssi) - Kristof Van Cauwenbergh (kristofvc) + - Paulius Jarmalavičius (pjarmalavicius) - Ramon Henrique Ornelas (ramonornela) - Markus S. (staabm) - Till Klampaeckel (till) @@ -1569,11 +1574,13 @@ Symfony is the result of the work of many people who made the code better - Matt Janssen - Peter Gribanov - Ben Johnson + - Florent Mata - kwiateusz - David Soria Parra - Sergiy Sokolenko - dinitrol - Penny Leach + - Yurii K - Richard Trebichavský - g123456789l - Jonathan Vollebregt @@ -1651,6 +1658,7 @@ Symfony is the result of the work of many people who made the code better - samuel laulhau (lalop) - Laurent Bachelier (laurentb) - Florent Viel (luxifer) + - Matthieu Mota (matthieumota) - Matthieu Moquet (mattketmo) - Moritz Borgmann (mborgmann) - Michal Čihař (mcihar) diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php new file mode 100644 index 0000000000000..3298b84d46278 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.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\Bridge\ProxyManager\LazyProxy\Instantiator; + +use ProxyManager\Factory\LazyLoadingValueHolderFactory as BaseFactory; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenerator; + +/** + * @internal + */ +class LazyLoadingValueHolderFactoryV1 extends BaseFactory +{ + private $generatorV1; + + /** + * {@inheritdoc} + */ + protected function getGenerator() + { + return $this->generatorV1 ?: $this->generatorV1 = new LazyLoadingValueHolderGenerator(); + } +} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php new file mode 100644 index 0000000000000..f41fc20b5d523 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator; + +use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; +use ProxyManager\Factory\LazyLoadingValueHolderFactory as BaseFactory; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenerator; + +/** + * @internal + */ +class LazyLoadingValueHolderFactoryV2 extends BaseFactory +{ + private $generator; + + /** + * {@inheritdoc} + */ + protected function getGenerator(): ProxyGeneratorInterface + { + return $this->generator ?: $this->generator = new LazyLoadingValueHolderGenerator(); + } +} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php index 0101026794c7c..33fc49e1012d9 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -36,7 +36,11 @@ public function __construct() $config = new Configuration(); $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); - $this->factory = new LazyLoadingValueHolderFactory($config); + if (method_exists('ProxyManager\Version', 'getVersion')) { + $this->factory = new LazyLoadingValueHolderFactoryV2($config); + } else { + $this->factory = new LazyLoadingValueHolderFactoryV1($config); + } } /** diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php new file mode 100644 index 0000000000000..1d9432f622b41 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper; + +use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator as BaseGenerator; +use Zend\Code\Generator\ClassGenerator; + +/** + * @internal + */ +class LazyLoadingValueHolderGenerator extends BaseGenerator +{ + /** + * {@inheritdoc} + */ + public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator) + { + parent::generate($originalClass, $classGenerator); + + if ($classGenerator->hasMethod('__destruct')) { + $destructor = $classGenerator->getMethod('__destruct'); + $body = $destructor->getBody(); + $newBody = preg_replace('/^(\$this->initializer[a-zA-Z0-9]++) && .*;\n\nreturn (\$this->valueHolder)/', '$1 || $2', $body); + + if ($body === $newBody) { + throw new \UnexpectedValueException(sprintf('Unexpected lazy-proxy format generated for method %s::__destruct()', $originalClass->name)); + } + + $destructor->setBody($newBody); + } + } +} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index eb72a7d5d83cb..40791358500a0 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -13,7 +13,6 @@ use ProxyManager\Generator\ClassGenerator; use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy; -use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php index b634a69488a34..858e9d76b64c9 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php @@ -39,6 +39,9 @@ public function testCreateProxyServiceWithRuntimeInstantiator() /* @var $foo1 \ProxyManager\Proxy\LazyLoadingInterface|\ProxyManager\Proxy\ValueHolderInterface */ $foo1 = $builder->get('foo1'); + $foo1->__destruct(); + $this->assertSame(0, $foo1::$destructorCount); + $this->assertSame($foo1, $builder->get('foo1'), 'The same proxy is retrieved on multiple subsequent calls'); $this->assertInstanceOf('\ProxyManagerBridgeFooClass', $foo1); $this->assertInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1); @@ -50,5 +53,8 @@ public function testCreateProxyServiceWithRuntimeInstantiator() $this->assertTrue($foo1->isProxyInitialized()); $this->assertInstanceOf('\ProxyManagerBridgeFooClass', $foo1->getWrappedValueHolderValue()); $this->assertNotInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1->getWrappedValueHolderValue()); + + $foo1->__destruct(); + $this->assertSame(1, $foo1::$destructorCount); } } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php index 16c898a370845..8ffc5be9af40a 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php @@ -2,6 +2,8 @@ class ProxyManagerBridgeFooClass { + public static $destructorCount = 0; + public $foo; public $moo; @@ -38,4 +40,9 @@ public function setBar($value = null) { $this->bar = $value; } + + public function __destruct() + { + ++self::$destructorCount; + } } diff --git a/src/Symfony/Bundle/DebugBundle/DebugBundle.php b/src/Symfony/Bundle/DebugBundle/DebugBundle.php index 335ec5abd2541..b00c06af78289 100644 --- a/src/Symfony/Bundle/DebugBundle/DebugBundle.php +++ b/src/Symfony/Bundle/DebugBundle/DebugBundle.php @@ -27,8 +27,10 @@ public function boot() $container = $this->container; // This code is here to lazy load the dump stack. This default - // configuration for CLI mode is overridden in HTTP mode on - // 'kernel.request' event + // configuration is overridden in CLI mode on 'console.command' event. + // The dump data collector is used by default, so dump output is sent to + // the WDT. In a CLI context, if dump is used too soon, the data collector + // will buffer it, and release it at the end of the script. VarDumper::setHandler(function ($var) use ($container) { $dumper = $container->get('data_collector.dump'); $cloner = $container->get('var_dumper.cloner'); diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 2c57de4af3e9e..873b09e808729 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -72,6 +72,7 @@ class Application private $dispatcher; private $terminalDimensions; private $defaultCommand; + private $initialized; /** * Constructor. @@ -84,12 +85,6 @@ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') $this->name = $name; $this->version = $version; $this->defaultCommand = 'list'; - $this->helperSet = $this->getDefaultHelperSet(); - $this->definition = $this->getDefaultInputDefinition(); - - foreach ($this->getDefaultCommands() as $command) { - $this->add($command); - } } public function setDispatcher(EventDispatcherInterface $dispatcher) @@ -189,10 +184,11 @@ public function doRun(InputInterface $input, OutputInterface $output) if (!$name) { $name = $this->defaultCommand; - $this->definition->setArguments(array_merge( - $this->definition->getArguments(), + $definition = $this->getDefinition(); + $definition->setArguments(array_merge( + $definition->getArguments(), array( - 'command' => new InputArgument('command', InputArgument::OPTIONAL, $this->definition->getArgument('command')->getDescription(), $name), + 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), ) )); } @@ -225,6 +221,10 @@ public function setHelperSet(HelperSet $helperSet) */ public function getHelperSet() { + if (!$this->helperSet) { + $this->helperSet = $this->getDefaultHelperSet(); + } + return $this->helperSet; } @@ -245,6 +245,10 @@ public function setDefinition(InputDefinition $definition) */ public function getDefinition() { + if (!$this->definition) { + $this->definition = $this->getDefaultInputDefinition(); + } + return $this->definition; } @@ -374,6 +378,8 @@ public function addCommands(array $commands) */ public function add(Command $command) { + $this->init(); + $command->setApplication($this); if (!$command->isEnabled()) { @@ -406,6 +412,8 @@ public function add(Command $command) */ public function get($name) { + $this->init(); + if (!isset($this->commands[$name])) { throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); } @@ -433,6 +441,8 @@ public function get($name) */ public function has($name) { + $this->init(); + return isset($this->commands[$name]); } @@ -510,6 +520,8 @@ public function findNamespace($namespace) */ public function find($name) { + $this->init(); + $allCommands = array_keys($this->commands); $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); $commands = preg_grep('{^'.$expr.'}', $allCommands); @@ -565,6 +577,8 @@ public function find($name) */ public function all($namespace = null) { + $this->init(); + if (null === $namespace) { return $this->commands; } @@ -1151,4 +1165,16 @@ private function extractAllNamespaces($name) return $namespaces; } + + private function init() + { + if ($this->initialized) { + return; + } + $this->initialized = true; + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } } diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php index 8ca836bd74a2d..2094a85c4cd6a 100644 --- a/src/Symfony/Component/Debug/DebugClassLoader.php +++ b/src/Symfony/Component/Debug/DebugClassLoader.php @@ -26,6 +26,7 @@ class DebugClassLoader { private $classLoader; private $isFinder; + private $loaded = array(); private $wasFinder; private static $caseCheck; private static $deprecated = array(); @@ -164,9 +165,10 @@ public function loadClass($class) ErrorHandler::stackErrors(); try { - if ($this->isFinder) { + if ($this->isFinder && !isset($this->loaded[$class])) { + $this->loaded[$class] = true; if ($file = $this->classLoader[0]->findFile($class)) { - require_once $file; + require $file; } } else { call_user_func($this->classLoader, $class); diff --git a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php index 133ca28e546b7..1843418e919c9 100644 --- a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php @@ -59,6 +59,23 @@ public function testIdempotence() $this->fail('DebugClassLoader did not register'); } + /** + * @expectedException \Exception + * @expectedExceptionMessage boo + */ + public function testThrowingClass() + { + try { + class_exists(__NAMESPACE__.'\Fixtures\Throwing'); + $this->fail('Exception expected'); + } catch (\Exception $e) { + $this->assertSame('boo', $e->getMessage()); + } + + // the second call also should throw + class_exists(__NAMESPACE__.'\Fixtures\Throwing'); + } + public function testUnsilencing() { if (\PHP_VERSION_ID >= 70000) { @@ -128,6 +145,7 @@ class ChildTestingStacking extends TestingStacking { function foo($bar) {} } /** * @expectedException \RuntimeException + * @expectedExceptionMessage Case mismatch between loaded and declared class names */ public function testNameCaseMismatch() { @@ -149,6 +167,7 @@ class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true); /** * @expectedException \RuntimeException + * @expectedExceptionMessage Case mismatch between loaded and declared class names */ public function testPsr4CaseMismatch() { diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/Throwing.php b/src/Symfony/Component/Debug/Tests/Fixtures/Throwing.php new file mode 100644 index 0000000000000..21e0aba17d358 --- /dev/null +++ b/src/Symfony/Component/Debug/Tests/Fixtures/Throwing.php @@ -0,0 +1,3 @@ +properties = $properties; @@ -264,11 +271,24 @@ public function setProperties(array $properties) return $this; } + /** + * Gets the properties to define when creating the service. + * + * @return array + */ public function getProperties() { return $this->properties; } + /** + * Sets a specific property. + * + * @param string $name + * @param mixed $value + * + * @return $this + */ public function setProperty($name, $value) { $this->properties[$name] = $value; @@ -291,7 +311,7 @@ public function addArgument($argument) } /** - * Sets a specific argument. + * Replaces a specific argument. * * @param int $index * @param mixed $argument diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index 810e7539fe1bd..9125a97836b6f 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -104,6 +104,10 @@ private function addService($id, $definition) $code .= sprintf(" factory_class: %s\n", $this->dumper->dump($definition->getFactoryClass(false))); } + if ($definition->isAbstract()) { + $code .= " abstract: true\n"; + } + if ($definition->isLazy()) { $code .= " lazy: true\n"; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php index f19a2f5cb8346..81bbd5316c444 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php @@ -12,8 +12,10 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\YamlDumper; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\Yaml\Yaml; class YamlDumperTest extends TestCase @@ -77,6 +79,16 @@ public function testAddService() } } + public function testDumpLoad() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('services_dump_load.yml'); + + $dumper = new YamlDumper($container); + $this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_dump_load.yml', $dumper->dump()); + } + private function assertEqualYamlStructure($yaml, $expected, $message = '') { $this->assertEquals(Yaml::parse($expected), Yaml::parse($yaml), $message); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml new file mode 100644 index 0000000000000..bcf8f31b36115 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml @@ -0,0 +1,4 @@ + +services: + foo: + abstract: true diff --git a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php index bfd9527a05c83..495a4a9d14a34 100644 --- a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php @@ -353,7 +353,7 @@ public function testText() public function testHtml() { $this->assertEquals('Bar', $this->createTestCrawler()->filterXPath('//a[5]')->html()); - $this->assertEquals('', trim($this->createTestCrawler()->filterXPath('//form[@id="FooFormId"]')->html())); + $this->assertEquals('', trim(preg_replace('~>\s+<~', '><', $this->createTestCrawler()->filterXPath('//form[@id="FooFormId"]')->html()))); try { $this->createTestCrawler()->filterXPath('//ol')->html(); diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index 0314621907356..177b708e8f054 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -150,7 +150,7 @@ public function setAutoEtag() * Sets the Content-Disposition header with the given filename. * * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT - * @param string $filename Optionally use this filename instead of the real name of the file + * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename * * @return $this @@ -162,7 +162,7 @@ public function setContentDisposition($disposition, $filename = '', $filenameFal } if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) { - $encoding = mb_detect_encoding($filename, null, true); + $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { $char = mb_substr($filename, $i, 1, $encoding); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcacheSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcacheSessionHandler.php index 962a3878d9767..4e490a05d4ce0 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcacheSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcacheSessionHandler.php @@ -95,7 +95,9 @@ public function write($sessionId, $data) */ public function destroy($sessionId) { - return $this->memcache->delete($this->prefix.$sessionId); + $this->memcache->delete($this->prefix.$sessionId); + + return true; } /** diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php index 76b08e2db944c..67a49ad6f5e2a 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -101,7 +101,9 @@ public function write($sessionId, $data) */ public function destroy($sessionId) { - return $this->memcached->delete($this->prefix.$sessionId); + $result = $this->memcached->delete($this->prefix.$sessionId); + + return $result || $this->memcached->getResultCode() == \Memcached::RES_NOTFOUND; } /** diff --git a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php index e4607201a2151..e41a2372b9df8 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php @@ -68,6 +68,17 @@ public function testSetContentDispositionGeneratesSafeFallbackFilename() $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html', $response->headers->get('Content-Disposition')); } + public function testSetContentDispositionGeneratesSafeFallbackFilenameForWronglyEncodedFilename() + { + $response = new BinaryFileResponse(__FILE__); + + $iso88591EncodedFilename = utf8_decode('föö.html'); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $iso88591EncodedFilename); + + // the parameter filename* is invalid in this case (rawurldecode('f%F6%F6') does not provide a UTF-8 string but an ISO-8859-1 encoded one) + $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%F6%F6.html', $response->headers->get('Content-Disposition')); + } + /** * @dataProvider provideRanges */ diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 60ced9c9d646e..c14964609235c 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -58,11 +58,11 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '2.7.33'; - const VERSION_ID = 20733; + const VERSION = '2.7.34'; + const VERSION_ID = 20734; const MAJOR_VERSION = 2; const MINOR_VERSION = 7; - const RELEASE_VERSION = 33; + const RELEASE_VERSION = 34; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '05/2018'; diff --git a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php index 29da4abf32ccf..045058991063d 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php @@ -144,11 +144,19 @@ public function write(Profile $profile) } } + $profileToken = $profile->getToken(); + // when there are errors in sub-requests, the parent and/or children tokens + // may equal the profile token, resulting in infinite loops + $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; + $childrenToken = array_filter(array_map(function ($p) use ($profileToken) { + return $profileToken !== $p->getToken() ? $p->getToken() : null; + }, $profile->getChildren())); + // Store profile $data = array( - 'token' => $profile->getToken(), - 'parent' => $profile->getParentToken(), - 'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()), + 'token' => $profileToken, + 'parent' => $parentToken, + 'children' => $childrenToken, 'data' => $profile->getCollectors(), 'ip' => $profile->getIp(), 'method' => $profile->getMethod(), diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index dad52401daf39..a6d6285481015 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\Bundle\BundleInterface; use Symfony\Component\HttpKernel\Config\EnvParametersResource; use Symfony\Component\HttpKernel\Kernel; @@ -25,6 +26,12 @@ class KernelTest extends TestCase { + public static function tearDownAfterClass() + { + $fs = new Filesystem(); + $fs->remove(__DIR__.'/Fixtures/cache'); + } + public function testConstructor() { $env = 'test_env'; diff --git a/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php b/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php index 84644b9fe7f2c..90012a7ad64ab 100644 --- a/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php +++ b/src/Symfony/Component/Intl/Data/Bundle/Reader/JsonBundleReader.php @@ -32,17 +32,15 @@ public function read($path, $locale) if (!file_exists($fileName)) { throw new ResourceBundleNotFoundException(sprintf( - 'The resource bundle "%s/%s.json" does not exist.', - $path, - $locale + 'The resource bundle "%s" does not exist.', + $fileName )); } if (!is_file($fileName)) { throw new RuntimeException(sprintf( - 'The resource bundle "%s/%s.json" is not a file.', - $path, - $locale + 'The resource bundle "%s" is not a file.', + $fileName )); } @@ -50,9 +48,8 @@ public function read($path, $locale) if (null === $data) { throw new RuntimeException(sprintf( - 'The resource bundle "%s/%s.json" contains invalid JSON: %s', - $path, - $locale, + 'The resource bundle "%s" contains invalid JSON: %s', + $fileName, self::getLastJsonError() )); } diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php index 50ee30a217da1..ce03e9f5d6642 100644 --- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php +++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php @@ -355,9 +355,9 @@ public function formatCurrency($value, $currency) /** * Format a number. * - * @param number $value The value to format - * @param int $type Type of the formatting, one of the format type constants - * Only type NumberFormatter::TYPE_DEFAULT is currently supported. + * @param int|float $value The value to format + * @param int $type Type of the formatting, one of the format type constants + * Only type NumberFormatter::TYPE_DEFAULT is currently supported. * * @return bool|string The formatted value or false on error * diff --git a/src/Symfony/Component/Security/Http/SecurityEvents.php b/src/Symfony/Component/Security/Http/SecurityEvents.php index 46c8257f18e74..3bd80723d1ef5 100644 --- a/src/Symfony/Component/Security/Http/SecurityEvents.php +++ b/src/Symfony/Component/Security/Http/SecurityEvents.php @@ -14,8 +14,11 @@ final class SecurityEvents { /** - * The INTERACTIVE_LOGIN event occurs after a user is logged in - * interactively for authentication based on http, cookies or X509. + * The INTERACTIVE_LOGIN event occurs after a user has actively logged + * into your website. It is important to distinguish this action from + * non-interactive authentication methods, such as: + * - authentication based on your session. + * - authentication using a HTTP basic or HTTP digest header. * * The event listener method receives a * Symfony\Component\Security\Http\Event\InteractiveLoginEvent instance. diff --git a/src/Symfony/Component/Validator/Constraints/File.php b/src/Symfony/Component/Validator/Constraints/File.php index b9c06bfdb142c..7756978b35216 100644 --- a/src/Symfony/Component/Validator/Constraints/File.php +++ b/src/Symfony/Component/Validator/Constraints/File.php @@ -18,6 +18,8 @@ * @Annotation * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) * + * @property int $maxSize + * * @author Bernhard Schussek */ class File extends Constraint diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php index f895ad40402dc..5b82cf6d5d9d6 100644 --- a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php @@ -381,6 +381,10 @@ public function addGetterMethodConstraints($property, $method, array $constraint */ public function mergeConstraints(ClassMetadata $source) { + if ($source->isGroupSequenceProvider()) { + $this->setGroupSequenceProvider(true); + } + foreach ($source->getConstraints() as $constraint) { $this->addConstraint(clone $constraint); } diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.el.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.el.xlf index 4fa0d42220500..a3199bcc9d79e 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.el.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.el.xlf @@ -100,7 +100,7 @@ This value is not valid. - Αυτή η τιμή δεν είναι έκγυρη. + Αυτή η τιμή δεν είναι έγκυρη. This value is not a valid time. @@ -136,11 +136,11 @@ This is not a valid IP address. - Αυτό δεν είναι μια έκγυρη διεύθυνση IP. + Αυτό δεν είναι μια έγκυρη διεύθυνση IP. This value is not a valid language. - Αυτή η τιμή δεν αντιστοιχεί σε μια έκγυρη γλώσσα. + Αυτή η τιμή δεν αντιστοιχεί σε μια έγκυρη γλώσσα. This value is not a valid locale. @@ -148,7 +148,7 @@ This value is not a valid country. - Αυτή η τιμή δεν αντιστοιχεί σε μια έκγυρη χώρα. + Αυτή η τιμή δεν αντιστοιχεί σε μια έγκυρη χώρα. This value is already used. diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/GroupSequenceProviderChildEntity.php b/src/Symfony/Component/Validator/Tests/Fixtures/GroupSequenceProviderChildEntity.php new file mode 100644 index 0000000000000..be7191f9b6e1d --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/GroupSequenceProviderChildEntity.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +class GroupSequenceProviderChildEntity extends GroupSequenceProviderEntity +{ +} diff --git a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php index 9a23e8cf355a0..a3f8a8c1ec8a3 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php @@ -24,6 +24,7 @@ class ClassMetadataTest extends TestCase const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity'; const PARENTCLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'; const PROVIDERCLASS = 'Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity'; + const PROVIDERCHILDCLASS = 'Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderChildEntity'; protected $metadata; @@ -301,6 +302,17 @@ public function testGroupSequenceProvider() $this->assertTrue($metadata->isGroupSequenceProvider()); } + public function testMergeConstraintsMergesGroupSequenceProvider() + { + $parent = new ClassMetadata(self::PROVIDERCLASS); + $parent->setGroupSequenceProvider(true); + + $metadata = new ClassMetadata(self::PROVIDERCHILDCLASS); + $metadata->mergeConstraints($parent); + + $this->assertTrue($metadata->isGroupSequenceProvider()); + } + /** * https://github.com/symfony/symfony/issues/11604. */ @@ -309,13 +321,3 @@ public function testGetPropertyMetadataReturnsEmptyArrayWithoutConfiguredMetadat $this->assertCount(0, $this->metadata->getPropertyMetadata('foo'), '->getPropertyMetadata() returns an empty collection if no metadata is configured for the given property'); } } - -class ParentClass -{ - public $example = 0; -} - -class ChildClass extends ParentClass -{ - public $example = 1; // overrides parent property of same name -} diff --git a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php index abbe9629540ba..cd892a9687e0c 100644 --- a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php @@ -160,7 +160,8 @@ public function dump(Data $data, $output = null) /** * Dumps the current line. * - * @param int $depth The recursive depth in the dumped structure for the line being dumped + * @param int $depth The recursive depth in the dumped structure for the line being dumped, + * or -1 to signal the end-of-dump to the line dumper callable */ protected function dumpLine($depth) { diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index 4f572caea3f8c..28596bf83d1ce 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -132,10 +132,10 @@ protected function getDumpHeader() function toggle(a, recursive) { var s = a.nextSibling || {}, oldClass = s.className, arrow, newClass; - if ('sf-dump-compact' == oldClass) { + if (/\bsf-dump-compact\b/.test(oldClass)) { arrow = '▼'; newClass = 'sf-dump-expanded'; - } else if ('sf-dump-expanded' == oldClass) { + } else if (/\bsf-dump-expanded\b/.test(oldClass)) { arrow = '▶'; newClass = 'sf-dump-compact'; } else { @@ -143,13 +143,13 @@ function toggle(a, recursive) { } a.lastChild.innerHTML = arrow; - s.className = newClass; + s.className = s.className.replace(/\bsf-dump-(compact|expanded)\b/, newClass); if (recursive) { try { a = s.querySelectorAll('.'+oldClass); for (s = 0; s < a.length; ++s) { - if (a[s].className !== newClass) { + if (-1 == a[s].className.indexOf(newClass)) { a[s].className = newClass; a[s].previousSibling.lastChild.innerHTML = arrow; } @@ -205,7 +205,7 @@ function isCtrlKey(e) { if (f && t && f[0] !== t[0]) { r.innerHTML = r.innerHTML.replace(new RegExp('^'+f[0].replace(rxEsc, '\\$1'), 'mg'), t[0]); } - if ('sf-dump-compact' == r.className) { + if (/\bsf-dump-compact\b/.test(r.className)) { toggle(s, isCtrlKey(e)); } } @@ -255,10 +255,10 @@ function isCtrlKey(e) { a.title = (a.title ? a.title+'\n[' : '[')+keyHint+'+click] Expand all children'; a.innerHTML += ''; a.className += ' sf-dump-toggle'; - if ('sf-dump' != elt.parentNode.className) { + if (!/\bsf-dump\b/.test(elt.parentNode.className)) { toggle(a); } - } else if ("sf-dump-ref" == elt.className && (a = elt.getAttribute('href'))) { + } else if (/\bsf-dump-ref\b/.test(elt.className) && (a = elt.getAttribute('href'))) { a = a.substr(1); elt.className += ' '+a;