2
votes

I am using JMSSerializerBundle to serialize my entities to json and deserialize json into entities, but I think this question applies for any deserialization techniques.

For example, this schema:

class Order
{
    private $id;

    /**
     * @Serializer\Type("ArrayCollection<MyBundle\Entity\Order\Item>")
     * @ORM\OneToMany(targetEntity="\MyBundle\Entity\Order\Item", mappedBy="order", cascade={"persist"})
     */
    private $items;
}

class Item
{

    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="\MyBundle\Entity\Order", inversedBy="items")
     */
    private $order;

    /**
     * @var integer $amount
     * @Serializer\Type("integer")
     * @ORM\Column(name="amount", type="integer")
     */
    private $amount;

}

Maps to this json: {"id":1,"items":[{"id":1,"amount":100}, {"id":2,"amount":200}]} and the same json is properly deserialized into an object of type MyBundle:Order that has a colletion of two MyBundle:Order/Item objects.

The problem is that when I try to persist this object, new entries are created in the database, rather than updating existing, ignoring the ids. How do I tell entity manager that theses objects should be updated, rather that created?

Update. Generally EntityManager::merge solution (as suggested by DaveM) is fine. But you must only merge existing objects. For example, if you have a json that represents a new Order entity that is connected to existing Order\Item entities

{"id":null,"items":[{"id":1,"amount":100}, {"id":2,"amount":200}]}

In this case you cannot just merge an Order object like this: $em->merge($order), because order is a new entity and entity manager will attempt to find an Order object with id = null and you will end up with a new Order and empty items array. So the solution is to loop the Order::$items array and merge each item individually. Then a new order will be created and connected with existing items.

2

2 Answers

6
votes

You need to use the merge() method on the EntityManager as merging entities refers to the merging of entities into the context of an EntityManager so that they can become managed again. In order to merge the state of an entity into an EntityManager use the EntityManager#merge($entity) method. The state of the passed entity will be merged into a managed copy of this entity and this copy will subsequently be returned.

$detachedEntity = unserialize($serializedEntity); 
$entity = $em->merge($detachedEntity);

Also be sure to note when you want to serialize/unserialize entities you have to make all entity properties protected, never private. The reason for this is, if you serialize a class that was a proxy instance before, the private variables won’t be serialized and a PHP Notice is thrown.

More information can be found in the doctrine documentation here:

http://doctrine-orm.readthedocs.org/en/2.0.x/reference/working-with-objects.html#merging-entities

2
votes

I know this question is three years old, but it mislead me to think the only answer was using the merge operation. I'd like to add my two cents:

The JMSSerializerBundle includes an object constructor for Doctrine entities. When you enable this constructor, the deserialized entities are managed entities that can be persisted(with $em->persist($entity)).

Please check this comment to understand other benefits from this. And here is how you can enable it.