Skip to content

[Config] allow changing the path separator #22253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions UPGRADE-4.1.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
UPGRADE FROM 4.0 to 4.1
=======================

Config
------

* Implementing `ParentNodeDefinitionInterface` without the `getChildNodeDefinitions()` method
is deprecated and will be unsupported in 5.0.

EventDispatcher
---------------

Expand Down
5 changes: 5 additions & 0 deletions UPGRADE-5.0.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
UPGRADE FROM 4.x to 5.0
=======================

Config
------

* Added the `getChildNodeDefinitions()` method to `ParentNodeDefinitionInterface`.

EventDispatcher
---------------

Expand Down
6 changes: 6 additions & 0 deletions src/Symfony/Component/Config/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
CHANGELOG
=========

4.1.0
-----

* added `setPathSeparator` method to `NodeBuilder` class
* added third `$pathSeparator` constructor argument to `BaseNode`

4.0.0
-----

Expand Down
16 changes: 9 additions & 7 deletions src/Symfony/Component/Config/Definition/BaseNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
*/
abstract class BaseNode implements NodeInterface
{
const DEFAULT_PATH_SEPARATOR = '.';

protected $name;
protected $parent;
protected $normalizationClosures = array();
Expand All @@ -32,18 +34,20 @@ abstract class BaseNode implements NodeInterface
protected $deprecationMessage = null;
protected $equivalentValues = array();
protected $attributes = array();
protected $pathSeparator;

/**
* @throws \InvalidArgumentException if the name contains a period
*/
public function __construct(?string $name, NodeInterface $parent = null)
public function __construct(?string $name, NodeInterface $parent = null, string $pathSeparator = self::DEFAULT_PATH_SEPARATOR)
{
if (false !== strpos($name, '.')) {
throw new \InvalidArgumentException('The name must not contain ".".');
if (false !== strpos($name, $pathSeparator)) {
throw new \InvalidArgumentException('The name must not contain "'.$pathSeparator.'".');
}

$this->name = $name;
$this->parent = $parent;
$this->pathSeparator = $pathSeparator;
}

public function setAttribute($key, $value)
Expand Down Expand Up @@ -230,13 +234,11 @@ public function getName()
*/
public function getPath()
{
$path = $this->name;

if (null !== $this->parent) {
$path = $this->parent->getPath().'.'.$path;
return $this->parent->getPath().$this->pathSeparator.$this->name;
}

return $path;
return $this->name;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ protected function getNodeBuilder()
protected function createNode()
{
if (null === $this->prototype) {
$node = new ArrayNode($this->name, $this->parent);
$node = new ArrayNode($this->name, $this->parent, $this->pathSeparator);

$this->validateConcreteNode($node);

Expand All @@ -418,7 +418,7 @@ protected function createNode()
$node->addChild($child->getNode());
}
} else {
$node = new PrototypedArrayNode($this->name, $this->parent);
$node = new PrototypedArrayNode($this->name, $this->parent, $this->pathSeparator);

$this->validatePrototypeNode($node);

Expand Down Expand Up @@ -545,4 +545,12 @@ protected function validatePrototypeNode(PrototypedArrayNode $node)
}
}
}

/**
* @return NodeDefinition[]
*/
public function getChildNodeDefinitions()
{
return $this->children;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function __construct(?string $name, NodeParentInterface $parent = null)
*/
protected function instantiateNode()
{
return new BooleanNode($this->name, $this->parent);
return new BooleanNode($this->name, $this->parent, $this->pathSeparator);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ protected function instantiateNode()
throw new \RuntimeException('You must call ->values() on enum nodes.');
}

return new EnumNode($this->name, $this->parent, $this->values);
return new EnumNode($this->name, $this->parent, $this->values, $this->pathSeparator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ class FloatNodeDefinition extends NumericNodeDefinition
*/
protected function instantiateNode()
{
return new FloatNode($this->name, $this->parent, $this->min, $this->max);
return new FloatNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ class IntegerNodeDefinition extends NumericNodeDefinition
*/
protected function instantiateNode()
{
return new IntegerNode($this->name, $this->parent, $this->min, $this->max);
return new IntegerNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator);
}
}
26 changes: 26 additions & 0 deletions src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;

Expand All @@ -33,6 +34,7 @@ abstract class NodeDefinition implements NodeParentInterface
protected $nullEquivalent;
protected $trueEquivalent = true;
protected $falseEquivalent = false;
protected $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR;
protected $parent;
protected $attributes = array();

Expand Down Expand Up @@ -346,4 +348,28 @@ protected function normalization()
* @throws InvalidDefinitionException When the definition is invalid
*/
abstract protected function createNode();

/**
* Set PathSeparator to use.
*
* @param string $separator
*
* @return $this
*/
public function setPathSeparator(string $separator)
{
if ($this instanceof ParentNodeDefinitionInterface) {
if (method_exists($this, 'getChildNodeDefinitions')) {
foreach ($this->getChildNodeDefinitions() as $child) {
$child->setPathSeparator($separator);
}
} else {
@trigger_error('Passing a ParentNodeDefinitionInterface without getChildNodeDefinitions() is deprecated since version 4.1 and will be removed in 5.0.', E_USER_DEPRECATED);
}
}

$this->pathSeparator = $separator;

return $this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@
* An interface that must be implemented by nodes which can have children.
*
* @author Victor Berchet <victor@suumit.com>
*
* @method NodeDefinition[] getChildNodeDefinitions() should be implemented since 4.1
*/
interface ParentNodeDefinitionInterface
{
/**
* @return NodeBuilder
*/
public function children();

public function append(NodeDefinition $node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ class ScalarNodeDefinition extends VariableNodeDefinition
*/
protected function instantiateNode()
{
return new ScalarNode($this->name, $this->parent);
return new ScalarNode($this->name, $this->parent, $this->pathSeparator);
}
}
24 changes: 21 additions & 3 deletions src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,31 @@ public function root($name, $type = 'array', NodeBuilder $builder = null)
*/
public function buildTree()
{
if (null === $this->root) {
throw new \RuntimeException('The configuration tree has no root node.');
}
$this->assertTreeHasRootNode();
if (null !== $this->tree) {
return $this->tree;
}

return $this->tree = $this->root->getNode(true);
}

public function setPathSeparator(string $separator)
{
$this->assertTreeHasRootNode();

// unset last built as changing path separator changes all nodes
$this->tree = null;

$this->root->setPathSeparator($separator);
}

/**
* @throws \RuntimeException if root node is not defined
*/
private function assertTreeHasRootNode()
{
if (null === $this->root) {
throw new \RuntimeException('The configuration tree has no root node.');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class VariableNodeDefinition extends NodeDefinition
*/
protected function instantiateNode()
{
return new VariableNode($this->name, $this->parent);
return new VariableNode($this->name, $this->parent, $this->pathSeparator);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Config/Definition/EnumNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ class EnumNode extends ScalarNode
{
private $values;

public function __construct(?string $name, NodeInterface $parent = null, array $values = array())
public function __construct(?string $name, NodeInterface $parent = null, array $values = array(), string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR)
{
$values = array_unique($values);
if (empty($values)) {
throw new \InvalidArgumentException('$values must contain at least one element.');
}

parent::__construct($name, $parent);
parent::__construct($name, $parent, $pathSeparator);
$this->values = $values;
}

Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Config/Definition/NumericNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class NumericNode extends ScalarNode
protected $min;
protected $max;

public function __construct(?string $name, NodeInterface $parent = null, $min = null, $max = null)
public function __construct(?string $name, NodeInterface $parent = null, $min = null, $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR)
{
parent::__construct($name, $parent);
parent::__construct($name, $parent, $pathSeparator);
$this->min = $min;
$this->max = $max;
}
Expand Down
56 changes: 56 additions & 0 deletions src/Symfony/Component/Config/Tests/Definition/BaseNodeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Config\Tests\Definition;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\NodeInterface;

class BaseNodeTest extends TestCase
{
/**
* @dataProvider providePath
*/
public function testGetPathForChildNode($expected, array $params)
{
$constructorArgs = array();
$constructorArgs[] = $params[0];

if (isset($params[1])) {
// Handle old PHPUnit version for PHP 5.5
$parent = method_exists($this, 'createMock')
? $this->createMock(NodeInterface::class)
: $this->getMock(NodeInterface::class);
$parent->method('getPath')->willReturn($params[1]);

$constructorArgs[] = $parent;

if (isset($params[2])) {
$constructorArgs[] = $params[2];
}
}

$node = $this->getMockForAbstractClass(BaseNode::class, $constructorArgs);

$this->assertSame($expected, $node->getPath());
}

public function providePath()
{
return array(
'name only' => array('root', array('root')),
'name and parent' => array('foo.bar.baz.bim', array('bim', 'foo.bar.baz')),
'name and separator' => array('foo', array('foo', null, '/')),
'name, parent and separator' => array('foo.bar/baz/bim', array('bim', 'foo.bar/baz', '/')),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Config\Tests\Definition\Builder;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition;

class NodeDefinitionTest extends TestCase
{
public function testDefaultPathSeparatorIsDot()
{
$node = $this->getMockForAbstractClass(NodeDefinition::class, array('foo'));

$this->assertAttributeSame('.', 'pathSeparator', $node);
}

public function testSetPathSeparatorChangesChildren()
{
$node = new ArrayNodeDefinition('foo');
$scalar = new ScalarNodeDefinition('bar');
$node->append($scalar);

$node->setPathSeparator('/');

$this->assertAttributeSame('/', 'pathSeparator', $node);
$this->assertAttributeSame('/', 'pathSeparator', $scalar);
}
}
Loading