diff --git a/link b/link
index a5030d0683..6a2ec15e22 100755
--- a/link
+++ b/link
@@ -37,7 +37,12 @@ if (!is_dir("$argv[1]/vendor/symfony")) {
$sfPackages = array('symfony/symfony' => __DIR__);
$filesystem = new Filesystem();
-foreach (glob(__DIR__.'/src/Symfony/{Bundle,Bridge,Component,Component/Security}/*', GLOB_BRACE | GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
+$braces = array('Bundle', 'Bridge', 'Component', 'Component/Security');
+$directories = call_user_func_array('array_merge', array_values(array_map(function ($part) {
+ return glob(__DIR__.'/src/Symfony/'.$part.'/*', GLOB_ONLYDIR | GLOB_NOSORT);
+}, $braces)));
+
+foreach ($directories as $dir) {
if ($filesystem->exists($composer = "$dir/composer.json")) {
$sfPackages[json_decode(file_get_contents($composer))->name] = $dir;
}
diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php
index f32e155078..abb1241653 100644
--- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php
+++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php
@@ -214,41 +214,62 @@ class DeprecationErrorHandler
return $b['count'] - $a['count'];
};
- $groups = array('unsilenced', 'remaining');
- if (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode) {
- $groups[] = 'remaining vendor';
- }
- array_push($groups, 'legacy', 'other');
+ $displayDeprecations = function ($deprecations) use ($colorize, $cmp) {
+ $groups = array('unsilenced', 'remaining');
+ if (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode) {
+ $groups[] = 'remaining vendor';
+ }
+ array_push($groups, 'legacy', 'other');
- foreach ($groups as $group) {
- if ($deprecations[$group.'Count']) {
- echo "\n", $colorize(
- sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']),
- 'legacy' !== $group && 'remaining vendor' !== $group
- ), "\n";
+ foreach ($groups as $group) {
+ if ($deprecations[$group.'Count']) {
+ echo "\n", $colorize(
+ sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']),
+ 'legacy' !== $group && 'remaining vendor' !== $group
+ ), "\n";
- uasort($deprecations[$group], $cmp);
+ uasort($deprecations[$group], $cmp);
- foreach ($deprecations[$group] as $msg => $notices) {
- echo "\n ", $notices['count'], 'x: ', $msg, "\n";
+ foreach ($deprecations[$group] as $msg => $notices) {
+ echo "\n ", $notices['count'], 'x: ', $msg, "\n";
- arsort($notices);
+ arsort($notices);
- foreach ($notices as $method => $count) {
- if ('count' !== $method) {
- echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n";
+ foreach ($notices as $method => $count) {
+ if ('count' !== $method) {
+ echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n";
+ }
}
}
}
}
- }
- if (!empty($notices)) {
- echo "\n";
+ if (!empty($notices)) {
+ echo "\n";
+ }
+ };
+
+ $displayDeprecations($deprecations);
+
+ // store failing status
+ $isFailing = DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount'];
+
+ // reset deprecations array
+ foreach ($deprecations as $group => $arrayOrInt) {
+ $deprecations[$group] = is_int($arrayOrInt) ? 0 : array();
}
- if (DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) {
- exit(1);
- }
+ register_shutdown_function(function () use (&$deprecations, $isFailing, $displayDeprecations, $mode) {
+ foreach ($deprecations as $group => $arrayOrInt) {
+ if (0 < (is_int($arrayOrInt) ? $arrayOrInt : count($arrayOrInt))) {
+ echo "Shutdown-time deprecations:\n";
+ break;
+ }
+ }
+ $displayDeprecations($deprecations);
+ if ($isFailing || DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) {
+ exit(1);
+ }
+ });
});
}
}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php
similarity index 76%
rename from src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php
rename to src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php
index 5ed545a512..2da40f2c20 100644
--- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php
+++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php
@@ -18,7 +18,7 @@ namespace Symfony\Bridge\PhpUnit\Legacy;
*
* @internal
*/
-class SymfonyTestsListener extends \PHPUnit_Framework_BaseTestListener
+class SymfonyTestsListenerForV5 extends \PHPUnit_Framework_BaseTestListener
{
private $trait;
@@ -34,26 +34,26 @@ class SymfonyTestsListener extends \PHPUnit_Framework_BaseTestListener
public function startTestSuite(\PHPUnit_Framework_TestSuite $suite)
{
- return $this->trait->startTestSuite($suite);
+ $this->trait->startTestSuite($suite);
}
public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time)
{
- return $this->trait->addSkippedTest($test, $e, $time);
+ $this->trait->addSkippedTest($test, $e, $time);
}
public function startTest(\PHPUnit_Framework_Test $test)
{
- return $this->trait->startTest($test);
+ $this->trait->startTest($test);
}
public function addWarning(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time)
{
- return $this->trait->addWarning($test, $e, $time);
+ $this->trait->addWarning($test, $e, $time);
}
public function endTest(\PHPUnit_Framework_Test $test, $time)
{
- return $this->trait->endTest($test, $time);
+ $this->trait->endTest($test, $time);
}
}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php
new file mode 100644
index 0000000000..d8e74e8192
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PhpUnit\Legacy;
+
+use PHPUnit\Framework\BaseTestListener;
+use PHPUnit\Framework\Test;
+use PHPUnit\Framework\TestSuite;
+use PHPUnit\Framework\Warning;
+
+/**
+ * Collects and replays skipped tests.
+ *
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+class SymfonyTestsListenerForV6 extends BaseTestListener
+{
+ private $trait;
+
+ public function __construct(array $mockedNamespaces = array())
+ {
+ $this->trait = new Legacy\SymfonyTestsListenerTrait($mockedNamespaces);
+ }
+
+ public function globalListenerDisabled()
+ {
+ $this->trait->globalListenerDisabled();
+ }
+
+ public function startTestSuite(TestSuite $suite)
+ {
+ $this->trait->startTestSuite($suite);
+ }
+
+ public function addSkippedTest(Test $test, \Exception $e, $time)
+ {
+ $this->trait->addSkippedTest($test, $e, $time);
+ }
+
+ public function startTest(Test $test)
+ {
+ $this->trait->startTest($test);
+ }
+
+ public function addWarning(Test $test, Warning $e, $time)
+ {
+ $this->trait->addWarning($test, $e, $time);
+ }
+
+ public function endTest(Test $test, $time)
+ {
+ $this->trait->endTest($test, $time);
+ }
+}
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php
new file mode 100644
index 0000000000..d3acd53960
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PhpUnit;
+
+use PHPUnit\Framework\Test;
+use PHPUnit\Framework\TestListener;
+use PHPUnit\Framework\TestListenerDefaultImplementation;
+use PHPUnit\Framework\TestSuite;
+use PHPUnit\Framework\Warning;
+
+/**
+ * Collects and replays skipped tests.
+ *
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+class SymfonyTestsListenerForV7 implements TestListener
+{
+ use TestListenerDefaultImplementation;
+
+ private $trait;
+
+ public function __construct(array $mockedNamespaces = array())
+ {
+ $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces);
+ }
+
+ public function globalListenerDisabled()
+ {
+ $this->trait->globalListenerDisabled();
+ }
+
+ public function startTestSuite(TestSuite $suite): void
+ {
+ $this->trait->startTestSuite($suite);
+ }
+
+ public function addSkippedTest(Test $test, \Throwable $t, float $time): void
+ {
+ $this->trait->addSkippedTest($test, $t, $time);
+ }
+
+ public function startTest(Test $test): void
+ {
+ $this->trait->startTest($test);
+ }
+
+ public function addWarning(Test $test, Warning $e, float $time): void
+ {
+ $this->trait->addWarning($test, $e, $time);
+ }
+
+ public function endTest(Test $test, float $time): void
+ {
+ $this->trait->endTest($test, $time);
+ }
+}
diff --git a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php
index c11fde9526..d46a79e00e 100644
--- a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php
+++ b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php
@@ -11,60 +11,10 @@
namespace Symfony\Bridge\PhpUnit;
-use PHPUnit\Framework\BaseTestListener;
-use PHPUnit\Framework\Test;
-use PHPUnit\Framework\TestSuite;
-use PHPUnit\Framework\Warning;
-
if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) {
- class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListener', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener');
-// Using an early return instead of a else does not work when using the PHPUnit phar due to some weird PHP behavior (the class
-// gets defined without executing the code before it and so the definition is not properly conditional)
+ class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener');
+} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) {
+ class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV6', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener');
} else {
- /**
- * Collects and replays skipped tests.
- *
- * @author Nicolas Grekas
- *
- * @final
- */
- class SymfonyTestsListener extends BaseTestListener
- {
- private $trait;
-
- public function __construct(array $mockedNamespaces = array())
- {
- $this->trait = new Legacy\SymfonyTestsListenerTrait($mockedNamespaces);
- }
-
- public function globalListenerDisabled()
- {
- $this->trait->globalListenerDisabled();
- }
-
- public function startTestSuite(TestSuite $suite)
- {
- return $this->trait->startTestSuite($suite);
- }
-
- public function addSkippedTest(Test $test, \Exception $e, $time)
- {
- return $this->trait->addSkippedTest($test, $e, $time);
- }
-
- public function startTest(Test $test)
- {
- return $this->trait->startTest($test);
- }
-
- public function addWarning(Test $test, Warning $e, $time)
- {
- return $this->trait->addWarning($test, $e, $time);
- }
-
- public function endTest(Test $test, $time)
- {
- return $this->trait->endTest($test, $time);
- }
- }
+ class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener');
}
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt
index 39a3e98586..7a0595a7dd 100644
--- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt
@@ -59,6 +59,10 @@ $foo = new FooTestCase();
$foo->testLegacyFoo();
$foo->testNonLegacyBar();
+register_shutdown_function(function () {
+ exit('I get precedence over any exit statements inside the deprecation error handler.');
+});
+
?>
--EXPECTF--
Unsilenced deprecation notices (3)
@@ -80,3 +84,4 @@ Other deprecation notices (1)
1x: root deprecation
+I get precedence over any exit statements inside the deprecation error handler.
diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt
new file mode 100644
index 0000000000..fddeed6085
--- /dev/null
+++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt
@@ -0,0 +1,91 @@
+--TEST--
+Test DeprecationErrorHandler in default mode
+--FILE--
+testLegacyFoo();
+$foo->testNonLegacyBar();
+
+register_shutdown_function(function () {
+ @trigger_error('root deprecation during shutdown', E_USER_DEPRECATED);
+});
+
+?>
+--EXPECTF--
+Unsilenced deprecation notices (3)
+
+ 2x: unsilenced foo deprecation
+ 2x in FooTestCase::testLegacyFoo
+
+ 1x: unsilenced bar deprecation
+ 1x in FooTestCase::testNonLegacyBar
+
+Remaining deprecation notices (1)
+
+ 1x: silenced bar deprecation
+ 1x in FooTestCase::testNonLegacyBar
+
+Legacy deprecation notices (1)
+
+Other deprecation notices (1)
+
+ 1x: root deprecation
+
+Shutdown-time deprecations:
+
+Other deprecation notices (1)
+
+ 1x: root deprecation during shutdown
diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit
index fbcedeca8c..fd7c800fed 100755
--- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit
+++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit
@@ -17,7 +17,7 @@ error_reporting(-1);
if (PHP_VERSION_ID >= 70200) {
// PHPUnit 6 is required for PHP 7.2+
- $PHPUNIT_VERSION = getenv('SYMFONY_PHPUNIT_VERSION') ?: '6.3';
+ $PHPUNIT_VERSION = getenv('SYMFONY_PHPUNIT_VERSION') ?: '6.5';
} elseif (PHP_VERSION_ID >= 50600) {
// PHPUnit 4 does not support PHP 7
$PHPUNIT_VERSION = getenv('SYMFONY_PHPUNIT_VERSION') ?: '5.7';
diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
index 6fc94bc8a2..1fb263f00f 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
@@ -370,12 +370,12 @@ trait ControllerTrait
/**
* Checks the validity of a CSRF token.
*
- * @param string $id The id used when generating the token
- * @param string $token The actual token sent with the request that should be validated
+ * @param string $id The id used when generating the token
+ * @param string|null $token The actual token sent with the request that should be validated
*
* @final since version 3.4
*/
- protected function isCsrfTokenValid(string $id, string $token): bool
+ protected function isCsrfTokenValid(string $id, ?string $token): bool
{
if (!$this->container->has('security.csrf.token_manager')) {
throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".');
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php
index e064ce9f17..9fcae720b2 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php
@@ -12,18 +12,11 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
class ProfilerPassTest extends TestCase
{
- private $profilerDefinition;
-
- protected function setUp()
- {
- $this->profilerDefinition = new Definition('ProfilerClass');
- }
-
/**
* Tests that collectors that specify a template but no "id" will throw
* an exception (both are needed if the template is specified).
@@ -31,17 +24,15 @@ class ProfilerPassTest extends TestCase
* Thus, a fully-valid tag looks something like this:
*
*
+ *
+ * @expectedException \InvalidArgumentException
*/
public function testTemplateNoIdThrowsException()
{
- // one service, with a template key, but no id
- $services = array(
- 'my_collector_service' => array(0 => array('template' => 'foo')),
- );
-
- $builder = $this->createContainerMock($services);
-
- $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
+ $builder = new ContainerBuilder();
+ $builder->register('profiler', 'ProfilerClass');
+ $builder->register('my_collector_service')
+ ->addTag('data_collector', array('template' => 'foo'));
$profilerPass = new ProfilerPass();
$profilerPass->process($builder);
@@ -49,45 +40,19 @@ class ProfilerPassTest extends TestCase
public function testValidCollector()
{
- // one service, with a template key, but no id
- $services = array(
- 'my_collector_service' => array(0 => array('template' => 'foo', 'id' => 'my_collector')),
- );
-
- $container = $this->createContainerMock($services);
-
- // fake the getDefinition() to return a Profiler definition
- $container->expects($this->atLeastOnce())
- ->method('getDefinition');
-
- // assert that the data_collector.templates parameter should be set
- $container->expects($this->once())
- ->method('setParameter')
- ->with('data_collector.templates', array('my_collector_service' => array('my_collector', 'foo')));
+ $container = new ContainerBuilder();
+ $profilerDefinition = $container->register('profiler', 'ProfilerClass');
+ $container->register('my_collector_service')
+ ->addTag('data_collector', array('template' => 'foo', 'id' => 'my_collector'));
$profilerPass = new ProfilerPass();
$profilerPass->process($container);
+ $this->assertSame(array('my_collector_service' => array('my_collector', 'foo')), $container->getParameter('data_collector.templates'));
+
// grab the method calls off of the "profiler" definition
- $methodCalls = $this->profilerDefinition->getMethodCalls();
+ $methodCalls = $profilerDefinition->getMethodCalls();
$this->assertCount(1, $methodCalls);
$this->assertEquals('add', $methodCalls[0][0]); // grab the method part of the first call
}
-
- private function createContainerMock($services)
- {
- $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'getDefinition', 'findTaggedServiceIds', 'setParameter'))->getMock();
- $container->expects($this->any())
- ->method('hasDefinition')
- ->with($this->equalTo('profiler'))
- ->will($this->returnValue(true));
- $container->expects($this->any())
- ->method('getDefinition')
- ->will($this->returnValue($this->profilerDefinition));
- $container->expects($this->atLeastOnce())
- ->method('findTaggedServiceIds')
- ->will($this->returnValue($services));
-
- return $container;
- }
}
diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php
index 6575f28c97..e142d18a84 100644
--- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php
+++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php
@@ -13,13 +13,14 @@ namespace Symfony\Bundle\TwigBundle\Tests\DependencyInjection\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Alias;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigLoaderPass;
class TwigLoaderPassTest extends TestCase
{
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var ContainerBuilder
*/
private $builder;
/**
@@ -33,64 +34,33 @@ class TwigLoaderPassTest extends TestCase
protected function setUp()
{
- $this->builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'setAlias', 'getDefinition'))->getMock();
+ $this->builder = new ContainerBuilder();
+ $this->builder->register('twig');
$this->chainLoader = new Definition('loader');
$this->pass = new TwigLoaderPass();
}
- public function testMapperPassWithOneTaggedLoaders()
+ public function testMapperPassWithOneTaggedLoader()
{
- $serviceIds = array(
- 'test_loader_1' => array(
- array(),
- ),
- );
-
- $this->builder->expects($this->once())
- ->method('hasDefinition')
- ->with('twig')
- ->will($this->returnValue(true));
- $this->builder->expects($this->once())
- ->method('findTaggedServiceIds')
- ->with('twig.loader')
- ->will($this->returnValue($serviceIds));
- $this->builder->expects($this->once())
- ->method('setAlias')
- ->with('twig.loader', 'test_loader_1')
- ->will($this->returnValue(new Alias('test_loader_1')));
+ $this->builder->register('test_loader_1')
+ ->addTag('twig.loader');
$this->pass->process($this->builder);
+
+ $this->assertSame('test_loader_1', (string) $this->builder->getAlias('twig.loader'));
}
public function testMapperPassWithTwoTaggedLoaders()
{
- $serviceIds = array(
- 'test_loader_1' => array(
- array(),
- ),
- 'test_loader_2' => array(
- array(),
- ),
- );
-
- $this->builder->expects($this->once())
- ->method('hasDefinition')
- ->with('twig')
- ->will($this->returnValue(true));
- $this->builder->expects($this->once())
- ->method('findTaggedServiceIds')
- ->with('twig.loader')
- ->will($this->returnValue($serviceIds));
- $this->builder->expects($this->once())
- ->method('getDefinition')
- ->with('twig.loader.chain')
- ->will($this->returnValue($this->chainLoader));
- $this->builder->expects($this->once())
- ->method('setAlias')
- ->with('twig.loader', 'twig.loader.chain')
- ->will($this->returnValue(new Alias('twig.loader.chain')));
+ $this->builder->setDefinition('twig.loader.chain', $this->chainLoader);
+ $this->builder->register('test_loader_1')
+ ->addTag('twig.loader');
+ $this->builder->register('test_loader_2')
+ ->addTag('twig.loader');
$this->pass->process($this->builder);
+
+ $this->assertSame('twig.loader.chain', (string) $this->builder->getAlias('twig.loader'));
$calls = $this->chainLoader->getMethodCalls();
$this->assertCount(2, $calls);
$this->assertEquals('addLoader', $calls[0][0]);
@@ -101,33 +71,15 @@ class TwigLoaderPassTest extends TestCase
public function testMapperPassWithTwoTaggedLoadersWithPriority()
{
- $serviceIds = array(
- 'test_loader_1' => array(
- array('priority' => 100),
- ),
- 'test_loader_2' => array(
- array('priority' => 200),
- ),
- );
-
- $this->builder->expects($this->once())
- ->method('hasDefinition')
- ->with('twig')
- ->will($this->returnValue(true));
- $this->builder->expects($this->once())
- ->method('findTaggedServiceIds')
- ->with('twig.loader')
- ->will($this->returnValue($serviceIds));
- $this->builder->expects($this->once())
- ->method('getDefinition')
- ->with('twig.loader.chain')
- ->will($this->returnValue($this->chainLoader));
- $this->builder->expects($this->once())
- ->method('setAlias')
- ->with('twig.loader', 'twig.loader.chain')
- ->will($this->returnValue(new Alias('twig.loader.chain')));
+ $this->builder->setDefinition('twig.loader.chain', $this->chainLoader);
+ $this->builder->register('test_loader_1')
+ ->addTag('twig.loader', array('priority' => 100));
+ $this->builder->register('test_loader_2')
+ ->addTag('twig.loader', array('priority' => 200));
$this->pass->process($this->builder);
+
+ $this->assertSame('twig.loader.chain', (string) $this->builder->getAlias('twig.loader'));
$calls = $this->chainLoader->getMethodCalls();
$this->assertCount(2, $calls);
$this->assertEquals('addLoader', $calls[0][0]);
@@ -141,15 +93,6 @@ class TwigLoaderPassTest extends TestCase
*/
public function testMapperPassWithZeroTaggedLoaders()
{
- $this->builder->expects($this->once())
- ->method('hasDefinition')
- ->with('twig')
- ->will($this->returnValue(true));
- $this->builder->expects($this->once())
- ->method('findTaggedServiceIds')
- ->with('twig.loader')
- ->will($this->returnValue(array()));
-
$this->pass->process($this->builder);
}
}
diff --git a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php
index e8563521ba..98d0e52693 100644
--- a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php
@@ -204,11 +204,12 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
public function getCalls()
{
- try {
- return $this->calls;
- } finally {
- $this->calls = array();
- }
+ return $this->calls;
+ }
+
+ public function clearCalls()
+ {
+ $this->calls = array();
}
protected function start($name)
diff --git a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php
index 33e08f167f..e4b08790ad 100644
--- a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php
+++ b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php
@@ -57,8 +57,7 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
{
$this->data = array();
foreach ($this->instances as $instance) {
- // Calling getCalls() will clear the calls.
- $instance->getCalls();
+ $instance->clearCalls();
}
}
diff --git a/src/Symfony/Component/Cache/Traits/FilesystemTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php
index bcb940cb0f..23974b3bc5 100644
--- a/src/Symfony/Component/Cache/Traits/FilesystemTrait.php
+++ b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php
@@ -36,9 +36,9 @@ trait FilesystemTrait
continue;
}
- if ($time >= (int) $expiresAt = fgets($h)) {
+ if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) {
fclose($h);
- $pruned = isset($expiresAt[0]) && @unlink($file) && !file_exists($file) && $pruned;
+ $pruned = @unlink($file) && !file_exists($file) && $pruned;
} else {
fclose($h);
}
@@ -60,11 +60,9 @@ trait FilesystemTrait
if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
continue;
}
- if ($now >= (int) $expiresAt = fgets($h)) {
+ if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) {
fclose($h);
- if (isset($expiresAt[0])) {
- @unlink($file);
- }
+ @unlink($file);
} else {
$i = rawurldecode(rtrim(fgets($h)));
$value = stream_get_contents($h);
@@ -94,7 +92,7 @@ trait FilesystemTrait
protected function doSave(array $values, $lifetime)
{
$ok = true;
- $expiresAt = time() + ($lifetime ?: 31557600); // 31557600s = 1 year
+ $expiresAt = $lifetime ? (time() + $lifetime) : 0;
foreach ($values as $id => $value) {
$ok = $this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".serialize($value), $expiresAt) && $ok;
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
index b3f0a4b51c..d34ee2fb77 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -120,6 +120,7 @@ class PhpDumper extends Dumper
'debug' => true,
'hot_path_tag' => 'container.hot_path',
'inline_class_loader_parameter' => 'container.dumper.inline_class_loader',
+ 'build_time' => time(),
), $options);
$this->namespace = $options['namespace'];
@@ -223,7 +224,7 @@ EOF;
array_pop($code);
$code["Container{$hash}/{$options['class']}.php"] = substr_replace($files[$options['class'].'.php'], "namespace ? "\nnamespace {$this->namespace};\n" : '';
- $time = time();
+ $time = $options['build_time'];
$id = hash('crc32', $hash.$time);
$code[$options['class'].'.php'] = <<getMockBuilder('Symfony\Component\DependencyInjection\ParameterBag\ParameterBag')
- ->setMethods(array('resolveValue'))
- ->getMock()
- ;
-
- $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')
- ->setMethods(array('getParameterBag'))
- ->getMock()
- ;
-
- $pb->expects($this->once())
- ->method('resolveValue')
- ->with($this->equalTo($enabled))
- ->will($this->returnValue($enabled))
- ;
-
- $container->expects($this->once())
- ->method('getParameterBag')
- ->will($this->returnValue($pb))
- ;
-
- $extension = $this->getMockBuilder('Symfony\Component\DependencyInjection\Extension\Extension')
- ->setMethods(array())
- ->getMockForAbstractClass()
- ;
-
- $r = new \ReflectionMethod('Symfony\Component\DependencyInjection\Extension\Extension', 'isConfigEnabled');
- $r->setAccessible(true);
-
- $r->invoke($extension, $container, array('enabled' => $enabled));
+ $extension = new EnableableExtension();
+ $this->assertSame($enabled, $extension->isConfigEnabled(new ContainerBuilder(), array('enabled' => $enabled)));
}
public function getResolvedEnabledFixtures()
@@ -66,18 +40,20 @@ class ExtensionTest extends TestCase
*/
public function testIsConfigEnabledOnNonEnableableConfig()
{
- $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')
- ->getMock()
- ;
+ $extension = new EnableableExtension();
- $extension = $this->getMockBuilder('Symfony\Component\DependencyInjection\Extension\Extension')
- ->setMethods(array())
- ->getMockForAbstractClass()
- ;
-
- $r = new \ReflectionMethod('Symfony\Component\DependencyInjection\Extension\Extension', 'isConfigEnabled');
- $r->setAccessible(true);
-
- $r->invoke($extension, $container, array());
+ $extension->isConfigEnabled(new ContainerBuilder(), array());
+ }
+}
+
+class EnableableExtension extends Extension
+{
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ }
+
+ public function isConfigEnabled(ContainerBuilder $container, array $config)
+ {
+ return parent::isConfigEnabled($container, $config);
}
}
diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php
index 1dab061f87..e7157b1b0a 100644
--- a/src/Symfony/Component/Finder/Finder.php
+++ b/src/Symfony/Component/Finder/Finder.php
@@ -313,6 +313,8 @@ class Finder implements \IteratorAggregate, \Countable
/**
* Excludes "hidden" directories and files (starting with a dot).
*
+ * This option is enabled by default.
+ *
* @param bool $ignoreDotFiles Whether to exclude "hidden" files or not
*
* @return $this
@@ -333,6 +335,8 @@ class Finder implements \IteratorAggregate, \Countable
/**
* Forces the finder to ignore version control directories.
*
+ * This option is enabled by default.
+ *
* @param bool $ignoreVCS Whether to exclude VCS files or not
*
* @return $this
diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php
index 9cd910ad18..8cb19c4790 100644
--- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php
+++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php
@@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
+use Symfony\Component\HttpFoundation\Request;
/**
* Creates the service-locators required by ServiceValueResolver.
@@ -148,6 +149,10 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
continue;
}
+ if (Request::class === $type) {
+ continue;
+ }
+
if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) {
$message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type);
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index 9632ca7e4a..61c35201a8 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -693,6 +693,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
'file' => $cache->getPath(),
'as_files' => true,
'debug' => $this->debug,
+ 'build_time' => $container->hasParameter('kernel.container_build_time') ? $container->getParameter('kernel.container_build_time') : time(),
));
$rootCode = array_pop($content);
diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php
index 81fc8b455d..ae421d919b 100644
--- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php
@@ -12,44 +12,39 @@
namespace Symfony\Component\HttpKernel\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;
class MergeExtensionConfigurationPassTest extends TestCase
{
public function testAutoloadMainExtension()
{
- $container = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ContainerBuilder')->setMethods(array('getExtensionConfig', 'loadFromExtension', 'getParameterBag', 'getDefinitions', 'getAliases', 'getExtensions'))->getMock();
- $params = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag')->getMock();
+ $container = new ContainerBuilder();
+ $container->registerExtension(new LoadedExtension());
+ $container->registerExtension(new NotLoadedExtension());
+ $container->loadFromExtension('loaded', array());
- $container->expects($this->at(0))
- ->method('getExtensionConfig')
- ->with('loaded')
- ->will($this->returnValue(array(array())));
- $container->expects($this->at(1))
- ->method('getExtensionConfig')
- ->with('notloaded')
- ->will($this->returnValue(array()));
- $container->expects($this->once())
- ->method('loadFromExtension')
- ->with('notloaded', array());
-
- $container->expects($this->any())
- ->method('getParameterBag')
- ->will($this->returnValue($params));
- $params->expects($this->any())
- ->method('all')
- ->will($this->returnValue(array()));
- $container->expects($this->any())
- ->method('getDefinitions')
- ->will($this->returnValue(array()));
- $container->expects($this->any())
- ->method('getAliases')
- ->will($this->returnValue(array()));
- $container->expects($this->any())
- ->method('getExtensions')
- ->will($this->returnValue(array()));
-
- $configPass = new MergeExtensionConfigurationPass(array('loaded', 'notloaded'));
+ $configPass = new MergeExtensionConfigurationPass(array('loaded', 'not_loaded'));
$configPass->process($container);
+
+ $this->assertTrue($container->hasDefinition('loaded.foo'));
+ $this->assertTrue($container->hasDefinition('not_loaded.bar'));
+ }
+}
+
+class LoadedExtension extends Extension
+{
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ $container->register('loaded.foo');
+ }
+}
+
+class NotLoadedExtension extends Extension
+{
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ $container->register('not_loaded.bar');
}
}
diff --git a/src/Symfony/Component/Lock/Lock.php b/src/Symfony/Component/Lock/Lock.php
index e906554f31..7936a5f7b6 100644
--- a/src/Symfony/Component/Lock/Lock.php
+++ b/src/Symfony/Component/Lock/Lock.php
@@ -89,7 +89,7 @@ final class Lock implements LockInterface, LoggerAwareInterface
return true;
} catch (LockConflictedException $e) {
$this->dirty = false;
- $this->logger->warning('Failed to acquire the "{resource}" lock. Someone else already acquired the lock.', array('resource' => $this->key));
+ $this->logger->notice('Failed to acquire the "{resource}" lock. Someone else already acquired the lock.', array('resource' => $this->key));
if ($blocking) {
throw $e;
@@ -97,7 +97,7 @@ final class Lock implements LockInterface, LoggerAwareInterface
return false;
} catch (\Exception $e) {
- $this->logger->warning('Failed to acquire the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e));
+ $this->logger->notice('Failed to acquire the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e));
throw new LockAcquiringException(sprintf('Failed to acquire the "%s" lock.', $this->key), 0, $e);
}
}
@@ -123,10 +123,10 @@ final class Lock implements LockInterface, LoggerAwareInterface
$this->logger->info('Expiration defined for "{resource}" lock for "{ttl}" seconds.', array('resource' => $this->key, 'ttl' => $this->ttl));
} catch (LockConflictedException $e) {
$this->dirty = false;
- $this->logger->warning('Failed to define an expiration for the "{resource}" lock, someone else acquired the lock.', array('resource' => $this->key));
+ $this->logger->notice('Failed to define an expiration for the "{resource}" lock, someone else acquired the lock.', array('resource' => $this->key));
throw $e;
} catch (\Exception $e) {
- $this->logger->warning('Failed to define an expiration for the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e));
+ $this->logger->notice('Failed to define an expiration for the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e));
throw new LockAcquiringException(sprintf('Failed to define an expiration for the "%s" lock.', $this->key), 0, $e);
}
}
@@ -148,7 +148,7 @@ final class Lock implements LockInterface, LoggerAwareInterface
$this->dirty = false;
if ($this->store->exists($this->key)) {
- $this->logger->warning('Failed to release the "{resource}" lock.', array('resource' => $this->key));
+ $this->logger->notice('Failed to release the "{resource}" lock.', array('resource' => $this->key));
throw new LockReleasingException(sprintf('Failed to release the "%s" lock.', $this->key));
}
}
diff --git a/src/Symfony/Component/Lock/Tests/LockTest.php b/src/Symfony/Component/Lock/Tests/LockTest.php
index ece5cf6679..7913644b9a 100644
--- a/src/Symfony/Component/Lock/Tests/LockTest.php
+++ b/src/Symfony/Component/Lock/Tests/LockTest.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\Lock\Tests;
use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
use Symfony\Component\Lock\Exception\LockConflictedException;
use Symfony\Component\Lock\Key;
use Symfony\Component\Lock\Lock;
@@ -192,6 +193,35 @@ class LockTest extends TestCase
$lock->release();
}
+ /**
+ * @expectedException \Symfony\Component\Lock\Exception\LockReleasingException
+ */
+ public function testReleaseThrowsAndLog()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
+ $lock = new Lock($key, $store, 10, true);
+ $lock->setLogger($logger);
+
+ $logger->expects($this->atLeastOnce())
+ ->method('notice')
+ ->with('Failed to release the "{resource}" lock.', array('resource' => $key));
+
+ $store
+ ->expects($this->once())
+ ->method('delete')
+ ->with($key);
+
+ $store
+ ->expects($this->once())
+ ->method('exists')
+ ->with($key)
+ ->willReturn(true);
+
+ $lock->release();
+ }
+
/**
* @dataProvider provideExpiredDates
*/
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php
index 89f2b10e22..182fcd8f23 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php
@@ -62,7 +62,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
not_get_and_head:
// post_and_head
- if ('/post_and_get' === $pathinfo) {
+ if ('/post_and_head' === $pathinfo) {
if (!in_array($requestMethod, array('POST', 'HEAD'))) {
$allow = array_merge($allow, array('POST', 'HEAD'));
goto not_post_and_head;
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
index e4c18c47b1..f29a6d6a30 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
@@ -354,7 +354,7 @@ class PhpMatcherDumperTest extends TestCase
array('GET', 'HEAD')
));
$headMatchCasesCollection->add('post_and_head', new Route(
- '/post_and_get',
+ '/post_and_head',
array(),
array(),
array(),
diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md
index 8253276c99..c680a19b16 100644
--- a/src/Symfony/Component/Serializer/CHANGELOG.md
+++ b/src/Symfony/Component/Serializer/CHANGELOG.md
@@ -30,6 +30,7 @@ CHANGELOG
* added getter for extra attributes in `ExtraAttributesException`
* improved `CsvEncoder` to handle variable nested structures
* CSV headers can be passed to the `CsvEncoder` via the `csv_headers` serialization context variable
+ * added `$context` when checking for encoding, decoding and normalizing in `Serializer`
3.3.0
-----
diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php
index 3c73ec3b20..92975a35bb 100644
--- a/src/Symfony/Component/Serializer/Serializer.php
+++ b/src/Symfony/Component/Serializer/Serializer.php
@@ -98,11 +98,11 @@ class Serializer implements SerializerInterface, ContextAwareNormalizerInterface
*/
final public function serialize($data, $format, array $context = array())
{
- if (!$this->supportsEncoding($format)) {
+ if (!$this->supportsEncoding($format, $context)) {
throw new NotEncodableValueException(sprintf('Serialization for the format %s is not supported', $format));
}
- if ($this->encoder->needsNormalization($format)) {
+ if ($this->encoder->needsNormalization($format, $context)) {
$data = $this->normalize($data, $format, $context);
}
@@ -114,7 +114,7 @@ class Serializer implements SerializerInterface, ContextAwareNormalizerInterface
*/
final public function deserialize($data, $type, $format, array $context = array())
{
- if (!$this->supportsDecoding($format)) {
+ if (!$this->supportsDecoding($format, $context)) {
throw new NotEncodableValueException(sprintf('Deserialization for the format %s is not supported', $format));
}
diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php
index 254009733e..6681379816 100644
--- a/src/Symfony/Component/Yaml/Dumper.php
+++ b/src/Symfony/Component/Yaml/Dumper.php
@@ -63,7 +63,10 @@ class Dumper
foreach ($input as $key => $value) {
if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r\n")) {
- $output .= sprintf("%s%s%s |\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '');
+ // If the first line starts with a space character, the spec requires a blockIndicationIndicator
+ // http://www.yaml.org/spec/1.2/spec.html#id2793979
+ $blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : '';
+ $output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator);
foreach (preg_split('/\n|\r\n/', $value) as $row) {
$output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row);
diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php
index edcb734440..0a8c023d01 100644
--- a/src/Symfony/Component/Yaml/Tests/DumperTest.php
+++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php
@@ -388,6 +388,17 @@ YAML;
$this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK));
}
+ public function testDumpMultiLineStringAsScalarBlockWhenFirstLineHasLeadingSpace()
+ {
+ $data = array(
+ 'data' => array(
+ 'multi_line' => " the first line has leading spaces\nThe second line does not.",
+ ),
+ );
+
+ $this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK));
+ }
+
public function testCarriageReturnIsMaintainedWhenDumpingAsMultiLineLiteralBlock()
{
$this->assertSame("- \"a\\r\\nb\\nc\"\n", $this->dumper->dump(array("a\r\nb\nc"), 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK));
diff --git a/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml b/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml
new file mode 100644
index 0000000000..3f2dedd10e
--- /dev/null
+++ b/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml
@@ -0,0 +1,4 @@
+data:
+ multi_line: |4
+ the first line has leading spaces
+ The second line does not.