Configuring validator with annotation and cache in silex

By Jean Hertel, 10/03/2017, in category Tips

Cache, PHP, Silex, Symfony, Validator

If you have read our article on how to configure Symfony Serializer with annotation and cache You should already know the basics of the setup you need. Like the Serializer, Validator can also read annotations and save cached class metadata.

To read the annotations, all you have to do is rewrite the function validator.mapping.class_metadata_factory. This function should return an instance of the object Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface.

If you want to do this, you can use the following code snippet:

<?php
use Silex\Provider\ValidatorServiceProvider;
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Doctrine\Common\Annotations\AnnotationReader;

$app->register(new ValidatorServiceProvider());

$app['validator.mapping.class_metadata_factory'] = function ($app) {
    $loader = new AnnotationLoader(new AnnotationReader());

    return new LazyLoadingMetadataFactory($loader);
};

This way your validator will automatically read the annotations that are in your class. Finally, just like in the case of the serializer, to cache the data you can pass the second argument of the class LazyLoadingMetadataFactory.

The difference to be noted here is that in the current version of Symfony / Validator (3.2) the class expects to receive a parameter of type Symfony\Component\Validator\Mapping\Cache\CacheInterface. Fortunately, if you are using the Doctrine cache, there is already a wrapper class that can be used, its name is DoctrineCache.

The following is a complete and functional example of the entire implementation:

<?php
use Doctrine\Common\Annotations\AnnotationRegistry;
use Silex\Application;
use Silex\Provider\ValidatorServiceProvider;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Mapping\Cache\DoctrineCache;

use \Memcache as MemcachePHP;
use Doctrine\Common\Cache\MemcacheCache as MemcacheWrapper;

use Symfony\Component\Validator\Constraints as Assert;

class Foo
{
    /**
     * @var int
     * @Assert\NotBlank(message="This field cannot be empty")
     */
    private $someProperty;

    public function getSomeProperty() {
        return $this->someProperty;
    }

    public function setSomeProperty($someProperty) {
        $this->someProperty = $someProperty;

        return $this;
    }
}

$loader = require __DIR__ . '/vendor/autoload.php';
AnnotationRegistry::registerLoader([$loader, 'loadClass']);

$memcache = new MemcachePHP();

if (! $memcache->connect('localhost', '11211')) {
    throw new \Exception('Unable to connect to memcache server');
}

$cacheDriver = new MemcacheWrapper();
$cacheDriver->setMemcache($memcache);


$app = new Application();

$app->register(new ValidatorServiceProvider());

$app['validator.mapping.class_metadata_factory'] = function ($app) use ($cacheDriver) {
    $loader = new AnnotationLoader(new AnnotationReader());

    $cache = new DoctrineCache($cacheDriver);

    return new LazyLoadingMetadataFactory($loader, $cache);
};

$app->get('/', function (Application $app) {
    $obj = new Foo();

    $validationIssues = $app['validator']->validate($obj);

    $errors = (string)$validationIssues;

    return new \Symfony\Component\HttpFoundation\Response("Issues found: " . $errors);
});

$app->run();

Note: If you have any class not found errors, I am using the following dependencies in my composer.json: