-
-
Notifications
You must be signed in to change notification settings - Fork 824
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BAOs, Tests, etal - Support HookInterface
and EventSubscriberInterface
for auto-registration
#20427
Conversation
(Standard links)
|
Documentation should probably go here: https://docs.civicrm.org/dev/en/latest/testing/phpunit/#reference |
EventDispatcherInterface
and HookInterface
for auto-registrationEventSubscriberInterface
and HookInterface
for auto-registration
ad12f72
to
0323b59
Compare
… an object ``` $map = EventScanner::findListeners($someObject); $dispatcher->addListenerMap($someObject, $map); ```
…r' to 'EventScanner' The removes duplicate code and expands the functionality to support methods of the form `_on_{$symfony_event}()`.
…terface` or `HookInterface` Before: There is no pleasant way for a BAO to register a listener for an event. True, one can use `Civi::dispatch()`, but (lacking a good initialization point) it's hard to register reliably. True, you can also add new items to `\Civi\Core\Container::createEventDispatcher()`, but this is rather out-of-the-way. After: A BAO class may optionally implement `EventDispatcherInterface` or `HookInterface`. Methods will be automatically registered. The list of listeners will be cached in the container.
This is just an example of doing declarative event-registration from a BAO.
This is just an example of doing declarative event-registration from a BAO.
Good point @mattwire. Submitted docs at https://lab.civicrm.org/documentation/docs/dev/-/merge_requests/916 |
EventSubscriberInterface
and HookInterface
for auto-registrationEventSubscriberInterface
and HookInterface
for auto-registration
EventSubscriberInterface
and HookInterface
for auto-registrationHookInterface
and EventSubscriberInterface
for auto-registration
Just an aside - this PR touches both
Both interfaces have test-coverage, and the delta between them is fairly small, so I don't think it's very hard to maintain both (or to add other variations!). In the docs PR, I briefly mentioned some of the trade-offs - namely, There is perhaps another discussion about blessing one preferred interface (e.g. phase-out |
@@ -115,6 +115,14 @@ class CRM_Core_BAO_RecurringEntity extends CRM_Core_DAO_RecurringEntity { | |||
const MODE_NEXT_ALL_ENTITY = 2; | |||
const MODE_ALL_ENTITY_IN_SERIES = 3; | |||
|
|||
public static function getSubscribedEvents() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@totten my only misgiving on this PR is that it adds a few functions with stingy addition of return hints (and possibly type hints). I think return hints are definitely preferred practice these days & it's probably better to set your IDE to recommend them (the EA plugin does this).
I think it's worth just reviewing the functions added in this PR & adding return hints & type hints where possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed up a few commits to add more type-hints. Some notes:
- For
getSubscribedEvents()
, the type-signature and semantics are dictated byEventSubscriberInterface
. I suppose, technically, it is legal for an implementation ofgetSubscribedEvents()
to specify a narrower return-type than the interface (e.g.getSubscribedEvents(): array
is narrower thangetSubscribedEvents()
). Though it feels odd to deviate from theinterface
. - There are some new/internal functions (e.g.
normalizeListenerMap()
andmergeListenerMap()
). These would be OK with more hints. Added via 60a1dbc. - For Symfony-style listeners (
on_*($event)
functions), I think it would be legit to always returnvoid
, so added that via e435f17. Although this reminds me an a (slightly unpleasant) fact... that sometimeshook_*($arg1, $arg2, ...)
will return data. 65bc654 fixes support for that edge-case. - I wouldn't agree PHP-language type-hints are "definitely preferred". Changes like phpunit's retroactive type-hinting seem like gratuitous dephell, IMHO. But, yeah, if it's a new or obscure function, and if the signature is amenable to type-hinting in the current language, then 👍.
Note: This is uncommon and discouraged for new hooks, but some hooks require returning values, e.g ```php function hook_foo() { return ['my-data']; } ``` This should fix compatibility with those.
When using Symfony-style listeners, all inputs and outputs for the event go through the event object. Note that `hook_*()` notation does allow return values, For these functions, it is *normal* to return void, but some existing hooks rely on returning array-data. It could be misleading if we made it appear that all `hook_*()` examples have to return void.
Overview
This expands the functionality for declaring event-listeners in different classes, making it easier to participate in Symfony events. This expansion applies in two dimensions:
hook_civicrm_*
andcivi.*
)The general approach is based on interface tagging -- in a supported context (e.g unit-test or BAO), you may mix-in
HookInterface
and/orEventSubscriberInterface
and then declare your listeners.Before
A headless test may implement
HookInterface
and then define methods with thehook_
prefix. It will be scanned for methods in the style of:However, this only works with traditional hooks. It does not work with Symfony-style events. Additionally, it only works with headless unit-tests -- there is no way to adapt it to other classes.
Separately, if an APIv4 subscriber (
Civi\Api4\Event\Subscriber\*
) implementsEventSubscriberInerface
, it will be scanned for listeners by callinggetSubscribedEvents()
:After
Declarative registration has been expanded in multiple ways.
(1) Declarative registration can be used on headless unit-tests (as before), APIv4 services (as before), and also on BAOs. All three types of classes support similar declarations (either
HookInterface
orEventSubscriberInterface
).(2) If a supported class implements the
HookInterface
, then it may use a naming-convention to declare listeners. The naming convention now works with all event-types.(3) If a supported class implements the
EventSubscriberInterface
, then it may usegetSubscribedEvents()
to declare a list of listeners.Technical Details
This exposes a couple new internal APIs for wiring-up listeners. Given an object or class, you may wire it up with:
The ideal arrangement is to split those two steps -- run the scan infrequently, store the results in a cache, and use the cache at runtime. For example, this uses the
Container
cache and defersaddListenerMap()
until runtime:Other comments
The declarative style of registration has some advantages:
require_once 'CachedCiviContainer.XXX.php'
) than to imperatively poll each potential listener speculatively (foreach ($baos as $bao) { require_once '$bao.php'; $bao::initialize(); }
).setUp()
, e.g. when activating an extension temporarily) -- declarative style requires a little less care/knowledge to get the registrations working correctly.