0

Not sure if there's a way out of this problem but...

I have a custom log module which listens for events that could come from multiple sources. All it does is listen for an event, and if it occurs logs an entry in the DB.

But there could be different recorded data coming from different modules and I want to separate concerns so there's no complex web of dependencies.

If Module L is the listener it might get EventA from module A and EventB from module B. The events have a common base but each module stores slightly different data in the log which needs to be set-up in each case.

Option 1) Module L could be set up to listen for "module_a.event" and "module_b.event" and process them separately but that is a "bad thing" because the listener is dependent on each of the other modules. The Listener shouldn't need to know how the data is stored, it doesn't care.

Option 2) Or Module L could have a specialised event "listener.event" that Module A and Module B calls having set up the data in the event correctly. But that means those modules have a dependency on the listener.

I don't like either option because of the dependency requirements in one direction or the other.

Bright idea: Create a plugin in Module L that builds the log structure for a particular module. So Module A has a plugin that handles its requirements, and Module B has another plugin that handles its needs.

There's no dependency in either direction because if Module L is not running then A and B don't care (the plugins will never be scanned or loaded). And if either or both of A and B are missing, Module L doesn't care.

And if I then need a Module C I just give it its own plugin.

But Module L still needs to listen for events from A, B and C (module_a.event, module_b.event and module_c.event). How does it know what events it should listen for?

Easy: Set a value in the annotation of each plugin giving the name of the event to listen for. We can load all the plugin definitions in the EventSubscriber and set it up. Fantastic. Problem solved. No dependency but every event type can be listened for and handled.

Except it doesn't work because: apparently event subscribers are initialised before plugin services.

My "module_l.services.yml" looks like this:

services:
  notifications_log.notifier:
    class: Drupal\notifications_log\EventSubscriber\NotifierSubscriber
    arguments: ['@plugin.manager.notifier', '@entity_type.manager',
                '@current_user', '@logger.factory']
    tags:
      - { name: event_subscriber }

  plugin.manager.notifier:
    class: Drupal\notifications_log\Plugin\NotifierManager
    parent: default_plugin_manager

Which seems fine (doesn't matter which way round they go BTW - I've tried) but we get a fail here, in the EventSubscriber:

  static function getSubscribedEvents() {
    /** @var NotifierManager $notifierManager */
    $notifierManager = \Drupal::service('plugin.manager.notifier');

    $events = [];
    foreach ($notifierManager->getNotifierEventNames() as $eventName => $plugin_id) {
      $events[$eventName] = ['onNotification', 0];
    }

    return $events;
  }

This is a static function so we have to fetch the plugin manager directly, and it can't find it.

Is there any way around this? I could find write some code to find all the plugins myself but that's a lot of faffing about.

What I really need is a way of dynamically adding to the Event subscribers, but I haven't found a way to do that.

If I could carry the plugin_id across as well, that would be useful but it's not a big problem to find the right plugin in the "on Notification" method.)

0

1 Answer 1

0

The solution to the the problem is to dynamically add event subscribers later in the page load process, so for example:

  /**
   * {@inheritdoc}
   *
   * We can't return the plugin-based events here, but we set up
   * a response to do that at a later stage
   *
   */
  static function getSubscribedEvents() {
    // Subscribe to kernel request very early.
    $events[KernelEvents::REQUEST][] = ['onRequest', 900];

    return $events;
  }

  /**
   * Add the plugin events at this stage - early in the process.
   *
   * @param GetResponseEvent $event
   */
  public function onRequest(GetResponseEvent $event) {
    // Find every event mentioned in a plugin...
    foreach ($this->notifierManager->getNotifierEventNames() as $eventName => $plugin_id) {
      // Add the event to the dispatcher as a listener, to call that routine...
      $this->dispatcher->addListener($eventName, [$this, 'onNotification']);
    }
  }

The list of event names is cached in getNotifierEventnames().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.