8000 feature #50438 [Validator] Add is_valid function to Expression constr… · symfony/symfony@30a35e4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 30a35e4

Browse files
feature #50438 [Validator] Add is_valid function to Expression constraint (verdet23, DEVizzent)
This PR was merged into the 6.4 branch. Discussion ---------- [Validator] Add is_valid function to Expression constraint | Q | A | ------------- | --- | Branch? | 6.4 for features | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #11940 | License | MIT | Doc PR | TODO Add `is_valid` function to the Expression constraint, api the same as `ValidatorInterface::validate` Commits ------- 3d1a798 [Validator] Add is_valid function to Expression constraint
2 parents a3674e1 + 3d1a798 commit 30a35e4

File tree

3 files changed

+110
-4
lines changed

3 files changed

+110
-4
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
6.4
55
---
66

7+
* Add `is_valid` function to the `Expression` constraint, its behavior is the same as `ValidatorInterface::validate`
78
* Allow single integer for the `versions` option of the `Uuid` constraint
89
* Allow single constraint to be passed to the `constraints` option of the `When` constraint
910
* Deprecate Doctrine annotations support in favor of native attributes

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Validator\Constraints;
1313

14+
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
15+
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
1416
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
1517
use Symfony\Component\Validator\Constraint;
1618
use Symfony\Component\Validator\ConstraintValidator;
@@ -20,13 +22,16 @@
2022
* @author Fabien Potencier <fabien@symfony.com>
2123
* @author Bernhard Schussek <bschussek@symfony.com>
2224
*/
23-
class ExpressionValidator extends ConstraintValidator
25+
class ExpressionValidator extends ConstraintValidator implements ExpressionFunctionProviderInterface
2426
{
25-
private ?ExpressionLanguage $expressionLanguage;
27+
private ExpressionLanguage $expressionLanguage;
2628

2729
public function __construct(ExpressionLanguage $expressionLanguage = null)
2830
{
29-
$this->expressionLanguage = $expressionLanguage;
31+
if ($expressionLanguage) {
32+
$this->expressionLanguage = clone $expressionLanguage;
33+
$this->expressionLanguage->registerProvider($this);
34+
}
3035
}
3136

3237
/**
@@ -41,6 +46,7 @@ public function validate(mixed $value, Constraint $constraint)
4146
$variables = $constraint->values;
4247
$variables['value'] = $value;
4348
$variables['this'] = $this->context->getObject();
49+
$variables['context'] = $this->context;
4450

4551
if ($constraint->negate xor $this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) {
4652
$this->context->buildViolation($constraint->message)
@@ -50,8 +56,27 @@ public function validate(mixed $value, Constraint $constraint)
5056
}
5157
}
5258

59+
public function getFunctions(): array
60+
{
61+
return [
62+
new ExpressionFunction('is_valid', function (...$arguments) {
63+
return sprintf(
64+
'0 === $context->getValidator()->inContext($context)->validate(%s)->getViolations()->count()',
65+
implode(', ', $arguments)
66+
);
67+
}, function (array $variables, ...$arguments): bool {
68+
return 0 === $variables['context']->getValidator()->inContext($variables['context'])->validate(...$arguments)->getViolations()->count();
69+
}),
70+
];
71+
}
72+
5373
private function getExpressionLanguage(): ExpressionLanguage
5474
{
55-
return $this->expressionLanguage ??= new ExpressionLanguage();
75+
if (!isset($this->expressionLanguage)) {
76+
$this->expressionLanguage = new ExpressionLanguage();
77+
$this->expressionLanguage->registerProvider($this);
78+
}
79+
80+
return $this->expressionLanguage;
5681
}
5782
}

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
1515
use Symfony\Component\Validator\Constraints\Expression;
1616
use Symfony\Component\Validator\Constraints\ExpressionValidator;
17+
use Symfony\Component\Validator\Constraints\NotNull;
18+
use Symfony\Component\Validator\Constraints\Range;
19+
use Symfony\Component\Validator\ConstraintViolation;
1720
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
1821
use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity;
1922
use Symfony\Component\Validator\Tests\Fixtures\ToString;
@@ -304,4 +307,81 @@ public function testViolationOnPass()
304307
->setCode(Expression::EXPRESSION_FAILED_ERROR)
305308
->assertRaised();
306309
}
310+
311+
public function testIsValidExpression()
312+
{
313+
$constraints = [new NotNull(), new Range(['min' => 2])];
314+
315+
$constraint = new Expression(
316+
['expression' => 'is_valid(this.data, a)', 'values' => ['a' => $constraints]]
317+
);
318+
319+
$object = new Entity();
320+
$object->data = 7;
321+
322+
$this->setObject($object);
323+
324+
$this->expectValidateValue(0, $object->data, $constraints);
325+
326+
$this->validator->validate($object, $constraint);
327+
328+
$this->assertNoViolation();
329+
}
330+
331+
public function testIsValidExpressionInvalid()
332+
{
333+
$constraints = [new Range(['min' => 2, 'max' => 5])];
334+
335+
$constraint = new Expression(
336+
['expression' => 'is_valid(this.data, a)', 'values' => ['a' => $constraints]]
337+
);
338+
339+
$object = new Entity();
340+
$object->data = 7;
341+
342+
$this->setObject($object);
343+
344+
$this->expectFailingValueValidation(
345+
0,
346+
7,
347+
$constraints,
348+
null,
349+
new ConstraintViolation('error_range', '', [], '', '', 7, null, 'range')
350+
);
351+
352+
$this->validator->validate($object, $constraint);
353+
354+
$this->assertCount(2, $this->context->getViolations());
355+
}
356+
357+
/**
358+
* @dataProvider provideCompileIsValid
359+
*/
360+
public function testCompileIsValid(string $expression, array $names, string $expected)
361+
{
362+
$provider = new ExpressionValidator();
363+
364+
$expressionLanguage = new ExpressionLanguage();
365+
$expressionLanguage->registerProvider($provider);
366+
367+
$result = $expressionLanguage->compile($expression, $names);
368+
369+
$this->assertSame($expected, $result);
370+
}
371+
372+
public static function provideCompileIsValid(): array
373+
{
374+
return [
375+
[
376+
'is_valid("foo", constraints)',
377+
['constraints'],
378+
'0 === $context->getValidator()->inContext($context)->validate("foo", $constraints)->getViolations()->count()',
379+
],
380+
[
381+
'is_valid(this.data, constraints, groups)',
382+
['this', 'constraints', 'groups'],
383+
'0 === $context->getValidator()->inContext($context)->validate($this->data, $constraints, $groups)->getViolations()->count()',
384+
],
385+
];
386+
}
307387
}

0 commit comments

Comments
 (0)
0