快速说明:
在@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
答案 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
}
我认为这是一种更常见的方法(可能适用于您,也可能不适合您,具体取决于反序列化方法的功能):
$id = $json['path-to-id']
$entity = $em->getRepository('MyBundle:MyEntity')->find($id)
$entity = new MyEntity(...)
$json
数据手动“合并”到$entity
(使用设置方法或其他)
作为旁注,如果您总是$em->merge(...)
,那么您不需要$em->persist(...)
。 $em->merge(...)
用于附加分离的实体,但如果您将新实体传递给它,它会将其转发到UnitOfWork#persistNew
(调度实体以便在下次刷新时插入),就像{{1}确实。
此外,如果您不介意阅读代码,快速查看UnitOfWork的内容可能是一种启发性的体验。
祝你好运。 : - )
如何使用一个能够很好地处理 new 和分离的实例的save方法,并且可以选择立即刷新它们,例如:
$em->persist(...)
如果你正在使用custom repositories,我想这种方法可以去那里。