Skip to content

Commit b49f501

Browse files
committed
merged branch jeanmonod/config-with-numeric-checks (PR symfony#4714)
Commits ------- 71db836 Better config validation handling for numerical values: * New node type Integer and Float * New expressions: min() and max() Discussion ---------- [2.2] [Config] Better handling for numerical values: * New node type Integer and Float * New expressions: ifLessThan(), ifGreaterThan(), ifInRange(), ifNotInRange() --------------------------------------------------------------------------- by fabpot at 2012-07-03T08:50:22Z As I said on PR symfony#4713, adding more method clutters the API without any big benefits. I'm -1 on the PR. --------------------------------------------------------------------------- by jeanmonod at 2012-07-03T08:54:36Z I have been discuss it with @schmittjoh at the sflive, he was thinking it could be a good addition. IMHO I think that if we want to encourage the usage of bundle configuration validation, we should make it as easy as possible to use... --------------------------------------------------------------------------- by jeanmonod at 2012-07-03T08:59:42Z A real life example: ->scalarNode('max_nb_items') ->validate() ->ifTrue(function($v){ return !is_int($v) || (is_int($v) && $v<1); }) ->thenInvalid('Must be a positive integer') ->end() ->end() could be replaced by ->integerNode('max_nb_items') ->validate() ->ifLessThan(1); ->thenInvalid('Must be a positive integer') ->end() ->end() --------------------------------------------------------------------------- by gnutix at 2012-07-03T09:03:06Z I agree with @jeanmonod on this matter, the bundle configuration validation is already kind of a hassle to understand (and read), so it would be a good addition IMHO. --------------------------------------------------------------------------- by fabpot at 2012-07-03T10:54:32Z @schmittjoh What's your point of view? --------------------------------------------------------------------------- by schmittjoh at 2012-07-03T14:10:37Z The integer and float nodes are valuable additions imo which I wanted to add myself several times but simply did not have the time for. As for the changes to the expression builder, I was not really passionate about them in Paris, but I did not mind either way. However, looking at this PR, I think they would be better implemented as methods on the definition builders, and validated directly by the nodes: ```php $builder->integerNode('foo')->range(1, 4)->end(); $builder->integerNode('foo')->mustBeGreaterThan(5)->end(); ``` This will also allow for these constraints to be introspected and added to generated documentation. --------------------------------------------------------------------------- by fabpot at 2012-07-03T17:57:25Z @jeanmonod Can you take into account the comments by @schmittjoh? --------------------------------------------------------------------------- by jeanmonod at 2012-07-03T19:40:24Z @fabpot Yes, I will try to move those 4 checks. @schmittjoh If I put those tests into the ScalarNodeDefinition did you think it's ok? And did I have to make anything special for the documentation introspection? --------------------------------------------------------------------------- by schmittjoh at 2012-07-03T19:56:00Z You can take a look at the EnumNodeDefinition, and the EnumNode. They are pretty simple, and should give you a good idea of how to implement it. On Tue, Jul 3, 2012 at 9:46 PM, Jeanmonod David < [email protected] > wrote: > @fabpot Yes, I will try to move those 4 checks. > > @schmittjoh If I put those tests into the ScalarNodeDefinition did you > think it's ok? And did I have to make anything special for the > documentation introspection? > > --- > Reply to this email directly or view it on GitHub: > symfony#4714 (comment) > --------------------------------------------------------------------------- by jeanmonod at 2012-07-03T21:37:18Z OK, I just refactor as requested. At the end, I didn't add the range() check. It can be easily done by chaining min and max, like this: $builder->integerNode('foo')->min(1)->max(4)->end(); @schmittjoh can you have a look? --------------------------------------------------------------------------- by schmittjoh at 2012-07-03T21:48:17Z Have you tested the builder API? Did you maybe forget to commit something? --------------------------------------------------------------------------- by jeanmonod at 2012-07-03T21:52:45Z Yes you are right, I forget the definition --------------------------------------------------------------------------- by jeanmonod at 2012-07-03T22:15:45Z OK, I realize now that I misunderstood the concept. I was thinking that a node was able to do self validation. But no, I will have to move my code to the node definition. So let's wait for a new commit... --------------------------------------------------------------------------- by jeanmonod at 2012-07-06T06:13:55Z @schmittjoh I just push the move to definition and the new abstract class Numeric. Can you review it? --------------------------------------------------------------------------- by jeanmonod at 2012-07-10T05:12:59Z @schmittjoh, @fabpot I fix all the mention points, can you have a look at the final result? --------------------------------------------------------------------------- by schmittjoh at 2012-07-10T06:38:20Z There are still some excessive blank lines if you want to be perfect, but overall looks good now. --------------------------------------------------------------------------- by jeanmonod at 2012-07-10T07:05:54Z @schmittjoh I think the comments of @Baachi are not well placed in the diff. I execute php-cs-fix on all code, so level of perfectness is already good ;) @fabpot Do you want some more complements before merging? --------------------------------------------------------------------------- by fabpot at 2012-07-10T07:07:21Z @jeanmonod I'm going to review the code once more and it will be merged for 2.2. Thanks for your work. --------------------------------------------------------------------------- by fabpot at 2012-09-18T13:58:48Z @jeanmonod Can you squash your commits before I merge? Thanks. --------------------------------------------------------------------------- by jeanmonod at 2012-09-18T14:36:59Z @fabpot Squash done --------------------------------------------------------------------------- by fabpot at 2012-09-19T04:07:13Z @jeanmonod One last thing: can you submit a PR on symfony/symfony-docs that update the documentation with the new possibilities? --------------------------------------------------------------------------- by jeanmonod at 2012-09-20T05:32:01Z @fabpot OK, Documentation PR done here: symfony/symfony-docs#1732
2 parents e7b55fc + 71db836 commit b49f501

File tree

12 files changed

+509
-1
lines changed

12 files changed

+509
-1
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+
}

src/Symfony/Component/Config/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+
}
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+
}
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+
}
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+
}

src/Symfony/Component/Config/Tests/Definition/Builder/ExprBuilderTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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
*/
@@ -200,6 +200,7 @@ protected function returnClosure($val)
200200

201201
/**
202202
* Assert that the given test builder, will return the given value
203+
*
203204
* @param mixed $value The value to test
204205
* @param TreeBuilder $treeBuilder The tree builder to finalize
205206
* @param mixed $config The config values that new to be finalized

src/Symfony/Component/Config/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)