Вызов
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());
}
// много кода...
}