@@ -26,8 +26,9 @@ First you need to create a Constraint class and extend :class:`Symfony\\Componen
26
26
#[\Attribute]
27
27
class ContainsAlphanumeric extends Constraint
28
28
{
29
- public $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.';
30
- public $mode = 'strict'; // If the constraint has configuration options, define them as public properties
29
+ public string $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.';
30
+ // If the constraint has configuration options, define them as public properties
31
+ public string $mode = 'strict';
31
32
}
32
33
33
34
Add ``@Annotation `` or ``#[\Attribute] `` to the constraint class if you want to
@@ -63,7 +64,7 @@ The validator class only has one required method ``validate()``::
63
64
64
65
class ContainsAlphanumericValidator extends ConstraintValidator
65
66
{
66
- public function validate($value, Constraint $constraint)
67
+ public function validate($value, Constraint $constraint): void
67
68
{
68
69
if (!$constraint instanceof ContainsAlphanumeric) {
69
70
throw new UnexpectedTypeException($constraint, ContainsAlphanumeric::class);
@@ -97,7 +98,7 @@ The validator class only has one required method ``validate()``::
97
98
}
98
99
}
99
100
100
- Inside ``validate ``, you don't need to return a value. Instead, you add violations
101
+ Inside ``validate() ``, you don't need to return a value. Instead, you add violations
101
102
to the validator's ``context `` property and a value will be considered valid
102
103
if it causes no violations. The ``buildViolation() `` method takes the error
103
104
message as its argument and returns an instance of
@@ -125,15 +126,15 @@ You can use custom validators like the ones provided by Symfony itself:
125
126
126
127
#[Assert\NotBlank]
127
128
#[AcmeAssert\ContainsAlphanumeric(mode: 'loose')]
128
- protected $name;
129
+ protected string $name;
129
130
130
131
// ...
131
132
}
132
133
133
134
.. code-block :: yaml
134
135
135
136
# config/validator/validation.yaml
136
- App\Entity\AcmeEntity :
137
+ App\Entity\User :
137
138
properties :
138
139
name :
139
140
- NotBlank : ~
@@ -148,7 +149,7 @@ You can use custom validators like the ones provided by Symfony itself:
148
149
xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
149
150
xsi : schemaLocation =" http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd" >
150
151
151
- <class name =" App\Entity\AcmeEntity " >
152
+ <class name =" App\Entity\User " >
152
153
<property name =" name" >
153
154
<constraint name =" NotBlank" />
154
155
<constraint name =" App\Validator\ContainsAlphanumeric" >
@@ -160,18 +161,20 @@ You can use custom validators like the ones provided by Symfony itself:
160
161
161
162
.. code-block :: php
162
163
163
- // src/Entity/AcmeEntity .php
164
+ // src/Entity/User .php
164
165
namespace App\Entity;
165
166
166
167
use App\Validator\ContainsAlphanumeric;
167
168
use Symfony\Component\Validator\Constraints\NotBlank;
168
169
use Symfony\Component\Validator\Mapping\ClassMetadata;
169
170
170
- class AcmeEntity
171
+ class User
171
172
{
172
- public $name;
173
+ protected string $name = '' ;
173
174
174
- public static function loadValidatorMetadata(ClassMetadata $metadata)
175
+ // ...
176
+
177
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
175
178
{
176
179
$metadata->addPropertyConstraint('name', new NotBlank());
177
180
$metadata->addPropertyConstraint('name', new ContainsAlphanumeric(['mode' => 'loose']));
@@ -200,22 +203,62 @@ Class Constraint Validator
200
203
~~~~~~~~~~~~~~~~~~~~~~~~~~
201
204
202
205
Besides validating a single property, a constraint can have an entire class
203
- as its scope. You only need to add this to the ``Constraint `` class::
206
+ as its scope.
207
+
208
+ For instance, imagine you also have a ``PaymentReceipt `` entity and you
209
+ need to make sure the email of the receipt payload matches the user's
210
+ email. First, create a constraint and override the ``getTargets() `` method::
211
+
212
+ // src/Validator/ConfirmedPaymentReceipt.php
213
+ namespace App\Validator;
214
+
215
+ use Symfony\Component\Validator\Constraint;
204
216
205
- public function getTargets()
217
+ /**
218
+ * @Annotation
219
+ */
220
+ class ConfirmedPaymentReceipt extends Constraint
206
221
{
207
- return self::CLASS_CONSTRAINT;
222
+ public string $userDoesNotMatchMessage = 'User\'s e-mail address does not match that of the receipt';
223
+
224
+ public function getTargets(): string
225
+ {
226
+ return self::CLASS_CONSTRAINT;
227
+ }
208
228
}
209
229
210
- With this, the validator's ``validate() `` method gets an object as its first argument::
230
+ Now, the constraint validator will get an object as the first argument to
231
+ ``validate() ``::
232
+
233
+ // src/Validator/ConfirmedPaymentReceiptValidator.php
234
+ namespace App\Validator;
235
+
236
+ use Symfony\Component\Validator\Constraint;
237
+ use Symfony\Component\Validator\ConstraintValidator;
238
+ use Symfony\Component\Validator\Exception\UnexpectedValueException;
211
239
212
- class ProtocolClassValidator extends ConstraintValidator
240
+ class ConfirmedPaymentReceiptValidator extends ConstraintValidator
213
241
{
214
- public function validate($protocol, Constraint $constraint)
242
+ /**
243
+ * @param PaymentReceipt $receipt
244
+ */
245
+ public function validate($receipt, Constraint $constraint): void
215
246
{
216
- if ($protocol->getFoo() != $protocol->getBar()) {
217
- $this->context->buildViolation($constraint->message)
218
- ->atPath('foo')
247
+ if (!$receipt instanceof PaymentReceipt) {
248
+ throw new UnexpectedValueException($receipt, PaymentReceipt::class);
249
+ }
250
+
251
+ if (!$constraint instanceof ConfirmedPaymentReceipt) {
252
+ throw new UnexpectedValueException($constraint, ConfirmedPaymentReceipt::class);
253
+ }
254
+
255
+ $receiptEmail = $receipt->getPayload()['email'] ?? null;
256
+ $userEmail = $receipt->getUser()->getEmail();
257
+
258
+ if ($userEmail !== $receiptEmail) {
259
+ $this->context
260
+ ->buildViolation($constraint->userDoesNotMatchMessage)
261
+ ->atPath('user.email')
219
262
->addViolation();
220
263
}
221
264
}
@@ -227,8 +270,7 @@ With this, the validator's ``validate()`` method gets an object as its first arg
227
270
associated. Use any :doc: `valid PropertyAccess syntax </components/property_access >`
228
271
to define that property.
229
272
230
- A class constraint validator is applied to the class itself, and
231
- not to the property:
273
+ A class constraint validator must be applied to the class itself:
232
274
233
275
.. configuration-block ::
234
276
@@ -248,44 +290,54 @@ not to the property:
248
290
.. code-block :: yaml
249
291
250
292
# config/validator/validation.yaml
251
- App\Entity\AcmeEntity :
293
+ App\Entity\PaymentReceipt :
252
294
constraints :
253
- - App\Validator\ProtocolClass : ~
295
+ - App\Validator\ConfirmedPaymentReceipt : ~
254
296
255
297
.. code-block :: xml
256
298
257
299
<!-- config/validator/validation.xml -->
258
- <class name =" App\Entity\AcmeEntity" >
259
- <constraint name =" App\Validator\ProtocolClass" />
260
- </class >
300
+ <?xml version =" 1.0" encoding =" UTF-8" ?>
301
+ <constraint-mapping xmlns =" http://symfony.com/schema/dic/constraint-mapping"
302
+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
303
+ xsi : schemaLocation =" http://symfony.com/schema/dic/constraint-mapping
304
+ https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd" >
305
+
306
+ <class name =" App\Entity\PaymentReceipt" >
307
+ <constraint name =" App\Validator\ConfirmedPaymentReceipt" />
308
+ </class >
309
+ </constraint-mapping >
261
310
262
311
.. code-block :: php
263
312
264
- // src/Entity/AcmeEntity .php
313
+ // src/Entity/PaymentReceipt .php
265
314
namespace App\Entity;
266
315
267
- use App\Validator\ProtocolClass ;
316
+ use App\Validator\ConfirmedPaymentReceipt ;
268
317
use Symfony\Component\Validator\Mapping\ClassMetadata;
269
318
270
- class AcmeEntity
319
+ class PaymentReceipt
271
320
{
272
321
// ...
273
322
274
- public static function loadValidatorMetadata(ClassMetadata $metadata)
323
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
275
324
{
276
- $metadata->addConstraint(new ProtocolClass ());
325
+ $metadata->addConstraint(new ConfirmedPaymentReceipt ());
277
326
}
278
327
}
279
328
280
329
Testing Custom Constraints
281
330
--------------------------
282
331
283
- Use the ``ConstraintValidatorTestCase `` utility to simplify the creation of
284
- unit tests for your custom constraints::
332
+ Use the :class: `Symfony\\ Component\\ Validator\\ Test\\ ConstraintValidatorTestCase` `
333
+ class to simplify writing unit tests for your custom constraints::
334
+
335
+ // tests/Validator/ContainsAlphanumericValidatorTest.php
336
+ namespace App\Tests\Validator;
285
337
286
- // ...
287
338
use App\Validator\ContainsAlphanumeric;
288
339
use App\Validator\ContainsAlphanumericValidator;
340
+ use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
289
341
290
342
class ContainsAlphanumericValidatorTest extends ConstraintValidatorTestCase
291
343
{
0 commit comments