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['this'] = $value;
|
||||
} else {
|
||||
// Extract the object that the property belongs to from the object
|
||||
// graph
|
||||
$path = new PropertyPath($this->context->getPropertyPath());
|
||||
$parentPath = $path->getParent();
|
||||
$root = $this->context->getRoot();
|
||||
|
||||
$variables['value'] = $value;
|
||||
$variables['this'] = $parentPath ? $this->getPropertyAccessor()->getValue($root, $parentPath) : $root;
|
||||
|
||||
if (is_object($root)) {
|
||||
// Extract the object that the property belongs to from the object
|
||||
// graph
|
||||
$path = new PropertyPath($this->context->getPropertyPath());
|
||||
$parentPath = $path->getParent();
|
||||
$variables['this'] = $parentPath ? $this->getPropertyAccessor()->getValue($root, $parentPath) : $root;
|
||||
} else {
|
||||
$variables['this'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) {
|
||||
|
|
|
@ -194,4 +194,60 @@ class ExpressionValidatorTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
$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());
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -142,7 +142,7 @@ class Validator implements ValidatorInterface, Mapping\Factory\MetadataFactoryIn
|
|||
*/
|
||||
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);
|
||||
|
||||
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.'));
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (!$metadata->hasPropertyMetadata($property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($metadata->getPropertyMetadata($property) as $propMeta) {
|
||||
$propMeta->accept($visitor, $value, $group, $property);
|
||||
$propMeta->accept($visitor, $value, $group, $propertyPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,16 +68,16 @@ interface ContextualValidatorInterface
|
|||
* Validates a value against the constraints specified for an object's
|
||||
* property.
|
||||
*
|
||||
* @param object $object The object
|
||||
* @param string $propertyName The name of the property
|
||||
* @param mixed $value The value to validate against the
|
||||
* property's constraints
|
||||
* @param array|null $groups The validation groups to validate. If
|
||||
* none is given, "Default" is assumed
|
||||
* @param object|string $objectOrClass The object or its class name
|
||||
* @param string $propertyName The name of the property
|
||||
* @param mixed $value The value to validate against the
|
||||
* property's constraints
|
||||
* @param array|null $groups The validation groups to validate. If
|
||||
* none is given, "Default" is assumed
|
||||
*
|
||||
* @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
|
||||
|
|
|
@ -194,6 +194,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||
$cacheKey = spl_object_hash($object);
|
||||
$propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
|
||||
|
||||
$previousValue = $this->context->getValue();
|
||||
$previousObject = $this->context->getObject();
|
||||
|
@ -209,7 +210,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||
$object,
|
||||
$cacheKey.':'.$propertyName,
|
||||
$propertyMetadata,
|
||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||
$propertyPath,
|
||||
$groups,
|
||||
null,
|
||||
TraversalStrategy::IMPLICIT,
|
||||
|
@ -226,9 +227,9 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||
/**
|
||||
* {@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) {
|
||||
// Cannot be UnsupportedMetadataException because of BC with
|
||||
|
@ -243,7 +244,17 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||
|
||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||
$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();
|
||||
$previousObject = $this->context->getObject();
|
||||
|
@ -257,7 +268,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
|||
$object,
|
||||
$cacheKey.':'.$propertyName,
|
||||
$propertyMetadata,
|
||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||
$propertyPath,
|
||||
$groups,
|
||||
null,
|
||||
TraversalStrategy::IMPLICIT,
|
||||
|
|
|
@ -130,10 +130,11 @@ class RecursiveValidator implements ValidatorInterface
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
|
||||
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
|
||||
{
|
||||
return $this->startContext($object)
|
||||
->validatePropertyValue($object, $propertyName, $value, $groups)
|
||||
// If a class name is passed, take $value as root
|
||||
return $this->startContext(is_object($objectOrClass) ? $objectOrClass : $value)
|
||||
->validatePropertyValue($objectOrClass, $propertyName, $value, $groups)
|
||||
->getViolations();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,18 +62,18 @@ interface ValidatorInterface extends MetadataFactoryInterface
|
|||
* Validates a value against the constraints specified for an object's
|
||||
* property.
|
||||
*
|
||||
* @param object $object The object
|
||||
* @param string $propertyName The name of the property
|
||||
* @param mixed $value The value to validate against the
|
||||
* property's constraints
|
||||
* @param array|null $groups The validation groups to validate. If
|
||||
* none is given, "Default" is assumed
|
||||
* @param object|string $objectOrClass The object or its class name
|
||||
* @param string $propertyName The name of the property
|
||||
* @param mixed $value The value to validate against the
|
||||
* property's constraints
|
||||
* @param array|null $groups The validation groups to validate. If
|
||||
* none is given, "Default" is assumed
|
||||
*
|
||||
* @return ConstraintViolationListInterface A list of constraint violations.
|
||||
* If the list is empty, validation
|
||||
* 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.
|
||||
|
|
|
@ -68,7 +68,7 @@ interface ValidatorInterface
|
|||
* The accepted values depend on the {@link MetadataFactoryInterface}
|
||||
* 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 $value The value to validate against the
|
||||
* constraints of the property.
|
||||
|
|
Reference in New Issue