Symfony2 flash消息作为事件监听器来处理错误


symfony2 flash messages as event listener to handle errors

我们已经设置了一个事件监听器,当实体被更新、删除或持久化时触发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'));
}
...