Skip to content

Commit fc723f8

Browse files
committed
Add is_valid function to Expression constraint
1 parent 445f0f1 commit fc723f8

File tree

8 files changed

+231
-0
lines changed

8 files changed

+231
-0
lines changed

src/Symfony/Component/ExpressionLanguage/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.2
5+
---
6+
7+
* Add `ExpressionLanguage::hasFunction()` and `ExpressionLanguage::hasFunctionByName()` methods.
8+
49
6.1
510
---
611

src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ public function addFunction(ExpressionFunction $function)
128128
$this->register($function->getName(), $function->getCompiler(), $function->getEvaluator());
129129
}
130130

131+
public function hasFunction(ExpressionFunction $function): bool
132+
{
133+
return $this->hasFunctionByName($function->getName());
134+
}
135+
136+
public function hasFunctionByName(string $functionName): bool
137+
{
138+
return isset($this->functions[$functionName]);
139+
}
140+
131141
public function registerProvider(ExpressionFunctionProviderInterface $provider)
132142
{
133143
foreach ($provider->getFunctions() as $function) {

src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,4 +384,28 @@ function (ExpressionLanguage $el) {
384384
],
385385
];
386386
}
387+
388+
public function testHasFunction(): void
389+
{
390+
$expressionLanguage = new ExpressionLanguage();
391+
392+
$function = new ExpressionFunction('foo', function () {}, function () {});
393+
394+
$this->assertFalse($expressionLanguage->hasFunction($function));
395+
396+
$expressionLanguage->addFunction($function);
397+
398+
$this->assertTrue($expressionLanguage->hasFunction($function));
399+
}
400+
401+
public function testHasFunctionByName(): void
402+
{
403+
$expressionLanguage = new ExpressionLanguage();
404+
405+
$this->assertFalse($expressionLanguage->hasFunctionByName('foo'));
406+
407+
$expressionLanguage->register('foo', function () {}, function () {});
408+
409+
$this->assertTrue($expressionLanguage->hasFunctionByName('foo'));
410+
}
387411
}

src/Symfony/Component/Validator/CHANGELOG.md

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

77
* Deprecate the "loose" e-mail validation mode, use "html5" instead
88
* Add the `negate` option to the `Expression` constraint, to inverse the logic of the violation's creation
9+
* Add `is_valid` function to the `Expression` constraint, api the same as `ValidatorInterface::validate`
910

1011
6.1
1112
---
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use LogicException;
15+
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
16+
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
17+
use Symfony\Component\Validator\Context\ExecutionContextInterface;
18+
19+
/**
20+
* Define some ExpressionLanguage functions.
21+
*
22+
* @author Ihor Khokhlov <eld2303@gmail.com>
23+
*/
24+
class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface
25+
{
26+
private ExecutionContextInterface $context;
27+
28+
public function __construct(ExecutionContextInterface $context)
29+
{
30+
$this->context = $context;
31+
}
32+
33+
public function getFunctions(): array
34+
{
35+
return [
36+
new ExpressionFunction('is_valid', function () {
37+
throw new LogicException('The "is_valid" function cannot be compiled.');
38+
}, function (array $variables, ...$arguments): bool {
39+
$context = $this->context;
40+
41+
$validator = $context->getValidator()->inContext($context);
42+
43+
$violations = $validator->validate(...$arguments)->getViolations();
44+
45+
return 0 === $violations->count();
46+
}),
47+
];
48+
}
49+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ private function getExpressionLanguage(): ExpressionLanguage
5656
$this->expressionLanguage = new ExpressionLanguage();
5757
}
5858

59+
if (false === $this->expressionLanguage->hasFunctionByName('is_valid')) {
60+
$this->expressionLanguage->registerProvider(new ExpressionLanguageProvider($this->context));
61+
}
62+
5963
return $this->expressionLanguage;
6064
}
6165
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Constraints;
13+
14+
use LogicException;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
17+
use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider;
18+
use Symfony\Component\Validator\Constraints\NotNull;
19+
use Symfony\Component\Validator\Constraints\Range;
20+
use Symfony\Component\Validator\ConstraintViolationListInterface;
21+
use Symfony\Component\Validator\Context\ExecutionContextInterface;
22+
use Symfony\Component\Validator\Validator\ContextualValidatorInterface;
23+
use Symfony\Component\Validator\Validator\ValidatorInterface;
24+
25+
class ExpressionLanguageProviderTest extends TestCase
26+
{
27+
public function testCompile(): void
28+
{
29+
$this->expectException(LogicException::class);
30+
$this->expectExceptionMessage('The "is_valid" function cannot be compiled.');
31+
32+
$context = $this->createMock(ExecutionContextInterface::class);
33+
34+
$provider = new ExpressionLanguageProvider($context);
35+
36+
$expressionLanguage = new ExpressionLanguage();
37+
$expressionLanguage->registerProvider($provider);
38+
39+
$expressionLanguage->compile('is_valid()');
40+
}
41+
42+
/**
43+
* @dataProvider dataProviderEvaluate
44+
*/
45+
public function testEvaluate(bool $expected, int $errorsCount): void
46+
{
47+
$constraints = [new NotNull(), new Range(['min' => 2])];
48+
49+
$violationList = $this->getMockBuilder(ConstraintViolationListInterface::class)
50+
->onlyMethods(['count'])
51+
->getMockForAbstractClass();
52+
$violationList->expects($this->once())
53+
->method('count')
54+
->willReturn($errorsCount);
55+
56+
$contextualValidator = $this->getMockBuilder(ContextualValidatorInterface::class)
57+
->onlyMethods(['getViolations', 'validate'])
58+
->getMockForAbstractClass();
59+
$contextualValidator->expects($this->once())
60+
->method('validate')
61+
->with('foo', $constraints)
62+
->willReturnSelf();
63+
$contextualValidator->expects($this->once())
64+
->method('getViolations')
65+
->willReturn($violationList);
66+
67+
$validator = $this->getMockBuilder(ValidatorInterface::class)
68+
->onlyMethods(['inContext'])
69+
->getMockForAbstractClass();
70+
71+
$context = $this->getMockBuilder(ExecutionContextInterface::class)
72+
->onlyMethods(['getValidator'])
73+
->getMockForAbstractClass();
74+
$context->expects($this->once())
75+
->method('getValidator')
76+
->willReturn($validator);
77+
78+
$validator->expects($this->once())
79+
->method('inContext')
80+
->with($context)
81+
->willReturn($contextualValidator);
82+
83+
$provider = new ExpressionLanguageProvider($context);
84+
85+
$expressionLanguage = new ExpressionLanguage();
86+
$expressionLanguage->registerProvider($provider);
87+
88+
$this->assertSame($expected, $expressionLanguage->evaluate('is_valid("foo", a)', ['a' => $constraints]));
89+
}
90+
91+
public function dataProviderEvaluate(): array
92+
{
93+
return [
94+
[true, 0],
95+
[false, 1],
96+
[false, 12],
97+
];
98+
}
99+
}

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
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;
1719
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
1820
use Symfony\Component\Validator\Tests\Fixtures\Annotation\Entity;
1921
use Symfony\Component\Validator\Tests\Fixtures\ToString;
@@ -304,4 +306,41 @@ public function testViolationOnPass()
304306
->setCode(Expression::EXPRESSION_FAILED_ERROR)
305307
->assertRaised();
306308
}
309+
310+
public function testIsValidExpression(): void
311+
{
312+
$constraints = [new NotNull(), new Range(['min' => 2])];
313+
314+
$constraint = new Expression(
315+
['expression' => 'is_valid(this.data, a)', 'values' => ['a' => $constraints]]
316+
);
317+
318+
$object = new Entity();
319+
$object->data = '7';
320+
321+
$this->setObject($object);
322+
323+
$this->expectValidateValue(0, $object->data, $constraints);
324+
325+
$this->validator->validate($object, $constraint);
326+
327+
$this->assertNoViolation();
328+
}
329+
330+
public function testExistingIsValidFunctionIsNotOverridden(): void
331+
{
332+
$used = false;
333+
334+
$expressionLanguage = new ExpressionLanguage();
335+
$expressionLanguage->register('is_valid', function () {}, function () use (&$used) {
336+
$used = true;
337+
});
338+
339+
$validator = new ExpressionValidator($expressionLanguage);
340+
$validator->initialize($this->context);
341+
342+
$validator->validate('foo', new Expression('is_valid()'));
343+
344+
$this->assertTrue($used);
345+
}
307346
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy