我们已经设置了一个事件监听器,当实体被更新、删除或持久化时触发flash消息,但是我们不能完全管理的是如何处理错误。
以下是services.yml
flash_messages:
class: Acme'AcmeBundle'EventListener'FlashMessages
tags:
- { name: doctrine.event_listener, event: postUpdate }
- { name: doctrine.event_listener, event: postRemove }
- { name: doctrine.event_listener, event: postPersist }
arguments: [ @session, @translator ]
这是Acme/AcmeBundle/EventListener/FlashMessages.php
namespace Acme'AcmeBundle'EventListener;
use
Doctrine'ORM'Event'LifecycleEventArgs,
Symfony'Component'HttpFoundation'Session'Session,
Symfony'Component'Translation'TranslatorInterface
;
class FlashMessages
{
private $session;
protected $translator;
public function __construct(Session $session, TranslatorInterface $translator)
{
$this->session = $session;
$this->translator = $translator;
}
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$this->session->getFlashBag()->add(
'success',
$this->translator->trans(
'%name% entity.write.success',
array('%name%' => $entity->getClassName())
)
);
}
public function postRemove(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$this->session->getFlashBag()->add(
'success',
$this->translator->trans(
'%name% entity.delete.success',
array('%name%' => $entity->getClassName())
)
);
}
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$this->session->getFlashBag()->add(
'success',
$this->translator->trans(
'%name% entity.create.success',
array('%name%' => $entity->getClassName())
)
);
}
}
我们要做的是清理控制器并将所有错误消息移动到事件侦听器中。例如,这是我们的DeliveryController.php
:
/**
* Finds and displays a Delivery entity.
*
* @Route("/{id}", name="delivery_show")
* @Method("GET")
* @Template()
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('AcmeBundle:Delivery')->find($id);
if (!$entity) {
/**
* @todo move this code to eventListener
*/
$entity = new Delivery();
$this->get('session')->getFlashBag()->add(
'danger',
$this->get('translator')->trans(
'%name% entity.find.fail',
array('%name%' => $entity->getClassName())
)
);
// end @todo
return new RedirectResponse($this->generateUrl('delivery'));
}
return array(
'entity' => $entity,
'menu_tree' => $this->menu_tree,
);
}
与此类似,理想情况下,我们也希望在实体创建、持久化和删除失败时处理错误。
作为参考,翻译由Acme/AcmeBundle/Resources/translations/messages.en.yml
%name% entity.create.fail: There wes an error creating the %name%. Please try again later.
%name% entity.create.success: %name% created successfully.
%name% entity.write.fail: There wes an error saving the %name%. Please try again later.
%name% entity.write.success: %name% saved successfully.
%name% entity.delete.fail: There wes an error deleting the %name%. Please try again later.
%name% entity.delete.success: %name% deleted successfully.
%name% entity.find.fail: %name% not found.
$entity->getClassName()
位于每个实体中,如下所示:
private $className;
/**
* Get class name
* @return string
*/
public function getClassName()
{
$entity = explode('''', get_class($this));
return end($entity);
}
我们最终改变了FlashMessages的工作方式,因为我们遇到了更新实体链和显示太多flash消息的问题。这样做可以阻止连锁效应。我们从postUpdate()
, postUpdate()
和postPersist()
到使用单个onFlush()
。请看下面的代码:
AcmeBundle/资源/config/services.yml
flash_messages:
class: Acme'AcmeBundle'EventListener'FlashMessages
tags:
- { name: doctrine.event_listener, event: onFlush }
arguments: [ @session, @translator, @service_container ]
AcmeBundle/EventListener FlashMessages.php
<?php
namespace Acme'AcmeBundle'EventListener;
use
Symfony'Component'HttpFoundation'Session'Session,
Symfony'Component'Translation'TranslatorInterface,
Doctrine'ORM'Event'OnFlushEventArgs,
Acme'AcmeBundle'Entity'DeliveryItem
;
class FlashMessages
{
private $session;
protected $translator;
public function __construct(Session $session, TranslatorInterface $translator)
{
$this->session = $session;
$this->translator = $translator;
}
public function onFlush(OnFlushEventArgs $args)
{
$this->em = $args->getEntityManager();
$uow = $this->em->getUnitOfWork();
$insert = current($uow->getScheduledEntityInsertions());
$update = current($uow->getScheduledEntityUpdates());
$delete = current($uow->getScheduledEntityDeletions());
// Don't show messages when updating individual DeliveryItem and StockHistory
if ($insert instanceof DeliveryItem ||
$update instanceof DeliveryItem ||
$delete instanceof DeliveryItem) {
return false;
}
// Flash message on insert
if ($uow->getScheduledEntityInsertions()) {
$this->session->getFlashBag()->add(
'success',
$this->translator->trans(
'%name% entity.create.success',
array('%name%' => $insert->getClassName())
)
);
}
// Flash message on update
if ($uow->getScheduledEntityUpdates()) {
$this->session->getFlashBag()->add(
'success',
$this->translator->trans(
'%name% entity.write.success',
array('%name%' => $update->getClassName())
)
);
}
// Flash message on delete
if ($uow->getScheduledEntityDeletions()) {
$this->session->getFlashBag()->add(
'success',
$this->translator->trans(
'%name% entity.delete.success',
array('%name%' => $delete->getClassName())
)
);
}
}
}
我们试图设置一个onKernelException(GetResponseForExceptionEvent $event)
函数来处理未找到的实体上的消息,但为了使其工作,我们仍然必须从控制器抛出一个异常,传递翻译后的消息…基本上我们仍然需要重复一大块代码,所以我们只能回到最初的解决方案;也就是说,直接从控制器的动作管理flash消息:
...
if (!$entity) {
$entity = new Delivery();
$this->get('session')->getFlashBag()->add(
'danger',
$this->get('translator')->trans(
'%name% entity.find.fail',
array('%name%' => $entity->getClassName())
)
);
return new RedirectResponse($this->generateUrl('delivery'));
}
...