8000 [Validator] Improved test coverage of NonRecursiveNodeTraverser · symfony/symfony@186c115 · GitHub
[go: up one dir, main page]

Skip to content

Commit 186c115

Browse files
committed
[Validator] Improved test coverage of NonRecursiveNodeTraverser
1 parent 822fe47 commit 186c115

File tree

8 files changed

+244
-33
lines changed

8 files changed

+244
-33
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Validator\Exception;
13+
14+
/**
15+
* @since 2.5
16+
* @author Bernhard Schussek <bschussek@gmail.com>
17+
*/
18+
class UnsupportedMetadataException extends InvalidArgumentException
19+
{
20+
}

src/Symfony/Component/Validator/Mapping/ClassMetadata.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ class ClassMetadata extends ElementMetadata implements LegacyMetadataInterface,
6464
*/
6565
public $groupSequenceProvider = false;
6666

67+
/**
68+
* The strategy for traversing traversable objects.
69+
*
70+
* By default, only instances of {@link \Traversable} are traversed.
71+
*
72+
* @var integer
73+
*/
74+
public $traversalStrategy = TraversalStrategy::IMPLICIT;
75+
6776
/**
6877
* @var \ReflectionClass
6978
*/
@@ -215,6 +224,8 @@ public function addConstraint(Constraint $constraint)
215224
$constraint->addImplicitGroupName($this->getDefaultGroup());
216225

217226
parent::addConstraint($constraint);
227+
228+
return $this;
218229
}
219230

220231
/**

src/Symfony/Component/Validator/Mapping/GenericMetadata.php

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 9E88 @@
1313

1414
use Symfony\Component\Validator\Constraint;
1515
use Symfony\Component\Validator\Constraints\Valid;
16+
use Symfony\Component\Validator\Exception\BadMethodCallException;
1617
use Symfony\Component\Validator\ValidationVisitorInterface;
1718

1819
/**
19-
* @since %%NextVersion%%
20+
* A generic container of {@link Constraint} objects.
21+
*
22+
* @since 2.5
2023
* @author Bernhard Schussek <bschussek@gmail.com>
2124
*/
2225
class GenericMetadata implements MetadataInterface
@@ -31,9 +34,27 @@ class GenericMetadata implements MetadataInterface
3134
*/
3235
public $constraintsByGroup = array();
3336

37+
/**
38+
* The strategy for cascading objects.
39+
*
40+
* By default, objects are not cascaded.
41+
*
42+
* @var integer
43+
*
44+
* @see CascadingStrategy
45+
*/
3446
public $cascadingStrategy = CascadingStrategy::NONE;
3547

36-
public $traversalStrategy = TraversalStrategy::IMPLICIT;
48+
/**
49+
* The strategy for traversing traversable objects.
50+
*
51+
* By default, traversable objects are not traversed.
52+
*
53+
* @var integer
54+
*
55+
* @see TraversalStrategy
56+
*/
57+
public $traversalStrategy = TraversalStrategy::NONE;
3758

3859
/**
3960
* Returns the names of the properties that should be serialized.
@@ -66,11 +87,22 @@ public function __clone()
6687
}
6788

6889
/**
69-
* Adds a constraint to this element.
90+
* Adds a constraint.
91+
*
92+
* If the constraint {@link Valid} is added, the cascading strategy will be
93+
* changed to {@link CascadingStrategy::CASCADE}. Depending on the
94+
* properties $traverse and $deep of that constraint, the traversal strategy
95+
* will be set to one of the following:
7096
*
71-
* @param Constraint $constraint
97+
* - {@link TraversalStrategy::IMPLICIT} if $traverse is enabled and $deep
98+
* is enabled
99+
* - {@link TraversalStrategy::IMPLICIT} | {@link TraversalStrategy::STOP_RECURSION}
100+
* if $traverse is enabled, but $deep is disabled
101+
* - {@link TraversalStrategy::NONE} if $traverse is disabled
72102
*
73-
* @return ElementMetadata
103+
* @param Constraint $constraint The constraint to add
104+
*
105+
* @return GenericMetadata This object
74106
*/
75107
public function addConstraint(Constraint $constraint)
76108
{
@@ -100,17 +132,26 @@ public function addConstraint(Constraint $constraint)
100132
return $this;
101133
}
102134

135+
/**
136+
* Adds an list of constraints.
137+
*
138+
* @param Constraint[] $constraints The constraints to add
139+
*
140+
* @return GenericMetadata This object
141+
*/
103142
public function addConstraints(array $constraints)
104143
{
105144
foreach ($constraints as $constraint) {
106145
$this->addConstraint($constraint);
107146
}
147+
148+
return $this;
108149
}
109150

110151
/**
111152
* Returns all constraints of this element.
112153
*
113-
* @return Constraint[] An array of Constraint instances
154+
* @return Constraint[] A list of Constraint instances
114155
*/
115156
public function getConstraints()
116157
{
@@ -132,30 +173,45 @@ public function hasConstraints()
132173
*
133174
* @param string $group The group name
134175
*
135-
* @return array An array with all Constraint instances belonging to the group
176+
* @return Constraint[] An list of all the Constraint instances belonging
177+
* to the group
136178
*/
137179
public function findConstraints($group)
138180
{
139181
return isset($this->constraintsByGroup[$group])
140-
? $this->constraintsByGroup[$group]
141-
: array();
182+
? $this->constraintsByGroup[$group]
183+
: array();
142184
}
143185

186+
/**
187+
* {@inheritdoc}
188+
*/
144189
public function getCascadingStrategy()
145190
{
146191
return $this->cascadingStrategy;
147192
}
148193

194+
/**
195+
* {@inheritdoc}
196+
*/
149197
public function getTraversalStrategy()
150198
{
151199
return $this->traversalStrategy;
152200
}
153201

154202
/**
155-
* {@inheritdoc}
203+
* Exists for compatibility with the deprecated
204+
* {@link Symfony\Component\Validator\MetadataInterface}.
205+
*
206+
* Should not be used.
207+
*
208+
* @throws BadMethodCallException
209+
*
210+
* @deprecated Implemented for backwards compatibility with Symfony < 2.5.
211+
* Will be removed in Symfony 3.0.
156212
*/
157213
public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath)
158214
{
159-
// Thanks PHP < 5.3.9
215+
throw new BadMethodCallException('Not supported.');
160216
}
161217
}

src/Symfony/Component/Validator/NodeTraverser/NonRecursiveNodeTraverser.php

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
use Symfony\Component\Validator\Context\ExecutionContextInterface;
1515
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
16+
use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
1617
use Symfony\Component\Validator\Mapping\CascadingStrategy;
1718
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
19+
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
1820
use Symfony\Component\Validator\Mapping\TraversalStrategy;
1921
use Symfony\Component\Validator\MetadataFactoryInterface;
2022
use Symfony\Component\Validator\Node\ClassNode;
@@ -191,6 +193,9 @@ private function visit(Node $node, ExecutionContextInterface $context)
191193
* @param \SplStack $nodeStack The stack for storing the
192194
* successor nodes
193195
*
196+
* @throws UnsupportedMetadataException If a property metadata does not
197+
* implement {@link PropertyMetadataInterface}
198+
*
194199
* @see ClassNode
195200
* @see PropertyNode
196201
* @see CollectionNode
@@ -215,6 +220,15 @@ private function traverseClassNode(ClassNode $node, ExecutionContextInterface $c
215220

216221
foreach ($node->metadata->getConstrainedProperties() as $propertyName) {
217222
foreach ($node->metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
223+
if (!$propertyMetadata instanceof PropertyMetadataInterface) {
224+
throw new UnsupportedMetadataException(sprintf(
225+
'The property metadata instances should implement '.
226+
'"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '.
227+
'got: "%s".',
228+
is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata)
229+
));
230+
}
231+
218232
$nodeStack->push(new PropertyNode(
219233
$node->value,
220234
$propertyMetadata->getPropertyValue($node->value),
@@ -424,24 +438,12 @@ private function traverseNode(Node $node, ExecutionContextInterface $context, \S
424438
return;
425439
}
426440

427-
// Traverse only if IMPLICIT or TRAVERSE
428-
if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
429-
return;
430-
}
431-
432-
// If IMPLICIT, stop unless we deal with a Traversable
433-
if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$node->value instanceof \Traversable) {
434-
return;
435-
}
441+
// Currently, the traversal strategy can only be TRAVERSE for a
442+
// generic node if the cascading strategy is CASCADE. Thus, traversable
443+
// objects will always be handled within cascadeObject() and there's
444+
// nothing more to do here.
436445

437-
// If TRAVERSE, the constructor will fail if we have no Traversable
438-
$nodeStack->push(new CollectionNode(
439-
$node->value,
440-
$node->propertyPath,
441-
$cascadedGroups,
442-
null,
443-
$traversalStrategy
444-
));
446+
// see GenericMetadata::addConstraint()
445447
}
446448

447449
/**
@@ -464,15 +466,22 @@ private function traverseNode(Node $node, ExecutionContextInterface $context, \S
464466
* and does not implement {@link \Traversable}
465467
* or if traversal is disabled via the
466468
* $traversalStrategy argument
467-
*
469+
* @throws UnsupportedMetadataException If the metadata returned by the
470+
* metadata factory does not implement
471+
* {@link ClassMetadataInterface}
468472
*/
469473
private function cascadeObject($object, $propertyPath, array $groups, $traversalStrategy, \SplStack $nodeStack)
470474
{
471475
try {
472476
$classMetadata = $this->metadataFactory->getMetadataFor($object);
473477

474478
if (!$classMetadata instanceof ClassMetadataInterface) {
475-
// error
479+
throw new UnsupportedMetadataException(sprintf(
480+
'The metadata factory should return instances of '.
481+
'"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
482+
'got: "%s".',
483+
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
484+
));
476485
}
477486

478487
$nodeStack->push(new ClassNode(
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Validator\Tests\Fixtures;
13+
14+
use Symfony\Component\Validator\ClassBasedInterface;
15+
use Symfony\Component\Validator\Mapping\ClassMetadata;
16+
use Symfony\Component\Validator\MetadataInterface;
17+
use Symfony\Component\Validator\PropertyMetadataContainerInterface;
18+
19+
class FakeClassMetadata extends ClassMetadata
20+
{
21+
public function addPropertyMetadata($propertyName, $metadata)
22+
{
23+
if (!isset($this->members[$propertyName])) {
24+
$this->members[$propertyName] = array();
25+
}
26+
27+
$this->members[$propertyName][] = $metadata;
28+
}
29+
}

src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ class FakeMetadataFactory implements MetadataFactoryInterface
2222

2323
public function getMetadataFor($class)
2424
{
25+
$hash = null;
26+
2527
if (is_object($class)) {
28+
$hash = spl_object_hash($class);
2629
$class = get_class($class);
2730
}
2831

@@ -31,6 +34,10 @@ public function getMetadataFor($class)
3134
}
3235

3336
if (!isset($this->metadatas[$class])) {
37+
if (isset($this->metadatas[$hash])) {
38+
return $this->metadatas[$hash];
39+
}
40+
3441
throw new NoSuchMetadataException(sprintf('No metadata for "%s"', $class));
3542
}
3643

@@ -39,24 +46,28 @@ public function getMetadataFor($class)
3946

4047
public function hasMetadataFor($class)
4148
{
49+
$hash = null;
50+
4251
if (is_object($class)) {
4352
$class = get_class($class);
53+
$hash = spl_object_hash($hash);
4454
}
4555

4656
if (!is_string($class)) {
4757
return false;
4858
}
4959

50-
return isset($this->metadatas[$class]);
60+
return isset($this->metadatas[$class]) || isset($this->metadatas[$hash]);
5161
}
5262

53-
public function addMetadata(ClassMetadata $metadata)
63+
public function addMetadata($metadata)
5464
{
5565
$this->metadatas[$metadata->getClassName()] = $metadata;
5666
}
5767

5868
public function addMetadataForValue($value, MetadataInterface $metadata)
5969
{
60-
$this->metadatas[$value] = $metadata;
70+
$key = is_object($value) ? spl_object_hash($value) : $value;
71+
$this->metadatas[$key] = $metadata;
6172
}
6273
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Validator\Tests\Fixtures;
13+
14+
use Symfony\Component\Validator\ClassBasedInterface;
15+
use Symfony\Component\Validator\MetadataInterface;
16+
use Symfony\Component\Validator\PropertyMetadataContainerInterface;
17+
18+
interface LegacyClassMetadata extends MetadataInterface, PropertyMetadataContainerInterface, ClassBasedInterface
19+
{
20+
}

0 commit comments

Comments
 (0)
0