Configurando serializer com annotation e cache no silex

Por Jean Hertel, 07/02/2017

symfony , silex , serializer , cache

Olá leitores,

Hoje precisei transformar alguns objetos em json para retornar através de de uma requisição web com o framework Silex. Como sofri bastante para configurar e descobrir como fazer o cache corretamente, decidi escrever este post na esperança de auxiliar outros que se aventurem por esse caminho.

O primeiro passo antes de mais nada é ter uma aplicação Silex rodando e funcional. Após fazer isso, você pode baixar a biblioteca e começar a serializar os objetos.

Existem duas bibliotecas muito boas para a serialização de objetos em geral. A primeira delas é o JMSSerializer, que é a mais completa e possui todo tipo de anotação. A segunda biblioteca é o componente de serialização do Symfony. Para ambos os casos existem providers disponiveis, porem o serializer do Symfony já vem pré-configurado no Silex, bastando apenas adicionar a dependencia. Para detalhes de como configurar o provider veja este link.

Após instalar a dependencia, você precisa configurá-la. No site do silex a sugestão é:

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

Com este código o serviço $app['serializer'] fica disponível. Não vou me ater ao uso do serializador, mas apenas ao detalhe de que ele suporta annotations.

Para configurar as annotations, é necessário adicionar um leitor de annotations na classe que vai extrair os metadados do objeto. Normalmente você vai querer extrair os metadados através dos getters e setters ou através das propriedades da classe. Para essa finalidade você pode usar respectivamente para get/set e para propriedades os leitores Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer e Symfony\Component\Serializer\Normalizer\PropertyNormalizer.

Para cada uma dessas classes é possível passar uma instancia de Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface. É esta instancia que podemos manipular para ler annotations:

<?php
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use \Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use \Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use \Doctrine\Common\Annotations\AnnotationReader;

$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$meuSerializerNinja = new GetSetMethodNormalizer($classMetadataFactory);

Agora que temos o serializer precisamos apenas registrá-lo junto ao container:

<?php
$app['serializer.normalizers'] = function () use ($meuSerializerNinja) {
    return [$meuSerializerNinja];
};

Por fim, se você quiser adicionar cache ao leitor de anotações, você pode fazer isso passando uma instancia de Doctrine\Common\Cache\Cache como segundo parâmetro do construtor de ClassMetadataFactory;

Exemplo completo e funcional:

<?php
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->get('/', function(\Silex\Application $app) {
    $foo = new Foo();

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

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


$app->run();

Espero que tenha ajudado vocês. Abraços.