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