Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JMS unserialize to-many relation does not remove with orphanremoval

I have a Symfony rest api build with fos restbundle and I'm deserializing a json PUT request in order to update a doctrine entity with a to-many relation. However, the to-many child objects which are configured with orphanremoval=true does not get removed from the database when they are not present in the json data.

The PUT request payload:

{
    "id": 1,
    "name":"Some name",
    "export_destinations": [
        {
            "id": 1,
            "type": "USER_STORAGE",
            "user": {"id": 5}
        }
        {
            "id": 2,
            "type": "SYSTEM_STORAGE"
        }
    ]
}

The controller action:

 /**
  * @Rest\Put("{id}")
  * @ParamConverter(
  *     "exportJob",
  *     converter="fos_rest.request_body",
  *     options={"deserializationContext"={"groups"={"put"}}}
  * )
  * @Rest\View(serializerGroups={"details"})
  * @param ExportJob $exportJob
  * @return ExportJob
  */
public function putAction(ExportJob $exportJob)
{
    $this->getManager()->persist($exportJob);
    $this->getManager()->flush();

    return $exportJob;
}

ExportJob entity

/**
 * @ORM\Entity()
 */
class ExportJob
{
    /**
     * @var ArrayCollection|ExportDestination[]
     *
     * @ORM\OneToMany(targetEntity="ExportDestination", mappedBy="exportJob", cascade={"persist", "remove", "merge"}, orphanRemoval=true)
     */
    protected $exportDestinations;

    /**
     * @param ExportDestination $exportDestination
     * @return $this
     */
    public function addExportDestination(ExportDestination $exportDestination)
    {
        $exportDestination->setExportJob($this);
        $this->exportDestinations->add($exportDestination);

        return $this;
    }

    /**
     * @param ExportDestination $exportDestination
     * @return $this
     */
    public function removeExportDestination(ExportDestination $exportDestination)
    {
        $this->exportDestinations->removeElement($exportDestination);
        $exportDestination->setExportJob(null);

        return $this;
    }
}

JMS meta data

MyProject\ExportBundle\Entity\ExportJob:
    exclusion_policy: ALL
    properties:
        id:
            groups: ['list', 'details', 'put']
            expose: true
        name:
            groups: ['list', 'details', 'put', 'patch', 'post']
            expose: true
        exportDestinations:
            groups: ['details', 'put', 'patch', 'post']
            expose: true
            type: 'ArrayCollection<MyProject\ExportBundle\Entity\ExportDestination>'

I am using the DoctrineObjectConstructor

    jms_serializer.object_constructor:
        alias: jms_serializer.doctrine_object_constructor
        public: false

Now when I leave out the second object from the export_destinations array in the json payload my exportJob in the controller action has only one exportDestination object in the array collection after deserialization. But when I persist, I expect doctrine to remove the exportDestination from the database since I have orphanremoval=true.

What I think the problem is, is that the removeExportDestination() method never gets called during deserialization what should set the relation to null on the inversed side. If that doesn't happen it will not delete the entity since it's not become an orphan yet.

Is there a way that JMS will use the add/remove methods for ArrayCollections during deserialization?

I've also tried to use merge() instead of persist() but did not make any difference

like image 374
Bert van Nes Avatar asked Mar 30 '18 11:03

Bert van Nes


1 Answers

You are right about that "the removeExportDestination() method never gets called during deserialization".

You could define the accessor property to do what you want:

exportDestinations:
        groups: ['details', 'put', 'patch', 'post']
        expose: true
        type: 'ArrayCollection<AppBundle\Entity\ExportDestination>'
        accessor:
            getter: "getExportDestinations"
            setter: "setExportDestinations"

and in ExportJob entity:

public function getExportDestinations()
{
    return $this->exportDestinations;
}

public function setExportDestinations($exportDestinations)
{
    // first detach existing related entities.
    foreach ($this->exportDestinations as $exportDestination) {
        $exportDestination->setExportJob(null);
    }
    $this->exportDestinations = $exportDestinations;
    foreach ($exportDestinations as $exportDestination) {
        $exportDestination->setExportJob($this);
    }
}

so that during deserialization "setExportDestinations" is called and it takes care of the relation removal.

Having said that, I am not sure if you should use orphanremoval=true on the relation, as

orphanRemoval=true, even if you will remove given ExportDestination from one ExportJob, and then attach to another ExportJob, this ExportDestination will be deleted during persist, because the reference has been deleted.

I would suggest to remove it and find another way to delete "orphan" ExportDestination entities.

like image 93
Jannes Botis Avatar answered Nov 08 '22 00:11

Jannes Botis