@@ -233,6 +233,230 @@ then your validator is already registered as a service and :doc:`tagged </servic
233233with the necessary ``validator.constraint_validator ``. This means you can
234234:ref: `inject services or configuration <services-constructor-injection >` like any other service.
235235
236+ Constraint Validators with custom options
237+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
238+
239+ Define public properties on the constraint class for the desired configuration
240+ options:
241+
242+ .. configuration-block ::
243+
244+ .. code-block :: php-annotations
245+
246+ // src/Validator/Foo.php
247+ namespace App\Validator;
248+
249+ use Symfony\Component\Validator\Constraint;
250+
251+ /**
252+ * @Annotation
253+ */
254+ class Foo extends Constraint
255+ {
256+ public $mandatoryFooOption;
257+ public $message = 'This value is invalid';
258+ public $optionalBarOption = false;
259+
260+ public function __construct(
261+ $mandatoryFooOption,
262+ string $message = null,
263+ bool $optionalBarOption = null,
264+ array $groups = null,
265+ $payload = null,
266+ array $options = []
267+ ) {
268+ if (\is_array($mandatoryFooOption)) {
269+ $options = array_merge($mandatoryFooOption, $options);
270+ } elseif (null !== $mandatoryFooOption) {
271+ $options['value'] = $mandatoryFooOption;
272+ }
273+
274+ parent::__construct($options, $groups, $payload);
275+
276+ $this->message = $message ?? $this->message;
277+ $this->optionalBarOption = $optionalBarOption ?? $this->optionalBarOption;
278+ }
279+
280+ public function getDefaultOption()
281+ {
282+ // If no associative array is passed to the constructor this
283+ // property is set instead.
284+
285+ return 'mandatoryFooOption';
286+ }
287+
288+ public function getRequiredOptions()
289+ {
290+ // return names of options which must be set.
291+
292+ return ['mandatoryFooOption'];
293+ }
294+ }
295+
296+ .. code-block :: php-attributes
297+
298+ // src/Validator/Foo.php
299+ namespace App\Validator;
300+
301+ use Symfony\Component\Validator\Constraint;
302+
303+ #[\Attribute]
304+ class Foo extends Constraint
305+ {
306+ public $mandatoryFooOption;
307+ public $message = 'This value is invalid';
308+ public $optionalBarOption = false;
309+
310+ public function __construct(
311+ $mandatoryFooOption,
312+ string $message = null,
313+ bool $optionalBarOption = null,
314+ array $groups = null,
315+ $payload = null,
316+ array $options = []
317+ ) {
318+ if (\is_array($mandatoryFooOption)) {
319+ $options = array_merge($mandatoryFooOption, $options);
320+ } elseif (null !== $mandatoryFooOption) {
321+ $options['value'] = $mandatoryFooOption;
322+ }
323+
324+ parent::__construct($options, $groups, $payload);
325+
326+ $this->message = $message ?? $this->message;
327+ $this->optionalBarOption = $optionalBarOption ?? $this->optionalBarOption;
328+ }
329+
330+ public function getDefaultOption()
331+ {
332+ return 'mandatoryFooOption';
333+ }
334+
335+ public function getRequiredOptions()
336+ {
337+ return ['mandatoryFooOption'];
338+ }
339+ }
340+
341+ Inside the validator, options can be accessed quite simple::
342+
343+ class FooValidator extends ConstraintValidator
344+ {
345+ public function validate($value, Constraint $constraint)
346+ {
347+ // Access the option of the constraint
348+ if ($constraint->optionalBarOption) {
349+ // ...
350+ }
351+
352+ // ...
353+ }
354+ }
355+
356+ Custom options can be passed to the constraints like for the ones provided by Symfony
357+ itself:
358+
359+ .. configuration-block ::
360+
361+ .. code-block :: php-annotations
362+
363+ // src/Entity/AcmeEntity.php
364+ namespace App\Entity;
365+
366+ use App\Validator as AcmeAssert;
367+ use Symfony\Component\Validator\Constraints as Assert;
368+
369+ class AcmeEntity
370+ {
371+ // ...
372+
373+ /**
374+ * @Assert\NotBlank
375+ * @AcmeAssert\Foo(
376+ * mandatoryFooOption="bar",
377+ * optionalBarOption=true
378+ * )
379+ */
380+ protected $name;
381+
382+ // ...
383+ }
384+
385+ .. code-block :: php-attributes
386+
387+ // src/Entity/AcmeEntity.php
388+ namespace App\Entity;
389+
390+ use App\Validator as AcmeAssert;
391+ use Symfony\Component\Validator\Constraints as Assert;
392+
393+ class AcmeEntity
394+ {
395+ // ...
396+
397+ #[Assert\NotBlank]
398+ #[AcmeAssert\Foo(
399+ mandatoryFooOption: 'bar',
400+ optionalBarOption: true
401+ )]
402+ protected $name;
403+
404+ // ...
405+ }
406+
407+ .. code-block :: yaml
408+
409+ # config/validator/validation.yaml
410+ App\Entity\AcmeEntity :
411+ properties :
412+ name :
413+ - NotBlank : ~
414+ - App\Validator\Foo :
415+ mandatoryFooOption : bar
416+ optionalBarOption : true
417+
418+ .. code-block :: xml
419+
420+ <!-- config/validator/validation.xml -->
421+ <?xml version =" 1.0" encoding =" UTF-8" ?>
422+ <constraint-mapping xmlns =" http://symfony.com/schema/dic/constraint-mapping"
423+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
424+ xsi : schemaLocation =" http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd" >
425+
426+ <class name =" App\Entity\AcmeEntity" >
427+ <property name =" name" >
428+ <constraint name =" NotBlank" />
429+ <constraint name =" App\Validator\Foo" >
430+ <option name =" mandatoryFooOption" >bar</option >
431+ <option name =" optionalBarOption" >true</option >
432+ </constraint >
433+ </property >
434+ </class >
435+ </constraint-mapping >
436+
437+ .. code-block :: php
438+
439+ // src/Entity/AcmeEntity.php
440+ namespace App\Entity;
441+
442+ use App\Validator\ContainsAlphanumeric;
443+ use Symfony\Component\Validator\Constraints\NotBlank;
444+ use Symfony\Component\Validator\Mapping\ClassMetadata;
445+
446+ class AcmeEntity
447+ {
448+ public $name;
449+
450+ public static function loadValidatorMetadata(ClassMetadata $metadata)
451+ {
452+ $metadata->addPropertyConstraint('name', new NotBlank());
453+ $metadata->addPropertyConstraint('name', new Foo([
454+ 'mandatoryFooOption' => 'bar',
455+ 'optionalBarOption' => true,
456+ ]));
457+ }
458+ }
459+
236460 Create a Reusable Set of Constraints
237461~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
238462
0 commit comments