Skip to content

Commit

Permalink
[DependencyInjection] added first version of the config normalizer
Browse files Browse the repository at this point in the history
This is mainly intended for complex configurations to ease the work you
have with normalizing different configuration formats (YAML, XML, and PHP).

First, you have to set-up a config tree:

    $treeBuilder = new TreeBuilder();
    $tree = $treeBuilder
        ->root('security_config', 'array')
            ->node('access_denied_url', 'scalar')->end()
            ->normalize('encoder')
            ->node('encoders', 'array')
                ->key('class')
                ->prototype('array')
                    ->before()->ifString()->then(function($v) { return array('algorithm' => $v); })->end()
                    ->node('algorithm', 'scalar')->end()
                    ->node('encode_as_base64', 'scalar')->end()
                    ->node('iterations', 'scalar')->end()
                ->end()
            ->end()
        ->end()
        ->buildTree()
    ;

This tree and the metadata attached to the different nodes is then used
to intelligently transform the passed config array:

    $normalizedConfig = $tree->normalize($config);
  • Loading branch information
schmittjoh authored and fabpot committed Feb 1, 2011
1 parent a28151a commit b484763
Show file tree
Hide file tree
Showing 11 changed files with 611 additions and 0 deletions.
105 changes: 105 additions & 0 deletions src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php
@@ -0,0 +1,105 @@
<?php

namespace Symfony\Component\DependencyInjection\Configuration;

use Symfony\Component\DependencyInjection\Extension\Extension;

class ArrayNode extends BaseNode implements PrototypeNodeInterface
{
protected $normalizeTransformations;
protected $children;
protected $prototype;
protected $keyAttribute;

public function __construct($name, NodeInterface $parent = null, array $beforeTransformations = array(), array $afterTransformations = array(), array $normalizeTransformations = array(), $keyAttribute = null)
{
parent::__construct($name, $parent, $beforeTransformations, $afterTransformations);

$this->children = array();
$this->normalizeTransformations = $normalizeTransformations;
$this->keyAttribute = $keyAttribute;
}

public function setName($name)
{
$this->name = $name;
}

public function setPrototype(PrototypeNodeInterface $node)
{
if (count($this->children) > 0) {
throw new \RuntimeException('An ARRAY node must either have concrete children, or a prototype node.');
}

$this->prototype = $node;
}

public function addChild(NodeInterface $node)
{
$name = $node->getName();
if (empty($name)) {
throw new \InvalidArgumentException('Node name cannot be empty.');
}
if (isset($this->children[$name])) {
throw new \InvalidArgumentException(sprintf('The node "%s" already exists.', $name));
}
if (null !== $this->prototype) {
throw new \RuntimeException('An ARRAY node must either have a prototype, or concrete children.');
}

$this->children[$name] = $node;
}

protected function validateType($value)
{
if (!is_array($value)) {
throw new InvalidTypeException(sprintf(
'Invalid type for path "%s". Expected array, but got %s',
$this->getPath(),
json_encode($value)
));
}
}

protected function normalizeValue($value)
{
foreach ($this->normalizeTransformations as $transformation) {
list($singular, $plural) = $transformation;

if (!isset($value[$singular])) {
continue;
}

$value[$plural] = Extension::normalizeConfig($value, $singular, $plural);
}

if (null !== $this->prototype) {
$normalized = array();
foreach ($value as $k => $v) {
if (null !== $this->keyAttribute && is_array($v) && isset($v[$this->keyAttribute])) {
$k = $v[$this->keyAttribute];
}

$this->prototype->setName($k);
if (null !== $this->keyAttribute) {
$normalized[$k] = $this->prototype->normalize($v);
} else {
$normalized[] = $this->prototype->normalize($v);
}
}

return $normalized;
}

$normalized = array();
foreach ($this->children as $name => $child) {
if (!array_key_exists($name, $value)) {
continue;
}

$normalized[$name] = $child->normalize($value[$name]);
}

return $normalized;
}
}
@@ -0,0 +1,64 @@
<?php

namespace Symfony\Component\DependencyInjection\Configuration;

abstract class BaseNode implements NodeInterface
{
protected $name;
protected $parent;
protected $beforeTransformations;
protected $afterTransformations;
protected $nodeFactory;

public function __construct($name, NodeInterface $parent = null, $beforeTransformations = array(), $afterTransformations = array())
{
if (false !== strpos($name, '.')) {
throw new \InvalidArgumentException('The name must not contain ".".');
}

$this->name = $name;
$this->parent = $parent;
$this->beforeTransformations = $beforeTransformations;
$this->afterTransformations = $afterTransformations;
}

public function getName()
{
return $this->name;
}

public function getPath()
{
$path = $this->name;

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

return $path;
}

public final function normalize($value)
{
// run before transformations
foreach ($this->beforeTransformations as $transformation) {
$value = $transformation($value);
}

// validate type
$this->validateType($value);

// normalize value
$value = $this->normalizeValue($value);

// run after transformations
foreach ($this->afterTransformations as $transformation) {
$value = $transformation($value);
}

return $value;
}

abstract protected function validateType($value);
abstract protected function normalizeValue($value);
}
@@ -0,0 +1,62 @@
<?php

namespace Symfony\Component\DependencyInjection\Configuration\Builder;

class ExprBuilder
{
public $parent;
public $ifPart;
public $thenPart;

public function __construct($parent)
{
$this->parent = $parent;
}

public function ifTrue(\Closure $closure)
{
$this->ifPart = $closure;

return $this;
}

public function ifString()
{
$this->ifPart = function($v) { return is_string($v); };

return $this;
}

public function ifNull()
{
$this->ifPart = function($v) { return null === $v; };

return $this;
}

public function ifArray()
{
$this->ifPart = function($v) { return is_array($v); };

return $this;
}

public function then(\Closure $closure)
{
$this->thenPart = $closure;

return $this;
}

public function end()
{
if (null === $this->ifPart) {
throw new \RuntimeException('You must specify an if part.');
}
if (null === $this->thenPart) {
throw new \RuntimeException('You must specify a then part.');
}

return $this->parent;
}
}
@@ -0,0 +1,92 @@
<?php

namespace Symfony\Component\DependencyInjection\Configuration\Builder;

class NodeBuilder
{
/************
* READ-ONLY
************/
public $name;
public $type;
public $key;
public $parent;
public $children;
public $prototype;
public $normalizeTransformations;
public $beforeTransformations;
public $afterTransformations;

public function __construct($name, $type, $parent = null)
{
$this->name = $name;
$this->type = $type;
$this->parent = $parent;

$this->children =
$this->beforeTransformations =
$this->afterTransformations =
$this->normalizeTransformations = array();
}

/****************************
* FLUID INTERFACE
****************************/

public function node($name, $type)
{
$node = new NodeBuilder($name, $type, $this);

return $this->children[$name] = $node;
}

public function normalize($key, $plural = null)
{
if (null === $plural) {
$plural = $key.'s';
}

$this->normalizeTransformations[] = array($key, $plural);

return $this;
}

public function key($name)
{
$this->key = $name;

return $this;
}

public function before(\Closure $closure = null)
{
if (null !== $closure) {
$this->beforeTransformations[] = $closure;

return $this;
}

return $this->beforeTransformations[] = new ExprBuilder($this);
}

public function prototype($type)
{
return $this->prototype = new NodeBuilder(null, $type, $this);
}

public function after(\Closure $closure = null)
{
if (null !== $closure) {
$this->afterTransformations[] = $closure;

return $this;
}

return $this->afterTransformations[] = new ExprBuilder($this);
}

public function end()
{
return $this->parent;
}
}

0 comments on commit b484763

Please sign in to comment.