Deserializando collections no silex

Por Jean Hertel, 06/04/2017, na categoria Dicas

collections, Serializer, Silex, Symfony

Hoje tive que transformar um JSON complexo em objetos e sofri bastante para descobrir a solução. O JSON em questão era algo parecido com isto:

{
    "name": "author name",
    "posts": [
        {
            "title": "some post 1"
        },
        {
            "title": "some post 2"
        }
    ]
}

As classes por sua vez:

<?php

class Author
{
    private $name;
    private $posts;

    public function __construct()
    {
        $this->posts = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    public function getPosts()
    {
        return $this->posts;
    }

    public function setPosts(array $posts)
    {

        $this->posts->clear();

        foreach ($posts as $post) {
            $this->addPosts($post);
        }
    }

    public function addPosts(Post $post)
    {
        $this->posts->add($post);
    }

    public function removePosts(Post $post)
    {
        $this->posts->removeElement($post);
    }
}

class Post
{
    private $title;

    public function getTitle()
    {
        return $this->title;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }
}

Mas como fazemos isso? Se você ainda não leu meu post sobre como configurar o serializer, leia aqui.

Se você configurou assim como eu fiz no outro post, apenas será utilizado get e set para carregar as propriedades das classes. Para ser possível deserializar arrays é preciso configurar um Symfony\Component\Serializer\Normalizer\ArrayDenormalizer.

Mas só isso não é o bastante, pois a coleção vai ser deserializada e repassada como um array. Para detectar corretamente o nosso objeto, precisamos informar ao serializador o seu tipo. Isto pode ser feito configurando um Symfony\Component\Serializer\Normalizer\ObjectNormalizer com um Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor.

Um jeito rápido de fazer isso é pegar o exemplo da outra página e alterar para ficar desta forma:

<?php
$app['serializer.normalizers'] = function () use ($app) {
    $propertyInfo = new \Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor();

    return [
        new \Symfony\Component\Serializer\Normalizer\ArrayDenormalizer(),
        new \Symfony\Component\Serializer\Normalizer\ObjectNormalizer(null, null, null, $propertyInfo)
    ];
};

A classe ObjectNormalizer lê as propriedades dos objetos e chama os seus getters e setters. Com o auxilio da classe ReflectionExtractor ela consegue saber os tipos de objetos. Mas como os tipos são descobertos? O importante aqui é ter os métodos addPosts e removePosts recebendo como parâmetro o objeto com o seu type hint correto. Desta forma o extrator consegue identificar qual objeto deve deserializar.

Um exemplo completo e funcional está disponivel neste repositório.

Um último detalhe: a classe ReflectionExtractor necessita das dependencias symfony/property-info e symfony/property-access.