2
votes

I'm trying to get the following working:

I've got an entity like:

<?php

namespace AppBundle\Entity;

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

/**
 * Contact
 *
 * @ORM\Table()
 * @ORM\Entity()
 */

class Contact
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;


    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\ServiceClient", inversedBy="contacts")
     * @ORM\JoinColumn(name="service_client", referencedColumnName="service_client")
     *
     * @JMS\Type("AppBundle\Entity\ServiceClient")
     * @JMS\SerializedName("serviceClient")
     */
    private $serviceClient;

}

I'm sending the following JSON over an HTTP request (Post, it's a new Contact, no ID):

{
 "name": "Lorem Ipsum",
 "serviceClient": {"service_client": "ipsum"}
}

What I expect is for the JMS Serializer to parse that relationship, and leting me persist the Contact object like this:

<?php
$contact = $this->get('serializer')->deserialize(
    $request->getContent(),
    Contact::class, 'json'
);
$this->em->persist($contact);
$this->em->flush();

In fact I got that working (I swear it was working) but now it's giving me the follwing error:

A new entity was found through the relationship 'AppBundle\Entity\Contact#serviceClient' that was not configured to cascade persist operations for entity: AppBundle\Entity\ServiceClient@000000006fafb93e00007f122bd10320. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={\"persist\"}). If you cannot find out which entity causes the problem implement 'AppBundle\Entity\ServiceClient#__toString()' to get a clue."

So it's tryign to persist the entity... a thing I do not want since the entity already exists. I just want Doctrine to put the reference, the foreign key.

Edit: It seems it's the constructor, if I set it to the doctrine_object_constructor it works like magic, the thing I do not understand is why it stop working in the first place.

Can anyone share any ideas or a cleaner way to do what I did?

    jms_serializer.object_constructor:
    alias: jms_serializer.doctrine_object_constructor
    public: false
2
When an entity gets serialized, itself and it's related entities get detached from the EntityManager, so when you deserialize it and try to persist, all those related entities will be treated a new entities. You need to "merge" them back to the EntityManager (doctrine-project.org/projects/doctrine-orm/en/2.7/cookbook/…).Quiquetas

2 Answers

0
votes

This problem happens when Doctrine cannot map your relationship to an existing record in the database, so it will try to create a new one with the data from the JSON object.

In your case, the JSON object: {"service_client": "ipsum"} cannot be mapped to an existing ServiceClient instance.

It's because the default JMS object constructor call the unserialize function (will be the one from your Entity if you defined this method) to construct the object, which mean this object will always be treated by Doctrine as new (has never been persisted).

By using doctrine_object_constructor, JMS will get the object from Doctrine. The object came from Doctrine not only have the attributes and methods you define in your entity, but also meta-data about whether it's an existing one, it's corresponding row from the database ( so Doctrine can detect update made on the record later and handle it), therefore Doctrine are able to avoid incorrect persisting.

-1
votes

Doctrine will try to persist the Contact with a reference of a ServiceClient entity given in the deserialization. In the entity definition at the level of the manyToOne definition you need to add :

@ORM\ManyToOne(targetEntity="AppBundle\Entity\ServiceClient", inversedBy="contacts", cascade={"persist"})