0

I've been working on custom modules that alter RequestEvents based on the entity type provided by the route.

The functionality from one entity to another doesn't change, but the data does, hence I split the module. The working example extends a class provided by a base module that holds the core functionality.

I'm not sure if it matters, be it run order precedence or what have you, but the names are akin to my_module_nodes and my_module_taxonomy_terms.

When I have just my_module_nodes installed, it works perfectly. When I install my_module_taxonomy_terms alongside it, my_module_taxonomy_terms appears to override the functionality of my_module_nodes.

I've debugged by logging to Watchdog the routes I'm checking against with both modules enabled, and it checks for the route defined in my_module_taxonomy_terms twice, rather than checking for the routes defined in each individual module. E.g. I would expect to see entity.node.canonical logged as well as entity.taxonomy_term.canonical, but I see entity.taxonomy_term.canonical logged twice per page load.

Note that I have attempted this with static strings in the __construct method as opposed to using arguments from the services file, but nothing changing. Also note that installation order does not change the result - my_module_taxonomy_terms always overrides my_module_nodes.

One of the two submodule event subscribers in question - identical to the other besides the data method.

/custom/my_module_base/src/MyModuleBaseSubscriberBase.php

namespace Drupal\my_module_base;

use Drupal\Core\Cache\CacheableJsonResponse;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Routing\CurrentRouteMatch;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * @package Drupal\my_module_base\JsonEntitySubscriberBase
 */
class MyModuleBaseSubscriberBase implements EventSubscriberInterface {
  
  /** @var \Drupal\Core\Cache\CacheBackendInterface */
  protected $cacheBackend;
  
  /** @var \Drupal\Core\Routing\CurrentRouteMatch */
  protected $routeMatch;

  /** @var string */
  protected $routeName;

  /** @var string */
  protected $routeParameter;
  
  /**
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
   *   The "cache.render" cache backend service.
   * @param \Drupal\Core\Routing\CurrentRouteMatch $route_match
   *   The "current_route_match" route match interface service.
   * @param string $route_name
   *   The Name of the route to check against.
   * @param string $route_parameter
   *   The Name of the route parameter to get.
   */
  public function __construct(CacheBackendInterface $cache_backend, CurrentRouteMatch $route_match, string $route_name, string $route_parameter) {
    $this->cacheBackend = $cache_backend;
    $this->routeMatch = $route_match;
    $this->routeName = $route_name;
    $this->routeParameter = $route_parameter;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('cache.render'),
      $container->get('current_route_match'),
      $container->get('route.name'),
      $container->get('route.parameter'),
    );
  }
  
  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = ['onRequest', 29];
    return $events;
  }

  /**
   * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
   *   The current RequestEvent object.
   */
  public function onRequest(RequestEvent $event) {
    $request = $event->getRequest();
    $route_match = $this->routeMatch->getRouteMatchFromRequest($request);

    if ($route_match->getRouteName() !== $this->routeName) {
      return;
    }

    $event->setResponse($this->getResponse(
      $route_match->getParameter($this->routeParameter)
    ));
  }

  // Other functionality...

  /**
   * @param mixed $entity
   * @return array
   */
  protected function getData(mixed $entity): array {
    return [
      'type' => $entity->getEntityTypeId(),
      'id' => $entity->id(),
    ];
  }

}

One of the two submodule event subscribers in question - identical to the other besides the data method.

/custom/my_module_base/modules/my_module_nodes/src/EventSubscriber/MyModuleNodesSubscriber.php

namespace Drupal\my_module_nodes\EventSubscriber;

use Drupal\my_module_base\MyModuleBaseSubscriberBase;

/**
 * @package Drupal\my_module_nodes\EventSubscriber
 */
class MyModuleNodesSubscriber extends MyModuleBaseSubscriberBase {
  /**
   * {@inheritdoc}
   */
  protected function getData(mixed $entity): array {
    return [
      'type' => $entity->getEntityTypeId(),
      'id' => $entity->id(),
      // Other values...
    ];
  }
}

One of the two submodule service files in question - identical to the other besides the parameters values.

/custom/my_module_base/modules/my_module_nodes/my_module_nodes.services.yml

parameters:
  route.name: entity.node.canonical
  route.parameter: node

services:
  my_module_nodes.subscriber:
    class: \Drupal\my_module_nodes\EventSubscriber\MyModuleNodesSubscriber
    arguments: ['@cache.render', '@current_route_match', '%route.name%', '%route.parameter%']
    tags:
    - { name: event_subscriber }

I've been tinkering on this project over the past week after work and I'm at a bit of a loss. Is there any other relevant information I should include? Thanks!

1 Answer 1

1

If you have the same container parameter name in different modules the module last in alphabetic order will overwrite previous values. The solution would be to prefix the parameter name with the module name if it is specific to this module.

But why use parameters when you can use string literals?

my_module_nodes.services.yml

services:
  my_module_nodes.subscriber:
    class: \Drupal\my_module_nodes\EventSubscriber\MyModuleNodesSubscriber
    arguments: ['@cache.render', '@current_route_match', 'entity.node.canonical', 'node']
    tags:
    - { name: event_subscriber }
3
  • Ah, makes sense! Honestly, I'm not sure how to get an unnamed argument. The documentation I've found on the get() method is fairly sparse, though it seems to expect a named argument. E.g. $container->get('cache.render'). How would I go about achieving this?
    – FrankieD
    Apr 26, 2023 at 21:39
  • 1
    For a container parameter you would need $container->getParameter(), but the create() method in your service class has no function, it's not used when the service is instantiated. The arguments in *.services.yml are the input of __construct() and it's no problem to feed __construct() with string literals.
    – 4uk4
    Apr 26, 2023 at 22:00
  • That did it. Thanks for clearing that up.
    – FrankieD
    Apr 26, 2023 at 22:12

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.