ti-enxame.com

Adicionar campos extras usando o pacote configurável JMS Serializer

Eu tenho uma entidade que normalmente serializo usando o pacote JMS Serializer. Eu tenho que adicionar à serialização alguns campos que não residem na própria entidade, mas são reunidos com algumas consultas de banco de dados.

Minha ideia era criar um objeto personalizado, preencher os campos com os campos de entidade e adicionar o personalizado. Mas isso parece um pouco complicado e caro para cada variação (eu uso muitos grupos de serialização) da classe.

Existe uma maneira melhor/padrão de fazer isso? Usando uma fábrica? Eventos pré/pós serialização?

Talvez eu possa ouvir a serialização e a verificação do tipo de entidade e dos grupos de serialização e adicionar os campos personalizados. Mas, em vez de fazer uma consulta para cada entidade, seria melhor reunir todos os dados das entidades relacionadas e adicioná-los a eles. Qualquer ajuda é apreciada

36
alex88

Eu encontrei a solução sozinho,

para adicionar um campo personalizado após a serialização, precisamos criar uma classe de ouvinte como esta:

<?php

namespace Acme\DemoBundle\Listener;

use JMS\DiExtraBundle\Annotation\Service;
use JMS\DiExtraBundle\Annotation\Tag;
use JMS\DiExtraBundle\Annotation\Inject;
use JMS\DiExtraBundle\Annotation\InjectParams;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Acme\DemoBundle\Entity\Team;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
use JMS\Serializer\EventDispatcher\ObjectEvent;
use JMS\Serializer\GraphNavigator;
use JMS\Serializer\JsonSerializationVisitor;

/**
 * Add data after serialization
 *
 * @Service("acme.listener.serializationlistener")
 * @Tag("jms_serializer.event_subscriber")
 */
class SerializationListener implements EventSubscriberInterface
{

    /**
     * @inheritdoc
     */
    static public function getSubscribedEvents()
    {
        return array(
            array('event' => 'serializer.post_serialize', 'class' => 'Acme\DemoBundle\Entity\Team', 'method' => 'onPostSerialize'),
        );
    }

    public function onPostSerialize(ObjectEvent $event)
    {
        $event->getVisitor()->addData('someKey','someValue');
    }
}

Dessa forma, você pode adicionar dados ao elemento serializado.

Em vez disso, se você deseja editar um objeto antes da serialização, use o evento pre_serialize, saiba que você já deve ter uma variável (e os grupos de serialização corretos) se desejar usar o pre_serialize para adicionar um valor.

69
alex88

Estou surpreso porque ninguém sugeriu uma maneira muito mais fácil. Você só precisa usar @VirtualProperty:

<?php

// ...
/**
 * @JMS\VirtualProperty
 * @JMS\SerializedName("someField")
 */
public function getSomeField()
{
    return $this->getTitle() . $this->getPromo();
}
14
James Akwuh

Para responder ainda mais à pergunta original. Aqui está como você limita os dados adicionados para alguns grupos serializados (neste exemplo some_data é adicionado apenas quando não estamos usando o grupo list:

public function onPostSerializeSomeEntityJson(ObjectEvent $event) {

    $entity = $event->getObject();

    if (!in_array('list', (array)$event->getContext()->attributes->get('groups'))) {

        $event->getVisitor()->addData('user_access', array(
            'some_data' => 'some_value'
        ));
    }
}

(array)$event->getContext()->attributes->get('groups') contém uma matriz dos grupos serializados usados.

10
Petter Kjelkenes

A resposta aceita funciona apenas quando o visitante é derivado de \JMS\Serializer\GenericSerializationVisitor. Isso significa que funcionará para JSON, mas falhará para XML.

Aqui está um exemplo de método que irá lidar com XML. Ele examina as interfaces que o objeto visitante suporta e age de maneira adequada. Mostra como você pode adicionar um elemento de link a objetos serializados JSON e XML ...

public function onPostSerialize(ObjectEvent $event)
{
    //obtain some data we want to add
    $link=array(
        'rel'=>'self',
        'href'=>'http://example.org/thing/1',
        'type'=>'application/thing+xml'
    );

    //see what our visitor supports...
    $visitor= $event->getVisitor();
    if ($visitor instanceof \JMS\Serializer\XmlSerializationVisitor)
    {
        //do XML things
        $doc=$visitor->getDocument();

        $element = $doc->createElement('link');
        foreach($link as $name => $value) {
            $element->setAttribute($name, $value);
        }
        $doc->documentElement->appendChild($element);
    } elseif ($visitor instanceof \JMS\Serializer\GenericSerializationVisitor)
    {
        $visitor->addData('link', $link);
    }


}
5
Paul Dixon

Que tal isso: http://jmsyst.com/libs/serializer/master/handlers

Em resumo, você define uma classe que recebe um objeto e retorna texto ou uma matriz (que será convertida em json).

Você tem a classe "IndexedStuff" que contém um campo calculado estranho que, por algum motivo, deve ser calculado no momento da serialização.

<?php

namespace Project/Model;

class IndexedStuff
{
   public $name;
   public $value;
   public $rawData;
}

Agora crie o manipulador

<?php

namespace Project/Serializer;

use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\GraphNavigator;
use JMS\Serializer\JsonSerializationVisitor;
use JMS\Serializer\Context;

class MyHandler implements SubscribingHandlerInterface
{
    public function setEntityManager(Registry $registry) {
         // Inject registry instead of entity manager to avoid circular dependency
         $this->em = $registry->getEntityManager();
    }
    public static function getSubscribingMethods()
    {
        return array(
            array(
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                'format' => 'json',
                'type' => 'Project/Model/IndexedStuff',
                'method' => 'serializeIndexedStuffToJson',
            ),
        );
    }

    public function serializeIndexedStuffToJson(JsonSerializationVisitor $visitor, Project/Model/IndexedStuff $stuff, array $type, Context $context)
    {
        // Build your object here and return it
        $score = $this->em->find("ProjectBundle:Calculator", $stuff->value)
        return array("score" => $score->getIndexScore(), "name"=> $score->name
    }
}

Por fim, registre o serviço

services:
  project.serializer.stuff:
      class: Project\Serializer\MyHandler
      calls:
        - [setEntityManager, ["@doctrine"]]

Agora, em todos os lugares em que você deseja serializar um objeto do tipo "IndexedStuff", você receberá um json como este

{"name": "myName", "score" => 0.3432}

Dessa forma, você pode personalizar completamente como sua entidade é serializada

3
Álvaro García