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 objectef6f5f5
[Validator] Fixed: Made it possible (again) to pass a class name to Validator::validatePropertyValue()
This commit is contained in:
commit
c43253e363
@ -74,14 +74,18 @@ class ExpressionValidator extends ConstraintValidator
|
|||||||
$variables['value'] = $value;
|
$variables['value'] = $value;
|
||||||
$variables['this'] = $value;
|
$variables['this'] = $value;
|
||||||
} else {
|
} else {
|
||||||
|
$root = $this->context->getRoot();
|
||||||
|
$variables['value'] = $value;
|
||||||
|
|
||||||
|
if (is_object($root)) {
|
||||||
// Extract the object that the property belongs to from the object
|
// Extract the object that the property belongs to from the object
|
||||||
// graph
|
// graph
|
||||||
$path = new PropertyPath($this->context->getPropertyPath());
|
$path = new PropertyPath($this->context->getPropertyPath());
|
||||||
$parentPath = $path->getParent();
|
$parentPath = $path->getParent();
|
||||||
$root = $this->context->getRoot();
|
|
||||||
|
|
||||||
$variables['value'] = $value;
|
|
||||||
$variables['this'] = $parentPath ? $this->getPropertyAccessor()->getValue($root, $parentPath) : $root;
|
$variables['this'] = $parentPath ? $this->getPropertyAccessor()->getValue($root, $parentPath) : $root;
|
||||||
|
} else {
|
||||||
|
$variables['this'] = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) {
|
if (!$this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) {
|
||||||
|
@ -194,4 +194,60 @@ class ExpressionValidatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
$this->validator->validate('2', $constraint);
|
$this->validator->validate('2', $constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When validatePropertyValue() is called with a class name
|
||||||
|
* https://github.com/symfony/symfony/pull/11498
|
||||||
|
*/
|
||||||
|
public function testSucceedingExpressionAtPropertyLevelWithoutRoot()
|
||||||
|
{
|
||||||
|
$constraint = new Expression('value == "1"');
|
||||||
|
|
||||||
|
$this->context->expects($this->any())
|
||||||
|
->method('getPropertyName')
|
||||||
|
->will($this->returnValue('property'));
|
||||||
|
|
||||||
|
$this->context->expects($this->any())
|
||||||
|
->method('getPropertyPath')
|
||||||
|
->will($this->returnValue(''));
|
||||||
|
|
||||||
|
$this->context->expects($this->any())
|
||||||
|
->method('getRoot')
|
||||||
|
->will($this->returnValue('1'));
|
||||||
|
|
||||||
|
$this->context->expects($this->never())
|
||||||
|
->method('addViolation');
|
||||||
|
|
||||||
|
$this->validator->validate('1', $constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When validatePropertyValue() is called with a class name
|
||||||
|
* https://github.com/symfony/symfony/pull/11498
|
||||||
|
*/
|
||||||
|
public function testFailingExpressionAtPropertyLevelWithoutRoot()
|
||||||
|
{
|
||||||
|
$constraint = new Expression(array(
|
||||||
|
'expression' => 'value == "1"',
|
||||||
|
'message' => 'myMessage',
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->context->expects($this->any())
|
||||||
|
->method('getPropertyName')
|
||||||
|
->will($this->returnValue('property'));
|
||||||
|
|
||||||
|
$this->context->expects($this->any())
|
||||||
|
->method('getPropertyPath')
|
||||||
|
->will($this->returnValue(''));
|
||||||
|
|
||||||
|
$this->context->expects($this->any())
|
||||||
|
->method('getRoot')
|
||||||
|
->will($this->returnValue('2'));
|
||||||
|
|
||||||
|
$this->context->expects($this->once())
|
||||||
|
->method('addViolation')
|
||||||
|
->with('myMessage');
|
||||||
|
|
||||||
|
$this->validator->validate('2', $constraint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -904,6 +904,57 @@ abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertNull($violations[0]->getCode());
|
$this->assertNull($violations[0]->getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testValidatePropertyValueWithClassName()
|
||||||
|
{
|
||||||
|
$test = $this;
|
||||||
|
|
||||||
|
$callback1 = function ($value, ExecutionContextInterface $context) use ($test) {
|
||||||
|
$propertyMetadatas = $test->metadata->getPropertyMetadata('firstName');
|
||||||
|
|
||||||
|
$test->assertSame($test::ENTITY_CLASS, $context->getClassName());
|
||||||
|
$test->assertSame('firstName', $context->getPropertyName());
|
||||||
|
$test->assertSame('', $context->getPropertyPath());
|
||||||
|
$test->assertSame('Group', $context->getGroup());
|
||||||
|
$test->assertSame($propertyMetadatas[0], $context->getMetadata());
|
||||||
|
$test->assertSame('Bernhard', $context->getRoot());
|
||||||
|
$test->assertSame('Bernhard', $context->getValue());
|
||||||
|
$test->assertSame('Bernhard', $value);
|
||||||
|
|
||||||
|
$context->addViolation('Message %param%', array('%param%' => 'value'));
|
||||||
|
};
|
||||||
|
|
||||||
|
$callback2 = function ($value, ExecutionContextInterface $context) {
|
||||||
|
$context->addViolation('Other violation');
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->metadata->addPropertyConstraint('firstName', new Callback(array(
|
||||||
|
'callback' => $callback1,
|
||||||
|
'groups' => 'Group',
|
||||||
|
)));
|
||||||
|
$this->metadata->addPropertyConstraint('lastName', new Callback(array(
|
||||||
|
'callback' => $callback2,
|
||||||
|
'groups' => 'Group',
|
||||||
|
)));
|
||||||
|
|
||||||
|
$violations = $this->validatePropertyValue(
|
||||||
|
self::ENTITY_CLASS,
|
||||||
|
'firstName',
|
||||||
|
'Bernhard',
|
||||||
|
'Group'
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @var ConstraintViolationInterface[] $violations */
|
||||||
|
$this->assertCount(1, $violations);
|
||||||
|
$this->assertSame('Message value', $violations[0]->getMessage());
|
||||||
|
$this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
|
||||||
|
$this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
|
||||||
|
$this->assertSame('', $violations[0]->getPropertyPath());
|
||||||
|
$this->assertSame('Bernhard', $violations[0]->getRoot());
|
||||||
|
$this->assertSame('Bernhard', $violations[0]->getInvalidValue());
|
||||||
|
$this->assertNull($violations[0]->getMessagePluralization());
|
||||||
|
$this->assertNull($violations[0]->getCode());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cannot be UnsupportedMetadataException for BC with Symfony < 2.5.
|
* Cannot be UnsupportedMetadataException for BC with Symfony < 2.5.
|
||||||
*
|
*
|
||||||
|
@ -142,7 +142,7 @@ class Validator implements ValidatorInterface, Mapping\Factory\MetadataFactoryIn
|
|||||||
*/
|
*/
|
||||||
public function validatePropertyValue($containingValue, $property, $value, $groups = null)
|
public function validatePropertyValue($containingValue, $property, $value, $groups = null)
|
||||||
{
|
{
|
||||||
$visitor = $this->createVisitor($containingValue);
|
$visitor = $this->createVisitor(is_object($containingValue) ? $containingValue : $value);
|
||||||
$metadata = $this->metadataFactory->getMetadataFor($containingValue);
|
$metadata = $this->metadataFactory->getMetadataFor($containingValue);
|
||||||
|
|
||||||
if (!$metadata instanceof PropertyMetadataContainerInterface) {
|
if (!$metadata instanceof PropertyMetadataContainerInterface) {
|
||||||
@ -153,13 +153,17 @@ class Validator implements ValidatorInterface, Mapping\Factory\MetadataFactoryIn
|
|||||||
throw new ValidatorException(sprintf('The metadata for '.$valueAsString.' does not support properties.'));
|
throw new ValidatorException(sprintf('The metadata for '.$valueAsString.' does not support properties.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If $containingValue is passed as class name, take $value as root
|
||||||
|
// and start the traversal with an empty property path
|
||||||
|
$propertyPath = is_object($containingValue) ? $property : '';
|
||||||
|
|
||||||
foreach ($this->resolveGroups($groups) as $group) {
|
foreach ($this->resolveGroups($groups) as $group) {
|
||||||
if (!$metadata->hasPropertyMetadata($property)) {
|
if (!$metadata->hasPropertyMetadata($property)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($metadata->getPropertyMetadata($property) as $propMeta) {
|
foreach ($metadata->getPropertyMetadata($property) as $propMeta) {
|
||||||
$propMeta->accept($visitor, $value, $group, $property);
|
$propMeta->accept($visitor, $value, $group, $propertyPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ interface ContextualValidatorInterface
|
|||||||
* Validates a value against the constraints specified for an object's
|
* Validates a value against the constraints specified for an object's
|
||||||
* property.
|
* property.
|
||||||
*
|
*
|
||||||
* @param object $object The object
|
* @param object|string $objectOrClass The object or its class name
|
||||||
* @param string $propertyName The name of the property
|
* @param string $propertyName The name of the property
|
||||||
* @param mixed $value The value to validate against the
|
* @param mixed $value The value to validate against the
|
||||||
* property's constraints
|
* property's constraints
|
||||||
@ -77,7 +77,7 @@ interface ContextualValidatorInterface
|
|||||||
*
|
*
|
||||||
* @return ContextualValidatorInterface This validator
|
* @return ContextualValidatorInterface This validator
|
||||||
*/
|
*/
|
||||||
public function validatePropertyValue($object, $propertyName, $value, $groups = null);
|
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the violations that have been generated so far in the context
|
* Returns the violations that have been generated so far in the context
|
||||||
|
@ -194,6 +194,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||||
$cacheKey = spl_object_hash($object);
|
$cacheKey = spl_object_hash($object);
|
||||||
|
$propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
|
||||||
|
|
||||||
$previousValue = $this->context->getValue();
|
$previousValue = $this->context->getValue();
|
||||||
$previousObject = $this->context->getObject();
|
$previousObject = $this->context->getObject();
|
||||||
@ -209,7 +210,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
$object,
|
$object,
|
||||||
$cacheKey.':'.$propertyName,
|
$cacheKey.':'.$propertyName,
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
null,
|
null,
|
||||||
TraversalStrategy::IMPLICIT,
|
TraversalStrategy::IMPLICIT,
|
||||||
@ -226,9 +227,9 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
|
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
|
||||||
{
|
{
|
||||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
$classMetadata = $this->metadataFactory->getMetadataFor($objectOrClass);
|
||||||
|
|
||||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||||
// Cannot be UnsupportedMetadataException because of BC with
|
// Cannot be UnsupportedMetadataException because of BC with
|
||||||
@ -243,7 +244,17 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
|
|
||||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||||
$cacheKey = spl_object_hash($object);
|
|
||||||
|
if (is_object($objectOrClass)) {
|
||||||
|
$object = $objectOrClass;
|
||||||
|
$cacheKey = spl_object_hash($objectOrClass);
|
||||||
|
$propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
|
||||||
|
} else {
|
||||||
|
// $objectOrClass contains a class name
|
||||||
|
$object = null;
|
||||||
|
$cacheKey = null;
|
||||||
|
$propertyPath = $this->defaultPropertyPath;
|
||||||
|
}
|
||||||
|
|
||||||
$previousValue = $this->context->getValue();
|
$previousValue = $this->context->getValue();
|
||||||
$previousObject = $this->context->getObject();
|
$previousObject = $this->context->getObject();
|
||||||
@ -257,7 +268,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||||||
$object,
|
$object,
|
||||||
$cacheKey.':'.$propertyName,
|
$cacheKey.':'.$propertyName,
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
||||||
null,
|
null,
|
||||||
TraversalStrategy::IMPLICIT,
|
TraversalStrategy::IMPLICIT,
|
||||||
|
@ -130,10 +130,11 @@ class RecursiveValidator implements ValidatorInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
|
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
|
||||||
{
|
{
|
||||||
return $this->startContext($object)
|
// If a class name is passed, take $value as root
|
||||||
->validatePropertyValue($object, $propertyName, $value, $groups)
|
return $this->startContext(is_object($objectOrClass) ? $objectOrClass : $value)
|
||||||
|
->validatePropertyValue($objectOrClass, $propertyName, $value, $groups)
|
||||||
->getViolations();
|
->getViolations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ interface ValidatorInterface extends MetadataFactoryInterface
|
|||||||
* Validates a value against the constraints specified for an object's
|
* Validates a value against the constraints specified for an object's
|
||||||
* property.
|
* property.
|
||||||
*
|
*
|
||||||
* @param object $object The object
|
* @param object|string $objectOrClass The object or its class name
|
||||||
* @param string $propertyName The name of the property
|
* @param string $propertyName The name of the property
|
||||||
* @param mixed $value The value to validate against the
|
* @param mixed $value The value to validate against the
|
||||||
* property's constraints
|
* property's constraints
|
||||||
@ -73,7 +73,7 @@ interface ValidatorInterface extends MetadataFactoryInterface
|
|||||||
* If the list is empty, validation
|
* If the list is empty, validation
|
||||||
* succeeded
|
* succeeded
|
||||||
*/
|
*/
|
||||||
public function validatePropertyValue($object, $propertyName, $value, $groups = null);
|
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a new validation context and returns a validator for that context.
|
* Starts a new validation context and returns a validator for that context.
|
||||||
|
@ -68,7 +68,7 @@ interface ValidatorInterface
|
|||||||
* The accepted values depend on the {@link MetadataFactoryInterface}
|
* The accepted values depend on the {@link MetadataFactoryInterface}
|
||||||
* implementation.
|
* implementation.
|
||||||
*
|
*
|
||||||
* @param string $containingValue The value containing the property.
|
* @param mixed $containingValue The value containing the property.
|
||||||
* @param string $property The name of the property to validate
|
* @param string $property The name of the property to validate
|
||||||
* @param string $value The value to validate against the
|
* @param string $value The value to validate against the
|
||||||
* constraints of the property.
|
* constraints of the property.
|
||||||
|
Reference in New Issue
Block a user