如何在Doctrine 3中替换EntityManager :: merge?

And*_*ord 7 symfony doctrine-orm

Symfony 2.8目前正在使用一个基于Web的项目Doctrine 2。该项目基本上是一个简单的待办事项列表应用程序,可以与移动应用程序(iOS / Android)同步。

阅读Doctrine 3我发现的更新说明时,EntityManager::merge 将不再受支持

ORM 3.0没有提供EntityManager#merge()的替代方法,因为合并语义应该是业务域的一部分,而不是应用程序的持久性域。如果您的应用程序严重依赖类似于CRUD的交互和/或PATCH静态操作,则应考虑使用诸如JMSSerializer之类的替代方法。

我不确定最好的/正确的替换方法是EntityManager::merge什么?

我在哪里使用合并:

在移动应用程序与Web应用程序同步期间,数据将以序列化JSON的形式传输,然后通过反序列化将其传输JMSSerializer到实体对象。当Web应用以ToDoEntry这种方式接收对象时,它可以是新的ToDo-Entry(Web应用中尚不知道)或更新的现有条目。无论哪种方式,接收到的对象都不由进行管理EntityManager。因此$em->persist($receivedObject)将始终尝试插入一个新对象。如果Web应用程序中已经存在ToDo-Entry并且需要更新,则失败(由于id的唯一约束)。

而是$em->merge($receivedObject)使用它自动检查是否需要插入或更新。

热点解决了吗?

当然,如果已经存在具有相同ID的实体,我可以检查每个接收到的对象。在这种情况下,可以加载现有对象并手动更新其属性。但是,这将非常麻烦。实际的项目当然会使用许多不同的实体,并且每个实体类型/类都需要使用自己的处理方法来检查哪些属性需要更新。有没有更好的解决方案?

And*_*ord 1

虽然我很久以前就已经发布了这个问题,但它仍然很活跃。到目前为止,我的解决方案是坚持使用 Doctrine 2.9 并继续使用该merge函数。现在我正在开发一个新项目,该项目应该已准备好 Doctrine 3,因此不应merge再使用它。

我的解决方案当然是针对我的特殊用例的。然而,也许它对其他方面也有用:

我的解决方案:

正如问题中所述,我使用该merge方法将反序列化的外部实体同步到 Web 数据库中,其中该实体的版本可能已经存在(需要更新)或不存在(需要插入)。

@合并注解

在我的例子中,实体具有不同的属性,其中一些属性可能与同步相关并且必须合并,而其他属性仅用于(网络)内部管理并且不得合并。为了告诉这些属性,我创建了一个自定义@Merge注释:

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

然后使用此注释来标记应合并的实体属性:

class ToDoEntry {
    /*
     * @Merge
     */
    protected $date; 


    /*
     * @Merge
     */
    protected $title;

    // only used internally, no need to merge
    protected $someInternalValue;  

    ... 
}
Run Code Online (Sandbox Code Playgroud)

同步+合并

在同步过程中,注释用于将标记的属性合并到现有实体中:

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];
}
Run Code Online (Sandbox Code Playgroud)

就是这样。如果将新属性添加到实体中,我只需决定是否应合并它,并在需要时添加注释。无需更新同步代码。