Сохранение других объектов внутри preUpdate слушателя Entity Entity

Для ясности продолжу здесь обсуждение началось здесь.

Внутри Doctrine Entity Listener, в методе preUpdate (где у меня есть доступ как к старому, так и к новому значению любого поля сущности), я пытаюсь сохранить сущность, не связанную с основной.

По сути, у меня есть сущность A, и когда я изменяю значение в одном из полей, которые я хочу записать, в таблице project_notification, поля oldValue, newValue плюс другие.

Если я не сбрасываю содержимое в методе preUpdate, новый объект уведомления не сохраняется в БД. Если я промою это, я войду в бесконечный цикл.

Это метод 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
    }
}

Погуглив немного, я обнаружил, что вы не можете вызвать сброс в слушателях, и здесь он предложил сохранить материал, который будет сохранен в массиве, чтобы сбросить его позже в onFlush. Тем не менее, он не работает (и, вероятно, не должен работать, так как экземпляр класса слушателя уничтожается после вызова preUpdate, поэтому все, что вы храните в качестве защищенного атрибута на уровне класса, теряется при последующем вызове onFlush или я что-то упустил?).

Вот обновленная версия слушателя:

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();
        }
    }
}

Возможно, я могу решить эту проблему, запустив событие из слушателя со всей необходимой информацией для выполнения моих операций регистрации после сброса... но:

1) я не знаю, смогу ли я это сделать

2) кажется немного излишним

Спасибо!

Ответ 1

Не используйте preUpdate, используйте onFlush - это позволяет вам получить доступ к API UnitOfWork, а затем вы можете сохранить сущности.

Например (это то, как я делаю это в 2.3, может быть изменено в более новых версиях)

    $this->getEntityManager()->persist($entity);
    $metaData = $this->getEntityManager()->getClassMetadata($className);
    $this->getUnitOfWork()->computeChangeSet($metaData, $entity);

Ответ 2

Я отдаю все кредиты Ричарду за то, что указал мне в правильном направлении, поэтому я принимаю его ответ. Тем не менее, я также публикую свой ответ с полным кодом для будущих посетителей.

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);
                }
            }
        }
    }
}