maks-rafalko/tactician-domain-events

Why there is no removeListener() method?

Opened this issue · 2 comments

Why there is no removeListener() method?

Could you please explain your use case why is it needed?

Maybe my use case is somewhat constructed, but I have set up a comand bus decorator via an di container (php-di) which allows me to access the event dispatcher through a public method. I have also created an event which is then triggered in the command handler when a record is deleted. If a client (listener) is interested in that event it calls the public method, gets the event dispatcher and registers itself to be notified. To test that, I’ve written two test methods, one which wants to be notified and another one which just tests the successful execution of the command. As the command bus and therefore the event dispatcher are not destroyed between the two test methods, the test method which not wants to be notified becomes also notified, because the listener is still active in the listener array from the first call to addListener(). As code says more then 1000 words, here some sample code:

/**
 * @test
 *
 * @expectedException \DomainException
 */
public function it_should_notify_the_caller_when_deleting_a_registration()
{
  $bus = resolve(CommandBus::class);

  $eventDispatcher = $bus->getEventDispatcher();

  $eventName = (new RegistrationWasDeleted())->getName();

  $eventDispatcher->addListener(
      $eventName,
      function ($event) use ($eventName, $eventDispatcher) {
          // if would be nice here if coulds say: please, dont notify me again.

          // $eventDispatcher->removeListener($eventName, $this);
          throw new \DomainException( ‘i have been notified’);
      }
  );

  $bus->handle(new DeleteRegistrationCommand($this->registration->getId()));
}

/** @test */
public function it_should_soft_delete_a_registration()
{
  $bus = resolve(CommandBus::class);

  $bus->handle(new DeleteRegistrationCommand($this->registration->getId()));

  // Domain exception is thrown, but it should not.
}

I helped myself by reimplementing a class which uses the EventDispatcherInterface and ContainsListenersInterface and of course added the removeListenerMethod($eventName, $listener).
As storage for the listeners I used the SplObjectStorage class so the i can lookup the listener and remove it if necessary. I do not know whether this the removeListener() method fits into the architecture of domain events, but i found a signature for the removeListener() method also available in the symfony event dispatcher interface. Here my implementation of addListener() and removeListener()

public function addListener($eventName, callable $listener)
{
    if (!$this->hasListeners($eventName)) {
       $this->listeners[$eventName] = new \SplObjectStorage;
    }

    $this->listeners[$eventName]->attach($listener)
}

public function removeListener($eventName, $listener = null)
{
   if (!$this->hasListeners($eventName)) {
        return;
   }

   if ($listener === null) {
        if ($this->listeners[$eventName] instanceof \SplObjectStorage) {
            $this->listeners[$eventName]->removeAll($this->listeners[$eventName]);
        }
        unset($this->listeners[$eventName]);
        return;
   }

   $this->listeners[$eventName]->detach($listener);
}

Hopefully, that makes sense.