Skip to content

Commit 75cf1c2

Browse files
committed
Describe how to create a custom constraint with options
1 parent 1e192eb commit 75cf1c2

File tree

1 file changed

+225
-1
lines changed

1 file changed

+225
-1
lines changed

validation/custom_constraint.rst

Lines changed: 225 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,230 @@ then your validator is already registered as a service and :doc:`tagged </servic
224224
with the necessary ``validator.constraint_validator``. This means you can
225225
:ref:`inject services or configuration <services-constructor-injection>` like any other service.
226226

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

@@ -276,7 +500,7 @@ not to the property:
276500
namespace App\Entity;
277501
278502
use App\Validator as AcmeAssert;
279-
503+
280504
/**
281505
* @AcmeAssert\ProtocolClass
282506
*/

0 commit comments

Comments
 (0)