diff --git a/UPGRADE-2.6.md b/UPGRADE-2.6.md new file mode 100644 index 0000000000..c03c373008 --- /dev/null +++ b/UPGRADE-2.6.md @@ -0,0 +1,19 @@ +UPGRADE FROM 2.5 to 2.6 +======================= + +Validator +--------- + + * The internal method `setConstraint()` was added to + `Symfony\Component\Validator\Context\ExecutionContextInterface`. With + this method, the context is informed about the constraint that is currently + being validated. + + If you implement this interface, make sure to add the method to your + implementation. The easiest solution is to just implement an empty method: + + ```php + public function setConstraint(Constraint $constraint) + { + } + ``` diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 6cb64292bd..ad92e56230 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -6,6 +6,8 @@ CHANGELOG * [BC BREAK] `FileValidator` disallow empty files * [BC BREAK] `UserPasswordValidator` source message change + * [BC BREAK] added internal `ExecutionContextInterface::setConstraint()` + * added `ConstraintViolation::getConstraint()` 2.5.0 ----- diff --git a/src/Symfony/Component/Validator/ConstraintViolation.php b/src/Symfony/Component/Validator/ConstraintViolation.php index d6a4e3095f..5cf57ed4f8 100644 --- a/src/Symfony/Component/Validator/ConstraintViolation.php +++ b/src/Symfony/Component/Validator/ConstraintViolation.php @@ -53,6 +53,11 @@ class ConstraintViolation implements ConstraintViolationInterface */ private $invalidValue; + /** + * @var Constraint|null + */ + private $constraint; + /** * @var mixed */ @@ -61,21 +66,23 @@ class ConstraintViolation implements ConstraintViolationInterface /** * Creates a new constraint violation. * - * @param string $message The violation message - * @param string $messageTemplate The raw violation message - * @param array $parameters The parameters to substitute in the - * raw violation message - * @param mixed $root The value originally passed to the - * validator - * @param string $propertyPath The property path from the root - * value to the invalid value - * @param mixed $invalidValue The invalid value that caused this - * violation - * @param int|null $plural The number for determining the plural - * form when translating the message - * @param mixed $code The error code of the violation + * @param string $message The violation message + * @param string $messageTemplate The raw violation message + * @param array $parameters The parameters to substitute in the + * raw violation message + * @param mixed $root The value originally passed to the + * validator + * @param string $propertyPath The property path from the root + * value to the invalid value + * @param mixed $invalidValue The invalid value that caused this + * violation + * @param int|null $plural The number for determining the plural + * form when translating the message + * @param mixed $code The error code of the violation + * @param Constraint|null $constraint The constraint that caused the + * violation */ - public function __construct($message, $messageTemplate, array $parameters, $root, $propertyPath, $invalidValue, $plural = null, $code = null) + public function __construct($message, $messageTemplate, array $parameters, $root, $propertyPath, $invalidValue, $plural = null, $code = null, Constraint $constraint = null) { $this->message = $message; $this->messageTemplate = $messageTemplate; @@ -84,6 +91,7 @@ class ConstraintViolation implements ConstraintViolationInterface $this->root = $root; $this->propertyPath = $propertyPath; $this->invalidValue = $invalidValue; + $this->constraint = $constraint; $this->code = $code; } @@ -188,6 +196,16 @@ class ConstraintViolation implements ConstraintViolationInterface return $this->invalidValue; } + /** + * Returns the constraint that caused the violation. + * + * @return Constraint|null The constraint or null if it is not known + */ + public function getConstraint() + { + return $this->constraint; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php index a30382d3b7..c38490739d 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Validator\Context; use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Validator\ClassBasedInterface; +use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\Exception\BadMethodCallException; @@ -99,6 +100,13 @@ class ExecutionContext implements ExecutionContextInterface */ private $group; + /** + * The currently validated constraint. + * + * @var Constraint|null + */ + private $constraint; + /** * Stores which objects have been validated in which group. * @@ -162,6 +170,14 @@ class ExecutionContext implements ExecutionContextInterface $this->group = $group; } + /** + * {@inheritdoc} + */ + public function setConstraint(Constraint $constraint) + { + $this->constraint = $constraint; + } + /** * {@inheritdoc} */ @@ -186,7 +202,8 @@ class ExecutionContext implements ExecutionContextInterface $this->propertyPath, $this->value, null, - null + null, + $this->constraint )); } @@ -197,6 +214,7 @@ class ExecutionContext implements ExecutionContextInterface { return new ConstraintViolationBuilder( $this->violations, + $this->constraint, $message, $parameters, $this->root, diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php index f63ce15261..cd4bae6057 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\Context; +use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionContextInterface; use Symfony\Component\Validator\Mapping\MetadataInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -136,6 +137,16 @@ interface ExecutionContextInterface extends LegacyExecutionContextInterface */ public function setGroup($group); + /** + * Sets the currently validated constraint. + * + * @param Constraint $constraint The validated constraint + * + * @internal Used by the validator engine. Should not be called by user + * code. + */ + public function setConstraint(Constraint $constraint); + /** * Marks an object as validated in a specific validation group. * diff --git a/src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php index 5aecf5470b..ef29fe34a7 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\Tests\Constraints; +use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\Context\ExecutionContext; @@ -49,6 +50,8 @@ abstract class AbstractConstraintValidatorTest extends \PHPUnit_Framework_TestCa protected $propertyPath; + protected $constraint; + protected function setUp() { $this->group = 'MyGroup'; @@ -57,6 +60,15 @@ abstract class AbstractConstraintValidatorTest extends \PHPUnit_Framework_TestCa $this->value = 'InvalidValue'; $this->root = 'root'; $this->propertyPath = 'property.path'; + + // Initialize the context with some constraint so that we can + // successfully build a violation. + // The 2.4 API does not keep a reference to the current + // constraint yet. There the violation stores null. + $this->constraint = Validation::API_VERSION_2_4 === $this->getApiVersion() + ? null + : new NotNull(); + $this->context = $this->createContext(); $this->validator = $this->createValidator(); $this->validator->initialize($this->context); @@ -108,6 +120,7 @@ abstract class AbstractConstraintValidatorTest extends \PHPUnit_Framework_TestCa $context->setGroup($this->group); $context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath); + $context->setConstraint($this->constraint); $validator->expects($this->any()) ->method('inContext') @@ -127,7 +140,8 @@ abstract class AbstractConstraintValidatorTest extends \PHPUnit_Framework_TestCa $propertyPath, $invalidValue, $plural, - $code + $code, + $this->constraint ); } diff --git a/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php index e920da9a72..ac63d94010 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php @@ -20,6 +20,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\Tests\Fixtures\Entity; +use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint; use Symfony\Component\Validator\Tests\Fixtures\FakeClassMetadata; use Symfony\Component\Validator\Tests\Fixtures\Reference; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -752,4 +753,13 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest $this->assertTrue($entity->initialized); } + + public function testPassConstraintToViolation() + { + $constraint = new FailingConstraint(); + $violations = $this->validate('Foobar', $constraint); + + $this->assertCount(1, $violations); + $this->assertSame($constraint, $violations[0]->getConstraint()); + } } diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index 29d0c6c00c..2da1887afd 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -856,6 +856,8 @@ class RecursiveContextualValidator implements ContextualValidatorInterface $context->markConstraintAsValidated($cacheKey, $constraintHash); } + $context->setConstraint($constraint); + $validator = $this->validatorFactory->getInstance($constraint); $validator->initialize($context); $validator->validate($value, $constraint); diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php index 969b79cded..6d6bd02774 100644 --- a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php +++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Validator\Violation; use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\Util\PropertyPath; @@ -72,12 +73,17 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface */ private $plural; + /** + * @var Constraint + */ + private $constraint; + /** * @var mixed */ private $code; - public function __construct(ConstraintViolationList $violations, $message, array $parameters, $root, $propertyPath, $invalidValue, TranslatorInterface $translator, $translationDomain = null) + public function __construct(ConstraintViolationList $violations, Constraint $constraint, $message, array $parameters, $root, $propertyPath, $invalidValue, TranslatorInterface $translator, $translationDomain = null) { $this->violations = $violations; $this->message = $message; @@ -87,6 +93,7 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface $this->invalidValue = $invalidValue; $this->translator = $translator; $this->translationDomain = $translationDomain; + $this->constraint = $constraint; } /** @@ -195,7 +202,8 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface $this->propertyPath, $this->invalidValue, $this->plural, - $this->code + $this->code, + $this->constraint )); } }