26

Problem:

When serializing a collection of Doctrine enitities the collection will still have 2 items though the items are empty.

Background:

I have a few entities which extends each other B extends A and C extends B. In the entity Test I have an array with objects of the type B. $test will have the expected values (collection with two items) at the moment of serialization.

$test contains a variable (array) collection one of the items in the array is of the type B and one of type C.

$sTest will get the collection of two items though the items are empty. This is how the string in $sTest lookslike after the serialization of $test "{"collection":[[],[]]}"

Test script:

$test = new Test();

$b = new B();
$b->setToken('asdf');
$b->setName('asdf');

$c = new C();
$c->setToken('asdf');
$c->setName('asdf');
$c->setDescription('asdf');

$test->addCollection($b);
$test->addCollection($c);

//Serialize
$serializer = $this->container->get('serializer');
$sTest = $serializer->serialize($test, 'json');

//Deserialize
$deserializer = $this->container->get('serializer');
$dTest = $deserializer->deserialize($sTest, 'Acme\DemoBundle\Entity\Test', 'json');

$em = $this->getDoctrine()->getManager();

$em->merge($dTest);
$em->flush();

A:

<?php

namespace Acme\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
 * @ORM\Entity
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap({"a" = "Acme\DemoBundle\Entity\A", "b" = "Acme\DemoBundle\Entity\B", "c" = "Acme\DemoBundle\Entity\C"})
 * 
 * @JMS\ExclusionPolicy("None")
 * @JMS\Discriminator(field = "type", map = {
 *          "a": "Acme\DemoBundle\Entity\A",
 *          "b": "Acme\DemoBundle\Entity\B",
 *          "c": "Acme\DemoBundle\Entity\C", * 
 *  })
 */
class A {

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

    /**
     * @ORM\Column(type="string", length=100)
     */
    protected $token;

    public function setToken($token){
        $this->token = $token;
    }    

    /**
     * @JMS\VirtualProperty
     * @JMS\SerializedName("type")
     */
    public function getDiscr()
    {
        return 'a';
    }

}

B:

<?php

namespace Acme\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;

/**
 * @ORM\Entity
 * @JMS\ExclusionPolicy("None")
 */
class B extends A {

    /**
     * @ORM\Column(type="string", length=100)
     */
    protected $name;

    /**
     * @ORM\ManyToOne(targetEntity="Acme\DemoBundle\Entity\Test", inversedBy="collection")
     * @ORM\JoinColumn(name="TestId", referencedColumnName="id")
     */
    private $test;

    public function setName($name) {
        $this->name = $name;
    }

    /**
     * @JMS\VirtualProperty
     * @JMS\SerializedName("type")
     */
    public function getDiscr() {
        return 'b';
    }


    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set token
     *
     * @param string $token
     * @return B
     */
    public function setToken($token)
    {
        $this->token = $token;

        return $this;
    }

    /**
     * Get token
     *
     * @return string 
     */
    public function getToken()
    {
        return $this->token;
    }

    /**
     * Set test
     *
     * @param \Acme\DemoBundle\Entity\Test $test
     * @return B
     */
    public function setTest(\Acme\DemoBundle\Entity\Test $test = null)
    {
        $this->test = $test;

        return $this;
    }

    /**
     * Get test
     *
     * @return \Acme\DemoBundle\Entity\Test 
     */
    public function getTest()
    {
        return $this->test;
    }
}

C:

<?php

namespace Acme\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;

/**
 * @ORM\Entity
 * @JMS\ExclusionPolicy("None")
 */
class C extends B {

    /**
     * @ORM\Column(type="text")
     */
    protected $description;

    public function setDescription($description) {
        $this->description = $description;
    }

    /**
     * @JMS\VirtualProperty
     * @JMS\SerializedName("type")
     */
    public function getDiscr() {
        return 'c';
    }

}

Test:

<?php

namespace Acme\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;

/**
 * @ORM\Entity
 */
class Test {

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

    /**
     * @ORM\OneToMany(targetEntity="Acme\DemoBundle\Entity\B", mappedBy="test", cascade={"all"})
     * @JMS\Type("ArrayCollection<'Acme\DemoBundle\Entity\B'>")
     */
    private $collection;


    /**
     * Constructor
     */
    public function __construct()
    {
        $this->collection = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Add collection
     *
     * @param \Acme\DemoBundle\Entity\B $collection
     * @return Test
     */
    public function addCollection(\Acme\DemoBundle\Entity\B $collection)
    {
        $this->collection[] = $collection;

        return $this;
    }

    /**
     * Remove collection
     *
     * @param \Acme\DemoBundle\Entity\B $collection
     */
    public function removeCollection(\Acme\DemoBundle\Entity\B $collection)
    {
        $this->collection->removeElement($collection);
    }

    /**
     * Get collection
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getCollection()
    {
        return $this->collection;
    }
}
7
  • Is this a doctrine or symfony problem? sound like a problem with JMS serializer... It's unclear to me what your exact problem is. What output are you getting, and what are you expecting? Commented Aug 12, 2014 at 13:47
  • @NDM You are right. The problem is actually the JMS serializer. I added some extra text to clarify what I serialize and what I get back as JSON. Commented Aug 12, 2014 at 14:00
  • 1
    is this quoting for the Test::$collection property correct? @JMS\Type("ArrayCollection<'Acme\DemoBundle\Entity\B'>") I'm finding example without the single quotes? Commented Aug 12, 2014 at 14:04
  • I Think there's also a bugreport on this issue? Commented Aug 12, 2014 at 14:05
  • @NDM tried it without quotes result is the same. The issue you mention could be related though is a bit different i guess because I don't get any of the values of B nor C in the collection. As I understand correctly the issue reported at least gets some variable returned in $elements Commented Aug 12, 2014 at 14:33

2 Answers 2

1

Incorrect annotation for Test::$collection

As pointed out by NDM, the annotation for Test::$collection is not correct, you need to omit the quotes when referencing the type:

diff --git a/src/Test.php b/src/Test.php
index c0da0c3..a5ea94e 100644
--- a/src/Test.php
+++ b/src/Test.php
@@ -19,7 +19,7 @@ class Test {

     /**
      * @ORM\OneToMany(targetEntity="Acme\DemoBundle\Entity\B",     mappedBy="test", cascade={"all"})
-     * @JMS\Type("ArrayCollection<'Acme\DemoBundle\Entity\B'>")
+     * @JMS\Type("ArrayCollection<Acme\DemoBundle\Entity\B>")
      */
     private $collection;

For reference, see http://jmsyst.com/libs/serializer/master/reference/annotations#type.

Missing annotations for A::$token and B::$name

Attempting to serialize after fixing the annotation for Test::$collection results in the following exceptions being thrown

JMS\Serializer\Exception\RuntimeException: 
You must define a type for Acme\DemoBundle\Entity\B::$name.

and

JMS\Serializer\Exception\RuntimeException: 
You must define a type for Acme\DemoBundle\Entity\A::$token.

Adding the missing annotation for A::$token:

diff --git a/src/A.php b/src/A.php
index eb89b36..f806581 100644
--- a/src/A.php
+++ b/src/A.php
@@ -29,6 +29,7 @@ class A {

     /**
      * @ORM\Column(type="string", length=100)
+     * @JMS\Type("string")
      */
     protected $token;

and for B::$name:

diff --git a/src/B.php b/src/B.php
index 71a8b0b..7b448c6 100644
--- a/src/B.php
+++ b/src/B.php
@@ -13,6 +13,7 @@ class B extends A {

     /**
      * @ORM\Column(type="string", length=100)
+     * @JMS\Type("string")
      */
     protected $name;

solves the issue and given your script from above, $test can be successfully serialized to

{
  "collection":[
    {
      "type":"b",
      "token":"asdf",
      "name":"asdf"
    },
    {
      "type":"c",
      "token":"asdf",
      "name":"asdf",
      "description":"asdf"
    }
  ]
}
Sign up to request clarification or add additional context in comments.

Comments

0

For a somewhat different requirement (JSON) I've created a base entity and implemented the behavior as follows:

function jsonSerialize()
{
    $arr = get_object_vars($this);
    foreach ($arr as $key => $var) {
        unset($arr[$key]);
        if (method_exists($var, 'jsonSerialize')) {
            $var = $var->jsonSerialize();
        } elseif ( $var instanceof \DateTime) {
            $var = $var->format('Y-m-d\TH:i:s\Z');
        }
        $arr[strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $key))] = $var;
    }
    return $arr;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.