Symfony dispatcher é uma ferramenta poderosa, mas como em qualquer framework, talvez seja muito projetado para propósitos gerais. Um caso de uso ausente é o despacho de evento na transação. Por exemplo, quando um ouvinte falha, você não deseja inserir linhas no banco de dados. Adicionar ouvinte de liberação com a prioridade mais baixa é muito arriscado. Eu prefiro ter o despachante que inicia uma transação e com base no resultado do despacho ele confirma ou reverte a transação.
Despachante transacional
Despachar eventos não é diferente do despachante Symfony clássico. Para simplificar, eu crio o despachante diretamente no exemplo de código, mas você deve usar DI no código de produção.
$dispatcher = new TransactionalDispatcher(
$container->get('event_dispatcher'),
$container->get('doctrine.orm.entity_manager')
);
$dispatcher->dispatch('event', new ...Event());
Código fonte
A classe do despachante, os testes e a configuração do serviço XML / YAML estão disponíveis na essência . Você pode usá-lo e modificá-lo como quiser. Aqui está a classe Dispatcher:
<?php
use DoctrineORMEntityManager;
use SymfonyComponentEventDispatcherEvent;
use SymfonyComponentEventDispatcherEventDispatcherInterface;
class TransactionalDispatcher
{
private $dispatcher;
private $entityManager;
private $event;
public function __construct(EventDispatcherInterface $d, EntityManager $m)
{
$this->dispatcher = $d;
$this->entityManager = $m;
}
public function dispatch($eventName, Event $event)
{
$this->beginTransaction();
$this->dispatchEvent($eventName, $event);
$this->endTransaction();
return $this->hasSucceed();
}
private function beginTransaction()
{
$this->entityManager->beginTransaction();
}
private function dispatchEvent($eventName, Event $event)
{
$this->event = $this->dispatcher->dispatch($eventName, $event);
}
private function endTransaction()
{
if ($this->hasSucceed()) {
$this->entityManager->commit();
} else {
$this->entityManager->rollback();
}
}
private function hasSucceed()
{
return !$this->event->isPropagationStopped();
}
}
Por que o dispatcher não implementa EventDispatcherInterface?
1. Symfony dispatcher retorna evento
Quando eu chamo o despachante, eu verifico se a propagação * NÃO * foi interrompida. Para mim, a abordagem do Symfony tem duas desvantagens que são causadas pelo fato de que o evento retorna verdadeiro quando a propagação falha:
- Negação em se:
if (!$dispatcher→dispatch(...)→isPropagationStopped())
- Zombar do despachante é mais difícil, porque você deve stub o objeto com o
isPropagationStopped
método
Meus testes terminaram como no código abaixo. Agora eu tenho apenas um desses testes em TransactionalDispatcherTest .
class ExampleTest extends PHPUnit_Framework_TestCase
{
private $hasDispatcherSucceed = true;
public function testWhichExpectsDispatcherFailure()
{
$this->hasDispatcherSucceed = false;
// ...
$this->executeSomething();
}
private function executeSomething()
{
$event = Mockery::mock('SymfonyComponentEventDispatcherEvent');
$event->shouldReceive('isPropagationStopped')->andReturn(!$this->hasDispatcherSucceed );
$dispatcher = Mockery::mock('SymfonyComponentEventDispatcherEventDispatcherInterface');
$dispatcher->shouldReceive('dispatch')->once()->andReturn($event);
// pass dispatcher to tested class, execute, assert expectations
}
}
2. Métodos não utilizados no Dispatcher (composição sobre herança)
Normalmente só preciso do método de envio. Em casos raros, eu adiciono ouvinte dinamicamente (com prioridade máxima). Mas eu nunca usei getListeners
, hasListeners
, removeListener
. Além disso, eu realmente não gosto subscribers
, prefiro registrar ouvintes em algum lugar na configuração YAML.