@@ -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
@@ -114,29 +114,29 @@ You can use custom validators like the ones provided by Symfony itself:
114
114
115
115
.. code-block :: php-annotations
116
116
117
- // src/Entity/AcmeEntity .php
117
+ // src/Entity/User .php
118
118
namespace App\Entity;
119
119
120
120
use App\Validator as AcmeAssert;
121
121
use Symfony\Component\Validator\Constraints as Assert;
122
122
123
- class AcmeEntity
123
+ class User
124
124
{
125
125
// ...
126
126
127
127
/**
128
128
* @Assert\NotBlank
129
129
* @AcmeAssert\ContainsAlphanumeric
130
130
*/
131
- protected $name;
131
+ protected string $name = '' ;
132
132
133
133
// ...
134
134
}
135
135
136
136
.. code-block :: yaml
137
137
138
138
# config/validator/validation.yaml
139
- App\Entity\AcmeEntity :
139
+ App\Entity\User :
140
140
properties :
141
141
name :
142
142
- NotBlank : ~
@@ -150,7 +150,7 @@ You can use custom validators like the ones provided by Symfony itself:
150
150
xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
151
151
xsi : schemaLocation =" http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd" >
152
152
153
- <class name =" App\Entity\AcmeEntity " >
153
+ <class name =" App\Entity\User " >
154
154
<property name =" name" >
155
155
<constraint name =" NotBlank" />
156
156
<constraint name =" App\Validator\ContainsAlphanumeric" />
@@ -160,18 +160,20 @@ You can use custom validators like the ones provided by Symfony itself:
160
160
161
161
.. code-block :: php
162
162
163
- // src/Entity/AcmeEntity .php
163
+ // src/Entity/User .php
164
164
namespace App\Entity;
165
165
166
166
use App\Validator\ContainsAlphanumeric;
167
167
use Symfony\Component\Validator\Constraints\NotBlank;
168
168
use Symfony\Component\Validator\Mapping\ClassMetadata;
169
169
170
- class AcmeEntity
170
+ class User
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,22 +196,62 @@ 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. You only need to add this to the ``Constraint `` class::
199
+ as its scope.
200
+
201
+ For instance, imagine you also have a ``PaymentReceipt `` entity and you
202
+ need to make sure the email of the receipt payload matches the user's
203
+ email. First, create a constraint and override the ``getTargets() `` method::
204
+
205
+ // src/Validator/ConfirmedPaymentReceipt.php
206
+ namespace App\Validator;
198
207
199
- public function getTargets()
208
+ use Symfony\Component\Validator\Constraint;
209
+
210
+ /**
211
+ * @Annotation
212
+ */
213
+ class ConfirmedPaymentReceipt extends Constraint
200
214
{
201
- return self::CLASS_CONSTRAINT;
215
+ public string $userDoesNotMatchMessage = 'User\'s e-mail address does not match that of the receipt';
216
+
217
+ public function getTargets(): string
218
+ {
219
+ return self::CLASS_CONSTRAINT;
220
+ }
202
221
}
203
222
204
- With this, the validator's ``validate() `` method gets an object as its first argument::
223
+ Now, the constraint validator will get an object as the first argument to
224
+ ``validate() ``::
225
+
226
+ // src/Validator/ConfirmedPaymentReceiptValidator.php
227
+ namespace App\Validator;
228
+
229
+ use Symfony\Component\Validator\Constraint;
230
+ use Symfony\Component\Validator\ConstraintValidator;
231
+ use Symfony\Component\Validator\Exception\UnexpectedValueException;
205
232
206
- class ProtocolClassValidator extends ConstraintValidator
233
+ class ConfirmedPaymentReceiptValidator extends ConstraintValidator
207
234
{
208
- public function validate($protocol, Constraint $constraint)
235
+ /**
236
+ * @param PaymentReceipt $receipt
237
+ */
238
+ public function validate($receipt, Constraint $constraint): void
209
239
{
210
- if ($protocol->getFoo() != $protocol->getBar()) {
211
- $this->context->buildViolation($constraint->message)
212
- ->atPath('foo')
240
+ if (!$receipt instanceof PaymentReceipt) {
241
+ throw new UnexpectedValueException($receipt, PaymentReceipt::class);
242
+ }
243
+
244
+ if (!$constraint instanceof ConfirmedPaymentReceipt) {
245
+ throw new UnexpectedValueException($constraint, ConfirmedPaymentReceipt::class);
246
+ }
247
+
248
+ $receiptEmail = $receipt->getPayload()['email'] ?? null;
249
+ $userEmail = $receipt->getUser()->getEmail();
250
+
251
+ if ($userEmail !== $receiptEmail) {
252
+ $this->context
253
+ ->buildViolation($constraint->userDoesNotMatchMessage)
254
+ ->atPath('user.email')
213
255
->addViolation();
214
256
}
215
257
}
@@ -221,67 +263,76 @@ With this, the validator's ``validate()`` method gets an object as its first arg
221
263
associated. Use any :doc: `valid PropertyAccess syntax </components/property_access >`
222
264
to define that property.
223
265
224
- A class constraint validator is applied to the class itself, and
225
- not to the property:
266
+ A class constraint validator must be applied to the class itself:
226
267
227
268
.. configuration-block ::
228
269
229
270
.. code-block :: php-annotations
230
271
231
- // src/Entity/AcmeEntity .php
272
+ // src/Entity/PaymentReceipt .php
232
273
namespace App\Entity;
233
274
234
- use App\Validator as AcmeAssert ;
235
-
275
+ use App\Validator\ConfirmedPaymentReceipt ;
276
+
236
277
/**
237
- * @AcmeAssert\ProtocolClass
278
+ * @ConfirmedPaymentReceipt
238
279
*/
239
- class AcmeEntity
280
+ class PaymentReceipt
240
281
{
241
282
// ...
242
283
}
243
284
244
285
.. code-block :: yaml
245
286
246
287
# config/validator/validation.yaml
247
- App\Entity\AcmeEntity :
288
+ App\Entity\PaymentReceipt :
248
289
constraints :
249
- - App\Validator\ProtocolClass : ~
290
+ - App\Validator\ConfirmedPaymentReceipt : ~
250
291
251
292
.. code-block :: xml
252
293
253
294
<!-- config/validator/validation.xml -->
254
- <class name =" App\Entity\AcmeEntity" >
255
- <constraint name =" App\Validator\ProtocolClass" />
256
- </class >
295
+ <?xml version =" 1.0" encoding =" UTF-8" ?>
296
+ <constraint-mapping xmlns =" http://symfony.com/schema/dic/constraint-mapping"
297
+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
298
+ xsi : schemaLocation =" http://symfony.com/schema/dic/constraint-mapping
299
+ https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd" >
300
+
301
+ <class name =" App\Entity\PaymentReceipt" >
302
+ <constraint name =" App\Validator\ConfirmedPaymentReceipt" />
303
+ </class >
304
+ </constraint-mapping >
257
305
258
306
.. code-block :: php
259
307
260
- // src/Entity/AcmeEntity .php
308
+ // src/Entity/PaymentReceipt .php
261
309
namespace App\Entity;
262
310
263
- use App\Validator\ProtocolClass ;
311
+ use App\Validator\ConfirmedPaymentReceipt ;
264
312
use Symfony\Component\Validator\Mapping\ClassMetadata;
265
313
266
- class AcmeEntity
314
+ class PaymentReceipt
267
315
{
268
316
// ...
269
317
270
- public static function loadValidatorMetadata(ClassMetadata $metadata)
318
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
271
319
{
272
- $metadata->addConstraint(new ProtocolClass ());
320
+ $metadata->addConstraint(new ConfirmedPaymentReceipt ());
273
321
}
274
322
}
275
323
276
324
Testing Custom Constraints
277
325
--------------------------
278
326
279
- Use the ``ConstraintValidatorTestCase `` utility to simplify the creation of
280
- unit tests for your custom constraints::
327
+ Use the :class: `Symfony\\ Component\\ Validator\\ Test\\ ConstraintValidatorTestCase` `
328
+ class to simplify writing unit tests for your custom constraints::
329
+
330
+ // tests/Validator/ContainsAlphanumericValidatorTest.php
331
+ namespace App\Tests\Validator;
281
332
282
- // ...
283
333
use App\Validator\ContainsAlphanumeric;
284
334
use App\Validator\ContainsAlphanumericValidator;
335
+ use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
285
336
286
337
class ContainsAlphanumericValidatorTest extends ConstraintValidatorTestCase
287
338
{
0 commit comments