Skip to content

Commit 5570795

Browse files
committed
[Validator] Combine symfony#13898 with recent changes
1 parent 9cbca1a commit 5570795

File tree

1 file changed

+52
-223
lines changed

1 file changed

+52
-223
lines changed

validation/custom_constraint.rst

Lines changed: 52 additions & 223 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ First you need to create a Constraint class and extend :class:`Symfony\\Componen
2424
*/
2525
class ContainsAlphanumeric extends Constraint
2626
{
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.';
2828
}
2929

3030
.. note::
@@ -64,7 +64,7 @@ The validator class only has one required method ``validate()``::
6464

6565
class ContainsAlphanumericValidator extends ConstraintValidator
6666
{
67-
public function validate($value, Constraint $constraint)
67+
public function validate($value, Constraint $constraint): void
6868
{
6969
if (!$constraint instanceof ContainsAlphanumeric) {
7070
throw new UnexpectedTypeException($constraint, ContainsAlphanumeric::class);
@@ -98,7 +98,7 @@ The validator class only has one required method ``validate()``::
9898
The feature to allow passing an object as the ``buildViolation()`` argument
9999
was introduced in Symfony 4.4.
100100

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
102102
to the validator's ``context`` property and a value will be considered valid
103103
if it causes no violations. The ``buildViolation()`` method takes the error
104104
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:
128128
* @Assert\NotBlank
129129
* @AcmeAssert\ContainsAlphanumeric
130130
*/
131-
protected $name;
131+
protected string $name = '';
132132
133133
// ...
134134
}
@@ -169,9 +169,11 @@ You can use custom validators like the ones provided by Symfony itself:
169169
170170
class AcmeEntity
171171
{
172-
public $name;
172+
protected string $name = '';
173173
174-
public static function loadValidatorMetadata(ClassMetadata $metadata)
174+
// ...
175+
176+
public static function loadValidatorMetadata(ClassMetadata $metadata): void
175177
{
176178
$metadata->addPropertyConstraint('name', new NotBlank());
177179
$metadata->addPropertyConstraint('name', new ContainsAlphanumeric());
@@ -194,64 +196,15 @@ Class Constraint Validator
194196
~~~~~~~~~~~~~~~~~~~~~~~~~~
195197

196198
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.
248200

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::
252205

253-
// src/AppBundle/Validator/Constraints/ConfirmedPaymentReceipt.php
254-
namespace AppBundle\Validator\Constraints;
206+
// src/Validator/ConfirmedPaymentReceipt.php
207+
namespace App\Validator;
255208

256209
use Symfony\Component\Validator\Constraint;
257210

@@ -260,18 +213,19 @@ You only need to add the ``getTargets()`` method to the ``Constraint`` class::
260213
*/
261214
class ConfirmedPaymentReceipt extends Constraint
262215
{
263-
public $userDoesntMatchMessage = 'User email does not match the receipt email';
216+
public string $userDoesNotMatchMessage = 'User email does not match the receipt email';
264217

265-
public function getTargets()
218+
public function getTargets(): string
266219
{
267220
return self::CLASS_CONSTRAINT;
268221
}
269222
}
270223

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()``::
272226

273-
// src/AppBundle/Validator/Constraints/ConfirmedPaymentReceiptValidator.php
274-
namespace AppBundle\Validator\Constraints;
227+
// src/Validator/ConfirmedPaymentReceiptValidator.php
228+
namespace App\Validator;
275229

276230
use Symfony\Component\Validator\Constraint;
277231
use Symfony\Component\Validator\ConstraintValidator;
@@ -281,9 +235,8 @@ With this, the validator's ``validate()`` method gets an object as its first arg
281235
{
282236
/**
283237
* @param PaymentReceipt $receipt
284-
* @param Constraint|ConfirmedPaymentReceipt $constraint
285238
*/
286-
public function validate($receipt, Constraint $constraint)
239+
public function validate($receipt, Constraint $constraint): void
287240
{
288241
if (!$receipt instanceof PaymentReceipt) {
289242
throw new UnexpectedValueException($receipt, PaymentReceipt::class);
@@ -298,7 +251,7 @@ With this, the validator's ``validate()`` method gets an object as its first arg
298251

299252
if ($userEmail !== $receiptEmail) {
300253
$this->context
301-
->buildViolation($constraint->userDoesntMatchMessage)
254+
->buildViolation($constraint->userDoesNotMatchMessage)
302255
->atPath('user.email')
303256
->addViolation();
304257
}
@@ -311,20 +264,19 @@ With this, the validator's ``validate()`` method gets an object as its first arg
311264
associated. Use any :doc:`valid PropertyAccess syntax </components/property_access>`
312265
to define that property.
313266

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:
316268

317269
.. configuration-block::
318270

319271
.. code-block:: php-annotations
320272
321-
// src/Entity/AcmeEntity.php
273+
// src/Entity/PaymentReceipt.php
322274
namespace App\Entity;
323275
324-
use App\Validator as AcmeAssert;
276+
use App\Validator\ConfirmedPaymentReceipt;
325277
326278
/**
327-
* @AppAssert\ConfirmedPaymentReceipt
279+
* @ConfirmedPaymentReceipt
328280
*/
329281
class PaymentReceipt
330282
{
@@ -333,44 +285,55 @@ not to the property:
333285
334286
.. code-block:: yaml
335287
336-
# src/AppBundle/Resources/config/validation.yml
337-
AppBundle\Model\PaymentReceipt:
288+
# config/validator/validation.yml
289+
AppBundle\Entity\PaymentReceipt:
338290
constraints:
339-
- AppBundle\Validator\Constraints\ConfirmedPaymentReceipt: ~
291+
- App\Validator\ConfirmedPaymentReceipt: ~
340292
341293
.. code-block:: xml
342294
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>
347306
348307
.. code-block:: php
349308
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;
352313
use Symfony\Component\Validator\Mapping\ClassMetadata;
353314
354315
class PaymentReceipt
355316
{
356317
// ...
357318
358-
public static function loadValidatorMetadata(ClassMetadata $metadata)
319+
public static function loadValidatorMetadata(ClassMetadata $metadata): void
359320
{
360321
$metadata->addConstraint(new ConfirmedPaymentReceipt());
361322
}
362323
}
363324
364-
<<<<<<< HEAD
365325
Testing Custom Constraints
366326
--------------------------
367327

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;
370333

371-
// ...
372334
use App\Validator\ContainsAlphanumeric;
373335
use App\Validator\ContainsAlphanumericValidator;
336+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
374337

375338
class ContainsAlphanumericValidatorTest extends ConstraintValidatorTestCase
376339
{
@@ -404,137 +367,3 @@ unit tests for your custom constraints::
404367
// ...
405368
}
406369
}
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

Comments
 (0)