web-dev-qa-db-fra.com

Ajouter des champs supplémentaires à l'aide du bundle JMS Serializer

J'ai une entité que je sérialise habituellement en utilisant le bundle JMS Serializer. Je dois ajouter à la sérialisation des champs qui ne résident pas dans l'entité elle-même mais qui sont regroupés avec quelques requêtes db.

Mon idée était de créer un objet personnalisé, de remplir les champs avec les champs d'entité et d'ajouter celui personnalisé. Mais cela semble un peu délicat et coûteux à faire pour chaque variation (j'utilise beaucoup de groupes de sérialisation) de la classe.

Existe-t-il un moyen meilleur/standard de procéder? Vous utilisez une usine? Événements pré/post-sérialisation?

Peut-être puis-je écouter le type d'entité de sérialisation et de vérification et les groupes de sérialisation ajouter les champs personnalisés? Mais au lieu de faire une requête pour chaque entité, il serait préférable de rassembler toutes les données des entités liées, puis de les y ajouter. Toute aide est appréciée

36
alex88

J'ai trouvé la solution par moi-même,

pour ajouter un champ personnalisé une fois la sérialisation terminée, nous devons créer une classe d'écoute comme celle-ci:

<?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');
    }
}

De cette façon, vous pouvez ajouter des données à l'élément sérialisé.

Au lieu de cela, si vous souhaitez modifier un objet juste avant la sérialisation, utilisez l'événement pre_serialize, sachez que vous devez déjà avoir une variable (et les bons groupes de sérialisation) si vous souhaitez utiliser pre_serialize pour ajouter une valeur.

69
alex88

Je m'étonne que personne n'ait proposé une solution beaucoup plus simple. Vous avez juste besoin d'utiliser @VirtualProperty:

<?php

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

Pour répondre davantage à la question d'origine. Voici comment limiter les données ajoutées pour certains groupes sérialisés (dans cet exemple some_data n'est ajouté que lorsque nous n'utilisons pas le groupe 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') contient un tableau des groupes sérialisés utilisés.

10
Petter Kjelkenes

La réponse acceptée ne fonctionne que lorsque le visiteur est dérivé de \JMS\Serializer\GenericSerializationVisitor. Cela signifie que cela fonctionnera pour JSON, mais échouera pour XML.

Voici un exemple de méthode qui s'adaptera à XML. Il examine les interfaces prises en charge par l'objet visiteur et agit de manière appropriée. Il montre comment vous pouvez ajouter un élément de lien aux objets sérialisés JSON et 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

Qu'en est-il de cela: http://jmsyst.com/libs/serializer/master/handlers

En résumé, vous définissez une classe qui reçoit un objet et renvoie du texte ou un tableau (qui sera converti en json).

Vous avez la classe "IndexedStuff" qui contient un champ calculé étrange qui, pour une raison quelconque, devrait être calculé au moment de la sérialisation.

<?php

namespace Project/Model;

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

Créez maintenant le gestionnaire

<?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
    }
}

Enregistrez enfin le service

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

Maintenant, partout où vous voulez sérialiser un objet de type "IndexedStuff", vous obtiendrez un json comme celui-ci

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

De cette façon, vous pouvez entièrement personnaliser la façon dont votre entité est sérialisée

3
Álvaro García