One of the ugly things about Symfony 1 validators was that their dependencies were generally fetched from far away, mainly by calling sfContext::getInstance(). With Symfony2 and it's service container, this isn't necessary anymore. Validators can be services. The example below is quite simple, but given you can inject anything you want, you can make it quite complicated. I show you how to create a validator that checks with the router of the value-under-validation is a route.

For validation two things are needed: a Constraint and a Validator. The Validator::isValid() method, receives two argument: the value to validate and a Constraint. The Constraint may have several options, and for example a message to use in case the value is not valid. These options and a message can be set as a public property.

First create the Route Constraint, e.g. in /src/Acme/DemoBundle/Validator/Constraints/Route.php

namespace Acme\DemoBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

class Route extends Constraint
{
    public $message = 'This route does not exist';

    public function validatedBy()
    {
        return 'acme.validator.route';
    }
}

The method validatedBy() returns a string, which is normally the class of the validator for this constraint (by default created by simply appending the string "Validator" to the class of "Constraint"). In this case, the method returns the id of the service that will be the validator.

Let's create the validator first and later make it into a service.


namespace Acme\DemoBundle\Validator\Constraints; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Routing\Exception\InvalidParameterException; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; use Symfony\Component\Routing\Router; class RouteValidator extends ConstraintValidator { protected $router; public function __construct(Router $router) { $this->router = $router; } public function isValid($value, Constraint $constraint) { if (null === $value || '' === $value) { return true; } if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) { throw new UnexpectedTypeException($value, 'string'); } $value = (string) $value; try { $route = $this->router->getGenerator()->generate($value); $valid = true; } catch (RouteNotFoundException $e) { $valid = false; } catch (MissingMandatoryParametersException $e) { // the route exists, but generate() trips over a missing parameter $valid = true; } catch (InvalidParameterException $e) { // the route exists, but generate() trips over an invalid parameter $valid = true; } if (!$valid) { $this->setMessage($constraint->message, array('' => $value)); return false; } return true; } }

Finally, let's make our new validator known to the service container and more specifically to the validator, by adding a tag to the service: "validator.constraint_validator". So the validator knows that a new constraint validator is in town.

<?xml version="1.0" ?>
<container xmlns="https://symfony.com/schema/dic/services"
    xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <service id="acme.validator.route" class="Acme\DemoBundle\Validator\Constraints\RouteValidator">
            <argument type="service" id="router" />
            <tag name="validator.constraint_validator" alias="acme.validator.route" />
        </service>
    </services>
</container>

Now, we can use the new Route constraint in the usual way. Or for example use it separately:

$validator = $this->get('acme.validator.route'); // get the validator from the service container

$constraint = new RouteConstraint();

if (!$validator->isValid('_welcome', $constraint)) {
    // the route exists
}
PHP Symfony2 validation service container dependency injection
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
Jesse

Exactly what I needed. Thanks!

Lars Janssen

Fabulously helpful, thanks for this!

cordoval

i am sorry for this dumb question but shouldn't this be as follows instead?```php if (!$validator->isValid('_welcome', $constraint)) { // the route DOES NOT exist }

Bitcoin Data

"Let’s create the validator first and later make it into a service."

Where/what file should this be created in? That is the one question lacking in all the symfony validation tutorials on the web.

Matthias Noback

Hi Bitcoin Data ;) As you can infer from the namespace of the RouteValidator you can just put the validator somewhere inside your project's src/ directory. But maybe you meant something else...

Stof

And just a though about your validator implementation: $router->getRouteCollection() should probably be avoided as it is really bad for performances. It will load all routing files which can be expensive, whereas the prod environment is generally meant to do it only on the first request.

Matthias Noback

Hi, thanks for your comment. I looked the method up in Symfony\Component\Routing\Router and it appears getRouteCollection makes only one call to the loader, and then stores the result in it's attribute "collection", so I think this should not be a problem for performance (oh, actually, this takes place in Symfony\Bundle\FrameworkBundle\Routing\Router)

Stof

The point is, in prod, it uses the cached router so the collection is not loaded at all

Matthias Noback

Ah, I get it. I just updated the post so the validation makes use of the UrlGenerator. In prod as well as dev, this makes use of the cached router.

Stof

To be able to use it as annotation, you simply need to add @Annotation in the phpdoc (like for core constraint) and then use the class as annotation:``

<?phpuse Acme\DemoBundle\Validator\Constraints\Route;// ... /** * @Route(message="This should be a route") */ protected $route; ``I'm not sure if the code example will be displayed properly in the comment.
Matthias Noback

Thanks for your suggestion!

Matthias Noback

I'm not sure, I haven't used much of the annotation related features; by the way, this post is almost something of a cookbook article, so I might fork the documentation repository and a page about this.

charlie

Nice, I was looking for documentation on this. Does this work with annotations as well or is something else needed for that?