@@ -24,7 +24,7 @@ First you need to create a Constraint class and extend :class:`Symfony\\Componen
24
24
*/
25
25
class ContainsAlphanumeric extends Constraint
26
26
{
27
- public $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.';
27
+ public string $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.';
28
28
}
29
29
30
30
.. note ::
@@ -64,7 +64,7 @@ The validator class only has one required method ``validate()``::
64
64
65
65
class ContainsAlphanumericValidator extends ConstraintValidator
66
66
{
67
- public function validate($value, Constraint $constraint)
67
+ public function validate($value, Constraint $constraint): void
68
68
{
69
69
if (!$constraint instanceof ContainsAlphanumeric) {
70
70
throw new UnexpectedTypeException($constraint, ContainsAlphanumeric::class);
@@ -98,7 +98,7 @@ The validator class only has one required method ``validate()``::
98
98
The feature to allow passing an object as the ``buildViolation() `` argument
99
99
was introduced in Symfony 4.4.
100
100
101
- 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
102
102
to the validator's ``context `` property and a value will be considered valid
103
103
if it causes no violations. The ``buildViolation() `` method takes the error
104
104
message as its argument and returns an instance of
@@ -128,7 +128,7 @@ You can use custom validators like the ones provided by Symfony itself:
128
128
* @Assert\NotBlank
129
129
* @AcmeAssert\ContainsAlphanumeric
130
130
*/
131
- protected $name;
131
+ protected string $name = '' ;
132
132
133
133
// ...
134
134
}
@@ -169,9 +169,11 @@ You can use custom validators like the ones provided by Symfony itself:
169
169
170
170
class AcmeEntity
171
171
{
172
- public $name;
172
+ protected string $name = '' ;
173
173
174
- public static function loadValidatorMetadata(ClassMetadata $metadata)
174
+ // ...
175
+
176
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
175
177
{
176
178
$metadata->addPropertyConstraint('name', new NotBlank());
177
179
$metadata->addPropertyConstraint('name', new ContainsAlphanumeric());
@@ -194,64 +196,15 @@ Class Constraint Validator
194
196
~~~~~~~~~~~~~~~~~~~~~~~~~~
195
197
196
198
Besides validating a single property, a constraint can have an entire class
197
- as its scope. Consider the following classes, that describe the receipt of some payment::
198
-
199
- // src/AppBundle/Model/PaymentReceipt.php
200
- class PaymentReceipt
201
- {
202
- /**
203
- * @var User
204
- */
205
- private $user;
206
-
207
- /**
208
- * @var array
209
- */
210
- private $payload;
211
-
212
- public function __construct(User $user, array $payload)
213
- {
214
- $this->user = $user;
215
- $this->payload = $payload;
216
- }
217
-
218
- public function getUser(): User
219
- {
220
- return $this->user;
221
- }
222
-
223
- public function getPayload(): array
224
- {
225
- return $this->payload;
226
- }
227
- }
228
-
229
- // src/AppBundle/Model/User.php
230
-
231
- class User
232
- {
233
- /**
234
- * @var string
235
- */
236
- private $email;
237
-
238
- public function __construct($email)
239
- {
240
- $this->email = $email;
241
- }
242
-
243
- public function getEmail(): string
244
- {
245
- return $this->email;
246
- }
247
- }
199
+ as its scope.
248
200
249
- As an example you're going to check if the email in receipt payload matches the user email.
250
- To validate the receipt, it is required to create the constraint first.
251
- You only need to add the ``getTargets() `` method to the ``Constraint `` class::
201
+ For instance, imagine you have a ``PaymentReceipt `` with a ``User `` object
202
+ and you need to make sure the email of the receipt payload matches the user's
203
+ email. First, create a constraint and override the ``getTargets() ``
204
+ method::
252
205
253
- // src/AppBundle/ Validator/Constraints /ConfirmedPaymentReceipt.php
254
- namespace AppBundle \Validator\Constraints ;
206
+ // src/Validator/ConfirmedPaymentReceipt.php
207
+ namespace App \Validator;
255
208
256
209
use Symfony\Component\Validator\Constraint;
257
210
@@ -260,18 +213,19 @@ You only need to add the ``getTargets()`` method to the ``Constraint`` class::
260
213
*/
261
214
class ConfirmedPaymentReceipt extends Constraint
262
215
{
263
- public $userDoesntMatchMessage = 'User email does not match the receipt email';
216
+ public string $userDoesNotMatchMessage = 'User email does not match the receipt email';
264
217
265
- public function getTargets()
218
+ public function getTargets(): string
266
219
{
267
220
return self::CLASS_CONSTRAINT;
268
221
}
269
222
}
270
223
271
- With this, the validator's ``validate() `` method gets an object as its first argument::
224
+ Now, the constraint validator will get an object as first argument to
225
+ ``validate() ``::
272
226
273
- // src/AppBundle/ Validator/Constraints /ConfirmedPaymentReceiptValidator.php
274
- namespace AppBundle \Validator\Constraints ;
227
+ // src/Validator/ConfirmedPaymentReceiptValidator.php
228
+ namespace App \Validator;
275
229
276
230
use Symfony\Component\Validator\Constraint;
277
231
use Symfony\Component\Validator\ConstraintValidator;
@@ -281,9 +235,8 @@ With this, the validator's ``validate()`` method gets an object as its first arg
281
235
{
282
236
/**
283
237
* @param PaymentReceipt $receipt
284
- * @param Constraint|ConfirmedPaymentReceipt $constraint
285
238
*/
286
- public function validate($receipt, Constraint $constraint)
239
+ public function validate($receipt, Constraint $constraint): void
287
240
{
288
241
if (!$receipt instanceof PaymentReceipt) {
289
242
throw new UnexpectedValueException($receipt, PaymentReceipt::class);
@@ -298,7 +251,7 @@ With this, the validator's ``validate()`` method gets an object as its first arg
298
251
299
252
if ($userEmail !== $receiptEmail) {
300
253
$this->context
301
- ->buildViolation($constraint->userDoesntMatchMessage )
254
+ ->buildViolation($constraint->userDoesNotMatchMessage )
302
255
->atPath('user.email')
303
256
->addViolation();
304
257
}
@@ -311,20 +264,19 @@ With this, the validator's ``validate()`` method gets an object as its first arg
311
264
associated. Use any :doc: `valid PropertyAccess syntax </components/property_access >`
312
265
to define that property.
313
266
314
- A class constraint validator is applied to the class itself, and
315
- not to the property:
267
+ A class constraint validator must be applied to the class itself:
316
268
317
269
.. configuration-block ::
318
270
319
271
.. code-block :: php-annotations
320
272
321
- // src/Entity/AcmeEntity .php
273
+ // src/Entity/PaymentReceipt .php
322
274
namespace App\Entity;
323
275
324
- use App\Validator as AcmeAssert ;
276
+ use App\Validator\ConfirmedPaymentReceipt ;
325
277
326
278
/**
327
- * @AppAssert\ ConfirmedPaymentReceipt
279
+ * @ConfirmedPaymentReceipt
328
280
*/
329
281
class PaymentReceipt
330
282
{
@@ -333,44 +285,55 @@ not to the property:
333
285
334
286
.. code-block :: yaml
335
287
336
- # src/AppBundle/Resources/ config/validation.yml
337
- AppBundle\Model \PaymentReceipt :
288
+ # config/validator /validation.yml
289
+ AppBundle\Entity \PaymentReceipt :
338
290
constraints :
339
- - AppBundle \Validator\Constraints \ConfirmedPaymentReceipt : ~
291
+ - App \Validator\ConfirmedPaymentReceipt : ~
340
292
341
293
.. code-block :: xml
342
294
343
- <!-- src/AppBundle/Resources/config/validation.xml -->
344
- <class name =" AppBundle\Model\PaymentReceipt" >
345
- <constraint name =" AppBundle\Validator\Constraints\ConfirmedPaymentReceipt" />
346
- </class >
295
+ <!-- config/validator/validation.xml -->
296
+ <?xml version =" 1.0" encoding =" UTF-8" ?>
297
+ <constraint-mapping xmlns =" http://symfony.com/schema/dic/constraint-mapping"
298
+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
299
+ xsi : schemaLocation =" http://symfony.com/schema/dic/constraint-mapping
300
+ https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd" >
301
+
302
+ <class name =" App\Entity\PaymentReceipt" >
303
+ <constraint name =" App\Validator\ConfirmedPaymentReceipt" />
304
+ </class >
305
+ </constraint-mapping >
347
306
348
307
.. code-block :: php
349
308
350
- // src/AppBundle/Model/PaymentReceipt.php
351
- use AppBundle\Validator\Constraints\ConfirmedPaymentReceipt;
309
+ // src/Entity/PaymentReceipt.php
310
+ namespace App\Entity;
311
+
312
+ use App\Validator\ConfirmedPaymentReceipt;
352
313
use Symfony\Component\Validator\Mapping\ClassMetadata;
353
314
354
315
class PaymentReceipt
355
316
{
356
317
// ...
357
318
358
- public static function loadValidatorMetadata(ClassMetadata $metadata)
319
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
359
320
{
360
321
$metadata->addConstraint(new ConfirmedPaymentReceipt());
361
322
}
362
323
}
363
324
364
- <<<<<<< HEAD
365
325
Testing Custom Constraints
366
326
--------------------------
367
327
368
- Use the ``ConstraintValidatorTestCase `` utility to simplify the creation of
369
- unit tests for your custom constraints::
328
+ Use the :class: `Symfony\\ Component\\ Validator\\ Test\\ ConstraintValidatorTestCase` `
329
+ class to simplify writing unit tests for your custom constraints::
330
+
331
+ // tests/Validator/ContainsAlphanumericValidatorTest.php
332
+ namespace App\Tests\Validator;
370
333
371
- // ...
372
334
use App\Validator\ContainsAlphanumeric;
373
335
use App\Validator\ContainsAlphanumericValidator;
336
+ use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
374
337
375
338
class ContainsAlphanumericValidatorTest extends ConstraintValidatorTestCase
376
339
{
@@ -404,137 +367,3 @@ unit tests for your custom constraints::
404
367
// ...
405
368
}
406
369
}
407
-
408
- How to Unit Test your Validator
409
- -------------------------------
410
-
411
- To create a unit test for you custom validator, your test case class should
412
- extend the ``ConstraintValidatorTestCase `` class and implement the ``createValidator() `` method::
413
-
414
- protected function createValidator()
415
- {
416
- return new ContainsAlphanumericValidator();
417
- }
418
-
419
- After that you can add any test cases you need to cover the validation logic::
420
-
421
- use AppBundle\Validator\Constraints\ContainsAlphanumeric;
422
- use AppBundle\Validator\Constraints\ContainsAlphanumericValidator;
423
- use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
424
-
425
- class ContainsAlphanumericValidatorTest extends ConstraintValidatorTestCase
426
- {
427
- protected function createValidator()
428
- {
429
- return new ContainsAlphanumericValidator();
430
- }
431
-
432
- /**
433
- * @dataProvider getValidStrings
434
- */
435
- public function testValidStrings($string)
436
- {
437
- $this->validator->validate($string, new ContainsAlphanumeric());
438
-
439
- $this->assertNoViolation();
440
- }
441
-
442
- public function getValidStrings()
443
- {
444
- return [
445
- ['Fabien'],
446
- ['SymfonyIsGreat'],
447
- ['HelloWorld123'],
448
- ];
449
- }
450
-
451
- /**
452
- * @dataProvider getInvalidStrings
453
- */
454
- public function testInvalidStrings($string)
455
- {
456
- $constraint = new ContainsAlphanumeric([
457
- 'message' => 'myMessage',
458
- ]);
459
-
460
- $this->validator->validate($string, $constraint);
461
-
462
- $this->buildViolation('myMessage')
463
- ->setParameter('{{ string }}', $string)
464
- ->assertRaised();
465
- }
466
-
467
- public function getInvalidStrings()
468
- {
469
- return [
470
- ['example_'],
471
- ['@$^&'],
472
- ['hello-world'],
473
- ['<body>'],
474
- ];
475
- }
476
- }
477
-
478
- You can also use the ``ConstraintValidatorTestCase `` class for creating test cases for class constraints::
479
-
480
- use AppBundle\Validator\Constraints\ConfirmedPaymentReceipt;
481
- use AppBundle\Validator\Constraints\ConfirmedPaymentReceiptValidator;
482
- use Symfony\Component\Validator\Exception\UnexpectedValueException;
483
- use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
484
-
485
- class ConfirmedPaymentReceiptValidatorTest extends ConstraintValidatorTestCase
486
- {
487
- protected function createValidator()
488
- {
489
- return new ConfirmedPaymentReceiptValidator();
490
- }
491
-
492
- public function testValidReceipt()
493
- {
494
- $receipt = new PaymentReceipt(new User('foo@bar.com'), ['email' => 'foo@bar.com', 'data' => 'baz']);
495
- $this->validator->validate($receipt, new ConfirmedPaymentReceipt());
496
-
497
- $this->assertNoViolation();
498
- }
499
-
500
- /**
501
- * @dataProvider getInvalidReceipts
502
- */
503
- public function testInvalidReceipt($paymentReceipt)
504
- {
505
- $this->validator->validate(
506
- $paymentReceipt,
507
- new ConfirmedPaymentReceipt(['userDoesntMatchMessage' => 'myMessage'])
508
- );
509
-
510
- $this->buildViolation('myMessage')
511
- ->atPath('property.path.user.email')
512
- ->assertRaised();
513
- }
514
-
515
- public function getInvalidReceipts()
516
- {
517
- return [
518
- [new PaymentReceipt(new User('foo@bar.com'), [])],
519
- [new PaymentReceipt(new User('foo@bar.com'), ['email' => 'baz@foo.com'])],
520
- ];
521
- }
522
-
523
- /**
524
- * @dataProvider getUnexpectedArguments
525
- */
526
- public function testUnexpectedArguments($value, $constraint)
527
- {
528
- self::expectException(UnexpectedValueException::class);
529
-
530
- $this->validator->validate($value, $constraint);
531
- }
532
-
533
- public function getUnexpectedArguments()
534
- {
535
- return [
536
- [new \stdClass(), new ConfirmedPaymentReceipt()],
537
- [new PaymentReceipt(new User('foo@bar.com'), []), new Unique()],
538
- ];
539
- }
540
- }
0 commit comments