Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit dfd8e53

Browse filesBrowse files
feature #59800 [Validator] Add support for closures in When (alexandre-daubois)
This PR was merged into the 7.3 branch. Discussion ---------- [Validator] Add support for closures in `When` | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT Closures can be used instead of Expressions in the `When` constraint since PHP 8.5, like in this example: ```php use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\When; class BlogPost { #[When(expression: static function (BlogPost $subject) { return $subject->published && !$subject->draft; }, constraints: [ new NotNull(), new NotBlank(), ])] public ?string $content; public bool $published; public bool $draft; } ``` Commits ------- f0adbea [Validator] Add support for closures in `When`
2 parents 560a484 + f0adbea commit dfd8e53
Copy full SHA for dfd8e53

File tree

6 files changed

+117
-4
lines changed
Filter options

6 files changed

+117
-4
lines changed

‎src/Symfony/Component/Validator/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/CHANGELOG.md
+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CHANGELOG
1111
* Add support for the `otherwise` option in the `When` constraint
1212
* Add support for multiple fields containing nested constraints in `Composite` constraints
1313
* Add the `stopOnFirstError` option to the `Unique` constraint to validate all elements
14+
* Add support for closures in the `When` constraint
1415

1516
7.2
1617
---

‎src/Symfony/Component/Validator/Constraints/When.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Constraints/When.php
+3-3
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,21 @@
2525
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
2626
class When extends Composite
2727
{
28-
public string|Expression $expression;
28+
public string|Expression|\Closure $expression;
2929
public array|Constraint $constraints = [];
3030
public array $values = [];
3131
public array|Constraint $otherwise = [];
3232

3333
/**
34-
* @param string|Expression|array<string,mixed> $expression The condition to evaluate, written with the ExpressionLanguage syntax
34+
* @param string|Expression|array<string,mixed>|\Closure(object): bool $expression The condition to evaluate, either as a closure or using the ExpressionLanguage syntax
3535
* @param Constraint[]|Constraint|null $constraints One or multiple constraints that are applied if the expression returns true
3636
* @param array<string,mixed>|null $values The values of the custom variables used in the expression (defaults to [])
3737
* @param string[]|null $groups
3838
* @param array<string,mixed>|null $options
3939
* @param Constraint[]|Constraint $otherwise One or multiple constraints that are applied if the expression returns false
4040
*/
4141
#[HasNamedArguments]
42-
public function __construct(string|Expression|array $expression, array|Constraint|null $constraints = null, ?array $values = null, ?array $groups = null, $payload = null, ?array $options = null, array|Constraint $otherwise = [])
42+
public function __construct(string|Expression|array|\Closure $expression, array|Constraint|null $constraints = null, ?array $values = null, ?array $groups = null, $payload = null, ?array $options = null, array|Constraint $otherwise = [])
4343
{
4444
if (!class_exists(ExpressionLanguage::class)) {
4545
throw new LogicException(\sprintf('The "symfony/expression-language" component is required to use the "%s" constraint. Try running "composer require symfony/expression-language".', __CLASS__));

‎src/Symfony/Component/Validator/Constraints/WhenValidator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Constraints/WhenValidator.php
+5-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ public function validate(mixed $value, Constraint $constraint): void
3535
$variables['this'] = $context->getObject();
3636
$variables['context'] = $context;
3737

38-
$result = $this->getExpressionLanguage()->evaluate($constraint->expression, $variables);
38+
if ($constraint->expression instanceof \Closure) {
39+
$result = ($constraint->expression)($context->getObject());
40+
} else {
41+
$result = $this->getExpressionLanguage()->evaluate($constraint->expression, $variables);
42+
}
3943

4044
if ($result) {
4145
$context->getValidator()->inContext($context)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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\Tests\Constraints\Fixtures;
13+
14+
use Symfony\Component\Validator\Constraints\NotBlank;
15+
use Symfony\Component\Validator\Constraints\NotNull;
16+
use Symfony\Component\Validator\Constraints\When;
17+
18+
#[When(expression: static function () {
19+
return true;
20+
}, constraints: new NotNull()
21+
)]
22+
class WhenTestWithClosure
23+
{
24+
#[When(expression: static function () {
25+
return true;
26+
}, constraints: [
27+
new NotNull(),
28+
new NotBlank(),
29+
])]
30+
private $foo;
31+
}

‎src/Symfony/Component/Validator/Tests/Constraints/WhenTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Tests/Constraints/WhenTest.php
+35
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\Validator\Mapping\ClassMetadata;
2323
use Symfony\Component\Validator\Mapping\Loader\AttributeLoader;
2424
use Symfony\Component\Validator\Tests\Constraints\Fixtures\WhenTestWithAttributes;
25+
use Symfony\Component\Validator\Tests\Constraints\Fixtures\WhenTestWithClosure;
2526

2627
final class WhenTest extends TestCase
2728
{
@@ -111,4 +112,38 @@ public function testAttributes()
111112
self::assertEquals([new Length(exactly: 10, groups: ['foo'])], $quuxConstraint->otherwise);
112113
self::assertSame(['foo'], $quuxConstraint->groups);
113114
}
115+
116+
/**
117+
* @requires PHP 8.5
118+
*/
119+
public function testAttributesWithClosure()
120+
{
121+
$loader = new AttributeLoader();
122+
$metadata = new ClassMetadata(WhenTestWithClosure::class);
123+
124+
self::assertTrue($loader->loadClassMetadata($metadata));
125+
126+
[$classConstraint] = $metadata->getConstraints();
127+
128+
self::assertInstanceOf(When::class, $classConstraint);
129+
self::assertInstanceOf(\Closure::class, $classConstraint->expression);
130+
self::assertEquals([
131+
new Callback(
132+
callback: 'callback',
133+
groups: ['Default', 'WhenTestWithClosure'],
134+
),
135+
], $classConstraint->constraints);
136+
self::assertEmpty($classConstraint->otherwise);
137+
138+
[$fooConstraint] = $metadata->properties['foo']->getConstraints();
139+
140+
self::assertInstanceOf(When::class, $fooConstraint);
141+
self::assertInstanceOf(\Closure::class, $fooConstraint->expression);
142+
self::assertEquals([
143+
new NotNull(groups: ['Default', 'WhenTestWithClosure']),
144+
new NotBlank(groups: ['Default', 'WhenTestWithClosure']),
145+
], $fooConstraint->constraints);
146+
self::assertEmpty($fooConstraint->otherwise);
147+
self::assertSame(['Default', 'WhenTestWithClosure'], $fooConstraint->groups);
148+
}
114149
}

‎src/Symfony/Component/Validator/Tests/Constraints/WhenValidatorTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Tests/Constraints/WhenValidatorTest.php
+42
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,34 @@ public function testConstraintsAreExecuted()
4040
));
4141
}
4242

43+
public function testConstraintsAreExecutedWhenClosureIsTrue()
44+
{
45+
$constraints = [
46+
new NotNull(),
47+
new NotBlank(),
48+
];
49+
50+
$this->expectValidateValue(0, 'Foo', $constraints);
51+
52+
$this->validator->validate('Foo', new When(
53+
expression: static fn () => true,
54+
constraints: $constraints,
55+
));
56+
}
57+
58+
public function testClosureTakesSubject()
59+
{
60+
$subject = new \stdClass();
61+
$this->setObject($subject);
62+
63+
$this->validator->validate($subject, new When(
64+
expression: static function ($closureSubject) use ($subject) {
65+
self::assertSame($subject, $closureSubject);
66+
},
67+
constraints: new NotNull(),
68+
));
69+
}
70+
4371
public function testConstraintIsExecuted()
4472
{
4573
$constraint = new NotNull();
@@ -65,6 +93,20 @@ public function testOtherwiseIsExecutedWhenFalse()
6593
));
6694
}
6795

96+
public function testOtherwiseIsExecutedWhenClosureReturnsFalse()
97+
{
98+
$constraint = new NotNull();
99+
$otherwise = new Length(exactly: 10);
100+
101+
$this->expectValidateValue(0, 'Foo', [$otherwise]);
102+
103+
$this->validator->validate('Foo', new When(
104+
expression: static fn () => false,
105+
constraints: $constraint,
106+
otherwise: $otherwise,
107+
));
108+
}
109+
68110
public function testConstraintsAreExecutedWithNull()
69111
{
70112
$constraints = [

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.