From 79f4dcd2dc18e49215fb58a084277213b09fff85 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Thu, 8 Aug 2019 12:57:01 -0400 Subject: [PATCH] [Validator] Allow objects implementing __toString() to be used as violation messages [Validator] updated changelog [Validator] updated typehint for ConstraintViolationInterface::getMessage() [Validator] fixed spacing issue inadvertantly added in previous commit [Validator] fixed coding standard issues [Validator] Address feedback [Validator] Fix coding standard violation [Validator] update tests [Validator] Address feedback, Round 2 [Validator] Document ConstraintViolationBuilder::__construct() parameter [Validator] Update changelog [Validator] Adjust parameter documentation order --- src/Symfony/Component/Validator/CHANGELOG.md | 1 + .../Validator/ConstraintViolation.php | 8 +++- .../ConstraintViolationInterface.php | 2 +- .../Context/ExecutionContextInterface.php | 4 +- .../Tests/ConstraintViolationTest.php | 48 +++++++++++++++++++ .../Violation/ConstraintViolationBuilder.php | 3 +- 6 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 33b2ba00b7..e749ae8a63 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -20,6 +20,7 @@ CHANGELOG `maxPropertyPath` options * added a new `notInRangeMessage` option to the `Range` constraint that will be used in the violation builder when both `min` and `max` are not null + * added ability to use stringable objects as violation messages 4.3.0 ----- diff --git a/src/Symfony/Component/Validator/ConstraintViolation.php b/src/Symfony/Component/Validator/ConstraintViolation.php index 22ce02843c..aea02356ff 100644 --- a/src/Symfony/Component/Validator/ConstraintViolation.php +++ b/src/Symfony/Component/Validator/ConstraintViolation.php @@ -32,7 +32,7 @@ class ConstraintViolation implements ConstraintViolationInterface /** * Creates a new constraint violation. * - * @param string $message The violation message + * @param string $message The violation message as a string or a stringable object * @param string $messageTemplate The raw violation message * @param array $parameters The parameters to substitute in the * raw violation message @@ -47,7 +47,7 @@ class ConstraintViolation implements ConstraintViolationInterface * @param string|null $code The error code of the violation * @param mixed $cause The cause of the violation */ - public function __construct(?string $message, ?string $messageTemplate, array $parameters, $root, ?string $propertyPath, $invalidValue, int $plural = null, $code = null, Constraint $constraint = null, $cause = null) + public function __construct($message, ?string $messageTemplate, array $parameters, $root, ?string $propertyPath, $invalidValue, int $plural = null, $code = null, Constraint $constraint = null, $cause = null) { if (null === $message) { @trigger_error(sprintf('Passing a null message when instantiating a "%s" is deprecated since Symfony 4.4.', __CLASS__), E_USER_DEPRECATED); @@ -58,6 +58,10 @@ class ConstraintViolation implements ConstraintViolationInterface @trigger_error(sprintf('Not using a string as the error code in %s() is deprecated since Symfony 4.4. A type-hint will be added in 5.0.', __METHOD__), E_USER_DEPRECATED); } + if (!\is_string($message) && !(\is_object($message) && method_exists($message, '__toString'))) { + throw new \TypeError('Constraint violation message should be a string or an object which implements the __toString() method.'); + } + $this->message = $message; $this->messageTemplate = $messageTemplate; $this->parameters = $parameters; diff --git a/src/Symfony/Component/Validator/ConstraintViolationInterface.php b/src/Symfony/Component/Validator/ConstraintViolationInterface.php index 5ac25cf9ba..5875d37e25 100644 --- a/src/Symfony/Component/Validator/ConstraintViolationInterface.php +++ b/src/Symfony/Component/Validator/ConstraintViolationInterface.php @@ -36,7 +36,7 @@ interface ConstraintViolationInterface /** * Returns the violation message. * - * @return string The violation message + * @return string The violation message as a string or a stringable object */ public function getMessage(); diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php index 6976833f2a..7b45c1460e 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php @@ -64,7 +64,7 @@ interface ExecutionContextInterface /** * Adds a violation at the current node of the validation graph. * - * @param string $message The error message + * @param string $message The error message as a string or a stringable object * @param array $params The parameters substituted in the error message */ public function addViolation($message, array $params = []); @@ -81,7 +81,7 @@ interface ExecutionContextInterface * ->setTranslationDomain('number_validation') * ->addViolation(); * - * @param string $message The error message + * @param string $message The error message as a string or a stringable object * @param array $parameters The parameters substituted in the error message * * @return ConstraintViolationBuilderInterface The violation builder diff --git a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php index d38431b640..053448e3c1 100644 --- a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php +++ b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Validator\Tests; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\Tests\Fixtures\CustomArrayObject; +use Symfony\Component\Validator\Tests\Fixtures\ToString; class ConstraintViolationTest extends TestCase { @@ -109,6 +111,52 @@ EOF; $this->assertSame($expected, (string) $violation); } + public function testMessageCanBeStringableObject() + { + $message = new ToString(); + $violation = new ConstraintViolation( + $message, + (string) $message, + [], + 'Root', + 'property.path', + null + ); + + $expected = <<<'EOF' +Root.property.path: + toString +EOF; + $this->assertSame($expected, (string) $violation); + $this->assertSame($message, $violation->getMessage()); + } + + public function testMessageCannotBeArray() + { + $this->expectException(\TypeError::class); + $violation = new ConstraintViolation( + ['cannot be an array'], + '', + [], + 'Root', + 'property.path', + null + ); + } + + public function testMessageObjectMustBeStringable() + { + $this->expectException(\TypeError::class); + $violation = new ConstraintViolation( + new CustomArrayObject(), + '', + [], + 'Root', + 'property.path', + null + ); + } + /** * @group legacy * @expectedDeprecation Not using a string as the error code in Symfony\Component\Validator\ConstraintViolation::__construct() is deprecated since Symfony 4.4. A type-hint will be added in 5.0. diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php index 9b3cfa68ab..0c2a6b4bd5 100644 --- a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php +++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php @@ -45,9 +45,10 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface private $cause; /** + * @param string $message The error message as a string or a stringable object * @param TranslatorInterface $translator */ - public function __construct(ConstraintViolationList $violations, Constraint $constraint, ?string $message, array $parameters, $root, string $propertyPath, $invalidValue, $translator, string $translationDomain = null) + public function __construct(ConstraintViolationList $violations, Constraint $constraint, $message, array $parameters, $root, string $propertyPath, $invalidValue, $translator, string $translationDomain = null) { if (null === $message) { @trigger_error(sprintf('Passing a null message when instantiating a "%s" is deprecated since Symfony 4.4.', __CLASS__), E_USER_DEPRECATED);