@@ -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
@@ -116,7 +117,7 @@ The validator class only has one required method ``validate()``::
116
117
117
118
class ContainsAlphanumericValidator extends ConstraintValidator
118
119
{
119
- public function validate($value, Constraint $constraint)
120
+ public function validate($value, Constraint $constraint): void
120
121
{
121
122
if (!$constraint instanceof ContainsAlphanumeric) {
122
123
throw new UnexpectedTypeException($constraint, ContainsAlphanumeric::class);
@@ -150,7 +151,7 @@ The validator class only has one required method ``validate()``::
150
151
}
151
152
}
152
153
153
- Inside ``validate ``, you don't need to return a value. Instead, you add violations
154
+ Inside ``validate() ``, you don't need to return a value. Instead, you add violations
154
155
to the validator's ``context `` property and a value will be considered valid
155
156
if it causes no violations. The ``buildViolation() `` method takes the error
156
157
message as its argument and returns an instance of
@@ -178,15 +179,15 @@ You can use custom validators like the ones provided by Symfony itself:
178
179
179
180
#[Assert\NotBlank]
180
181
#[AcmeAssert\ContainsAlphanumeric(mode: 'loose')]
181
- protected $name;
182
+ protected string $name;
182
183
183
184
// ...
184
185
}
185
186
186
187
.. code-block :: yaml
187
188
188
189
# config/validator/validation.yaml
189
- App\Entity\AcmeEntity :
190
+ App\Entity\User :
190
191
properties :
191
192
name :
192
193
- NotBlank : ~
@@ -201,7 +202,7 @@ You can use custom validators like the ones provided by Symfony itself:
201
202
xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
202
203
xsi : schemaLocation =" http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd" >
203
204
204
- <class name =" App\Entity\AcmeEntity " >
205
+ <class name =" App\Entity\User " >
205
206
<property name =" name" >
206
207
<constraint name =" NotBlank" />
207
208
<constraint name =" App\Validator\ContainsAlphanumeric" >
@@ -213,18 +214,20 @@ You can use custom validators like the ones provided by Symfony itself:
213
214
214
215
.. code-block :: php
215
216
216
- // src/Entity/AcmeEntity .php
217
+ // src/Entity/User .php
217
218
namespace App\Entity;
218
219
219
220
use App\Validator\ContainsAlphanumeric;
220
221
use Symfony\Component\Validator\Constraints\NotBlank;
221
222
use Symfony\Component\Validator\Mapping\ClassMetadata;
222
223
223
- class AcmeEntity
224
+ class User
224
225
{
225
- public $name;
226
+ protected string $name = '';
227
+
228
+ // ...
226
229
227
- public static function loadValidatorMetadata(ClassMetadata $metadata)
230
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
228
231
{
229
232
$metadata->addPropertyConstraint('name', new NotBlank());
230
233
$metadata->addPropertyConstraint('name', new ContainsAlphanumeric(['mode' => 'loose']));
@@ -253,22 +256,62 @@ Class Constraint Validator
253
256
~~~~~~~~~~~~~~~~~~~~~~~~~~
254
257
255
258
Besides validating a single property, a constraint can have an entire class
256
- as its scope. You only need to add this to the ``Constraint `` class::
259
+ as its scope.
260
+
261
+ For instance, imagine you also have a ``PaymentReceipt `` entity and you
262
+ need to make sure the email of the receipt payload matches the user's
263
+ email. First, create a constraint and override the ``getTargets() `` method::
264
+
265
+ // src/Validator/ConfirmedPaymentReceipt.php
266
+ namespace App\Validator;
267
+
268
+ use Symfony\Component\Validator\Constraint;
257
269
258
- public function getTargets()
270
+ /**
271
+ * @Annotation
272
+ */
273
+ class ConfirmedPaymentReceipt extends Constraint
259
274
{
260
- return self::CLASS_CONSTRAINT;
275
+ public string $userDoesNotMatchMessage = 'User\'s e-mail address does not match that of the receipt';
276
+
277
+ public function getTargets(): string
278
+ {
279
+ return self::CLASS_CONSTRAINT;
280
+ }
261
281
}
262
282
263
- With this, the validator's ``validate() `` method gets an object as its first argument::
283
+ Now, the constraint validator will get an object as the first argument to
284
+ ``validate() ``::
285
+
286
+ // src/Validator/ConfirmedPaymentReceiptValidator.php
287
+ namespace App\Validator;
288
+
289
+ use Symfony\Component\Validator\Constraint;
290
+ use Symfony\Component\Validator\ConstraintValidator;
291
+ use Symfony\Component\Validator\Exception\UnexpectedValueException;
264
292
265
- class ProtocolClassValidator extends ConstraintValidator
293
+ class ConfirmedPaymentReceiptValidator extends ConstraintValidator
266
294
{
267
- public function validate($protocol, Constraint $constraint)
295
+ /**
296
+ * @param PaymentReceipt $receipt
297
+ */
298
+ public function validate($receipt, Constraint $constraint): void
268
299
{
269
- if ($protocol->getFoo() != $protocol->getBar()) {
270
- $this->context->buildViolation($constraint->message)
271
- ->atPath('foo')
300
+ if (!$receipt instanceof PaymentReceipt) {
301
+ throw new UnexpectedValueException($receipt, PaymentReceipt::class);
302
+ }
303
+
304
+ if (!$constraint instanceof ConfirmedPaymentReceipt) {
305
+ throw new UnexpectedValueException($constraint, ConfirmedPaymentReceipt::class);
306
+ }
307
+
308
+ $receiptEmail = $receipt->getPayload()['email'] ?? null;
309
+ $userEmail = $receipt->getUser()->getEmail();
310
+
311
+ if ($userEmail !== $receiptEmail) {
312
+ $this->context
313
+ ->buildViolation($constraint->userDoesNotMatchMessage)
314
+ ->atPath('user.email')
272
315
->addViolation();
273
316
}
274
317
}
@@ -280,8 +323,7 @@ With this, the validator's ``validate()`` method gets an object as its first arg
280
323
associated. Use any :doc: `valid PropertyAccess syntax </components/property_access >`
281
324
to define that property.
282
325
283
- A class constraint validator is applied to the class itself, and
284
- not to the property:
326
+ A class constraint validator must be applied to the class itself:
285
327
286
328
.. configuration-block ::
287
329
@@ -301,44 +343,54 @@ not to the property:
301
343
.. code-block :: yaml
302
344
303
345
# config/validator/validation.yaml
304
- App\Entity\AcmeEntity :
346
+ App\Entity\PaymentReceipt :
305
347
constraints :
306
- - App\Validator\ProtocolClass : ~
348
+ - App\Validator\ConfirmedPaymentReceipt : ~
307
349
308
350
.. code-block :: xml
309
351
310
352
<!-- config/validator/validation.xml -->
311
- <class name =" App\Entity\AcmeEntity" >
312
- <constraint name =" App\Validator\ProtocolClass" />
313
- </class >
353
+ <?xml version =" 1.0" encoding =" UTF-8" ?>
354
+ <constraint-mapping xmlns =" http://symfony.com/schema/dic/constraint-mapping"
355
+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
356
+ xsi : schemaLocation =" http://symfony.com/schema/dic/constraint-mapping
357
+ https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd" >
358
+
359
+ <class name =" App\Entity\PaymentReceipt" >
360
+ <constraint name =" App\Validator\ConfirmedPaymentReceipt" />
361
+ </class >
362
+ </constraint-mapping >
314
363
315
364
.. code-block :: php
316
365
317
- // src/Entity/AcmeEntity .php
366
+ // src/Entity/PaymentReceipt .php
318
367
namespace App\Entity;
319
368
320
- use App\Validator\ProtocolClass ;
369
+ use App\Validator\ConfirmedPaymentReceipt ;
321
370
use Symfony\Component\Validator\Mapping\ClassMetadata;
322
371
323
- class AcmeEntity
372
+ class PaymentReceipt
324
373
{
325
374
// ...
326
375
327
- public static function loadValidatorMetadata(ClassMetadata $metadata)
376
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
328
377
{
329
- $metadata->addConstraint(new ProtocolClass ());
378
+ $metadata->addConstraint(new ConfirmedPaymentReceipt ());
330
379
}
331
380
}
332
381
333
382
Testing Custom Constraints
334
383
--------------------------
335
384
336
- Use the ``ConstraintValidatorTestCase `` utility to simplify the creation of
337
- unit tests for your custom constraints::
385
+ Use the :class: `Symfony\\ Component\\ Validator\\ Test\\ ConstraintValidatorTestCase` `
386
+ class to simplify writing unit tests for your custom constraints::
387
+
388
+ // tests/Validator/ContainsAlphanumericValidatorTest.php
389
+ namespace App\Tests\Validator;
338
390
339
- // ...
340
391
use App\Validator\ContainsAlphanumeric;
341
392
use App\Validator\ContainsAlphanumericValidator;
393
+ use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
342
394
343
395
class ContainsAlphanumericValidatorTest extends ConstraintValidatorTestCase
344
396
{
0 commit comments