Skip to content

Commit b68677c

Browse files
Provide a way to process just a specific path from a config node
1 parent fe88b91 commit b68677c

File tree

8 files changed

+407
-0
lines changed

8 files changed

+407
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Matthias\SymfonyConfigTest\Partial\Exception;
4+
5+
use Symfony\Component\Config\Definition\BaseNode;
6+
7+
class ChildIsNotAnArrayNode extends InvalidNodeNavigation
8+
{
9+
public function __construct(BaseNode $parentNode, $nodeName)
10+
{
11+
parent::__construct(
12+
sprintf(
13+
'Child node "%s" is not an array node (current path: "%s")',
14+
$nodeName,
15+
self::renderTravelledPath($parentNode)
16+
)
17+
);
18+
}
19+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Matthias\SymfonyConfigTest\Partial\Exception;
4+
5+
use Symfony\Component\Config\Definition\BaseNode;
6+
7+
abstract class InvalidNodeNavigation extends \LogicException
8+
{
9+
protected static function renderTravelledPath(BaseNode $node)
10+
{
11+
if ($node->getParent()) {
12+
$prefix = self::renderTravelledPath($node->getParent());
13+
$prefix .= '.';
14+
} else {
15+
$prefix = '';
16+
}
17+
18+
return $prefix . $node->getName();
19+
}
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Matthias\SymfonyConfigTest\Partial\Exception;
4+
5+
use Symfony\Component\Config\Definition\NodeInterface;
6+
7+
class UndefinedChildNode extends InvalidNodeNavigation
8+
{
9+
public function __construct(NodeInterface $parentNode, $childNodeName)
10+
{
11+
parent::__construct(
12+
sprintf(
13+
'Undefined child node "%s" (the part of the path that was successful: "%s")',
14+
$childNodeName,
15+
self::renderTravelledPath($parentNode)
16+
)
17+
);
18+
}
19+
}

Partial/PartialNode.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace Matthias\SymfonyConfigTest\Partial;
4+
5+
use Matthias\SymfonyConfigTest\Partial\Exception\ChildIsNotAnArrayNode;
6+
use Matthias\SymfonyConfigTest\Partial\Exception\UndefinedChildNode;
7+
use Symfony\Component\Config\Definition\ArrayNode;
8+
9+
class PartialNode
10+
{
11+
private static $nodeChildrenProperty;
12+
13+
/**
14+
* Provide an ArrayNode instance (e.g. the root node created by a TreeBuilder) and a path that is relevant to you,
15+
* e.g. "dbal.connections": this will strip every node that is not contained in the given path (e.g. the "orm" node
16+
* would be removed entirely.
17+
*
18+
* @param ArrayNode $node
19+
* @param string $breadcrumbPath
20+
*/
21+
public static function excludeEverythingNotInBreadcrumbPath(ArrayNode $node, $breadcrumbPath)
22+
{
23+
$breadcrumbPath = explode('.', $breadcrumbPath);
24+
25+
self::excludeEverythingNotInPath($node, $breadcrumbPath);
26+
}
27+
28+
/**
29+
* @param array $path
30+
*/
31+
public static function excludeEverythingNotInPath(ArrayNode $node, array $path)
32+
{
33+
if (empty($path)) {
34+
return;
35+
}
36+
37+
$nextNodeName = array_shift($path);
38+
39+
$nextNode = self::childNode($node, $nextNodeName);
40+
41+
if (!($nextNode instanceof ArrayNode)) {
42+
throw new ChildIsNotAnArrayNode($node, $nextNodeName);
43+
}
44+
45+
$children = self::nodeChildrenProperty()->getValue($node);
46+
foreach ($children as $name => $child) {
47+
if ($name !== $nextNodeName) {
48+
unset($children[$name]);
49+
}
50+
}
51+
self::nodeChildrenProperty()->setValue($node, $children);
52+
53+
self::excludeEverythingNotInPath($nextNode, $path);
54+
}
55+
56+
private static function childNode(ArrayNode $node, $childNodeName)
57+
{
58+
$children = self::nodeChildrenProperty()->getValue($node);
59+
60+
if (!isset($children[$childNodeName])) {
61+
throw new UndefinedChildNode(
62+
$node,
63+
$childNodeName
64+
);
65+
}
66+
67+
return $children[$childNodeName];
68+
}
69+
70+
private static function nodeChildrenProperty()
71+
{
72+
if (!isset(self::$nodeChildrenProperty)) {
73+
self::$nodeChildrenProperty = new \ReflectionProperty(
74+
'Symfony\Component\Config\Definition\ArrayNode',
75+
'children'
76+
);
77+
self::$nodeChildrenProperty->setAccessible(true);
78+
}
79+
80+
return self::$nodeChildrenProperty;
81+
}
82+
}

Partial/PartialProcessor.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Matthias\SymfonyConfigTest\Partial;
4+
5+
use Symfony\Component\Config\Definition\ArrayNode;
6+
use Symfony\Component\Config\Definition\ConfigurationInterface;
7+
use Symfony\Component\Config\Definition\Processor;
8+
9+
class PartialProcessor
10+
{
11+
public function process(ArrayNode $node, $breadcrumbPath, array $configs)
12+
{
13+
PartialNode::excludeEverythingNotInBreadcrumbPath($node, $breadcrumbPath);
14+
15+
$processor = new Processor();
16+
17+
return $processor->process($node, $configs);
18+
}
19+
20+
public function processConfiguration(ConfigurationInterface $configuration, $breadcrumbPath, array $configs)
21+
{
22+
return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $breadcrumbPath, $configs);
23+
}
24+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Matthias\SymfonyConfigTest\Tests\Partial\Fixtures;
4+
5+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
6+
use Symfony\Component\Config\Definition\ConfigurationInterface;
7+
8+
class ConfigurationStub implements ConfigurationInterface
9+
{
10+
public function getConfigTreeBuilder()
11+
{
12+
$treeBuilder = new TreeBuilder();
13+
$root = $treeBuilder->root('root');
14+
$root
15+
->children()
16+
->arrayNode('only_test_this_node')
17+
->children()
18+
->scalarNode('scalar_node')
19+
->end()
20+
->end()
21+
->end()
22+
->arrayNode('ignore_this_node')
23+
// this would normally trigger an error
24+
->isRequired()
25+
->end()
26+
->end();
27+
28+
return $treeBuilder;
29+
}
30+
}

Tests/Partial/PartialNodeTest.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
namespace Matthias\SymfonyConfigTest\Tests\Partial;
4+
5+
use Matthias\SymfonyConfigTest\Partial\PartialNode;
6+
use Symfony\Component\Config\Definition\ArrayNode;
7+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
8+
9+
class PartialNodeTest extends \PHPUnit_Framework_TestCase
10+
{
11+
/**
12+
* @test
13+
*/
14+
public function it_strips_children_that_are_not_in_the_given_path_with_one_name()
15+
{
16+
$treeBuilder = new TreeBuilder();
17+
$root = $treeBuilder->root('root');
18+
$root
19+
->children()
20+
->arrayNode('node_1')
21+
->children()
22+
->scalarNode('node_1_scalar_node')
23+
->end()
24+
->end()
25+
->end()
26+
->arrayNode('node_2')
27+
->children()
28+
->scalarNode('node_2_scalar_node');
29+
30+
$node = $treeBuilder->buildTree();
31+
/** @var ArrayNode $node */
32+
33+
PartialNode::excludeEverythingNotInPath($node, ['node_2']);
34+
35+
$this->nodeOnlyHasChild($node, 'node_2');
36+
}
37+
38+
/**
39+
* @test
40+
*/
41+
public function it_strips_children_that_are_not_in_the_given_path_with_several_names()
42+
{
43+
$treeBuilder = new TreeBuilder();
44+
$root = $treeBuilder->root('root');
45+
$root
46+
->children()
47+
->arrayNode('node_1')
48+
->children()
49+
->arrayNode('node_1_a')
50+
->children()
51+
->scalarNode('scalar_node')
52+
->end()
53+
->end()
54+
->end()
55+
->arrayNode('node_1_b')
56+
->children()
57+
->scalarNode('scalar_node')
58+
->end()
59+
->end()
60+
->end()
61+
->end()
62+
->end()
63+
->arrayNode('node_2')
64+
->children()
65+
->scalarNode('scalar_node');
66+
67+
$node = $treeBuilder->buildTree();
68+
/** @var ArrayNode $node */
69+
70+
PartialNode::excludeEverythingNotInPath($node, ['node_1', 'node_1_b']);
71+
72+
$node1 = $this->nodeOnlyHasChild($node, 'node_1');
73+
$this->nodeOnlyHasChild($node1, 'node_1_b');
74+
}
75+
76+
/**
77+
* @test
78+
*/
79+
public function it_fails_when_a_requested_child_node_does_not_exist()
80+
{
81+
$treeBuilder = new TreeBuilder();
82+
$root = $treeBuilder->root('root');
83+
$root
84+
->children()
85+
->arrayNode('sub_node')
86+
->children()
87+
->arrayNode('sub_sub_tree');
88+
89+
$node = $treeBuilder->buildTree();
90+
91+
$this->setExpectedException(
92+
'Matthias\SymfonyConfigTest\Partial\Exception\UndefinedChildNode',
93+
'Undefined child node "non_existing_node" (the part of the path that was successful: "root.sub_node")'
94+
);
95+
PartialNode::excludeEverythingNotInPath($node, ['sub_node', 'non_existing_node']);
96+
}
97+
98+
/**
99+
* @test
100+
*/
101+
public function it_fails_when_a_requested_child_node_is_no_array_node_itself()
102+
{
103+
$treeBuilder = new TreeBuilder();
104+
$root = $treeBuilder->root('root');
105+
$root
106+
->children()
107+
->arrayNode('sub_node')
108+
->children()
109+
->scalarNode('scalar_node');
110+
111+
$node = $treeBuilder->buildTree();
112+
113+
$this->setExpectedException(
114+
'Matthias\SymfonyConfigTest\Partial\Exception\ChildIsNotAnArrayNode',
115+
'Child node "scalar_node" is not an array node (current path: "root.sub_node")'
116+
);
117+
PartialNode::excludeEverythingNotInPath($node, ['sub_node', 'scalar_node']);
118+
}
119+
120+
private function nodeOnlyHasChild(ArrayNode $node, $nodeName)
121+
{
122+
$children = $node->getChildren();
123+
$this->assertCount(1, $children);
124+
$firstChild = reset($children);
125+
$this->assertSame($nodeName, $firstChild->getName());
126+
127+
return $firstChild;
128+
}
129+
}

0 commit comments

Comments
 (0)