(REF) Define EventScannerPass (extracted from CRM_Api4_Services)
authorTim Otten <totten@civicrm.org>
Mon, 15 Aug 2022 23:32:04 +0000 (16:32 -0700)
committerTim Otten <totten@civicrm.org>
Tue, 16 Aug 2022 22:21:33 +0000 (15:21 -0700)
The `CRM_Api4_Services` implementation has a secret behavior that applies
to all services - it detects the tag 'event_subscriber' and registers with
the dispatcher.

This kind of behavior is often implemented as "compiler pass" and given its
own documentation.

CRM/Api4/Services.php
Civi/Core/Compiler/EventScannerPass.php [new file with mode: 0644]
Civi/Core/Container.php

index f5581efd0fadc7cce6289f6ee9c1e65624867e81..34f64e02391b85a80d7f75a5d2f5edcbc8258ea5 100644 (file)
@@ -16,7 +16,6 @@
  * @copyright CiviCRM LLC https://civicrm.org/licensing
  */
 
-use Civi\Core\Event\EventScanner;
 use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
 use Symfony\Component\Config\FileLocator;
@@ -38,15 +37,6 @@ class CRM_Api4_Services {
       [new Reference('action_object_provider')]
     );
 
-    // add event subscribers$container->get(
-    $dispatcher = $container->getDefinition('dispatcher');
-    $subscribers = $container->findTaggedServiceIds('event_subscriber');
-
-    foreach (array_keys($subscribers) as $subscriber) {
-      $listenerMap = EventScanner::findListeners($container->findDefinition($subscriber)->getClass());
-      $dispatcher->addMethodCall('addSubscriberServiceMap', [$subscriber, $listenerMap]);
-    }
-
     // add spec providers
     $providers = $container->findTaggedServiceIds('spec_provider');
     $gatherer = $container->getDefinition('spec_gatherer');
diff --git a/Civi/Core/Compiler/EventScannerPass.php b/Civi/Core/Compiler/EventScannerPass.php
new file mode 100644 (file)
index 0000000..9ad002a
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+namespace Civi\Core\Compiler;
+
+use Civi\Core\Event\EventScanner;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Scan for services that have the tag 'event_subscriber'.
+ *
+ * Specifically, any class tagged as `event_subscriber` will be scanned for event listeners.
+ * The subscriber should implement a relevant interface, such as:
+ *
+ * - HookInterface: The class uses `hook_*()` methods.
+ * - EventSubscriberInterface: the class provides a `getSubscribedEvents()` method.
+ *
+ * The list of listeners will be extracted stored as part of the container-cache.
+ *
+ * NOTE: This is similar to Symfony's `RegisterListenersPass()` but differs in a few ways:
+ *   - Works with both HookInterface and EventSubscriberInterface
+ *   - Watches tag 'event_subscriber' (not 'kernel.event_listener' or 'kernel.event_subscriber')
+ */
+class EventScannerPass implements CompilerPassInterface {
+
+  public function process(ContainerBuilder $container) {
+    $dispatcher = $container->getDefinition('dispatcher');
+    $subscribers = $container->findTaggedServiceIds('event_subscriber');
+
+    foreach (array_keys($subscribers) as $subscriber) {
+      $listenerMap = EventScanner::findListeners($container->findDefinition($subscriber)->getClass());
+      $dispatcher->addMethodCall('addSubscriberServiceMap', [$subscriber, $listenerMap]);
+    }
+  }
+
+}
index d0b70f120e5653f637b47cba55082a2c5116ff99..8cc7d2c0cf75bfbd37fa77cce771b610b6cf0567 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace Civi\Core;
 
+use Civi\Core\Compiler\EventScannerPass;
 use Civi\Core\Event\EventScanner;
 use Civi\Core\Lock\LockManager;
 use Symfony\Component\Config\ConfigCache;
@@ -90,6 +91,7 @@ class Container {
   public function createContainer() {
     $civicrm_base_path = dirname(dirname(__DIR__));
     $container = new ContainerBuilder();
+    $container->addCompilerPass(new EventScannerPass());
     $container->addCompilerPass(new RegisterListenersPass());
     $container->addObjectResource($this);
     $container->setParameter('civicrm_base_path', $civicrm_base_path);