JMSSerializerBundle с абстрактным классом — Symfony2

Я хочу сериализовать и десериализовать объект с его зависимостями, но я не могу сериализовать элементы, относящиеся к абстрактному классу.

Иерархия:

Тест -> несколько Calls, где класс Call является абстрактным классом и расширяется с помощью TestCallExecuteQuery (та же проблема с $conditions)

Test.php:

/**
 * @ORM\Entity(repositoryClass="Gedmo\Sortable\Entity\Repository\SortableRepository")
 * @ORM\Table(name="cfa_test")
 * @JMSSer\ExclusionPolicy("all")
 */
class Test
{

    /**
     * @ORM\OneToMany(targetEntity="TestCall", mappedBy="test", cascade={"all"}, orphanRemoval=true)
     * @JMSSer\Expose
     * @JMSSer\Groups({"export"})
     * @JMSSer\Type("ArrayCollection<App\Bundle\CapFileAnalyzerBundle\Entity\TestCall>")
     */
    protected $calls;

    /**
     * @ORM\OneToMany(targetEntity="TestCondition", mappedBy="test", cascade={"all"}, orphanRemoval=true)
     * @JMSSer\Expose
     * @JMSSer\Groups({"export"})
     * @JMSSer\Type("ArrayCollection<App\Bundle\CapFileAnalyzerBundle\Entity\TestCondition>")
     */
    protected $conditions;

TestCall.php:

/**
 * @ORM\Entity
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\Table(name="cfa_test_call")
 * @ORM\DiscriminatorColumn(name="type", type="string")
 * @ORM\DiscriminatorMap({
 *      "executeQuery" = "App\Bundle\CapFileAnalyzerBundle\Entity\TestCallExecuteQuery",
 *      "call" = "App\Bundle\CapFileAnalyzerBundle\Entity\TestCall"
 * })
 * @JMSSer\ExclusionPolicy("all")
 * @JMSSer\Discriminator(field="serializedType", map={
 *      "executeQuery"="App\Bundle\CapFileAnalyzerBundle\Entity\TestCallExecuteQuery",
 *      "call" = "App\Bundle\CapFileAnalyzerBundle\Entity\TestCall"
 * })
 */
abstract class TestCall
{
    /**
     * @JMSSer\Expose
     * @JMSSer\Groups({"export"})
     */
    protected $type = 'call';

    /**
     * @ORM\Id
     * @ORM\Column(name="id", type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\ManyToOne(targetEntity="Test", inversedBy="calls")
     */
    protected $test;
  /**
     * @JMSSer\VirtualProperty()
     * @JMSSer\SerializedName("serializedType")
     */
    public function getDiscr()
    {
        return $this->type;
    }

TestCallExecuteQuery.php:

/**
 * @ORM\Entity
 * @JMSSer\ExclusionPolicy("all")
 */
class TestCallExecuteQuery extends TestCall
{

    protected $type = 'executeQuery';

    /**
     * @ORM\Column(name="`query`", type="text")
     * @JMSSer\Expose
     * @JMSSer\Groups({"export"})
     */
    protected $query;

    /**
     * @ORM\Column(name="`return`", type="string", nullable=true)
     * @JMSSer\Expose
     * @JMSSer\Groups({"export"})
     */
    protected $return;

Поэтому я следовал инструкциям, найденным в Интернете:

  • @JMSSer\Expose аннотация с @JMSSer\ExclusionPolicy("all") в каждом классе
  • @JMSSer\Discriminator аннотация поверх абстрактного класса TestCall для сопоставления с классом-расширителем (TestcallExecuteQuery)

Но.. Когда я сериализую, я получаю только свойство типа TestCall, но не свойство query или return, определенное в TestCallExecuteQuery:

{"tests":[{"calls":[{"type":"executeQuery"},{"type":"executeQuery"}], ... }

Я знаю, что это возможно, потому что я получил их ОДИН РАЗ, но я не смог воспроизвести это, даже повернув время вспять..

{"tests":[{"calls":[{"query":"SELECT * FROM table","return":"return_1"}], ... }

ИЗМЕНИТЬ:

Хорошо, я, вероятно, получил query и return, изменив Test.php :

/**
 * @JMSSer\Type("ArrayCollection<App\Bundle\CapFileAnalyzerBundle\Entity\TestCall>")
 */
    protected $calls;

To :

/**
 * @JMSSer\Type("ArrayCollection<App\Bundle\CapFileAnalyzerBundle\Entity\TestCallExecuteQuery>")
 */
    protected $calls;

Что я делаю не так ?


person Delphine    schedule 20.04.2016    source источник


Ответы (1)


Итак ! Я нашел решение, потеряв рассудок в течение нескольких дней!

Решение состоит в том, чтобы создать два прослушивателя событий PreSerialize и PostSerialize.

Прежде всего, я удалил эту часть в TestCall.php (абстрактный класс):

/**
 * @JMSSer\VirtualProperty()
 * @JMSSer\SerializedName("serializedType")
 */
public function getDiscr()
{
    return $this->type;
}

И добавил эти аннотации в TestCallExecuteQuery.php (класс расширения):

/**
 * @JMSSer\Type("string")
 * @JMSSer\Expose
 * @JMSSer\Groups({"export"})
 */
protected $type = 'executeQuery';

Мой слушатель выглядит так:

<?php

namespace App\Bundle\CapFileAnalyzerBundle\EventListener;

use JMS\Serializer\EventDispatcher\Events;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;
use JMS\Serializer\EventDispatcher\PreSerializeEvent;

class JMSSerializerListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            ['event' => Events::PRE_SERIALIZE, 'method' => 'onPreSerialize'],
            ['event' => Events::POST_SERIALIZE, 'method' => 'onPostSerialize']
        ];
    }

    /**
     * @param PreSerializeEvent $event
     */
    public function onPreSerialize(PreSerializeEvent $event)
    {
        $object = $event->getObject();

        if (is_object($object) &&
            is_subclass_of($object, 'App\Bundle\CapFileAnalyzerBundle\Entity\TestCall') &&
            get_class($object) !== $event->getType()['name']
        ) {
            $event->setType(get_class($event->getObject()));
        }
    }

    /**
     * @param ObjectEvent $event
     */
    public function onPostSerialize(ObjectEvent $event){
        $object = $event->getObject();
        if (is_object($object) &&
            is_a($object, 'App\Bundle\CapFileAnalyzerBundle\Entity\TestCallExecuteQuery')) {
            $event->getVisitor()->addData("serializedType", $object->getType());
        }
    }
}

Объявление слушателя:

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <parameters>      
        <parameter key="cfa.events.jmsserializer_listener.class">App\Bundle\CapFileAnalyzerBundle\EventListener\JMSSerializerListener</parameter>
    </parameters>

    <services>
         <service id="cfa.events.jmsserializer_listener" class="%cfa.events.jmsserializer_listener.class%">
            <tag name="jms_serializer.event_subscriber"/>
        </service>

    </services>
</container>

поясняю немного:

  1. Событие PreSerailize

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

Дамп объекта $event:

PreSerializeEvent {#977 ▼
  -object: TestCallExecuteQuery {#981 ▼
    #type: "executeQuery"
    #query: "SELECT * FROM table_name"
    #return: "return_3"
    #id: 2
    #test: Test {#948 ▶}
  }
  #type: array:2 [▼
    "name" => "App\Bundle\CapFileAnalyzerBundle\Entity\TestCall"
    "params" => []
  ]
  -context: SerializationContext {#420 ▶}
}
  1. Событие PostSerialze

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

Примечание: JMSSerializeBundle не сериализует "виртуальное" свойство, напрямую добавленное в класс, например, с помощью такого метода:

public function createProperty($name, $value) {    
     $this->{$name} = $value;    
}

Возможно, в JMSSerializerBundle добавляются дискриминаторы/виртуальные свойства, поэтому они не сериализованы. Точно не знаю.

person Delphine    schedule 21.04.2016