Skip to content

Commit

Permalink
Container - Use ClassScanner to setup HookListeners. Add test.
Browse files Browse the repository at this point in the history
Before: While setting up the container-cache, it scans for these patterns:

* BAOs implementing HookInterface
* BAOs implementing EventDispatcherInterface

After: While setting up the container-cache, it scans for these patterns:

* BAOs implementing HookInterface (*unchanged*)
* BAOs implementing EventDispatcherInterface (*unchanged*)
* Extensions for classes (./Civi and ./CRM) implementing HookInterface (*new*)

Comment: For the moment, the `EventDispatcherInterface` is not (on its own)
sufficient to activate auto-registration.  This is because (1) the interface
is quite common in the world-at-large, so we'd prone to over-listening and
(2) we don't control the versioning-cycle, so the dephell is likely worse.

However, if you really want it, I think that it will pick up classes that
implement both `HookInterface` and `EventDispatcherInterface`.
  • Loading branch information
totten committed Jun 21, 2022
1 parent a3ef886 commit 9936dce
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 4 deletions.
11 changes: 7 additions & 4 deletions Civi/Core/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -375,12 +375,15 @@ public function createContainer() {
))->addTag('kernel.event_subscriber')->setPublic(TRUE);

$dispatcherDefn = $container->getDefinition('dispatcher');
foreach (\CRM_Core_DAO_AllCoreTables::getBaoClasses() as $baoEntity => $baoClass) {
$listenerMap = EventScanner::findListeners($baoClass, $baoEntity);
$baoClasses = array_flip(\CRM_Core_DAO_AllCoreTables::getBaoClasses());
$hookClasses = ClassScanner::get(['interface' => HookInterface::class]);
$allEventClasses = array_merge(array_keys($baoClasses), $hookClasses);
foreach ($allEventClasses as $eventClass) {
$listenerMap = EventScanner::findListeners($eventClass, $baoClasses[$eventClass] ?? NULL);
if ($listenerMap) {
$file = (new \ReflectionClass($baoClass))->getFileName();
$file = (new \ReflectionClass($eventClass))->getFileName();
$container->addResource(new \Symfony\Component\Config\Resource\FileResource($file));
$dispatcherDefn->addMethodCall('addListenerMap', [$baoClass, $listenerMap]);
$dispatcherDefn->addMethodCall('addListenerMap', [$eventClass, $listenerMap]);
}
}

Expand Down
9 changes: 9 additions & 0 deletions mixin/interface-php@1/example/CRM/Shimmy/ShimmyHooks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

class CRM_Shimmy_ShimmyHooks implements \Civi\Core\HookInterface {

public static function hook_civicrm_shimmyFooBar(array &$data, string $for): void {
$data[] = "hello $for";
}

}
16 changes: 16 additions & 0 deletions mixin/interface-php@1/example/tests/mixin/InterfaceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,38 @@ public function testInstalled($cv) {
// Assert that WorkflowMessageInterface's are registered.
$items = $cv->api4('WorkflowMessage', 'get', ['where' => [['name', '=', 'shimmy_message_example']]]);
$this->assertEquals('CRM_Shimmy_ShimmyMessage', $items[0]['class']);

// Assert that HookInterface's are registered.
$hookData = $this->fireHookShimmyFooBar($cv);
$this->assertEquals(['hello world'], $hookData);
}

public function testDisabled($cv) {
// Assert that WorkflowMessageInterface's are removed.
$items = $cv->api4('WorkflowMessage', 'get', ['where' => [['name', '=', 'shimmy_message_example']]]);
$this->assertEmpty($items);

// Assert that HookInterface's are removed.
$hookData = $this->fireHookShimmyFooBar($cv);
$this->assertEquals([], $hookData);
}

public function testUninstalled($cv) {
// Assert that WorkflowMessageInterface's are removed.
$items = $cv->api4('WorkflowMessage', 'get', ['where' => [['name', '=', 'shimmy_message_example']]]);
$this->assertEmpty($items);

// Assert that HookInterface's are removed.
$hookData = $this->fireHookShimmyFooBar($cv);
$this->assertEquals([], $hookData);
}

protected static function getPath($suffix = ''): string {
return dirname(__DIR__, 2) . $suffix;
}

protected function fireHookShimmyFooBar($cv): array {
return $cv->phpEval('$d=[]; Civi::dispatcher()->dispatch("hook_civicrm_shimmyFooBar", \Civi\Core\Event\GenericHookEvent::create(["data"=>&$d,"for"=>"world"])); return $d;');
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ public function api4($entity, $action, $params): array {
return (array) civicrm_api4($entity, $action, $params);
}

public function phpEval(string $expr): array {
// phpcs:ignore
return eval($expr);
}

};
}

Expand All @@ -124,6 +129,10 @@ public function api4($entity, $action, $params): array {
return $this->cv('api4 --in=json ' . escapeshellarg("$entity.$action"), json_encode($params));
}

public function phpEval(string $expr): array {
return $this->cv('php:eval ' . escapeshellarg($expr));
}

/**
* Call the "cv" command.
*
Expand Down

0 comments on commit 9936dce

Please sign in to comment.