- {% if exception.message is not empty %}
-
-
+
+
+
{{- exception.message|nl2br|format_file_from_text -}}
@@ -25,7 +25,6 @@
- {% endif %}
diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig
index 4ce8013c44..036af2b025 100644
--- a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig
+++ b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig
@@ -512,14 +512,14 @@
var altContent = toggle.getAttribute('data-toggle-alt-content');
toggle.innerHTML = currentContent !== altContent ? altContent : originalContent;
});
+ }
- /* Prevents from disallowing clicks on links inside toggles */
- var toggleLinks = document.querySelectorAll('.sf-toggle a');
- for (var i = 0; i < toggleLinks.length; i++) {
- addEventListener(toggleLinks[i], 'click', function(e) {
- e.stopPropagation();
- });
- }
+ /* Prevents from disallowing clicks on links inside toggles */
+ var toggleLinks = document.querySelectorAll('.sf-toggle a');
+ for (var i = 0; i < toggleLinks.length; i++) {
+ addEventListener(toggleLinks[i], 'click', function(e) {
+ e.stopPropagation();
+ });
}
}
};
diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig
index ba3dd9412e..9d03015f2f 100644
--- a/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig
+++ b/src/Symfony/Bundle/TwigBundle/Resources/views/exception.css.twig
@@ -79,7 +79,8 @@ header .container { display: flex; justify-content: space-between; }
.exception-hierarchy .icon { margin: 0 3px; opacity: .7; }
.exception-hierarchy .icon svg { height: 13px; width: 13px; vertical-align: -2px; }
-.exception-message-wrapper { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 0 8px; }
+.exception-without-message .exception-message-wrapper { display: none; }
+.exception-message-wrapper .container { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 0 8px; }
.exception-message { flex-grow: 1; }
.exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; }
.exception-message.long { font-size: 18px; }
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig
index 8a64ed5936..c849cb2966 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.css.twig
@@ -16,6 +16,9 @@
margin: 1em 0;
padding: 10px;
}
+.exception-summary.exception-without-message {
+ display: none;
+}
.exception-message {
color: #B0413E;
@@ -26,6 +29,6 @@
display: none;
}
-.exception-message-wrapper {
+.exception-message-wrapper .container {
min-height: auto;
}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig
index ad87620e3e..3032cc6c68 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig
@@ -380,13 +380,12 @@
100% { background: #222; }
}
-.sf-toolbar-block.sf-toolbar-block-dump {
- position: static;
-}
-
.sf-toolbar-block.sf-toolbar-block-dump .sf-toolbar-info {
max-width: none;
- right: 0;
+ width: 100%;
+ position: fixed;
+ box-sizing: border-box;
+ left: 0;
}
.sf-toolbar-block-dump pre.sf-dump {
diff --git a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php
index 8a86a42099..623124ab8d 100644
--- a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php
+++ b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php
@@ -25,6 +25,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
private $exists;
private static $autoloadLevel = 0;
+ private static $autoloadedClass;
private static $existsCache = array();
/**
@@ -57,6 +58,8 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
/**
* {@inheritdoc}
+ *
+ * @throws \ReflectionException when a parent class/interface/trait is not found
*/
public function isFresh($timestamp)
{
@@ -68,12 +71,13 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
if (!self::$autoloadLevel++) {
spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
}
+ $autoloadedClass = self::$autoloadedClass;
+ self::$autoloadedClass = $this->resource;
try {
$exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
- } catch (\ReflectionException $e) {
- $exists = false;
} finally {
+ self::$autoloadedClass = $autoloadedClass;
if (!--self::$autoloadLevel) {
spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass');
}
@@ -112,7 +116,10 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
*/
private static function throwOnRequiredClass($class)
{
- $e = new \ReflectionException("Class $class does not exist");
+ if (self::$autoloadedClass === $class) {
+ return;
+ }
+ $e = new \ReflectionException("Class $class not found");
$trace = $e->getTrace();
$autoloadFrame = array(
'function' => 'spl_autoload_call',
@@ -138,6 +145,18 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ
case 'is_callable':
return;
}
+
+ $props = array(
+ 'file' => $trace[$i]['file'],
+ 'line' => $trace[$i]['line'],
+ 'trace' => array_slice($trace, 1 + $i),
+ );
+
+ foreach ($props as $p => $v) {
+ $r = new \ReflectionProperty('Exception', $p);
+ $r->setAccessible(true);
+ $r->setValue($e, $v);
+ }
}
throw $e;
diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md
index cdf866ee9a..726f4db820 100644
--- a/src/Symfony/Component/Console/CHANGELOG.md
+++ b/src/Symfony/Component/Console/CHANGELOG.md
@@ -14,7 +14,8 @@ CHANGELOG
3.4.0
-----
- * added `CommandLoaderInterface` and PSR-11 `ContainerCommandLoader`
+ * added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11
+ `ContainerCommandLoader` for commands lazy-loading
3.3.0
-----
diff --git a/src/Symfony/Component/Console/CommandLoader/FactoryCommandLoader.php b/src/Symfony/Component/Console/CommandLoader/FactoryCommandLoader.php
new file mode 100644
index 0000000000..d9c2055710
--- /dev/null
+++ b/src/Symfony/Component/Console/CommandLoader/FactoryCommandLoader.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\CommandLoader;
+
+use Symfony\Component\Console\Exception\CommandNotFoundException;
+
+/**
+ * A simple command loader using factories to instantiate commands lazily.
+ *
+ * @author Maxime Steinhausser
+ */
+class FactoryCommandLoader implements CommandLoaderInterface
+{
+ private $factories;
+
+ /**
+ * @param callable[] $factories Indexed by command names
+ */
+ public function __construct(array $factories)
+ {
+ $this->factories = $factories;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($name)
+ {
+ return isset($this->factories[$name]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($name)
+ {
+ if (!isset($this->factories[$name])) {
+ throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
+ }
+
+ $factory = $this->factories[$name];
+
+ return $factory();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNames()
+ {
+ return array_keys($this->factories);
+ }
+}
diff --git a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php
index 1ceed91573..67034098c2 100644
--- a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php
+++ b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php
@@ -70,20 +70,15 @@ class AddConsoleCommandPass implements CompilerPassInterface
$serviceIds[$commandId] = false;
$commandName = $tags[0]['command'];
+ unset($tags[0]);
$lazyCommandMap[$commandName] = $id;
$lazyCommandRefs[$id] = new TypedReference($id, $class);
$aliases = array();
foreach ($tags as $tag) {
- if (!isset($tag['command'])) {
- throw new InvalidArgumentException(sprintf('Missing "command" attribute on tag "%s" for service "%s".', $this->commandTag, $id));
- }
- if ($commandName !== $tag['command']) {
- throw new InvalidArgumentException(sprintf('The "command" attribute must be the same on each "%s" tag for service "%s".', $this->commandTag, $id));
- }
- if (isset($tag['alias'])) {
- $aliases[] = $tag['alias'];
- $lazyCommandMap[$tag['alias']] = $id;
+ if (isset($tag['command'])) {
+ $aliases[] = $tag['command'];
+ $lazyCommandMap[$tag['command']] = $id;
}
}
diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php
index a3e403fc65..015444fec5 100644
--- a/src/Symfony/Component/Console/Tests/ApplicationTest.php
+++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php
@@ -14,7 +14,7 @@ namespace Symfony\Component\Console\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
+use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
@@ -34,7 +34,6 @@ use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\EventDispatcher\EventDispatcher;
class ApplicationTest extends TestCase
@@ -128,10 +127,9 @@ class ApplicationTest extends TestCase
$commands = $application->all('foo');
$this->assertCount(1, $commands, '->all() takes a namespace as its first argument');
- $application->setCommandLoader(new ContainerCommandLoader(
- new ServiceLocator(array('foo-bar' => function () { return new \Foo1Command(); })),
- array('foo:bar1' => 'foo-bar')
- ));
+ $application->setCommandLoader(new FactoryCommandLoader(array(
+ 'foo:bar1' => function () { return new \Foo1Command(); },
+ )));
$commands = $application->all('foo');
$this->assertCount(2, $commands, '->all() takes a namespace as its first argument');
$this->assertInstanceOf(\FooCommand::class, $commands['foo:bar'], '->all() returns the registered commands');
@@ -201,9 +199,9 @@ class ApplicationTest extends TestCase
$this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name');
$this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias');
- $application->setCommandLoader(new ContainerCommandLoader(new ServiceLocator(array(
- 'foo-bar' => function () { return new \Foo1Command(); },
- )), array('foo:bar1' => 'foo-bar', 'afoobar1' => 'foo-bar')));
+ $application->setCommandLoader(new FactoryCommandLoader(array(
+ 'foo:bar1' => function () { return new \Foo1Command(); },
+ )));
$this->assertTrue($application->has('afoobar'), '->has() returns true if an instance is registered for an alias even with command loader');
$this->assertEquals($foo, $application->get('foo:bar'), '->get() returns an instance by name even with command loader');
@@ -320,9 +318,9 @@ class ApplicationTest extends TestCase
public function testFindWithCommandLoader()
{
$application = new Application();
- $application->setCommandLoader(new ContainerCommandLoader(new ServiceLocator(array(
- 'foo-bar' => $f = function () { return new \FooCommand(); },
- )), array('foo:bar' => 'foo-bar')));
+ $application->setCommandLoader(new FactoryCommandLoader(array(
+ 'foo:bar' => $f = function () { return new \FooCommand(); },
+ )));
$this->assertInstanceOf('FooCommand', $application->find('foo:bar'), '->find() returns a command if its name exists');
$this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $application->find('h'), '->find() returns a command if its name exists');
@@ -1382,8 +1380,9 @@ class ApplicationTest extends TestCase
$container->addCompilerPass(new AddConsoleCommandPass());
$container
->register('lazy-command', LazyCommand::class)
- ->addTag('console.command', array('command' => 'lazy:command', 'alias' => 'lazy:alias'))
- ->addTag('console.command', array('command' => 'lazy:command', 'alias' => 'lazy:alias2'));
+ ->addTag('console.command', array('command' => 'lazy:command'))
+ ->addTag('console.command', array('command' => 'lazy:alias'))
+ ->addTag('console.command', array('command' => 'lazy:alias2'));
$container->compile();
$application = new Application();
diff --git a/src/Symfony/Component/Console/Tests/CommandLoader/FactoryCommandLoaderTest.php b/src/Symfony/Component/Console/Tests/CommandLoader/FactoryCommandLoaderTest.php
new file mode 100644
index 0000000000..5ee6cd1ec3
--- /dev/null
+++ b/src/Symfony/Component/Console/Tests/CommandLoader/FactoryCommandLoaderTest.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Tests\CommandLoader;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
+
+class FactoryCommandLoaderTest extends TestCase
+{
+ public function testHas()
+ {
+ $loader = new FactoryCommandLoader(array(
+ 'foo' => function () { return new Command('foo'); },
+ 'bar' => function () { return new Command('bar'); },
+ ));
+
+ $this->assertTrue($loader->has('foo'));
+ $this->assertTrue($loader->has('bar'));
+ $this->assertFalse($loader->has('baz'));
+ }
+
+ public function testGet()
+ {
+ $loader = new FactoryCommandLoader(array(
+ 'foo' => function () { return new Command('foo'); },
+ 'bar' => function () { return new Command('bar'); },
+ ));
+
+ $this->assertInstanceOf(Command::class, $loader->get('foo'));
+ $this->assertInstanceOf(Command::class, $loader->get('bar'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException
+ */
+ public function testGetUnknownCommandThrows()
+ {
+ (new FactoryCommandLoader(array()))->get('unknown');
+ }
+
+ public function testGetCommandNames()
+ {
+ $loader = new FactoryCommandLoader(array(
+ 'foo' => function () { return new Command('foo'); },
+ 'bar' => function () { return new Command('bar'); },
+ ));
+
+ $this->assertSame(array('foo', 'bar'), $loader->getNames());
+ }
+}
diff --git a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php
index 6454dd63df..596d9e0d2d 100644
--- a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php
+++ b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php
@@ -56,13 +56,14 @@ class AddConsoleCommandPassTest extends TestCase
$this->assertSame(array($alias => $id), $container->getParameter('console.command.ids'));
}
- public function testProcessRegisterLazyCommands()
+ public function testProcessRegistersLazyCommands()
{
$container = new ContainerBuilder();
- $container
+ $command = $container
->register('my-command', MyCommand::class)
->setPublic(false)
- ->addTag('console.command', array('command' => 'my:command', 'alias' => 'my:alias'))
+ ->addTag('console.command', array('command' => 'my:command'))
+ ->addTag('console.command', array('command' => 'my:alias'))
;
(new AddConsoleCommandPass())->process($container);
@@ -74,6 +75,7 @@ class AddConsoleCommandPassTest extends TestCase
$this->assertSame(array('my:command' => 'my-command', 'my:alias' => 'my-command'), $commandLoader->getArgument(1));
$this->assertEquals(array(array('my-command' => new ServiceClosureArgument(new TypedReference('my-command', MyCommand::class)))), $commandLocator->getArguments());
$this->assertSame(array('console.command.symfony_component_console_tests_dependencyinjection_mycommand' => false), $container->getParameter('console.command.ids'));
+ $this->assertSame(array(array('setName', array('my:command')), array('setAliases', array(array('my:alias')))), $command->getMethodCalls());
}
public function visibilityProvider()
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
index 48c7c81248..3927f33a74 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
@@ -97,8 +97,8 @@ class AutowirePass extends AbstractRecursivePass
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return $value;
}
- if (!$reflectionClass = $this->container->getReflectionClass($value->getClass())) {
- $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" does not exist.', $this->currentId, $value->getClass()));
+ if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
+ $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass()));
return $value;
}
@@ -342,7 +342,7 @@ class AutowirePass extends AbstractRecursivePass
return;
}
- if ($definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass())) {
+ if ($definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass(), false)) {
return;
}
@@ -394,7 +394,7 @@ class AutowirePass extends AbstractRecursivePass
*/
private function createAutowiredDefinition($type)
{
- if (!($typeHint = $this->container->getReflectionClass($type)) || !$typeHint->isInstantiable()) {
+ if (!($typeHint = $this->container->getReflectionClass($type, false)) || !$typeHint->isInstantiable()) {
return;
}
@@ -428,8 +428,8 @@ class AutowirePass extends AbstractRecursivePass
private function createTypeNotFoundMessage(TypedReference $reference, $label)
{
- if (!$r = $this->container->getReflectionClass($type = $reference->getType())) {
- $message = sprintf('has type "%s" but this class does not exist.', $type);
+ if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) {
+ $message = sprintf('has type "%s" but this class cannot be loaded.', $type);
} else {
$message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists';
$message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $this->createTypeAlternatives($reference));
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php
index 60829884c4..97b083fa13 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php
@@ -26,7 +26,7 @@ trait PriorityTaggedServiceTrait
*
* The order of additions must be respected for services having the same priority,
* and knowing that the \SplPriorityQueue class does not respect the FIFO method,
- * we should not use this class.
+ * we should not use that class.
*
* @see https://bugs.php.net/bug.php?id=53710
* @see https://bugs.php.net/bug.php?id=60926
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php
index 82f13ed2d0..32d7ad2911 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php
@@ -66,7 +66,7 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
$instanceofTags = array();
foreach ($conditionals as $interface => $instanceofDefs) {
- if ($interface !== $class && (!$container->getReflectionClass($class))) {
+ if ($interface !== $class && (!$container->getReflectionClass($class, false))) {
continue;
}
diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
index 3423ad515c..0d00ace140 100644
--- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
+++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
@@ -320,12 +320,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
* Retrieves the requested reflection class and registers it for resource tracking.
*
* @param string $class
+ * @param bool $throw
*
* @return \ReflectionClass|null
*
+ * @throws \ReflectionException when a parent class/interface/trait is not found and $throw is true
+ *
* @final
*/
- public function getReflectionClass($class)
+ public function getReflectionClass($class, $throw = true)
{
if (!$class = $this->getParameterBag()->resolveValue($class)) {
return;
@@ -340,6 +343,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class);
}
} catch (\ReflectionException $e) {
+ if ($throw) {
+ throw $e;
+ }
$classReflector = false;
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
index b94b189125..413cae6d65 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
@@ -330,7 +330,7 @@ class AutowirePassTest extends TestCase
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
- * @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class does not exist.
+ * @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded.
*/
public function testClassNotFoundThrowsException()
{
@@ -345,7 +345,7 @@ class AutowirePassTest extends TestCase
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException
- * @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" but this class does not exist.
+ * @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" but this class cannot be loaded.
*/
public function testParentClassNotFoundThrowsException()
{
@@ -685,7 +685,7 @@ class AutowirePassTest extends TestCase
public function provideNotWireableCalls()
{
return array(
- array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class does not exist.'),
+ array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded.'),
array('setDifferentNamespace', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setDifferentNamespace()" references class "stdClass" but no such service exists. It cannot be auto-registered because it is from a different root namespace.'),
array(null, 'Cannot autowire service "foo": method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setProtectedMethod()" must be public.'),
);
diff --git a/src/Symfony/Component/HttpFoundation/Cookie.php b/src/Symfony/Component/HttpFoundation/Cookie.php
index 4d709c0e91..a2139ff6ba 100644
--- a/src/Symfony/Component/HttpFoundation/Cookie.php
+++ b/src/Symfony/Component/HttpFoundation/Cookie.php
@@ -126,6 +126,10 @@ class Cookie
$this->httpOnly = (bool) $httpOnly;
$this->raw = (bool) $raw;
+ if (null !== $sameSite) {
+ $sameSite = strtolower($sameSite);
+ }
+
if (!in_array($sameSite, array(self::SAMESITE_LAX, self::SAMESITE_STRICT, null), true)) {
throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.');
}
diff --git a/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php b/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php
index 79cda128dc..264fafa097 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/CookieTest.php
@@ -211,4 +211,10 @@ class CookieTest extends TestCase
$cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure');
$this->assertFalse($cookie->isHttpOnly());
}
+
+ public function testSameSiteAttributeIsCaseInsensitive()
+ {
+ $cookie = new Cookie('foo', 'bar', 0, '/', null, false, true, false, 'Lax');
+ $this->assertEquals('lax', $cookie->getSameSite());
+ }
}
diff --git a/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php b/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php
index 39b03613cf..ff04a3d6ee 100644
--- a/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php
@@ -108,7 +108,8 @@ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface
return call_user_func_array(array($this->propertyInfoExtractor, $method), $arguments);
}
- $key = $this->escape($method.'.'.$serializedArguments);
+ // Calling rawurlencode escapes special characters not allowed in PSR-6's keys
+ $key = rawurlencode($method.'.'.$serializedArguments);
if (array_key_exists($key, $this->arrayCache)) {
return $this->arrayCache[$key];
@@ -126,29 +127,4 @@ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface
return $this->arrayCache[$key] = $value;
}
-
- /**
- * Escapes a key according to PSR-6.
- *
- * Replaces characters forbidden by PSR-6 and the _ char by the _ char followed by the ASCII
- * code of the escaped char.
- *
- * @param string $key
- *
- * @return string
- */
- private function escape($key)
- {
- return strtr($key, array(
- '{' => '_123',
- '}' => '_125',
- '(' => '_40',
- ')' => '_41',
- '/' => '_47',
- '\\' => '_92',
- '@' => '_64',
- ':' => '_58',
- '_' => '_95',
- ));
- }
}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/PropertyInfoCacheExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/PropertyInfoCacheExtractorTest.php
index 455d39fa96..d31a7bd51d 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/PropertyInfoCacheExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/PropertyInfoCacheExtractorTest.php
@@ -61,29 +61,4 @@ class PropertyInfoCacheExtractorTest extends AbstractPropertyInfoExtractorTest
parent::testGetProperties();
parent::testGetProperties();
}
-
- /**
- * @dataProvider escapeDataProvider
- */
- public function testEscape($toEscape, $expected)
- {
- $reflectionMethod = new \ReflectionMethod($this->propertyInfo, 'escape');
- $reflectionMethod->setAccessible(true);
-
- $this->assertSame($expected, $reflectionMethod->invoke($this->propertyInfo, $toEscape));
- }
-
- public function escapeDataProvider()
- {
- return array(
- array('foo_bar', 'foo_95bar'),
- array('foo_95bar', 'foo_9595bar'),
- array('foo{bar}', 'foo_123bar_125'),
- array('foo(bar)', 'foo_40bar_41'),
- array('foo/bar', 'foo_47bar'),
- array('foo\bar', 'foo_92bar'),
- array('foo@bar', 'foo_64bar'),
- array('foo:bar', 'foo_58bar'),
- );
- }
}
diff --git a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php
index 0434ff850a..79f806404b 100644
--- a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php
+++ b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php
@@ -122,8 +122,14 @@ class DefaultAuthenticationSuccessHandler implements AuthenticationSuccessHandle
return $targetUrl;
}
- if ($this->options['use_referer'] && ($targetUrl = $request->headers->get('Referer')) && parse_url($targetUrl, PHP_URL_PATH) !== $this->httpUtils->generateUri($request, $this->options['login_path'])) {
- return $targetUrl;
+ if ($this->options['use_referer']) {
+ $targetUrl = $request->headers->get('Referer');
+ if (false !== $pos = strpos($targetUrl, '?')) {
+ $targetUrl = substr($targetUrl, 0, $pos);
+ }
+ if ($targetUrl !== $this->httpUtils->generateUri($request, $this->options['login_path'])) {
+ return $targetUrl;
+ }
}
return $this->options['default_target_path'];
diff --git a/src/Symfony/Component/Security/Http/HttpUtils.php b/src/Symfony/Component/Security/Http/HttpUtils.php
index ed737a2f61..56add79926 100644
--- a/src/Symfony/Component/Security/Http/HttpUtils.php
+++ b/src/Symfony/Component/Security/Http/HttpUtils.php
@@ -108,7 +108,7 @@ class HttpUtils
$parameters = $this->urlMatcher->match($request->getPathInfo());
}
- return $path === $parameters['_route'];
+ return isset($parameters['_route']) && $path === $parameters['_route'];
} catch (MethodNotAllowedException $e) {
return false;
} catch (ResourceNotFoundException $e) {
diff --git a/src/Symfony/Component/Security/Http/Tests/Authentication/DefaultAuthenticationSuccessHandlerTest.php b/src/Symfony/Component/Security/Http/Tests/Authentication/DefaultAuthenticationSuccessHandlerTest.php
index 577fa506bc..b42f840358 100644
--- a/src/Symfony/Component/Security/Http/Tests/Authentication/DefaultAuthenticationSuccessHandlerTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Authentication/DefaultAuthenticationSuccessHandlerTest.php
@@ -12,193 +12,92 @@
namespace Symfony\Component\Security\Http\Tests\Authentication;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
+use Symfony\Component\Security\Http\HttpUtils;
class DefaultAuthenticationSuccessHandlerTest extends TestCase
{
- private $httpUtils = null;
-
- private $request = null;
-
- private $token = null;
-
- protected function setUp()
+ /**
+ * @dataProvider getRequestRedirections
+ */
+ public function testRequestRedirections(Request $request, $options, $redirectedUrl)
{
- $this->httpUtils = $this->getMockBuilder('Symfony\Component\Security\Http\HttpUtils')->getMock();
- $this->request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock();
- $this->request->headers = $this->getMockBuilder('Symfony\Component\HttpFoundation\HeaderBag')->getMock();
- $this->token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock();
+ $urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock();
+ $urlGenerator->expects($this->any())->method('generate')->will($this->returnValue('http://localhost/login'));
+ $httpUtils = new HttpUtils($urlGenerator);
+ $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock();
+ $handler = new DefaultAuthenticationSuccessHandler($httpUtils, $options);
+ if ($request->hasSession()) {
+ $handler->setProviderKey('admin');
+ }
+ $this->assertSame('http://localhost'.$redirectedUrl, $handler->onAuthenticationSuccess($request, $token)->getTargetUrl());
}
- public function testRequestIsRedirected()
- {
- $response = $this->expectRedirectResponse('/');
-
- $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array());
- $result = $handler->onAuthenticationSuccess($this->request, $this->token);
-
- $this->assertSame($response, $result);
- }
-
- public function testDefaultTargetPathCanBeForced()
- {
- $options = array(
- 'always_use_default_target_path' => true,
- 'default_target_path' => '/dashboard',
- );
-
- $response = $this->expectRedirectResponse('/dashboard');
-
- $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options);
- $result = $handler->onAuthenticationSuccess($this->request, $this->token);
-
- $this->assertSame($response, $result);
- }
-
- public function testTargetPathIsPassedWithRequest()
- {
- $this->request->expects($this->once())
- ->method('get')->with('_target_path')
- ->will($this->returnValue('/dashboard'));
-
- $response = $this->expectRedirectResponse('/dashboard');
-
- $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array());
- $result = $handler->onAuthenticationSuccess($this->request, $this->token);
-
- $this->assertSame($response, $result);
- }
-
- public function testTargetPathIsPassedAsNestedParameterWithRequest()
- {
- $this->request->expects($this->once())
- ->method('get')->with('_target_path')
- ->will($this->returnValue(array('value' => '/dashboard')));
-
- $response = $this->expectRedirectResponse('/dashboard');
-
- $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array('target_path_parameter' => '_target_path[value]'));
- $result = $handler->onAuthenticationSuccess($this->request, $this->token);
-
- $this->assertSame($response, $result);
- }
-
- public function testTargetPathParameterIsCustomised()
- {
- $options = array('target_path_parameter' => '_my_target_path');
-
- $this->request->expects($this->once())
- ->method('get')->with('_my_target_path')
- ->will($this->returnValue('/dashboard'));
-
- $response = $this->expectRedirectResponse('/dashboard');
-
- $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options);
- $result = $handler->onAuthenticationSuccess($this->request, $this->token);
-
- $this->assertSame($response, $result);
- }
-
- public function testTargetPathIsTakenFromTheSession()
+ public function getRequestRedirections()
{
$session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')->getMock();
- $session->expects($this->once())
- ->method('get')->with('_security.admin.target_path')
- ->will($this->returnValue('/admin/dashboard'));
- $session->expects($this->once())
- ->method('remove')->with('_security.admin.target_path');
+ $session->expects($this->once())->method('get')->with('_security.admin.target_path')->will($this->returnValue('/admin/dashboard'));
+ $session->expects($this->once())->method('remove')->with('_security.admin.target_path');
+ $requestWithSession = Request::create('/');
+ $requestWithSession->setSession($session);
- $this->request->expects($this->any())
- ->method('getSession')
- ->will($this->returnValue($session));
-
- $response = $this->expectRedirectResponse('/admin/dashboard');
-
- $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array());
- $handler->setProviderKey('admin');
-
- $result = $handler->onAuthenticationSuccess($this->request, $this->token);
-
- $this->assertSame($response, $result);
- }
-
- public function testTargetPathIsPassedAsReferer()
- {
- $options = array('use_referer' => true);
-
- $this->request->headers->expects($this->once())
- ->method('get')->with('Referer')
- ->will($this->returnValue('/dashboard'));
-
- $response = $this->expectRedirectResponse('/dashboard');
-
- $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options);
- $result = $handler->onAuthenticationSuccess($this->request, $this->token);
-
- $this->assertSame($response, $result);
- }
-
- public function testRefererHasToBeDifferentThanLoginUrl()
- {
- $options = array('use_referer' => true);
-
- $this->request->headers->expects($this->any())
- ->method('get')->with('Referer')
- ->will($this->returnValue('/login'));
-
- $this->httpUtils->expects($this->once())
- ->method('generateUri')->with($this->request, '/login')
- ->will($this->returnValue('/login'));
-
- $response = $this->expectRedirectResponse('/');
-
- $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options);
- $result = $handler->onAuthenticationSuccess($this->request, $this->token);
-
- $this->assertSame($response, $result);
- }
-
- public function testRefererWithoutParametersHasToBeDifferentThanLoginUrl()
- {
- $options = array('use_referer' => true);
-
- $this->request->headers->expects($this->any())
- ->method('get')->with('Referer')
- ->will($this->returnValue('/subfolder/login?t=1&p=2'));
-
- $this->httpUtils->expects($this->once())
- ->method('generateUri')->with($this->request, '/login')
- ->will($this->returnValue('/subfolder/login'));
-
- $response = $this->expectRedirectResponse('/');
-
- $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, $options);
- $result = $handler->onAuthenticationSuccess($this->request, $this->token);
-
- $this->assertSame($response, $result);
- }
-
- public function testRefererTargetPathIsIgnoredByDefault()
- {
- $this->request->headers->expects($this->never())->method('get');
-
- $response = $this->expectRedirectResponse('/');
-
- $handler = new DefaultAuthenticationSuccessHandler($this->httpUtils, array());
- $result = $handler->onAuthenticationSuccess($this->request, $this->token);
-
- $this->assertSame($response, $result);
- }
-
- private function expectRedirectResponse($path)
- {
- $response = new Response();
- $this->httpUtils->expects($this->once())
- ->method('createRedirectResponse')
- ->with($this->request, $path)
- ->will($this->returnValue($response));
-
- return $response;
+ return array(
+ 'default' => array(
+ Request::create('/'),
+ array(),
+ '/',
+ ),
+ 'forced target path' => array(
+ Request::create('/'),
+ array('always_use_default_target_path' => true, 'default_target_path' => '/dashboard'),
+ '/dashboard',
+ ),
+ 'target path as query string' => array(
+ Request::create('/?_target_path=/dashboard'),
+ array(),
+ '/dashboard',
+ ),
+ 'target path name as query string is customized' => array(
+ Request::create('/?_my_target_path=/dashboard'),
+ array('target_path_parameter' => '_my_target_path'),
+ '/dashboard',
+ ),
+ 'target path name as query string is customized and nested' => array(
+ Request::create('/?_target_path[value]=/dashboard'),
+ array('target_path_parameter' => '_target_path[value]'),
+ '/dashboard',
+ ),
+ 'target path in session' => array(
+ $requestWithSession,
+ array(),
+ '/admin/dashboard',
+ ),
+ 'target path as referer' => array(
+ Request::create('/', 'GET', array(), array(), array(), array('HTTP_REFERER' => 'http://localhost/dashboard')),
+ array('use_referer' => true),
+ '/dashboard',
+ ),
+ 'target path as referer is ignored if not configured' => array(
+ Request::create('/', 'GET', array(), array(), array(), array('HTTP_REFERER' => 'http://localhost/dashboard')),
+ array(),
+ '/',
+ ),
+ 'target path should be different than login URL' => array(
+ Request::create('/', 'GET', array(), array(), array(), array('HTTP_REFERER' => 'http://localhost/login')),
+ array('use_referer' => true, 'login_path' => '/login'),
+ '/',
+ ),
+ 'target path should be different than login URL (query string does not matter)' => array(
+ Request::create('/', 'GET', array(), array(), array(), array('HTTP_REFERER' => 'http://localhost/login?t=1&p=2')),
+ array('use_referer' => true, 'login_path' => '/login'),
+ '/',
+ ),
+ 'target path should be different than login URL (login_path as a route)' => array(
+ Request::create('/', 'GET', array(), array(), array(), array('HTTP_REFERER' => 'http://localhost/login?t=1&p=2')),
+ array('use_referer' => true, 'login_path' => 'login_route'),
+ '/',
+ ),
+ );
}
}
diff --git a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php
index 3d0e63b6fe..b508012665 100644
--- a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php
@@ -221,6 +221,19 @@ class HttpUtilsTest extends TestCase
$utils->checkRequestPath($this->getRequest(), 'foobar');
}
+ public function testCheckPathWithoutRouteParam()
+ {
+ $urlMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface')->getMock();
+ $urlMatcher
+ ->expects($this->any())
+ ->method('match')
+ ->willReturn(array('_controller' => 'PathController'))
+ ;
+
+ $utils = new HttpUtils(null, $urlMatcher);
+ $this->assertFalse($utils->checkRequestPath($this->getRequest(), 'path/index.html'));
+ }
+
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Matcher must either implement UrlMatcherInterface or RequestMatcherInterface
diff --git a/src/Symfony/Component/VarDumper/Caster/DateCaster.php b/src/Symfony/Component/VarDumper/Caster/DateCaster.php
index 2fc57c190b..b96660605f 100644
--- a/src/Symfony/Component/VarDumper/Caster/DateCaster.php
+++ b/src/Symfony/Component/VarDumper/Caster/DateCaster.php
@@ -27,7 +27,7 @@ class DateCaster
$fromNow = (new \DateTime())->diff($d);
$title = $d->format('l, F j, Y')
- ."\n".$fromNow->format('%R').(ltrim($fromNow->format('%yy %mm %dd %H:%I:%Ss'), ' 0ymd:s') ?: '0s').' from now'
+ ."\n".$fromNow->format('%R').self::formatInterval($fromNow).' from now'
.($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '')
;
@@ -38,4 +38,34 @@ class DateCaster
return $a;
}
+
+ public static function castInterval(\DateInterval $interval, array $a, Stub $stub, $isNested, $filter)
+ {
+ $now = new \DateTimeImmutable();
+ $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp();
+ $title = number_format($numberOfSeconds, 0, '.', ' ').'s';
+
+ $i = array(Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title));
+
+ return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a;
+ }
+
+ private static function formatInterval(\DateInterval $i)
+ {
+ $format = '%R '
+ .($i->y ? '%yy ' : '')
+ .($i->m ? '%mm ' : '')
+ .($i->d ? '%dd ' : '')
+ ;
+
+ if (\PHP_VERSION_ID >= 70100 && isset($i->f)) {
+ $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:%S.%F' : '';
+ } else {
+ $format .= $i->h || $i->i || $i->s ? '%H:%I:%S' : '';
+ }
+
+ $format = '%R ' === $format ? '0s' : $format;
+
+ return $i->format(rtrim($format));
+ }
}
diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
index 1f0a5e2425..4ece7a7206 100644
--- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
+++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
@@ -110,6 +110,7 @@ abstract class AbstractCloner implements ClonerInterface
'RedisArray' => array('Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'),
'DateTimeInterface' => array('Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'),
+ 'DateInterval' => array('Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'),
':curl' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'),
':dba' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'),
diff --git a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php
index b266258623..83f6aa7c31 100644
--- a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php
+++ b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php
@@ -46,8 +46,8 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
{
$this->flags = (int) $flags;
$this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8');
- $this->decimalPoint = (string) 0.5;
- $this->decimalPoint = $this->decimalPoint[1];
+ $this->decimalPoint = localeconv();
+ $this->decimalPoint = $this->decimalPoint['decimal_point'];
$this->setOutput($output ?: static::$defaultOutput);
if (!$output && is_string(static::$defaultOutput)) {
static::$defaultOutput = $this->outputStream;
@@ -123,6 +123,13 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
*/
public function dump(Data $data, $output = null)
{
+ $this->decimalPoint = localeconv();
+ $this->decimalPoint = $this->decimalPoint['decimal_point'];
+
+ if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(LC_NUMERIC, 0) : null) {
+ setlocale(LC_NUMERIC, 'C');
+ }
+
if ($returnDump = true === $output) {
$output = fopen('php://memory', 'r+b');
}
@@ -143,6 +150,9 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
if ($output) {
$this->setOutput($prevOutput);
}
+ if ($locale) {
+ setlocale(LC_NUMERIC, $locale);
+ }
}
}
diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php
index 4de1f39efe..d5860df5e1 100644
--- a/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php
+++ b/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\VarDumper\Tests\Caster;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Caster\DateCaster;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
@@ -80,4 +81,103 @@ EODUMP;
array('2017-04-30 00:00:00.000000', '+02:00', '2017-04-30 00:00:00.000000 +02:00'),
);
}
+
+ /**
+ * @dataProvider provideIntervals
+ */
+ public function testDumpInterval($intervalSpec, $invert, $expected)
+ {
+ $interval = new \DateInterval($intervalSpec);
+ $interval->invert = $invert;
+
+ $xDump = <<assertDumpMatchesFormat($xDump, $interval);
+ }
+
+ /**
+ * @dataProvider provideIntervals
+ */
+ public function testDumpIntervalExcludingVerbosity($intervalSpec, $invert, $expected)
+ {
+ $interval = new \DateInterval($intervalSpec);
+ $interval->invert = $invert;
+
+ $xDump = <<assertDumpMatchesFormat($xDump, $interval, Caster::EXCLUDE_VERBOSE);
+ }
+
+ /**
+ * @dataProvider provideIntervals
+ */
+ public function testCastInterval($intervalSpec, $invert, $xInterval, $xSeconds)
+ {
+ $interval = new \DateInterval($intervalSpec);
+ $interval->invert = $invert;
+ $stub = new Stub();
+
+ $cast = DateCaster::castInterval($interval, array('foo' => 'bar'), $stub, false, Caster::EXCLUDE_VERBOSE);
+
+ $xDump = << $xInterval
+]
+EODUMP;
+
+ $this->assertDumpMatchesFormat($xDump, $cast);
+
+ if (null === $xSeconds) {
+ return;
+ }
+
+ $xDump = <<assertDumpMatchesFormat($xDump, $cast["\0~\0interval"]);
+ }
+
+ public function provideIntervals()
+ {
+ $i = new \DateInterval('PT0S');
+ $ms = \PHP_VERSION_ID >= 70100 && isset($i->f) ? '.000000' : '';
+
+ return array(
+ array('PT0S', 0, '0s', '0s'),
+ array('PT1S', 0, '+ 00:00:01'.$ms, '1s'),
+ array('PT2M', 0, '+ 00:02:00'.$ms, '120s'),
+ array('PT3H', 0, '+ 03:00:00'.$ms, '10 800s'),
+ array('P4D', 0, '+ 4d', '345 600s'),
+ array('P5M', 0, '+ 5m', null),
+ array('P6Y', 0, '+ 6y', null),
+ array('P1Y2M3DT4H5M6S', 0, '+ 1y 2m 3d 04:05:06'.$ms, null),
+
+ array('PT0S', 1, '0s', '0s'),
+ array('PT1S', 1, '- 00:00:01'.$ms, '-1s'),
+ array('PT2M', 1, '- 00:02:00'.$ms, '-120s'),
+ array('PT3H', 1, '- 03:00:00'.$ms, '-10 800s'),
+ array('P4D', 1, '- 4d', '-345 600s'),
+ array('P5M', 1, '- 5m', null),
+ array('P6Y', 1, '- 6y', null),
+ array('P1Y2M3DT4H5M6S', 1, '- 1y 2m 3d 04:05:06'.$ms, null),
+ );
+ }
}