@PrePersist在merge()中调用但不在persist()中调用:我应该使用什么样的Lifecycle回调?

时间:2013-11-28 01:33:21

标签: php symfony doctrine-orm

快速说明:

在@PrePersist和@PreUpdate上使用带有生命周期回调的merge()时,会在空白实体上调用PrePersist,而不是我正在尝试合并的实体,因此我尝试合并的实体永远不会命中我的@PrePersist回调!

@PreUpdate永远不会开火。

我可以在病态情况下触发两个回调,因此事件登记不是罪魁祸首。

更长的说明:

我有一个实体,它有一些验证规则,我想在任何时候持久保存该类的实例时运行。如果验证失败,它将抛出一个“ValidationException”,我的堆栈知道如何处理并反馈给我的客户端UI。

我通过使用symfony的内置验证器来运行我的验证(特别是使用UniqueEntity约束)。喜欢这样

$errorList = $validator->validate($newEntity);
if(count($errorList) > 0) {
    throw new ValidationException($errorList);
}

如果我将此代码放在Controller中,这完全符合我的预期并且我的测试通过了。

现在我想要实现此任何时间实体持久化,因此开发人员不必显式调用validate(强制验证)。我尝试注册@PrePersist回调并注入验证器:

protected $validator;
public function prePersist($args) {
    $entity = $args->getEntity();
    $errorList = $this->validator->validate($newEntity);
    if(count($errorList) > 0) {
        throw new ValidationException($errorList);
    }
}

这适用于简单的情况。

$obj = new Entity();
$obj->setAField('foo');
$em->persist($obj);
$em->flush();

不幸的是,我的实际构造函数使用了merge。这是因为我的“saveEntity”路由从客户端接收JSON包,并处理保存新实体和保存对现有实体的编辑。它看起来像这样:

$json = $request->request->get('objJSON');
$detachedEntity = $this->serializer->deserialize($json, 'MyEntity');
   //do any clean up here. In some cases you need to manually retrieve and set 
   //associated classes...
$mergedEntity = $em->merge($detachedEntity);
   //do any post-merge actions

$em->persist($mergedEntity);
$em->flush();

此工作流程通常有效。但是我的验证失败了!经过symfony代码的核心长期冒险后,我找到了原因:

merge()创建一个空实体并将其保留。这个空实体没有填充任何属性,因此验证忽略它(忽略空值)。在它持续存在之后,它会合并传递的实体中的字段并返回控制器。

//From UnitOfWork.php:doMerge()  - line 1788
// If there is no ID, it is actually NEW.
        if ( ! $id) {
            $managedCopy = $this->newInstance($class);

            $this->persistNew($class, $managedCopy);

这会在空白实体上触发@PrePersist(有用!)。然后它将来自传入的detachedEntity的数据合并到此管理实体中。

我尝试在合并后跟踪persist()上的代码,但是我很难找到实际将数据提交到数据库的位置......

当调用最终持久化时,它不会插入实体,因此不会触发@PrePersist。好。这有点令人费解,但我认为我可以将其视为UPDATE(我想在任何时候实体发生变化时都会运行此检查),所以我使用相同的代码注册了一个@PreUpdate回调。永远不会被召唤。

那么..我的工作流程是否存在严重错误,或者我可以调用不同的回调?

我发现@PrePersist是通过合并制作的空白实体来调用真的很令人讨厌,所以如果有其他方法可以做到这一点,我更愿意。

更新这可能是一个错误...... http://www.doctrine-project.org/jira/browse/DDC-2406?page=com.atlassian.jira.plugin.system.issuetabpanels:changehistory-tabpanel

1 个答案:

答案 0 :(得分:2)

检查您的标识符值

如果合并操作检测到您将其传递给分离的实体(而不是新实体),则应该获得@PreUpdate回调,但在调用->flush()之前它不会触发。 / p>

如果您只获得@PrePersist个回调,则应检查$detachedEntity是否实际包含允许@ORM\Id的标识符值(通常是标有$em->merge(...)的属性)把它作为分离处理。

要对此进行测试,您可以验证ClassMetadata#getIdentifierValues是否返回实体的id值。获取classmetadata实例有点麻烦,但根据上面的代码,这应该有效:

$id = $em->getClassMetadata(get_class($detachedEntity))
         ->getIdentifierValues($detachedEntity);
if (!$id) {
    // $entity will be considered as NEW and trigger @PrePersist
    // instead of @PreUpdate
}

另一种策略

我认为这是一种更常见的方法(可能适用于您,也可能不适合您,具体取决于反序列化方法的功能):

  1. 从json数据中获取ID:$id = $json['path-to-id']
  2. a)获取(托管)实体:$entity = $em->getRepository('MyBundle:MyEntity')->find($id)
  3. b)...或创建一个新的,如果是你的情况:$entity = new MyEntity(...)
  4. 将剩余的$json数据手动“合并”到$entity(使用设置方法或其他)
  5. 坚持/冲洗
  6. 其他提示

    作为旁注,如果您总是$em->merge(...),那么您不需要$em->persist(...)$em->merge(...)用于附加分离的实体,但如果您将新实体传递给它,它会将其转发到UnitOfWork#persistNew(调度实体以便在下次刷新时插入),就像{{1}确实。

    此外,如果您不介意阅读代码,快速查看UnitOfWork的内容可能是一种启发性的体验。

    祝你好运。 : - )

    编辑(附加建议)

    如何使用一个能够很好地处理 new 分离的实例的save方法,并且可以选择立即刷新它们,例如:

    $em->persist(...)

    如果你正在使用custom repositories,我想这种方法可以去那里。