Reputation: 489
We have set up an event listener that triggers flash messages when an entity gets updated, removed or persisted, but what we can't quite manage is how to handle errors.
Here is the relevant code from 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 ]
and this is the listener in 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())
)
);
}
}
What we are trying to do, is to clean the controller and move all the error messages into the event listener. For example, this is our 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,
);
}
Like that, ideally we want to handle errors also when an entity fails to get created, persisted, and deleted.
For reference, translations are held by 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.
And $entity->getClassName()
sits in every entity as it follows:
private $className;
/**
* Get class name
* @return string
*/
public function getClassName()
{
$entity = explode('\\', get_class($this));
return end($entity);
}
Upvotes: 2
Views: 4066
Reputation: 489
We ended up up changing the way our FlashMessages worked, because we had an issue with chain of entities getting updated and too many flash messages being displayed. Doing int the following way allowed us to stop the chain effect. We went from postUpdate()
, postUpdate()
and postPersist()
to using a single onFlush()
. See our code below:
AcmeBundle/Resources/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())
)
);
}
}
}
We tried to set up a onKernelException(GetResponseForExceptionEvent $event)
function to handle the messages on entities not found, but in order to make that working we still had to throw an exception from the controller, passing the message translated... Basically we still had to repeat a good chunk of code, so we just rolled back to the original solution; that is, managing flash messages straight away from the controller's actions:
...
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'));
}
...
Upvotes: 3