Merge branch '2.7' into 2.8

* 2.7:
  [DependencyInjection] fix dumped YAML snytax
  Remove InputOption::VALUE_REQUIRED mode from $default parameter description as InputOption::setDefault() throws an exception only when called in InputOption::VALUE_NONE mode. In practice the $default value could still be accessed in InputOption::VALUE_REQUIRED mode in case InputOption was never set but accessed from InputDefinition::getOption() method
  [Form] Fixed violation mapping if multiple forms are using the same (or part of the same) property path
  [TwigBridge] Symfony 3.1 forward compatibility
This commit is contained in:
Fabien Potencier 2016-02-16 07:09:38 +01:00
commit fc2fa79bb5
11 changed files with 65 additions and 55 deletions

View File

@ -12,6 +12,7 @@
namespace Symfony\Bridge\Twig\Extension; namespace Symfony\Bridge\Twig\Extension;
use Symfony\Component\Yaml\Dumper as YamlDumper; use Symfony\Component\Yaml\Dumper as YamlDumper;
use Symfony\Component\Yaml\Yaml;
/** /**
* Provides integration of the Yaml component with Twig. * Provides integration of the Yaml component with Twig.
@ -40,7 +41,7 @@ class YamlExtension extends \Twig_Extension
} }
if (defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) { if (defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) {
$dumpObjects = (int) $dumpObjects; return $dumper->dump($input, $inline, 0, is_bool($dumpObjects) ? Yaml::DUMP_OBJECT : 0);
} }
return $dumper->dump($input, $inline, 0, false, $dumpObjects); return $dumper->dump($input, $inline, 0, false, $dumpObjects);

View File

@ -390,7 +390,7 @@ class Command
* @param string $shortcut The shortcut (can be null) * @param string $shortcut The shortcut (can be null)
* @param int $mode The option mode: One of the InputOption::VALUE_* constants * @param int $mode The option mode: One of the InputOption::VALUE_* constants
* @param string $description A description text * @param string $description A description text
* @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or InputOption::VALUE_NONE) * @param mixed $default The default value (must be null for InputOption::VALUE_NONE)
* *
* @return Command The current instance * @return Command The current instance
*/ */

View File

@ -39,7 +39,7 @@ class InputOption
* @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int $mode The option mode: One of the VALUE_* constants * @param int $mode The option mode: One of the VALUE_* constants
* @param string $description A description text * @param string $description A description text
* @param mixed $default The default value (must be null for self::VALUE_REQUIRED or self::VALUE_NONE) * @param mixed $default The default value (must be null for self::VALUE_NONE)
* *
* @throws InvalidArgumentException If option mode is invalid or incompatible * @throws InvalidArgumentException If option mode is invalid or incompatible
*/ */

View File

@ -65,7 +65,7 @@ class YamlDumper extends Dumper
$class = substr($class, 1); $class = substr($class, 1);
} }
$code .= sprintf(" class: %s\n", $class); $code .= sprintf(" class: %s\n", $this->dumper->dump($class));
} }
if (!$definition->isPublic()) { if (!$definition->isPublic()) {
@ -117,7 +117,7 @@ class YamlDumper extends Dumper
} }
if ($definition->getFactoryClass(false)) { if ($definition->getFactoryClass(false)) {
$code .= sprintf(" factory_class: %s\n", $definition->getFactoryClass(false)); $code .= sprintf(" factory_class: %s\n", $this->dumper->dump($definition->getFactoryClass(false)));
} }
if ($definition->isLazy()) { if ($definition->isLazy()) {

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\YamlDumper; use Symfony\Component\DependencyInjection\Dumper\YamlDumper;
use Symfony\Component\Yaml\Yaml;
class YamlDumperTest extends \PHPUnit_Framework_TestCase class YamlDumperTest extends \PHPUnit_Framework_TestCase
{ {
@ -27,17 +28,14 @@ class YamlDumperTest extends \PHPUnit_Framework_TestCase
{ {
$dumper = new YamlDumper($container = new ContainerBuilder()); $dumper = new YamlDumper($container = new ContainerBuilder());
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services1.yml', $dumper->dump(), '->dump() dumps an empty container as an empty YAML file'); $this->assertEqualYamlStructure(self::$fixturesPath.'/yaml/services1.yml', $dumper->dump(), '->dump() dumps an empty container as an empty YAML file');
$container = new ContainerBuilder();
$dumper = new YamlDumper($container);
} }
public function testAddParameters() public function testAddParameters()
{ {
$container = include self::$fixturesPath.'/containers/container8.php'; $container = include self::$fixturesPath.'/containers/container8.php';
$dumper = new YamlDumper($container); $dumper = new YamlDumper($container);
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services8.yml', $dumper->dump(), '->dump() dumps parameters'); $this->assertEqualYamlStructure(self::$fixturesPath.'/yaml/services8.yml', $dumper->dump(), '->dump() dumps parameters');
} }
/** /**
@ -65,7 +63,7 @@ class YamlDumperTest extends \PHPUnit_Framework_TestCase
{ {
$container = include self::$fixturesPath.'/containers/container9.php'; $container = include self::$fixturesPath.'/containers/container9.php';
$dumper = new YamlDumper($container); $dumper = new YamlDumper($container);
$this->assertEquals(str_replace('%path%', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR, file_get_contents(self::$fixturesPath.'/yaml/services9.yml')), $dumper->dump(), '->dump() dumps services'); $this->assertEqualYamlStructure(str_replace('%path%', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR, file_get_contents(self::$fixturesPath.'/yaml/services9.yml')), $dumper->dump(), '->dump() dumps services');
$dumper = new YamlDumper($container = new ContainerBuilder()); $dumper = new YamlDumper($container = new ContainerBuilder());
$container->register('foo', 'FooClass')->addArgument(new \stdClass()); $container->register('foo', 'FooClass')->addArgument(new \stdClass());
@ -84,4 +82,9 @@ class YamlDumperTest extends \PHPUnit_Framework_TestCase
$dumper = new YamlDumper($container); $dumper = new YamlDumper($container);
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services24.yml', $dumper->dump()); $this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services24.yml', $dumper->dump());
} }
private function assertEqualYamlStructure($yaml, $expected, $message = '')
{
$this->assertEquals(Yaml::parse($expected), Yaml::parse($yaml), $message);
}
} }

View File

@ -19,8 +19,8 @@ services:
configurator: sc_configure configurator: sc_configure
foo.baz: foo.baz:
class: %baz_class% class: '%baz_class%'
factory_class: %baz_class% factory_class: '%baz_class%'
factory_method: getInstance factory_method: getInstance
configurator: ['%baz_class%', configureStatic1] configurator: ['%baz_class%', configureStatic1]
factory_service: factory_service:
@ -28,6 +28,6 @@ services:
factory_method: getInstance factory_method: getInstance
factory_service: foo.baz factory_service: foo.baz
foo_bar: foo_bar:
class: %foo_class% class: '%foo_class%'
shared: false shared: false
scope: prototype scope: prototype

View File

@ -6,4 +6,4 @@ services:
class: BAR class: BAR
project: project:
test: %project.parameter.foo% test: '%project.parameter.foo%'

View File

@ -2,7 +2,7 @@ services:
foo: { class: FooClass } foo: { class: FooClass }
baz: { class: BazClass } baz: { class: BazClass }
not_shared: { class: FooClass, shared: false } not_shared: { class: FooClass, shared: false }
file: { class: FooClass, file: %path%/foo.php } file: { class: FooClass, file: '%path%/foo.php' }
arguments: { class: FooClass, arguments: [foo, '@foo', [true, false]] } arguments: { class: FooClass, arguments: [foo, '@foo', [true, false]] }
configurator1: { class: FooClass, configurator: sc_configure } configurator1: { class: FooClass, configurator: sc_configure }
configurator2: { class: FooClass, configurator: ['@baz', configure] } configurator2: { class: FooClass, configurator: ['@baz', configure] }

View File

@ -18,7 +18,7 @@ services:
factory: [Bar\FooClass, getInstance] factory: [Bar\FooClass, getInstance]
configurator: sc_configure configurator: sc_configure
foo.baz: foo.baz:
class: %baz_class% class: '%baz_class%'
factory: ['%baz_class%', getInstance] factory: ['%baz_class%', getInstance]
configurator: ['%baz_class%', configureStatic1] configurator: ['%baz_class%', configureStatic1]
bar: bar:
@ -26,11 +26,11 @@ services:
arguments: [foo, '@foo.baz', '%foo_bar%'] arguments: [foo, '@foo.baz', '%foo_bar%']
configurator: ['@foo.baz', configure] configurator: ['@foo.baz', configure]
foo_bar: foo_bar:
class: %foo_class% class: '%foo_class%'
shared: false shared: false
method_call1: method_call1:
class: Bar\FooClass class: Bar\FooClass
file: %path%foo.php file: '%path%foo.php'
calls: calls:
- [setBar, ['@foo']] - [setBar, ['@foo']]
- [setBar, ['@?foo2']] - [setBar, ['@?foo2']]

View File

@ -148,12 +148,9 @@ class ViolationMapper implements ViolationMapperInterface
*/ */
private function matchChild(FormInterface $form, PropertyPathIteratorInterface $it) private function matchChild(FormInterface $form, PropertyPathIteratorInterface $it)
{ {
// Remember at what property path underneath "data" $target = null;
// we are looking. Check if there is a child with that
// path, otherwise increase path by one more piece
$chunk = ''; $chunk = '';
$foundChild = null; $foundAtIndex = null;
$foundAtIndex = 0;
// Construct mapping rules for the given form // Construct mapping rules for the given form
$rules = array(); $rules = array();
@ -165,17 +162,11 @@ class ViolationMapper implements ViolationMapperInterface
} }
} }
// Skip forms inheriting their parent data when iterating the children $children = iterator_to_array(new \RecursiveIteratorIterator(
$childIterator = new \RecursiveIteratorIterator(
new InheritDataAwareIterator($form) new InheritDataAwareIterator($form)
); ));
// Make the path longer until we find a matching child
while (true) {
if (!$it->valid()) {
return;
}
while ($it->valid()) {
if ($it->isIndex()) { if ($it->isIndex()) {
$chunk .= '['.$it->current().']'; $chunk .= '['.$it->current().']';
} else { } else {
@ -197,33 +188,27 @@ class ViolationMapper implements ViolationMapperInterface
} }
} }
// Test children unless we already found one /** @var FormInterface $child */
if (null === $foundChild) { foreach ($children as $key => $child) {
foreach ($childIterator as $child) { $childPath = (string) $child->getPropertyPath();
/* @var FormInterface $child */ if ($childPath === $chunk) {
$childPath = (string) $child->getPropertyPath(); $target = $child;
$foundAtIndex = $it->key();
// Child found, mark as return value } elseif (0 === strpos($childPath, $chunk)) {
if ($chunk === $childPath) { continue;
$foundChild = $child;
$foundAtIndex = $it->key();
}
} }
unset($children[$key]);
} }
// Add element to the chunk
$it->next(); $it->next();
// If we reached the end of the path or if there are no
// more matching mapping rules, return the found child
if (null !== $foundChild && (!$it->valid() || count($rules) === 0)) {
// Reset index in case we tried to find mapping
// rules further down the path
$it->seek($foundAtIndex);
return $foundChild;
}
} }
if (null !== $foundAtIndex) {
$it->seek($foundAtIndex);
}
return $target;
} }
/** /**

View File

@ -1539,4 +1539,25 @@ class ViolationMapperTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array($this->getFormError($violation, $grandChild)), iterator_to_array($grandChild->getErrors()), $grandChildName.' should have an error, but has none'); $this->assertEquals(array($this->getFormError($violation, $grandChild)), iterator_to_array($grandChild->getErrors()), $grandChildName.' should have an error, but has none');
} }
} }
public function testBacktrackIfSeveralSubFormsWithSamePropertyPath()
{
$violation = $this->getConstraintViolation('data.address[street]');
$parent = $this->getForm('parent');
$child1 = $this->getForm('subform1', 'address');
$child2 = $this->getForm('subform2', 'address');
$grandChild = $this->getForm('street');
$parent->add($child1);
$parent->add($child2);
$child2->add($grandChild);
$this->mapper->mapViolation($violation, $parent);
// The error occurred on the child of the second form with the same path
$this->assertCount(0, $parent->getErrors(), $parent->getName().' should not have an error, but has one');
$this->assertCount(0, $child1->getErrors(), $child1->getName().' should not have an error, but has one');
$this->assertCount(0, $child2->getErrors(), $child2->getName().' should not have an error, but has one');
$this->assertEquals(array($this->getFormError()), $grandChild->getErrors(), $grandChild->getName().' should have an error, but has none');
}
} }