Extending of a Symfony constraint in order to add your own validation rules
In this post will see how to extend a native Symfony constraint in order to add some custom validation rules. We will see a concrete example with the Email constraint on which we will add a new rule that prevents an email without domain extension to pass the validation.
Posted on 2019-02-14 by COil (Updated on 2019-02-21)
Symfony Symfony4 validation email formPublished in "A week of Symfony n°633" on Symfony.com
Goal
The goal will be to make adresses like "myemail@yahoo" invalid as the domain extension is missing. Those types of email are indeed valid from the RFC point of vue but concretely all services just won't work with thoses addresses. Note that using the "loose" mode of the native constraint can lead to the same result but in this case you lose the benefit of using the strict mode. (for example the email "toto@toto@toto.toto" is considered valid when using the "loose" mode)
Configuration
This post was written using the following components:
Pre-requisites
We will assume that you are already familiar with Symfony, the Form component and you know how to create a basic form with a field with some validation constraints. If that's not case, check out the documentation right now! 🤓
Let's go!
When wanting to create a form constraint one needs two classes: The constraint and the validator.
The constraint
This file is used to declare the configuration. Let's look at the code: (the file you are viewing is in fact the real file used in this project thanks to a view source code Twig helper)
<?php
declare(strict_types=1);
namespace App\Component\Validator;
use Symfony\Component\Validator\Constraints\Email as BaseEmail;
/**
* New email validation rule, "forbid values without domain extension".
*/
class Email extends BaseEmail
{
final public const INVALID_DOMAIN_EXTENSION = '7da53a8b-56f3-4288-bb3e-ee9ede4ef9a2';
public bool $forbidEmptyDomainExtension = false;
public string $messageInvalidDomainExtension = 'bad_email_domain_extension';
}
As you can see we extend the native Email
constraint and we have three configuration items.
- The
INVALID_DOMAIN_EXTENSION
constant: An error code to identify the missing domain extension error. $forbidEmptyDomainExtension
: A boolean attribute that will control the activation of the new validation rule, by default it's not activated to keep the same behaviour as the native constraint.$messageInvalidDomainExtension
: The translation key used to translate the error message on the form.
The validator
<?php
declare(strict_types=1);
namespace App\Component\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\EmailValidator as BaseEmailValidator;
/**
* Provide an additional check to avoid empty domain extensions.
*/
class EmailValidator extends BaseEmailValidator
{
public function validate($value, Constraint $constraint): void
{
parent::validate($value, $constraint);
if ($value === null || $value === '') {
return;
}
if (!$constraint instanceof Email) {
throw new \RuntimeException('Unexpected constraint type bound to the validator.');
}
if (!$constraint->forbidEmptyDomainExtension) {
return;
}
$domain = explode('@', (string) $value)[1] ?? '';
$extension = explode('.', $domain)[1] ?? false;
if (!$extension) {
$this->context->buildViolation($constraint->messageInvalidDomainExtension)
->setCode(Email::INVALID_DOMAIN_EXTENSION)
->addViolation();
}
}
}
Again here, we extend the native validator. In the main validate()
function we call the parent one and continue only if we have a value to handle.
Next, we check the type of the constraint to have the autocompletion of the class Email
constants and attributes. Then, we check the activation of the new validation rule. The main validation code will check that the value has a domain extension. And eventually, if the extension is not found we add a violation to the context associated with the validator. It will be used to display the error on the form.
Usage
To use our new constraint, replace the use
statement in your form type class like below:
<?php
declare(strict_types=1);
namespace App\Form\Type\User;
Then in the buildForm
function of your form type, use the new constraint and activate its custom validation rule: (note that all other options of the native Email constraint are still available)
public function buildForm(FormBuilderInterface $b, array $options): void
{
$b->add('email', EmailType::class, [
'label' => 'form.email',
'constraints' => [
new NotBlank(),
new Email(['mode' => 'strict', 'forbidEmptyDomainExtension' => true]),
]
]);
And finally, don't forget to add the translation of the error message to your i18n file:
# translations/validators.en.yml
bad_email_domain_extension: >
You have forgotten the extension of your email's domain, og: ".com"
That's it! 😁
Conclusion
Of course this was a basic example but you can add more complex validation rules. Your can directly test this constraint on the registration form of this website.
PS: Note that the files' paths will be different (remove the bundle prefix) if you are using Flex: Tokeeen doesn't use it yet even it is powered by Symfony 4.2.
Call to action
Did you like these posts? You can help us back in several ways:
- Create an account and test Tokeeen which is still BETA.
- Subscribe to the newsletter and get notified of the official launch.
- Subscribe to the RSS feed
- Follow us on Twitter
- Report any error/typo (use the contact form).
- Report something that could be improved.
Thank you for reading! And see you soon on Tokeeen! 😉
Suivez C0il sur Twitter