The Compound constraint allows you to group other constraints into a single reusable constraint to apply the same validation in different parts of your application. For example, you can validate your password policy by checking all conditions with a set of constraints like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Validator/MatchPasswordPolicy.php
use Symfony\Component\Validator\Constraints as Assert;
#[\Attribute]
class MatchPasswordPolicy extends Assert\Compound
{
protected function getConstraints(array $options): array
{
return [
new Assert\NotBlank(allowNull: false),
new Assert\Length(min: 8, max: 255),
new Assert\NotCompromisedPassword(),
new Assert\Type('string'),
new Assert\Regex('/[A-Z]+/'),
// ...
];
}
}
Then, in your User
entity, you can apply this compound constraint like this:
1 2 3 4 5 6 7 8 9 10 11 12
// src/Entity/User.php
namespace App\Entity\User;
use App\Validator\Constraints as Assert;
class User
{
#[Assert\MatchPasswordPolicy]
public string $plainPassword;
// ...
}
In Symfony 7.2 we've added some features to make working with compound constraints easier.
Easier Compound Constraint Tests
Testing compound constraints used to be unnecessarily complex. That's why we're
introducing a new CompoundConstraintTestCase
class to simplify those tests.
This is an abstract class that requires defining a single method:
1 2 3 4 5 6
abstract class CompoundConstraintTestCase extends TestCase
{
abstract protected function createCompound(): Compound;
// ...
}
Using the same MatchPasswordPolicy
compound constraint shown earlier, you
can now write a test for it like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
// tests/Validator/MatchPasswordPolicyTest.php
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Test\CompoundConstraintTestCase;
class MatchPasswordPolicyTest extends CompoundConstraintTestCase
{
public function createCompound(): Assert\Compound
{
return new MatchPasswordPolicy();
}
/**
* @dataProvider provideInvalidPasswords
*/
public function testInvalid(mixed $password, string $code): void
{
$this->validateValue($password);
$this->assertViolationIsRaisedByCompound($code);
}
public static function provideInvalidPasswords(): \Generator
{
yield 'Blank' => ['', Assert\NotBlank::IS_BLANK_ERROR];
yield 'Too short' => ['a', Assert\Length::TOO_SHORT_ERROR];
yield 'Not a string' => [1, Assert\Type::INVALID_TYPE_ERROR];
// ...
}
public function testValid(): void
{
$this->validateValue('VeryStr0ngP4$$wOrD');
$this->assertNoViolation();
}
}
Add Groups and Payload Constructor Arguments
A while ago, we added the $groups
and $payload
arguments to the constructor
of many composite constraints, but we didn't add them to the compound constraint.
In Symfony 7.2, we're adding those arguments so you can pass them in the constructor:
1 2 3 4 5 6 7 8 9 10
class User
{
#[Assert\MatchPasswordPolicy(
groups: ['registration'],
payload: ['severity' => 'error'],
)]
public string $plainPassword;
// ...
}