Skip to content

Commit 19b321d

Browse files
committed
Better config validation handling for numerical values: * New node type Integer and Float * New expressions: min() and max()
1 parent 40b6b23 commit 19b321d

12 files changed

+514
-6
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Config\Definition\Builder;
13+
14+
use Symfony\Component\Config\Definition\FloatNode;
15+
16+
/**
17+
* This class provides a fluent interface for defining a float node.
18+
*
19+
* @author Jeanmonod David <[email protected]>
20+
*/
21+
class FloatNodeDefinition extends NumericNodeDefinition
22+
{
23+
/**
24+
* Instantiate a Node
25+
*
26+
* @return FloatNode The node
27+
*/
28+
protected function instantiateNode()
29+
{
30+
return new FloatNode($this->name, $this->parent, $this->min, $this->max);
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Config\Definition\Builder;
13+
14+
use Symfony\Component\Config\Definition\IntegerNode;
15+
16+
/**
17+
* This class provides a fluent interface for defining an integer node.
18+
*
19+
* @author Jeanmonod David <[email protected]>
20+
*/
21+
class IntegerNodeDefinition extends NumericNodeDefinition
22+
{
23+
/**
24+
* Instantiate a Node
25+
*
26+
* @return IntegerNode The node
27+
*/
28+
protected function instantiateNode()
29+
{
30+
return new IntegerNode($this->name, $this->parent, $this->min, $this->max);
31+
}
32+
}

Definition/Builder/NodeBuilder.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public function __construct()
3131
'variable' => __NAMESPACE__.'\\VariableNodeDefinition',
3232
'scalar' => __NAMESPACE__.'\\ScalarNodeDefinition',
3333
'boolean' => __NAMESPACE__.'\\BooleanNodeDefinition',
34+
'integer' => __NAMESPACE__.'\\IntegerNodeDefinition',
35+
'float' => __NAMESPACE__.'\\FloatNodeDefinition',
3436
'array' => __NAMESPACE__.'\\ArrayNodeDefinition',
3537
'enum' => __NAMESPACE__.'\\EnumNodeDefinition',
3638
);
@@ -86,6 +88,30 @@ public function booleanNode($name)
8688
return $this->node($name, 'boolean');
8789
}
8890

91+
/**
92+
* Creates a child integer node.
93+
*
94+
* @param string $name the name of the node
95+
*
96+
* @return IntegerNodeDefinition The child node
97+
*/
98+
public function integerNode($name)
99+
{
100+
return $this->node($name, 'integer');
101+
}
102+
103+
/**
104+
* Creates a child float node.
105+
*
106+
* @param string $name the name of the node
107+
*
108+
* @return FloatNodeDefinition The child node
109+
*/
110+
public function floatNode($name)
111+
{
112+
return $this->node($name, 'float');
113+
}
114+
89115
/**
90116
* Creates a child EnumNode.
91117
*
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Config\Definition\Builder;
13+
14+
/**
15+
* Abstract class that contain common code of integer and float node definition.
16+
*
17+
* @author David Jeanmonod <[email protected]>
18+
*/
19+
abstract class NumericNodeDefinition extends ScalarNodeDefinition
20+
{
21+
22+
protected $min;
23+
protected $max;
24+
25+
/**
26+
* Ensure the value is smaller than the given reference
27+
*
28+
* @param mixed $max
29+
*
30+
* @return NumericNodeDefinition
31+
*/
32+
public function max($max)
33+
{
34+
if (isset($this->min) && $this->min > $max) {
35+
throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s)', $max, $this->min));
36+
}
37+
$this->max = $max;
38+
39+
return $this;
40+
}
41+
42+
/**
43+
* Ensure the value is bigger than the given reference
44+
*
45+
* @param mixed $min
46+
*
47+
* @return NumericNodeDefinition
48+
*/
49+
public function min($min)
50+
{
51+
if (isset($this->max) && $this->max < $min) {
52+
throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s)', $min, $this->max));
53+
}
54+
$this->min = $min;
55+
56+
return $this;
57+
}
58+
}

Definition/FloatNode.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Config\Definition;
13+
14+
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
15+
16+
/**
17+
* This node represents a float value in the config tree.
18+
*
19+
* @author Jeanmonod David <[email protected]>
20+
*/
21+
class FloatNode extends NumericNode
22+
{
23+
/**
24+
* {@inheritDoc}
25+
*/
26+
protected function validateType($value)
27+
{
28+
// Integers are also accepted, we just cast them
29+
if (is_int($value)) {
30+
$value = (float) $value;
31+
}
32+
33+
if (!is_float($value)) {
34+
$ex = new InvalidTypeException(sprintf(
35+
'Invalid type for path "%s". Expected float, but got %s.',
36+
$this->getPath(),
37+
gettype($value)
38+
));
39+
$ex->setPath($this->getPath());
40+
41+
throw $ex;
42+
}
43+
}
44+
}

Definition/IntegerNode.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Config\Definition;
13+
14+
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
15+
16+
/**
17+
* This node represents an integer value in the config tree.
18+
*
19+
* @author Jeanmonod David <[email protected]>
20+
*/
21+
class IntegerNode extends NumericNode
22+
{
23+
/**
24+
* {@inheritDoc}
25+
*/
26+
protected function validateType($value)
27+
{
28+
if (!is_int($value)) {
29+
$ex = new InvalidTypeException(sprintf(
30+
'Invalid type for path "%s". Expected int, but got %s.',
31+
$this->getPath(),
32+
gettype($value)
33+
));
34+
$ex->setPath($this->getPath());
35+
36+
throw $ex;
37+
}
38+
}
39+
}

Definition/NumericNode.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Config\Definition;
13+
14+
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
15+
16+
/**
17+
* This node represents a numeric value in the config tree
18+
*
19+
* @author David Jeanmonod <[email protected]>
20+
*/
21+
class NumericNode extends ScalarNode
22+
{
23+
protected $min;
24+
protected $max;
25+
26+
public function __construct($name, NodeInterface $parent = null, $min = null, $max = null)
27+
{
28+
parent::__construct($name, $parent);
29+
$this->min = $min;
30+
$this->max = $max;
31+
}
32+
33+
/**
34+
* {@inheritDoc}
35+
*/
36+
protected function finalizeValue($value)
37+
{
38+
$value = parent::finalizeValue($value);
39+
40+
$errorMsg = null;
41+
if (isset($this->min) && $value < $this->min) {
42+
$errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than: %s', $value, $this->getPath(), $this->min);
43+
}
44+
if (isset($this->max) && $value > $this->max) {
45+
$errorMsg = sprintf('The value %s is too big for path "%s". Should be less than: %s', $value, $this->getPath(), $this->max);
46+
}
47+
if (isset($errorMsg)) {
48+
$ex = new InvalidConfigurationException($errorMsg);
49+
$ex->setPath($this->getPath());
50+
throw $ex;
51+
}
52+
53+
return $value;
54+
}
55+
56+
}

Tests/Definition/Builder/ExprBuilderTest.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
1515

16-
1716
class ExprBuilderTest extends \PHPUnit_Framework_TestCase
1817
{
1918

@@ -160,6 +159,7 @@ public function testThenUnsetExpression()
160159
protected function getTestBuilder()
161160
{
162161
$builder = new TreeBuilder();
162+
163163
return $builder
164164
->root('test')
165165
->children()
@@ -171,7 +171,7 @@ protected function getTestBuilder()
171171
/**
172172
* Close the validation process and finalize with the given config
173173
* @param TreeBuilder $testBuilder The tree builder to finalize
174-
* @param array $config The config you want to use for the finalization, if nothing provided
174+
* @param array $config The config you want to use for the finalization, if nothing provided
175175
* a simple array('key'=>'value') will be used
176176
* @return array The finalized config values
177177
*/
@@ -191,17 +191,18 @@ protected function finalizeTestBuilder($testBuilder, $config=null)
191191
* @param $val The value that the closure must return
192192
* @return Closure
193193
*/
194-
protected function returnClosure($val) {
194+
protected function returnClosure($val)
195+
{
195196
return function($v) use ($val) {
196197
return $val;
197198
};
198199
}
199200

200201
/**
201202
* Assert that the given test builder, will return the given value
202-
* @param mixed $value The value to test
203-
* @param TreeBuilder $test The tree builder to finalize
204-
* @param mixed $config The config values that new to be finalized
203+
* @param mixed $value The value to test
204+
* @param TreeBuilder $test The tree builder to finalize
205+
* @param mixed $config The config values that new to be finalized
205206
*/
206207
protected function assertFinalizedValueIs($value, $treeBuilder, $config=null)
207208
{

Tests/Definition/Builder/NodeBuilderTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ public function testNodeTypesAreNotCaseSensitive()
7676

7777
$this->assertEquals(get_class($node1), get_class($node2));
7878
}
79+
80+
public function testNumericNodeCreation()
81+
{
82+
$builder = new NodeBuilder();
83+
84+
$node = $builder->integerNode('foo')->min(3)->max(5);
85+
$this->assertEquals('Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition', get_class($node));
86+
87+
$node = $builder->floatNode('bar')->min(3.0)->max(5.0);
88+
$this->assertEquals('Symfony\Component\Config\Definition\Builder\FloatNodeDefinition', get_class($node));
89+
}
7990
}
8091

8192
class SomeNodeDefinition extends BaseVariableNodeDefinition

0 commit comments

Comments
 (0)