Skip to content

Commit

Permalink
feature #31287 [Config] Introduce find method in ArrayNodeDefinition …
Browse files Browse the repository at this point in the history
…to ease configuration tree manipulation (jschaedl)

This PR was squashed before being merged into the 4.4 branch (closes #31287).

Discussion
----------

[Config] Introduce find method in ArrayNodeDefinition to ease configuration tree manipulation

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | #27534   <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | tbd.

### Description

This PR introduces a new `find(string $nodePath)`method in the `ArrayNodeDefinition` class, which helps you finding the right node to prepend configuration to ease configuration tree manipulation.

### How to use it
```php
class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        ...

        $rootNode
            ->children()
                ->arrayNode('social_media_channels')
                    ->children()
                        ->booleanNode('enable')->end()
                        ->arrayNode('twitter')->end()
                        ->arrayNode('facebook')->end()
                        ->arrayNode('instagram')->end()
                    ->end()
                ->end()
            ->end()
        ;

        $this->changeSocialMediaChannelConfiguration($rootNode->find('social_media_channels.enable'));
        $this->addTwitterConfiguration($rootNode->find('social_media_channels.twitter'));
        $this->addFacebookConfiguration($rootNode->find('social_media_channels.facebook'));
        $this->addInstagramConfiguration($rootNode->find('social_media_channels.instagram'));

        return $treeBuilder;
    }

    private function changeSocialMediaChannelConfiguration(NodeDefinition $node)
    {
        $node
            ->defaultTrue()
        ;
    }

    private function addTwitterConfiguration(NodeDefinition $node)
    {
        $node
            ->children()
                ->integerNode('client_id')->end()
                ->scalarNode('client_secret')->end()
            ->end()
        ;
    }

    private function addFacebookConfiguration(NodeDefinition $node)
    {
        $node
            ->children()
                ->integerNode('client_id')->end()
                ->scalarNode('client_secret')->end()
            ->end()
        ;
    }

    private function addInstagramConfiguration(NodeDefinition $node)
    {
        $node
            ->children()
                ->integerNode('client_id')->end()
                ->scalarNode('client_secret')->end()
            ->end()
        ;
    }
}

```

Commits
-------

e3b248a [Config] Introduce find method in ArrayNodeDefinition to ease configuration tree manipulation
  • Loading branch information
fabpot committed Jun 14, 2019
2 parents f06a35b + e3b248a commit 50c62d7
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
Expand Up @@ -523,4 +523,26 @@ public function getChildNodeDefinitions()
{
return $this->children;
}

/**
* Finds a node defined by the given $nodePath.
*
* @param string $nodePath The path of the node to find. e.g "doctrine.orm.mappings"
*/
public function find(string $nodePath): NodeDefinition
{
$firstPathSegment = (false === $pathSeparatorPos = strpos($nodePath, $this->pathSeparator))
? $nodePath
: substr($nodePath, 0, $pathSeparatorPos);

if (null === $node = ($this->children[$firstPathSegment] ?? null)) {
throw new \RuntimeException(sprintf('Node with name "%s" does not exist in the current node "%s".', $firstPathSegment, $this->name));
}

if (false === $pathSeparatorPos) {
return $node;
}

return $node->find(substr($nodePath, $pathSeparatorPos + \strlen($this->pathSeparator)));
}
}
Expand Up @@ -13,6 +13,8 @@

use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
use Symfony\Component\Config\Definition\Processor;
Expand Down Expand Up @@ -357,6 +359,85 @@ public function testCannotBeEmptyOnConcreteNode()
$node->getNode()->finalize([]);
}

public function testFindShouldThrowExceptionIfNodeDoesNotExistInRootNode()
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Node with name "child" does not exist in the current node "root".');

$rootNode = new ArrayNodeDefinition('root');
$rootNode
->children()
->arrayNode('social_media_channels')->end()
->end()
;

$rootNode->find('child');
}

public function testFindShouldHandleComplexConfigurationProperly()
{
$rootNode = new ArrayNodeDefinition('root');
$rootNode
->children()
->arrayNode('social_media_channels')
->children()
->booleanNode('enable')->end()
->arrayNode('twitter')->end()
->arrayNode('facebook')->end()
->arrayNode('instagram')
->children()
->booleanNode('enable')->end()
->arrayNode('accounts')->end()
->end()
->end()
->end()
->append(
$mailerNode = (new ArrayNodeDefinition('mailer'))
->children()
->booleanNode('enable')->end()
->arrayNode('transports')->end()
->end()
)
->end()
;

$this->assertNode('social_media_channels', ArrayNodeDefinition::class, $rootNode->find('social_media_channels'));
$this->assertNode('enable', BooleanNodeDefinition::class, $rootNode->find('social_media_channels.enable'));
$this->assertNode('twitter', ArrayNodeDefinition::class, $rootNode->find('social_media_channels.twitter'));
$this->assertNode('facebook', ArrayNodeDefinition::class, $rootNode->find('social_media_channels.facebook'));
$this->assertNode('instagram', ArrayNodeDefinition::class, $rootNode->find('social_media_channels.instagram'));
$this->assertNode('enable', BooleanNodeDefinition::class, $rootNode->find('social_media_channels.instagram.enable'));
$this->assertNode('accounts', ArrayNodeDefinition::class, $rootNode->find('social_media_channels.instagram.accounts'));

$this->assertNode('enable', BooleanNodeDefinition::class, $mailerNode->find('enable'));
$this->assertNode('transports', ArrayNodeDefinition::class, $mailerNode->find('transports'));
}

public function testFindShouldWorkProperlyForNonDefaultPathSeparator()
{
$rootNode = new ArrayNodeDefinition('root');
$rootNode
->setPathSeparator('.|')
->children()
->arrayNode('mailer.configuration')
->children()
->booleanNode('enable')->end()
->arrayNode('transports')->end()
->end()
->end()
;

$this->assertNode('mailer.configuration', ArrayNodeDefinition::class, $rootNode->find('mailer.configuration'));
$this->assertNode('enable', BooleanNodeDefinition::class, $rootNode->find('mailer.configuration.|enable'));
$this->assertNode('transports', ArrayNodeDefinition::class, $rootNode->find('mailer.configuration.|transports'));
}

protected function assertNode(string $expectedName, string $expectedType, NodeDefinition $actualNode): void
{
$this->assertInstanceOf($expectedType, $actualNode);
$this->assertSame($expectedName, $this->getField($actualNode, 'name'));
}

protected function getField($object, $field)
{
$reflection = new \ReflectionProperty($object, $field);
Expand Down

0 comments on commit 50c62d7

Please sign in to comment.