Skip to content

Commit c43253e

Browse files
committed
bug #11498 [Validator] Made it possible (again) to pass a class name to validatePropertyValue() (webmozart)
This PR was merged into the 2.5 branch. Discussion ---------- [Validator] Made it possible (again) to pass a class name to validatePropertyValue() | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #11139 | License | MIT | Doc PR | - In the 2.4 API it was possible to do both: ```php $validator->validatePropertyValue($object, 'propertyName', $myValue); $validator->validatePropertyValue('\Vendor\Namespace\ClassName', 'propertyName', $myValue); ``` In the 2.5 API, the second case was not supported anymore. This is fixed now. Together with the fix comes also a small change (also in the 2.4 API) which I'll demonstrate with a code snippet: ```php $metadata->addPropertyConstraint('ClassName', 'propertyName', new Callback( function ($value, $context) { var_dump($context->getRoot()); var_dump($context->getPropertyPath()); } )); $validator->validatePropertyValue('ClassName', 'propertyName', 'foobar'); ``` Before this PR, the output would be: ``` string(9) "ClassName" string(12) "propertyName" ``` This doesn't make a lot of sense, because usually the following condition holds during validation: ```php '' === $context->getPropertyPath() || $value === $propertyAccessor->getValue($context->getRoot(), $context->getPropertyPath()) ``` which obviously cannot work if root is a class name. Thus I changed the root and property path to become: ``` string(6) "foobar" string(0) "" ``` With this change, the condition holds also in this case. Commits ------- 2bf1b37 [Validator] Fixed ExpressionValidator when the validation root is not an object ef6f5f5 [Validator] Fixed: Made it possible (again) to pass a class name to Validator::validatePropertyValue()
2 parents 3ee327c + 2bf1b37 commit c43253e

9 files changed

+158
-31
lines changed

src/Symfony/Component/Validator/Constraints/ExpressionValidator.php

+10-6
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,18 @@ public function validate($value, Constraint $constraint)
7474
$variables['value'] = $value;
7575
$variables['this'] = $value;
7676
} else {
77-
// Extract the object that the property belongs to from the object
78-
// graph
79-
$path = new PropertyPath($this->context->getPropertyPath());
80-
$parentPath = $path->getParent();
8177
$root = $this->context->getRoot();
82-
8378
$variables['value'] = $value;
84-
$variables['this'] = $parentPath ? $this->getPropertyAccessor()->getValue($root, $parentPath) : $root;
79+
80+
if (is_object($root)) {
81+
// Extract the object that the property belongs to from the object
82+
// graph
83+
$path = new PropertyPath($this->context->getPropertyPath());
84+
$parentPath = $path->getParent();
85+
$variables['this'] = $parentPath ? $this->getPropertyAccessor()->getValue($root, $parentPath) : $root;
86+
} else {
87+
$variables['this'] = null;
88+
}
8589
}
8690

8791
if (!$this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) {

src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php

+56
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,60 @@ public function testFailingExpressionAtNestedPropertyLevel()
194194

195195
$this->validator->validate('2', $constraint);
196196
}
197+
198+
/**
199+
* When validatePropertyValue() is called with a class name
200+
* https://github.com/symfony/symfony/pull/11498
201+
*/
202+
public function testSucceedingExpressionAtPropertyLevelWithoutRoot()
203+
{
204+
$constraint = new Expression('value == "1"');
205+
206+
$this->context->expects($this->any())
207+
->method('getPropertyName')
208+
->will($this->returnValue('property'));
209+
210+
$this->context->expects($this->any())
211+
->method('getPropertyPath')
212+
->will($this->returnValue(''));
213+
214+
$this->context->expects($this->any())
215+
->method('getRoot')
216+
->will($this->returnValue('1'));
217+
218+
$this->context->expects($this->never())
219+
->method('addViolation');
220+
221+
$this->validator->validate('1', $constraint);
222+
}
223+
224+
/**
225+
* When validatePropertyValue() is called with a class name
226+
* https://github.com/symfony/symfony/pull/11498
227+
*/
228+
public function testFailingExpressionAtPropertyLevelWithoutRoot()
229+
{
230+
$constraint = new Expression(array(
231+
'expression' => 'value == "1"',
232+
'message' => 'myMessage',
233+
));
234+
235+
$this->context->expects($this->any())
236+
->method('getPropertyName')
237+
->will($this->returnValue('property'));
238+
239+
$this->context->expects($this->any())
240+
->method('getPropertyPath')
241+
->will($this->returnValue(''));
242+
243+
$this->context->expects($this->any())
244+
->method('getRoot')
245+
->will($this->returnValue('2'));
246+
247+
$this->context->expects($this->once())
248+
->method('addViolation')
249+
->with('myMessage');
250+
251+
$this->validator->validate('2', $constraint);
252+
}
197253
}

src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php

+51
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,57 @@ public function testValidatePropertyValue()
904904
$this->assertNull($violations[0]->getCode());
905905
}
906906

907+
public function testValidatePropertyValueWithClassName()
908+
{
909+
$test = $this;
910+
911+
$callback1 = function ($value, ExecutionContextInterface $context) use ($test) {
912+
$propertyMetadatas = $test->metadata->getPropertyMetadata('firstName');
913+
914+
$test->assertSame($test::ENTITY_CLASS, $context->getClassName());
915+
$test->assertSame('firstName', $context->getPropertyName());
916+
$test->assertSame('', $context->getPropertyPath());
917+
$test->assertSame('Group', $context->getGroup());
918+
$test->assertSame($propertyMetadatas[0], $context->getMetadata());
919+
$test->assertSame('Bernhard', $context->getRoot());
920+
$test->assertSame('Bernhard', $context->getValue());
921+
$test->assertSame('Bernhard', $value);
922+
923+
$context->addViolation('Message %param%', array('%param%' => 'value'));
924+
};
925+
926+
$callback2 = function ($value, ExecutionContextInterface $context) {
927+
$context->addViolation('Other violation');
928+
};
929+
930+
$this->metadata->addPropertyConstraint('firstName', new Callback(array(
931+
'callback' => $callback1,
932+
'groups' => 'Group',
933+
)));
934+
$this->metadata->addPropertyConstraint('lastName', new Callback(array(
935+
'callback' => $callback2,
936+
'groups' => 'Group',
937+
)));
938+
939+
$violations = $this->validatePropertyValue(
940+
self::ENTITY_CLASS,
941+
'firstName',
942+
'Bernhard',
943+
'Group'
944+
);
945+
946+
/** @var ConstraintViolationInterface[] $violations */
947+
$this->assertCount(1, $violations);
948+
$this->assertSame('Message value', $violations[0]->getMessage());
949+
$this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
950+
$this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
951+
$this->assertSame('', $violations[0]->getPropertyPath());
952+
$this->assertSame('Bernhard', $violations[0]->getRoot());
953+
$this->assertSame('Bernhard', $violations[0]->getInvalidValue());
954+
$this->assertNull($violations[0]->getMessagePluralization());
955+
$this->assertNull($violations[0]->getCode());
956+
}
957+
907958
/**
908959
* Cannot be UnsupportedMetadataException for BC with Symfony < 2.5.
909960
*

src/Symfony/Component/Validator/Validator.php

+6-2
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public function validateProperty($containingValue, $property, $groups = null)
142142
*/
143143
public function validatePropertyValue($containingValue, $property, $value, $groups = null)
144144
{
145-
$visitor = $this->createVisitor($containingValue);
145+
$visitor = $this->createVisitor(is_object($containingValue) ? $containingValue : $value);
146146
$metadata = $this->metadataFactory->getMetadataFor($containingValue);
147147

148148
if (!$metadata instanceof PropertyMetadataContainerInterface) {
@@ -153,13 +153,17 @@ public function validatePropertyValue($containingValue, $property, $value, $grou
153153
throw new ValidatorException(sprintf('The metadata for '.$valueAsString.' does not support properties.'));
154154
}
155155

156+
// If $containingValue is passed as class name, take $value as root
157+
// and start the traversal with an empty property path
158+
$propertyPath = is_object($containingValue) ? $property : '';
159+
156160
foreach ($this->resolveGroups($groups) as $group) {
157161
if (!$metadata->hasPropertyMetadata($property)) {
158162
continue;
159163
}
160164

161165
foreach ($metadata->getPropertyMetadata($property) as $propMeta) {
162-
$propMeta->accept($visitor, $value, $group, $property);
166+
$propMeta->accept($visitor, $value, $group, $propertyPath);
163167
}
164168
}
165169

src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php

+7-7
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,16 @@ public function validateProperty($object, $propertyName, $groups = null);
6868
* Validates a value against the constraints specified for an object's
6969
* property.
7070
*
71-
* @param object $object The object
72-
* @param string $propertyName The name of the property
73-
* @param mixed $value The value to validate against the
74-
* property's constraints
75-
* @param array|null $groups The validation groups to validate. If
76-
* none is given, "Default" is assumed
71+
* @param object|string $objectOrClass The object or its class name
72+
* @param string $propertyName The name of the property
73+
* @param mixed $value The value to validate against the
74+
* property's constraints
75+
* @param array|null $groups The validation groups to validate. If
76+
* none is given, "Default" is assumed
7777
*
7878
* @return ContextualValidatorInterface This validator
7979
*/
80-
public function validatePropertyValue($object, $propertyName, $value, $groups = null);
80+
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null);
8181

8282
/**
8383
* Returns the violations that have been generated so far in the context

src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php

+16-5
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ public function validateProperty($object, $propertyName, $groups = null)
194194
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
195195
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
196196
$cacheKey = spl_object_hash($object);
197+
$propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
197198

198199
$previousValue = $this->context->getValue();
199200
$previousObject = $this->context->getObject();
@@ -209,7 +210,7 @@ public function validateProperty($object, $propertyName, $groups = null)
209210
$object,
210211
$cacheKey.':'.$propertyName,
211212
$propertyMetadata,
212-
PropertyPath::append($this->defaultPropertyPath, $propertyName),
213+
$propertyPath,
213214
$groups,
214215
null,
215216
TraversalStrategy::IMPLICIT,
@@ -226,9 +227,9 @@ public function validateProperty($object, $propertyName, $groups = null)
226227
/**
227228
* {@inheritdoc}
228229
*/
229-
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
230+
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
230231
{
231-
$classMetadata = $this->metadataFactory->getMetadataFor($object);
232+
$classMetadata = $this->metadataFactory->getMetadataFor($objectOrClass);
232233

233234
if (!$classMetadata instanceof ClassMetadataInterface) {
234235
// Cannot be UnsupportedMetadataException because of BC with
@@ -243,7 +244,17 @@ public function validatePropertyValue($object, $propertyName, $value, $groups =
243244

244245
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
245246
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
246-
$cacheKey = spl_object_hash($object);
247+
248+
if (is_object($objectOrClass)) {
249+
$object = $objectOrClass;
250+
$cacheKey = spl_object_hash($objectOrClass);
251+
$propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
252+
} else {
253+
// $objectOrClass contains a class name
254+
$object = null;
255+
$cacheKey = null;
256+
$propertyPath = $this->defaultPropertyPath;
257+
}
247258

248259
$previousValue = $this->context->getValue();
249260
$previousObject = $this->context->getObject();
@@ -257,7 +268,7 @@ public function validatePropertyValue($object, $propertyName, $value, $groups =
257268
$object,
258269
$cacheKey.':'.$propertyName,
259270
$propertyMetadata,
260-
PropertyPath::append($this->defaultPropertyPath, $propertyName),
271+
$propertyPath,
261272
$groups,
262273
null,
263274
TraversalStrategy::IMPLICIT,

src/Symfony/Component/Validator/Validator/RecursiveValidator.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,11 @@ public function validateProperty($object, $propertyName, $groups = null)
130130
/**
131131
* {@inheritdoc}
132132
*/
133-
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
133+
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
134134
{
135-
return $this->startContext($object)
136-
->validatePropertyValue($object, $propertyName, $value, $groups)
135+
// If a class name is passed, take $value as root
136+
return $this->startContext(is_object($objectOrClass) ? $objectOrClass : $value)
137+
->validatePropertyValue($objectOrClass, $propertyName, $value, $groups)
137138
->getViolations();
138139
}
139140
}

src/Symfony/Component/Validator/Validator/ValidatorInterface.php

+7-7
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,18 @@ public function validateProperty($object, $propertyName, $groups = null);
6262
* Validates a value against the constraints specified for an object's
6363
* property.
6464
*
65-
* @param object $object The object
66-
* @param string $propertyName The name of the property
67-
* @param mixed $value The value to validate against the
68-
* property's constraints
69-
* @param array|null $groups The validation groups to validate. If
70-
* none is given, "Default" is assumed
65+
* @param object|string $objectOrClass The object or its class name
66+
* @param string $propertyName The name of the property
67+
* @param mixed $value The value to validate against the
68+
* property's constraints
69+
* @param array|null $groups The validation groups to validate. If
70+
* none is given, "Default" is assumed
7171
*
7272
* @return ConstraintViolationListInterface A list of constraint violations.
7373
* If the list is empty, validation
7474
* succeeded
7575
*/
76-
public function validatePropertyValue($object, $propertyName, $value, $groups = null);
76+
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null);
7777

7878
/**
7979
* Starts a new validation context and returns a validator for that context.

src/Symfony/Component/Validator/ValidatorInterface.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function validateProperty($containingValue, $property, $groups = null);
6868
* The accepted values depend on the {@link MetadataFactoryInterface}
6969
* implementation.
7070
*
71-
* @param string $containingValue The value containing the property.
71+
* @param mixed $containingValue The value containing the property.
7272
* @param string $property The name of the property to validate
7373
* @param string $value The value to validate against the
7474
* constraints of the property.

0 commit comments

Comments
 (0)