Validation¶
Validation is a very common task in web applications. Data entered in forms needs to be validated. Data also needs to be validated before it is written into a database or passed to a web service.
Symfony2 ships with a Validator component that makes this task easy and transparent. This component is based on the JSR303 Bean Validation specification. What? A Java specification in PHP? You heard right, but it’s not as bad as it sounds. Let’s look at how it can be used in PHP.
The Basics of Validation¶
The best way to understand validation is to see it in action. To start, suppose you’ve created a plain-old-PHP object that you need to use somewhere in your application:
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
class Author
{
public $name;
}
So far, this is just an ordinary class that serves some purpose inside your application. The goal of validation is to tell you whether or not the data of an object is valid. For this to work, you’ll configure a list of rules (called constraints) that the object must follow in order to be valid. These rules can be specified via a number of different formats (YAML, XML, annotations, or PHP).
For example, to guarantee that the $name
property is not empty, add the
following:
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: name: - NotBlank: ~
- Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\NotBlank() */ public $name; }
- XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> <class name="Acme\BlogBundle\Entity\Author"> <property name="name"> <constraint name="NotBlank" /> </property> </class> </constraint-mapping>
- PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; class Author { public $name; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('name', new NotBlank()); } }
Tip
Protected and private properties can also be validated, as well as “getter” methods (see validator-constraint-targets).
Using the validator
Service¶
Next, to actually validate an Author
object, use the validate
method
on the validator
service (class Symfony\Component\Validator\Validator
).
The job of the validator
is easy: to read the constraints (i.e. rules)
of a class and verify whether or not the data on the object satisfies those
constraints. If validation fails, an array of errors is returned. Take this
simple example from inside a controller:
use Symfony\Component\HttpFoundation\Response;
use Acme\BlogBundle\Entity\Author;
// ...
public function indexAction()
{
$author = new Author();
// ... do something to the $author object
$validator = $this->get('validator');
$errors = $validator->validate($author);
if (count($errors) > 0) {
return new Response(print_r($errors, true));
} else {
return new Response('The author is valid! Yes!');
}
}
If the $name
property is empty, you will see the following error
message:
Acme\BlogBundle\Author.name:
This value should not be blank
If you insert a value into the name
property, the happy success message
will appear.
Tip
Most of the time, you won’t interact directly with the validator
service or need to worry about printing out the errors. Most of the time,
you’ll use validation indirectly when handling submitted form data. For
more information, see the Validation and Forms.
You could also pass the collection of errors into a template.
if (count($errors) > 0) {
return $this->render('AcmeBlogBundle:Author:validate.html.twig', array(
'errors' => $errors,
));
} else {
// ...
}
Inside the template, you can output the list of errors exactly as needed:
- Twig
{# src/Acme/BlogBundle/Resources/views/Author/validate.html.twig #} <h3>The author has the following errors</h3> <ul> {% for error in errors %} <li>{{ error.message }}</li> {% endfor %} </ul>
- PHP
<!-- src/Acme/BlogBundle/Resources/views/Author/validate.html.php --> <h3>The author has the following errors</h3> <ul> <?php foreach ($errors as $error): ?> <li><?php echo $error->getMessage() ?></li> <?php endforeach; ?> </ul>
Note
Each validation error (called a “constraint violation”), is represented by
a Symfony\Component\Validator\ConstraintViolation
object.
Validation and Forms¶
The validator
service can be used at any time to validate any object.
In reality, however, you’ll usually work with the validator
indirectly
when working with forms. Symfony’s form library uses the validator
service
internally to validate the underlying object after values have been submitted
and bound. The constraint violations on the object are converted into FieldError
objects that can easily be displayed with your form. The typical form submission
workflow looks like the following from inside a controller:
use Acme\BlogBundle\Entity\Author;
use Acme\BlogBundle\Form\AuthorType;
use Symfony\Component\HttpFoundation\Request;
// ...
public function updateAction(Request $request)
{
$author = new Acme\BlogBundle\Entity\Author();
$form = $this->createForm(new AuthorType(), $author);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// the validation passed, do something with the $author object
return $this->redirect($this->generateUrl('...'));
}
}
return $this->render('BlogBundle:Author:form.html.twig', array(
'form' => $form->createView(),
));
}
Note
This example uses an AuthorType
form class, which is not shown here.
For more information, see the Forms chapter.
Configuration¶
The Symfony2 validator is enabled by default, but you must explicitly enable annotations if you’re using the annotation method to specify your constraints:
- YAML
# app/config/config.yml framework: validation: { enable_annotations: true }
- XML
<!-- app/config/config.xml --> <framework:config> <framework:validation enable_annotations="true" /> </framework:config>
- PHP
// app/config/config.php $container->loadFromExtension('framework', array('validation' => array( 'enable_annotations' => true, )));
Constraints¶
The validator
is designed to validate objects against constraints (i.e.
rules). In order to validate an object, simply map one or more constraints
to its class and then pass it to the validator
service.
Behind the scenes, a constraint is simply a PHP object that makes an assertive statement. In real life, a constraint could be: “The cake must not be burned”. In Symfony2, constraints are similar: they are assertions that a condition is true. Given a value, a constraint will tell you whether or not that value adheres to the rules of the constraint.
Supported Constraints¶
Symfony2 packages a large number of the most commonly-needed constraints:
Basic Constraints¶
These are the basic constraints: use them to assert very basic things about the value of properties or the return value of methods on your object.
Collection Constraints¶
Other Constraints¶
You can also create your own custom constraints. This topic is covered in the “How to create a Custom Validation Constraint” article of the cookbook.
Constraint Configuration¶
Some constraints, like NotBlank,
are simple whereas others, like the Choice
constraint, have several configuration options available. Suppose that the
Author
class has another property, gender
that can be set to either
“male” or “female”:
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: gender: - Choice: { choices: [male, female], message: Choose a valid gender. }
- Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\Choice( * choices = { "male", "female" }, * message = "Choose a valid gender." * ) */ public $gender; }
- XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> <class name="Acme\BlogBundle\Entity\Author"> <property name="gender"> <constraint name="Choice"> <option name="choices"> <value>male</value> <value>female</value> </option> <option name="message">Choose a valid gender.</option> </constraint> </property> </class> </constraint-mapping>
- PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; class Author { public $gender; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('gender', new Choice(array( 'choices' => array('male', 'female'), 'message' => 'Choose a valid gender.', ))); } }
The options of a constraint can always be passed in as an array. Some constraints,
however, also allow you to pass the value of one, “default”, option in place
of the array. In the case of the Choice
constraint, the choices
options can be specified in this way.
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: gender: - Choice: [male, female]
- Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\Choice({"male", "female"}) */ protected $gender; }
- XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> <class name="Acme\BlogBundle\Entity\Author"> <property name="gender"> <constraint name="Choice"> <value>male</value> <value>female</value> </constraint> </property> </class> </constraint-mapping>
- PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\Choice; class Author { protected $gender; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('gender', new Choice(array('male', 'female'))); } }
This is purely meant to make the configuration of the most common option of a constraint shorter and quicker.
If you’re ever unsure of how to specify an option, either check the API documentation for the constraint or play it safe by always passing in an array of options (the first method shown above).
Constraint Targets¶
Constraints can be applied to a class property (e.g. name
) or a public
getter method (e.g. getFullName
). The first is the most common and easy
to use, but the second allows you to specify more complex validation rules.
Properties¶
Validating class properties is the most basic validation technique. Symfony2
allows you to validate private, protected or public properties. The next
listing shows you how to configure the $firstName
property of an Author
class to have at least 3 characters.
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: firstName: - NotBlank: ~ - MinLength: 3
- Annotations
// Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\NotBlank() * @Assert\MinLength(3) */ private $firstName; }
- XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\Author"> <property name="firstName"> <constraint name="NotBlank" /> <constraint name="MinLength">3</constraint> </property> </class>
- PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\MinLength; class Author { private $firstName; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('firstName', new NotBlank()); $metadata->addPropertyConstraint('firstName', new MinLength(3)); } }
Getters¶
Constraints can also be applied to the return value of a method. Symfony2 allows you to add a constraint to any public method whose name starts with “get” or “is”. In this guide, both of these types of methods are referred to as “getters”.
The benefit of this technique is that it allows you to validate your object
dynamically. For example, suppose you want to make sure that a password field
doesn’t match the first name of the user (for security reasons). You can
do this by creating an isPasswordLegal
method, and then asserting that
this method must return true
:
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: getters: passwordLegal: - "True": { message: "The password cannot match your first name" }
- Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\True(message = "The password cannot match your first name") */ public function isPasswordLegal() { // return true or false } }
- XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\Author"> <getter property="passwordLegal"> <constraint name="True"> <option name="message">The password cannot match your first name</option> </constraint> </getter> </class>
- PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\True; class Author { public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addGetterConstraint('passwordLegal', new True(array( 'message' => 'The password cannot match your first name', ))); } }
Now, create the isPasswordLegal()
method, and include the logic you need:
public function isPasswordLegal()
{
return ($this->firstName != $this->password);
}
Note
The keen-eyed among you will have noticed that the prefix of the getter (“get” or “is”) is omitted in the mapping. This allows you to move the constraint to a property with the same name later (or vice versa) without changing your validation logic.
Classes¶
Some constraints apply to the entire class being validated. For example, the Callback constraint is a generic constraint that’s applied to the class itself. When that class is validated, methods specified by that constraint are simply executed so that each can provide more custom validation.
Validation Groups¶
So far, you’ve been able to add constraints to a class and ask whether or not that class passes all of the defined constraints. In some cases, however, you’ll need to validate an object against only some of the constraints on that class. To do this, you can organize each constraint into one or more “validation groups”, and then apply validation against just one group of constraints.
For example, suppose you have a User
class, which is used both when a
user registers and when a user updates his/her contact information later:
- YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\User: properties: email: - Email: { groups: [registration] } password: - NotBlank: { groups: [registration] } - MinLength: { limit: 7, groups: [registration] } city: - MinLength: 2
- Annotations
// src/Acme/BlogBundle/Entity/User.php namespace Acme\BlogBundle\Entity; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; class User implements UserInterface { /** * @Assert\Email(groups={"registration"}) */ private $email; /** * @Assert\NotBlank(groups={"registration"}) * @Assert\MinLength(limit=7, groups={"registration"}) */ private $password; /** * @Assert\MinLength(2) */ private $city; }
- XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\User"> <property name="email"> <constraint name="Email"> <option name="groups"> <value>registration</value> </option> </constraint> </property> <property name="password"> <constraint name="NotBlank"> <option name="groups"> <value>registration</value> </option> </constraint> <constraint name="MinLength"> <option name="limit">7</option> <option name="groups"> <value>registration</value> </option> </constraint> </property> <property name="city"> <constraint name="MinLength">7</constraint> </property> </class>
- PHP
// src/Acme/BlogBundle/Entity/User.php namespace Acme\BlogBundle\Entity; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\MinLength; class User { public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('email', new Email(array( 'groups' => array('registration') ))); $metadata->addPropertyConstraint('password', new NotBlank(array( 'groups' => array('registration') ))); $metadata->addPropertyConstraint('password', new MinLength(array( 'limit' => 7, 'groups' => array('registration') ))); $metadata->addPropertyConstraint('city', new MinLength(3)); } }
With this configuration, there are two validation groups:
Default
- contains the constraints not assigned to any other group;registration
- contains the constraints on theemail
andpassword
fields only.
To tell the validator to use a specific group, pass one or more group names
as the second argument to the validate()
method:
$errors = $validator->validate($author, array('registration'));
Of course, you’ll usually work with validation indirectly through the form library. For information on how to use validation groups inside forms, see Validation Groups.
Validating Values and Arrays¶
So far, you’ve seen how you can validate entire objects. But sometimes, you just want to validate a simple value - like to verify that a string is a valid email address. This is actually pretty easy to do. From inside a controller, it looks like this:
// add this to the top of your class
use Symfony\Component\Validator\Constraints\Email;
public function addEmailAction($email)
{
$emailConstraint = new Email();
// all constraint "options" can be set this way
$emailConstraint->message = 'Invalid email address';
// use the validator to validate the value
$errorList = $this->get('validator')->validateValue($email, $emailConstraint);
if (count($errorList) == 0) {
// this IS a valid email address, do something
} else {
// this is *not* a valid email address
$errorMessage = $errorList[0]->getMessage()
// do something with the error
}
// ...
}
By calling validateValue
on the validator, you can pass in a raw value and
the constraint object that you want to validate that value against. A full
list of the available constraints - as well as the full class name for each
constraint - is available in the constraints reference
section .
The validateValue
method returns a Symfony\Component\Validator\ConstraintViolationList
object, which acts just like an array of errors. Each error in the collection
is a Symfony\Component\Validator\ConstraintViolation
object,
which holds the error message on its getMessage method.
Final Thoughts¶
The Symfony2 validator
is a powerful tool that can be leveraged to
guarantee that the data of any object is “valid”. The power behind validation
lies in “constraints”, which are rules that you can apply to properties or
getter methods of your object. And while you’ll most commonly use the validation
framework indirectly when using forms, remember that it can be used anywhere
to validate any object.