How to replace EntityManager::merge in Doctrine 3? How to replace EntityManager::merge in Doctrine 3? symfony symfony

How to replace EntityManager::merge in Doctrine 3?


You can try to use registerManaged() method of Doctrine\ORM\UnitOfWork.

// $this->em <--- Doctrine Entity Manager// $entity <--- detached Entity (and we know that this entity already exists in DB for example)$id = [$entity->getId()]; //array$data = $entity->toArray(); //array$this->em->getUnitOfWork()->registerManaged($entity, $id, $data);

Of course, You can check the state of Your Entity using getEntityState() of Doctrine\ORM\UnitOfWork before/after perfoming needed actions.

$this->eM->getUnitOfWork()->getEntityState($entity, $assert = 3)

$assert <-- This parameter can be set to improve performance of entity state detection by potentially avoiding a database lookup if the distinction between NEW and DETACHED is either known or does not matter for the caller of the method.


Actually the code to handle this can be just a few lines. In background Doctrine will issue a query to search for your entity if not already in memory, so you can do the same by doing the query yourself with result cache enabled, and then just use PropertyAccessor to map the data.

https://symfony.com/doc/current/components/property_access.html

See this gist for a POC https://gist.github.com/stevro/99060106bbe54d64d3fbcf9a61e6a273


While I have posted this question quite a while ago, it is still quite active. Until now my solution was to stick with Doctrine 2.9 and keep using the merge function. Now I am working on new project which should be Doctrine 3 ready and should thus not use the merge anymore.

My solution is of course specific for my special use case. However, maybe it is also useful for other:

My Solution:

As described in the question I use the merge method to sync deserialized, external entities into the web database where a version of this entity might already exist (UPDATE required) or not (INSERT required).

@Merge Annotation

In my case entities have different properties where some might be relevant for syncing and must be merged while others are only used for (web) internal housekeeping and must not be merged. To tell these properties appart, I have created a custom @Merge annotation:

use Doctrine\Common\Annotations\Annotation;    /** * @Annotation * @Target("PROPERTY") */final class SyncMerge { }

This annotation is then be used to mark the entities properties which should be merged:

class ToDoEntry {    /*     * @Merge     */    protected $date;     /*     * @Merge     */    protected $title;    // only used internally, no need to merge    protected $someInternalValue;      ... }

Sync + Merge

During the sync process the annotation is used to merge the marked properties into existing entities:

public function mergeDeserialisedEntites(array $deserializedEntities, string $entityClass): void {    foreach ($deserializedEntities as $deserializedEntity) {        $classMergingInfos = $this->getMergingInfos($class);           $existingEntity = $this->entityManager->find($class, $deserializedEntity->getId());                if (null !== $existingEntity) {            // UPDATE existing entity            // ==> Apply all properties marked by the Merge annotation            foreach ($classMergingInfos as $propertyName => $reflectionProperty) {                $deserializedValue = $reflectionProperty->getValue($deserializedEntity);                $reflectionProperty->setValue($existingEntity, $deserializedEntity);            }                        // Continue with existing entity to trigger update instead of insert on persist            $deserializedEntity = $existingEntity;        }        // If $existingEntity was used an UPDATE will be triggerd        // or an INSERT instead        $this->entityManager->persist($deserializedEntity);    }    $this->entityManager->flush();}private $mergingInfos = [];private function getMergingInfos($class) {    if (!isset($this->mergingInfos[$class])) {        $reflectionClass = new \ReflectionClass($class);        $classProperties = $reflectionClass->getProperties();                $propertyInfos = [];                // Check which properties are marked by @Merge annotation and save information        foreach ($classProperties as $reflectionProperty) {            $annotation = $this->annotationReader->getPropertyAnnotation($reflectionProperty, Merge::class);                        if ($annotation instanceof Merge) {                 $reflectionProperty->setAccessible(true);                $propertyInfos[$reflectionProperty->getName()] = $reflectionProperty;            }        }                $this->mergingInfos[$class] = $propertyInfos;    }        return $this->mergingInfos[$class];}

That's it. If new properties are added to an entity I have only to decide whether it should be merged or not and add the annotation if needed. No need to update the sync code.