Задать вопрос
Ответы пользователя по тегу Doctrine ORM
  • Почему когда подписываюсь на Doctrine.Events::preUpdate, он выполняется бесконечно?

    Frostealth
    @Frostealth
    Backend Developer
    Вызов flush() в процессе обработки preUpdate приводит к бесконечной рекурсии, ибо обработчик события вызывается в процессе выполнения предыдущего вызова flush().
    Цепочка вложенных вызовов в вашем случае выглядит следующим образом:

    - EntityManager::flush()
    - UnitOfWork::commit()
    - UnitOfWork::executeUpdates()
    - ListenersInvoker::invoke()
    - OrderEventSubscriber::preUpdate() - вызывается еще до обновления Order и его удаления из UnitOfWork
    - $this->notificationsCreator->createChangeStatusNotification() - создает рекурсию вызовом `flush()`
    - EntityManager::flush() - снова пытается обновить данные Order и вызывает обработку событий `preUpdate`
    - ...
    - EntityManager::flush()
    - ...

    Вызов методов persist(), remove() и т.п. в процессе обработки событий доктрины может привести к неожиданным результатам. Данные просто могут быть не сохранены, как минимум. Крайне не рекомендую менять какие-либо данные в Entity, EM/UoW при обработке событий доктрины.

    В вашем случае можно реализовать свое событие, например OrderStatusChanged. Но лучше использовать более конкретные события вроде OrderCompleted и OrderCanceled.
    И воспользоваться symfony/event-dispatcher.
    Либо создавать уведомление непосредственно там, где выполняете изменение статуса заказа (контроллер, сервис и т.п., зависит от вашей архитектуры) и вызывается flush().
    пример верного вызова

    $this->em->flush();
    $this->events->dispatch(new OrderCompleted($order->id));
    
    // или
    $this->em->flush();
    $this->notificationCreator->createChangeStatusNotification($order->getCustomer());



    P.S. Рекомендую использовать Guard Clauses для уменьшения вложенности и улучшения чтения кода.
    пример

    public function preUpdate(PreUpdateEventArgs $args)
    {
        $entity = $args->getObject();
        if (!$entity instanceof Order) {
        	return;
        }
    
        $this->logger->info($entity);
        $onlyStatusChanged = count($args->getEntityChangeSet()) === 1 && $args->hasChangedField('status');
        if ($onlyStatusChanged) {
            $this->notificationsCreator->createChangeStatusNotification($entity->getCustomer());
        }
        // много кода...
    }

    Ответ написан
    1 комментарий