Добавьте дополнительные поля с помощью пакета JMS Serializer.

У меня есть объект, который я обычно сериализую с помощью пакета JMS Serializer. Я должен добавить к сериализации некоторые поля, которые не находятся в самом объекте, но собираются с некоторыми запросами к БД.

Моя идея состояла в том, чтобы создать пользовательский объект, заполнить поля полями сущности и добавить пользовательский. Но это кажется немного сложным и дорогим для каждой вариации (я использую много групп сериализации) класса.

Есть ли лучший/стандартный способ сделать это? Использование фабрики? События до/после сериализации?

Может быть, я могу прослушать сериализацию и проверить тип объекта, а группы сериализации добавить настраиваемые поля? Но вместо того, чтобы делать запрос для каждой сущности, было бы лучше собрать все данные связанных сущностей, а затем добавить их к ним. Любая помощь приветствуется


person alex88    schedule 21.02.2013    source источник


Ответы (6)


Я нашел решение сам,

чтобы добавить настраиваемое поле после завершения сериализации, мы должны создать класс слушателя следующим образом:

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

Таким образом, вы можете добавлять данные в сериализованный элемент.

Вместо этого, если вы хотите отредактировать объект непосредственно перед сериализацией, используйте событие pre_serialize, имейте в виду, что у вас уже должна быть переменная (и правильные группы сериализации), если вы хотите использовать pre_serialize для добавления значения.

person alex88    schedule 21.05.2013
comment
Выяснил сам, делай это в pre_serialize, а не публикуй. - person Geshan; 15.08.2013
comment
@Гешан ага! Извините за задержку - person alex88; 17.08.2013
comment
Что делать, если дополнительный ключ должен быть открыт для определенных групп? Можно ли добавить свойство @JMS\Groups через этот прослушиватель событий? - person werd; 01.10.2013
comment
@werd Я не совсем уверен в этом, вам нужно выяснить, есть ли у посетителя или чего-либо, что вы можете получить от события, список групп сериализации. В противном случае вы можете использовать предварительную сериализацию и установить поле уже внутри класса, который вы сериализуете, и поместить туда групповую логику. - person alex88; 01.10.2013
comment
Я нашел @VirtualProperty, но почему-то он не работает. Какие-либо предложения? - person werd; 01.10.2013
comment
Как получить данные объекта? Есть что-нибудь вроде -›getData? - person iBet7o; 03.10.2013
comment
@ iBet7o не после сериализации, а только при предварительной сериализации, где вы можете изменить данные объекта и получить полный объект - person alex88; 03.10.2013
comment
Обратите внимание, что это не работает для XmlSerializationVisitor - person Touki; 09.01.2014
comment
Обратите внимание, что это работает только для некоторых посетителей, а именно для подклассов GenericSerializationVisitor. - person apfelbox; 02.10.2014
comment
имейте в виду, что addData устарел, а setData имеет другой контекст в вызовах до и после сериализации (pre — это данные родительского объекта, а post — данные объекта) - person pscheit; 28.06.2018

Я удивлен, почему никто не предложил гораздо более простой способ. Вам нужно просто использовать @VirtualProperty:

<?php

// ...
/**
 * @JMS\VirtualProperty
 * @JMS\SerializedName("someField")
 */
public function getSomeField()
{
    return $this->getTitle() . $this->getPromo();
}
person James Akwuh    schedule 29.08.2015
comment
Кажется, это классный способ сделать это, но мне нужно иметь сгенерированный маршрут в моем сериализованном объекте, поэтому мне нужно иметь контейнер в сущности, но это кажется плохой практикой... Любые советы? - person Kaz; 09.09.2015
comment
@Kaz, тогда вам нужно создать собственный Сервис для этой цели. Конечно, использование контейнера вне модели — плохая практика из-за разделения задач. - person James Akwuh; 09.09.2015

Для дальнейшего ответа на исходный вопрос. Вот как вы ограничиваете добавленные данные для некоторых сериализованных групп (в этом примере some_data добавляется только тогда, когда мы не используем группу 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') содержит массив используемых сериализованных групп.

person Petter Kjelkenes    schedule 24.08.2014
comment
Несколько обновлений для вашего отличного ответа: $visitor = $event->getVisitor(); $attributes = $event->getContext()->attributes; $groups = $attributes-›get('groups'); if ($groups instanceof Some) { if (in_array(Track::GROUP_TRACK_URL, $groups-›get(), true)) { $visitor-›addData('url', 'TODO добавить url'); } } - person borN_free; 27.04.2015

Принятый ответ работает только тогда, когда посетитель происходит от \JMS\Serializer\GenericSerializationVisitor. Это означает, что он будет работать для JSON, но не для XML.

Вот пример метода, который справится с XML. Он смотрит на интерфейсы, поддерживаемые объектом посетителя, и действует соответствующим образом. Он показывает, как вы можете добавить элемент ссылки как к сериализованным объектам JSON, так и к 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);
    }


}
person Paul Dixon    schedule 14.10.2014

Как насчет этого: http://jmsyst.com/libs/serializer/master/handlers

Таким образом, вы определяете класс, который получает объект и возвращает текст или массив (который будет преобразован в json).

У вас есть класс IndexedStuff, который содержит странное вычисляемое поле, которое по какой-то причине должно вычисляться во время сериализации.

<?php

namespace Project/Model;

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

Теперь создайте обработчик

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

Наконец зарегистрируйте сервис

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

Теперь везде, где вы хотите сериализовать объект типа «IndexedStuff», вы получите такой json

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

Таким образом, вы можете полностью настроить сериализацию вашего объекта.

person Álvaro García    schedule 13.06.2016

addData устарел, начиная с версии 2.0.0, поэтому нам нужно сделать это так:

use JMS\Serializer\EventDispatcher\ObjectEvent;

class MySerializerHandler {

    public function onPostSerialize(ObjectEvent $event)
    {
        /** @var MySpecialObjectType $object */
        $myObject = $event->getObject();

        $key = 'customDataKey';
        $value = 'myvalue';

        $event->getVisitor()->visitProperty(
            new StaticPropertyMetadata('', $key, $value),
            $value
        );
    }
}

services.yaml

services:
    MySerializerHandler:
        tags:
          - { name: jms_serializer.event_listener, class: 'MySpecialObjectType', event: serializer.post_serialize, method: 'onPostSerialize' }

https://github.com/schmittjoh/serializer/blob/c9c82c841b8ebe682ca44972d64fded215f72974/UPGRADING.md#from-1130-to-200

person pscheit    schedule 03.03.2020