Ser*_*gri 18 symfony doctrine-orm
为了清楚我在这里继续讨论开始在这里.
在Doctrine Entity Listener中,在preUpdate方法中(我可以访问实体任何字段的旧值和新值)我试图保持与焦点实体无关的实体.
基本上我有实体A,当我在我想要写的一个字段中更改一个值时,在project_notification表中,字段oldValue,newValue加上其他字段.
如果我没有在preUpdate方法中刷新,则新通知实体不会存储在DB中.如果我冲洗它,我进入一个无限循环.
这是preUpdate方法:
public function preUpdate(ProjectTolerances $tolerances, PreUpdateEventArgs $event)
{
if ($event->hasChangedField('riskToleranceFlag')) {
$project = $tolerances->getProject();
$em = $event->getEntityManager();
$notification = new ProjectNotification();
$notification->setValueFrom($event->getOldValue('riskToleranceFlag'));
$notification->setValueTo($event->getNewValue('riskToleranceFlag'));
$notification->setEntity('Entity'); //TODO substitute with the real one
$notification->setField('riskToleranceFlag');
$notification->setProject($project);
$em->persist($notification);
// $em->flush(); // gives infinite loop
}
}
Run Code Online (Sandbox Code Playgroud)
谷歌搜索了一下我发现你不能在监听器内调用flush,这里建议将这些东西存放在一个数组中,以便稍后在onFlush中进行刷新.尽管如此它不起作用(并且它可能不起作用,因为在调用preUpdate之后,侦听器类的实例会被破坏,因此当您稍后调用onFlush时,无论您在类级别保存为protected属性都会丢失或者我错过了什么?).
以下是监听器的更新版本:
class ProjectTolerancesListener
{
protected $toBePersisted = [];
public function preUpdate(ProjectTolerances $tolerances, PreUpdateEventArgs $event)
{
$uow = $event->getEntityManager()->getUnitOfWork();
// $hasChanged = false;
if ($event->hasChangedField('riskToleranceFlag')) {
$project = $tolerances->getProject();
$notification = new ProjectNotification();
$notification->setValueFrom($event->getOldValue('riskToleranceFlag'));
$notification->setValueTo($event->getNewValue('riskToleranceFlag'));
$notification->setEntity('Entity'); //TODO substitute with the real one
$notification->setField('riskToleranceFlag');
$notification->setProject($project);
if(!empty($this->toBePersisted))
{
array_push($toBePersisted, $notification);
}
else
{
$toBePersisted[0] = $notification;
}
}
}
public function postFlush(LifecycleEventArgs $event)
{
if(!empty($this->toBePersisted)) {
$em = $event->getEntityManager();
foreach ($this->toBePersisted as $element) {
$em->persist($element);
}
$this->toBePersisted = [];
$em->flush();
}
}
}
Run Code Online (Sandbox Code Playgroud)
也许我可以通过从侦听器内部触发事件来解决这个问题,并在刷新后执行我的日志记录操作...但是:
1)我不知道我是否能做到
2)这似乎有点矫枉过正
谢谢!
Ser*_*gri 35
我把理查德的所有学分都指向了正确的方向,所以我接受了他的回答.不过,我也会用未来访客的完整代码发布我的答案.
class ProjectEntitySubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
'onFlush',
);
}
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $keyEntity => $entity) {
if ($entity instanceof ProjectTolerances) {
foreach ($uow->getEntityChangeSet($entity) as $keyField => $field) {
$notification = new ProjectNotification();
// place here all the setters
$em->persist($notification);
$classMetadata = $em->getClassMetadata('AppBundle\Entity\ProjectNotification');
$uow->computeChangeSet($classMetadata, $notification);
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
Ric*_*ard 22
不要使用preUpdate,使用onFlush - 这允许您访问UnitOfWork API,然后您可以保留实体.
例如(我在2.3中这样做,可能会在较新版本中更改)
$this->getEntityManager()->persist($entity);
$metaData = $this->getEntityManager()->getClassMetadata($className);
$this->getUnitOfWork()->computeChangeSet($metaData, $entity);
Run Code Online (Sandbox Code Playgroud)
正如 David Baucum 所说,最初的问题涉及 Doctrine 实体监听器,但作为解决方案,该操作最终使用了事件监听器。
我确信由于无限循环问题,更多人会偶然发现这个主题。对于那些采用已接受答案的人,请注意 onFlush 事件(当使用如上所述的事件侦听器时)是与可能在更新队列中的所有实体一起执行的,而实体侦听器仅在对它被“分配”到的实体。
我使用 symfony 4.4 和 API 平台设置了一个自定义审核系统,并且仅使用实体监听器就达到了预期的结果。
注意:然而,经过测试和工作,命名空间和函数已被修改,这纯粹是为了演示如何在 Doctrine 实体监听器中操作另一个实体。
// this goes into the main entity
/**
* @ORM\EntityListeners({"App\Doctrine\MyEntityListener"})
*/
Run Code Online (Sandbox Code Playgroud)
<?
// App\Doctrine\MyEntityListener.php
namespace App\Doctrine;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\Security;
// whenever an Employee record is inserted/updated
// log changes to EmployeeAudit
use App\Entity\Employee;
use App\Entity\EmployeeAudit;
private $security;
private $currentUser;
private $em;
private $audit;
public function __construct(Security $security, EntityManagerInterface $em) {
$this->security = $security;
$this->currentUser = $security->getUser();
$this->em = $em;
}
// HANDLING NEW RECORDS
/**
* since prePersist is called only when inserting a new record, the only purpose of this method
* is to mark our object as a new entry
* this method might not be necessary, but for some reason, if we set something like
* $this->isNewEntry = true, the postPersist handler will not pick up on that
* might be just me doing something wrong
*
* @param Employee $obj
* @ORM\PrePersist()
*/
public function prePersist(Employee $obj){
if(!($obj instanceof Employee)){
return;
}
$isNewEntry = !$obj->getId();
$obj->markAsNewEntry($isNewEntry);// custom Employee method (just sets an internal var to true or false, which can later be retrieved)
}
/**
* @param Employee $obj
* @ORM\PostPersist()
*/
public function postPersist(Employee $obj){
// in this case, we can flush our EmployeeAudit object safely
$this->prepareAuditEntry($obj);
}
// END OF NEW RECORDS HANDLING
// HANDLING UPDATES
/**
* @see {https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/events.html}
* @param Employee $obj
* @param PreUpdateEventArgs $args
* @ORM\PreUpdate()
*/
public function preUpdate(Employee $obj, PreUpdateEventArgs $args){
$entity = $args->getEntity();
$changeset = $args->getEntityChangeSet();
// we just prepare our EmployeeAudit obj but don't flush anything
$this->audit = $this->prepareAuditEntry($obj, $changeset, $flush = false);
}
/**
* @ORM\PostUpdate()
*/
public function postUpdate(){
// if the preUpdate handler was called, $this->audit should exist
// NOTE: the preUpdate handler DOES NOT get called, if nothing changed
if($this->audit){
$this->em->persist($this->audit);
$this->em->flush();
}
// don't forget to unset this
$this->audit = null;
}
// END OF HANDLING UPDATES
// AUDITOR
private function prepareAuditEntry(Employee $obj, $changeset = [], $flush = true){
if(!($obj instanceof Employee) || !$obj->getId()){
// at this point, we need a DB id
return;
}
$audit = new EmployeeAudit();
// this part was cut out, since it is custom
// here you would set things to your EmployeeAudit object
// either get them from $obj, compare with the changeset, etc...
// setting some custom fields
// in case it is a new insert, the changedAt datetime will be identical to the createdAt datetime
$changedAt = $obj->isNewInsert() ? $obj->getCreatedAt() : new \DateTime('@'.strtotime('now'));
$changedFields = array_keys($changeset);
$changedCount = count($changedFields);
$changedBy = $this->currentUser->getId();
$entryId = $obj->getId();
$audit->setEntryId($entryId);
$audit->setChangedFields($changedFields);
$audit->setChangedCount($changedCount);
$audit->setChangedBy($changedBy);
$audit->setChangedAt($changedAt);
if(!$flush){
return $audit;
}
else{
$this->em->persist($audit);
$this->em->flush();
}
}
Run Code Online (Sandbox Code Playgroud)
这个想法是不保留/刷新 preUpdate 中的任何内容(除了准备数据,因为您可以访问变更集和内容),并在更新的情况下执行 postUpdate ,或者在新插入的情况下执行 postPersist 。
| 归档时间: |
|
| 查看次数: |
10675 次 |
| 最近记录: |