Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this data being overwritten by another component?

I'm doing some programming in Silex with the symfony components and I think I have found a bug with the symfony/serializer and the symfony/validator components.

First let me explain what I'm traing to achieve, then let's go to the code. My objective is to annotate a class with information like serialization directives as well as validation directives. As the reading of these annotations can cost a litle cpu, I like to cache them in memory. For this purpose, I'm using memcache wrapper in the Doctrine/Common/Cache package.

The problem I face is that both the symfony/serializer and the symfony/validator write Metadata to the cache using the class name as key. When they try to retrieve the metadata later, they throw an exception, because the cache has invalid metadata, either an instance of Symfony\Component\Validator\Mapping\ClassMetadata or Symfony\Component\Serializer\Mapping\ClassMetadataInterface.

Following is a reproductible example (sorry if its big, I tried to make as small as possible):

use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

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

    /**
     * @return int
     * @Groups({"some_group"})
     */
    public function getSomeProperty() {
        return $this->someProperty;
    }
}


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

$loader = require_once __DIR__ . '/../vendor/autoload.php';

\Doctrine\Common\Annotations\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 \Silex\Application();

$app->register(new Silex\Provider\SerializerServiceProvider());

$app['serializer.normalizers'] = function () use ($app, $cacheDriver) {
    $classMetadataFactory = new Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory(
        new Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader(new AnnotationReader()), $cacheDriver);

    return [new Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer($classMetadataFactory) ];
};

$app->register(new Silex\Provider\ValidatorServiceProvider(), [
    'validator.mapping.class_metadata_factory' =>
        new \Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory(
            new \Symfony\Component\Validator\Mapping\Loader\AnnotationLoader(new AnnotationReader()),
            new \Symfony\Component\Validator\Mapping\Cache\DoctrineCache($cacheDriver)
        )
]);

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

    $app['validator']->validate($foo);
    $json = $app['serializer']->serialize($foo, 'json');

    return new \Symfony\Component\HttpFoundation\JsonResponse($json, \Symfony\Component\HttpFoundation\Response::HTTP_OK, [], true);
});

$app->error(function (\Exception $e, \Symfony\Component\HttpFoundation\Request $request, $code) {
    return new \Symfony\Component\HttpFoundation\Response('We are sorry, but something went terribly wrong.' . $e->getMessage());
});

$app->run();

After running this example you get fatal errors. Can anyone confirm that I'm not making a hard mistake here?

Currently my workaround for this is rewrite the DoctrineCache class making use of a namespace for the cache keys. Its working, but I think its ugly.

like image 251
jlHertel Avatar asked Feb 06 '17 18:02

jlHertel


1 Answers

I think what you need to do is two separate CacheDrivers. See https://github.com/doctrine/cache/blob/master/lib/Doctrine/Common/Cache/CacheProvider.php for how namespaces are used there.

You could:

$validatorCacheDriver = new MemcacheWrapper();
$validatorCacheDriver->setMemcache($memcache);
$validatorCacheDriver->setNamespace('symfony_validator');

$serializerCacheDriver = new MemcacheWrapper();
$serializerCacheDriver->setMemcache($memcache);
$serializerCacheDriver->setNamespace('symfony_serializer');

// note that the two drivers are using the same memcache instance, 
// so only one connection will be used.

$app['serializer.normalizers'] = function () use ($app, $serializerCacheDriver) {
    $classMetadataFactory = new Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory(
        new Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader(new AnnotationReader()), $serializerCacheDriver);

    return [new Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer($classMetadataFactory) ];
};

$app->register(new Silex\Provider\ValidatorServiceProvider(), [
    'validator.mapping.class_metadata_factory' =>
        new \Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory(
            new \Symfony\Component\Validator\Mapping\Loader\AnnotationLoader(new AnnotationReader()),
            new \Symfony\Component\Validator\Mapping\Cache\DoctrineCache($validatorCacheDriver)
        )
]);

I've trimmed the code to only show the parts that play some part in the solution. I hope this helps!

like image 147
jkrnak Avatar answered Nov 12 '22 09:11

jkrnak