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