From 5d43845560d19b1f30277a193e71acec75f2d3a5 Mon Sep 17 00:00:00 2001 From: colemanw Date: Mon, 24 Jul 2023 13:38:51 -0400 Subject: [PATCH] Autoload ActionMappings by enabling global class scanning This adds the `AutoSubscriber` base class which is a little simpler to implement than its cousin `AutoService` when all you need to do is subscribe to events. --- Civi/ActionSchedule/MappingBase.php | 9 ++++++++- Civi/Core/ClassScanner.php | 7 ++++--- Civi/Core/Container.php | 8 -------- Civi/Core/Service/AutoDefinition.php | 4 ++++ Civi/Core/Service/AutoService.php | 1 - Civi/Core/Service/AutoSubscriber.php | 27 +++++++++++++++++++++++++++ 6 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 Civi/Core/Service/AutoSubscriber.php diff --git a/Civi/ActionSchedule/MappingBase.php b/Civi/ActionSchedule/MappingBase.php index cf4f997fc1..4544b69717 100644 --- a/Civi/ActionSchedule/MappingBase.php +++ b/Civi/ActionSchedule/MappingBase.php @@ -12,6 +12,7 @@ namespace Civi\ActionSchedule; use Civi\Api4\Utils\CoreUtil; +use Civi\Core\Service\AutoSubscriber; /** * Base implementation of MappingInterface. @@ -19,7 +20,13 @@ use Civi\Api4\Utils\CoreUtil; * Extend this class to register a new type of ActionSchedule mapping. * Note: When choosing a value to return from `getId()`, use a "machine name" style string. */ -abstract class MappingBase implements MappingInterface { +abstract class MappingBase extends AutoSubscriber implements MappingInterface { + + public static function getSubscribedEvents(): array { + return [ + 'civi.actionSchedule.getMappings' => 'onRegisterActionMappings', + ]; + } /** * Register this action mapping type with CRM_Core_BAO_ActionSchedule. diff --git a/Civi/Core/ClassScanner.php b/Civi/Core/ClassScanner.php index 7863d07edf..cb250218bf 100644 --- a/Civi/Core/ClassScanner.php +++ b/Civi/Core/ClassScanner.php @@ -137,11 +137,12 @@ class ClassScanner { $civicrmRoot = \Civi::paths()->getPath('[civicrm.root]/'); - // TODO: Consider expanding this search. + // Scan all core classes that might implement an interface we're looking for. + // Excludes internal and legacy classes, upgraders, pages & other classes that don't need to be scanned. $classes = []; static::scanFolders($classes, $civicrmRoot, 'Civi/Test/ExampleData', '\\'); - static::scanFolders($classes, $civicrmRoot, 'CRM/*/WorkflowMessage', '_'); - static::scanFolders($classes, $civicrmRoot, 'CRM/*/Import', '_'); + // Most older CRM_ stuff doesn't implement event listeners & services so can be skipped. + static::scanFolders($classes, $civicrmRoot, 'CRM', '_', ';(Upgrade|Utils|Exception|_DAO|_Page|_Form|_Controller|_StateMachine|_Selector|_CodeGen);'); static::scanFolders($classes, $civicrmRoot, 'Civi', '\\', ';\\\(Security|Test)\\\;'); $cache->set($cacheKey, $classes, static::TTL); diff --git a/Civi/Core/Container.php b/Civi/Core/Container.php index 245c3b835e..c229d8d814 100644 --- a/Civi/Core/Container.php +++ b/Civi/Core/Container.php @@ -477,14 +477,6 @@ class Container { 'CRM_Core_LegacyErrorHandler', 'handleException', ], -200); - $dispatcher->addListener('civi.actionSchedule.getMappings', ['CRM_Activity_ActionMapping', 'onRegisterActionMappings']); - $dispatcher->addListener('civi.actionSchedule.getMappings', ['CRM_Contact_ActionMapping', 'onRegisterActionMappings']); - $dispatcher->addListener('civi.actionSchedule.getMappings', ['CRM_Contribute_ActionMapping_ByPage', 'onRegisterActionMappings']); - $dispatcher->addListener('civi.actionSchedule.getMappings', ['CRM_Contribute_ActionMapping_ByType', 'onRegisterActionMappings']); - $dispatcher->addListener('civi.actionSchedule.getMappings', ['CRM_Event_ActionMapping_ByType', 'onRegisterActionMappings']); - $dispatcher->addListener('civi.actionSchedule.getMappings', ['CRM_Event_ActionMapping_ByEvent', 'onRegisterActionMappings']); - $dispatcher->addListener('civi.actionSchedule.getMappings', ['CRM_Event_ActionMapping_ByTemplate', 'onRegisterActionMappings']); - $dispatcher->addListener('civi.actionSchedule.getMappings', ['CRM_Member_ActionMapping', 'onRegisterActionMappings']); return $dispatcher; } diff --git a/Civi/Core/Service/AutoDefinition.php b/Civi/Core/Service/AutoDefinition.php index 5ab8c02d5b..62d10e81cd 100644 --- a/Civi/Core/Service/AutoDefinition.php +++ b/Civi/Core/Service/AutoDefinition.php @@ -26,6 +26,10 @@ class AutoDefinition { $result = []; $classDoc = ReflectionUtils::parseDocBlock($class->getDocComment()); + // AutoSubscriber is an internal service by default + if (is_a($className, AutoSubscriber::class, TRUE)) { + $classDoc += ['service' => TRUE, 'internal' => TRUE]; + } if (!empty($classDoc['service'])) { $serviceName = static::pickName($classDoc, $class->getName()); $def = static::createBaseline($class, $classDoc); diff --git a/Civi/Core/Service/AutoService.php b/Civi/Core/Service/AutoService.php index 4679beca88..5b76cf7ea1 100644 --- a/Civi/Core/Service/AutoService.php +++ b/Civi/Core/Service/AutoService.php @@ -45,7 +45,6 @@ namespace Civi\Core\Service; * = REQUIREMENTS / LIMITATIONS = * * - To scan an extension, one must use `scan-classes@1.0.0` or `hook_scanClasses`. - * - At time of writing, `ClassScanner` may not scan all core folders. * - AutoServices are part of the container. They cannot be executed until the container has * started. Consequently, the services cannot subscribe to some early/boot-time events * (eg `hook_entityTypes` or `hook_container`). diff --git a/Civi/Core/Service/AutoSubscriber.php b/Civi/Core/Service/AutoSubscriber.php new file mode 100644 index 0000000000..5edd58a810 --- /dev/null +++ b/Civi/Core/Service/AutoSubscriber.php @@ -0,0 +1,27 @@ +