dongpan1871 2016-04-20 09:09
浏览 24
已采纳

JMSSerializerBundle与抽象类 - Symfony2

I want to serialize and deserialize an entity with its dependancies but I am unable to serialize elements that concern an abstract class.

Hierarchy :

Test --> several Calls where Call class is an abstract class and is extended by TestCallExecuteQuery (same issue with $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;

So I followed instructions found over Internet :

  • @JMSSer\Expose annotation with @JMSSer\ExclusionPolicy("all") in every class
  • @JMSSer\Discriminator annotation on top of abstract class TestCall to map with extender class (TestcallExecuteQuery)

But.. When I serialize I get only type property of TestCall but not query or return property defined in TestCallExecuteQuery :

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

I know it's possible because I got them ONCE but I was unable to reproduce that even by turning back the clock..

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

EDIT :

Ok I probably got query and return by changing in Test.php :

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

To :

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

What am I doing wrong ?

  • 写回答

1条回答 默认 最新

  • douyin4875 2016-04-21 10:12
    关注

    Ok so ! I found a solution after losing my mind during several days !

    The solution is to create two event listeners PreSerialize and PostSerialize.

    First of all I removed this part in TestCall.php (Abstract Class) :

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

    And added these annotations in TestCallExecuteQuery.php (extender class) :

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

    My listener looks like that :

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

    Declaration of listener :

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

    I explain a little :

    1. PreSerailize Event

    If object to serialize is a subclass of my Abstract class (TestCall in my case) I have to force the type of the event object to be serialized into the concerned subclass (TestCallExecuteQuery in my case). In fact, right object (TestCallExecuteQuery) is passed but it's mapped with its parent class (Abstract class TestCall)

    Dump of $event object :

    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 Event

    If serialized object is my child class I add a visitor property (not a virtual even custom)..

    NB : JMSSerializeBundle does not serialize "virtual" property directly added to class for example by a method like that :

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

    Maybe Discriminator/Virtual Property are added like that in JMSSerializerBundle and that's why they are not serialized.. Don't really know.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 关于#.net#的问题:End Function
  • ¥15 无法import pycausal
  • ¥15 VS2022创建MVC framework提示:预安装的程序包具有对缺少的注册表值的引用
  • ¥15 weditor无法连接模拟器Local server not started, start with?
  • ¥20 6-3 String类定义
  • ¥15 嵌入式--定时器使用
  • ¥20 51单片机学习中的问题
  • ¥30 Windows Server 2016利用兩張網卡處理兩個不同網絡
  • ¥15 Python中knn问题
  • ¥15 使用C#,asp.net读取Excel文件并保存到Oracle数据库