Api4 Services - Lazy-load subscriber-objects
This refines the way in which `Civi/Api4/Event/Subscriber/**.php` are loaded.
This makes it safer (from a performance POV) to continue adding more
subscribers/listeners without worrying that it will impact the quantity of
files/classes/opcodes/SLOC loaded in a typical page-view.
A good way to visualize this change is to skim `getDispatcherService()`
(`[civicrm.compile]/CachedCiviContainer.*.php`) before and after the patch.
(Examples included below.)
Before
------
During every page-load, you need to register event-listeners. For
subscriber-objects (like `Civi/Api4/Event/Subscriber/**.php`), you get the list
of subscriptions by calling `getSubscribedEvents()`. Therefore, on every
page-load, you must load/process the subscriber (regardless of whether it will
actually be used) on the chance it that may be needed.
In `CachedCiviContainer.*.php`, you will see snippets like:
```php
protected function getDispatcherService()
{
...
$instance->addSubscriber(${($_ = isset($this->services['Civi_Api4_Event_Subscriber_ActivityPreCreationSubscriber']) ? $this->services['Civi_Api4_Event_Subscriber_ActivityPreCreationSubscriber'] : ($this->services['Civi_Api4_Event_Subscriber_ActivityPreCreationSubscriber'] = new \Civi\Api4\Event\Subscriber\ActivityPreCreationSubscriber())) && false ?: '_'});
$instance->addSubscriber(${($_ = isset($this->services['Civi_Api4_Event_Subscriber_ActivitySchemaMapSubscriber']) ? $this->services['Civi_Api4_Event_Subscriber_ActivitySchemaMapSubscriber'] : ($this->services['Civi_Api4_Event_Subscriber_ActivitySchemaMapSubscriber'] = new \Civi\Api4\Event\Subscriber\ActivitySchemaMapSubscriber())) && false ?: '_'});
...
```
Observe that it instantiates `ActivitySchemaMapSubscriber` then passes the instance to `addSubscriber()`.
After
-----
You only need to instantiate service-objects if (a) you are building a fresh container or (b) actually running an event.
This works by calling `getSubscribedEvents()` when building the container. The
list of events is cached in the container.
In `CachedCiviContainer.*.php`, you will see snippets like:
```php
protected function getDispatcherService()
{
...
$instance->addSubscriberServiceMap('Civi_Api4_Event_Subscriber_ActivityPreCreationSubscriber', ['civi.api.prepare' => 'onApiPrepare']);
$instance->addSubscriberServiceMap('Civi_Api4_Event_Subscriber_ActivitySchemaMapSubscriber', ['api.schema_map.build' => 'onSchemaBuild']);
...
```
Observe that it alludes to `ActivityPreCreationSubscriber` symbolically but
does not need an actual instance.
Comments
-----------------
1. To see that this is equivalent, I used `cv debug:event-dispatcher` before
and after the patch. This requires an updated version of `cv`, and the
formatting is a little a different, but it does show the same list of
listeners.
2. There could be some concern like, "What happens if you're upgrading and
have a cached list of subscription events?" Well, note that
`CRM_Api4_Services::hook_container` already puts a cached list of subscribers
in the container. It also registers the `FileResource`. Thus, if a file
`Civi/Api4/Event/Subscriber/**.php` changes, it already makes the decision to
recompile based on `filemtime()`.
3. There should already be a lot of test-coverage which hits code-paths for these listeners.
(If this were generally non-functional, you'd see massive failures.)
4. For `r-run`, I picked an arbitary subscriber (`ActivitySchemaMapSubscriber`), then:
* At the start of the file, add a statement to log whenever the file is read.
```php
file_put_contents('/tmp/parselog.txt', sprintf("%s: %s: %s\n\n",date('Y-m-d H:i:s'), __FILE__, \CRM_Core_Error::formatBacktrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 15))), FILE_APPEND);
```
* In a separate window, do a `tail -f /tmp/parselog.txt`.
* Edit the file to add/remove listeners (like `hook_civicrm_alterContent`)
* Request some Civi page (`curl 'http://dmaster.127.0.0.1.nip.io:8001/civicrm/admin?reset=1'`). It's not important that it actually runs the full page...
just that we boot up Civi to look for the page.
* Alternately repeat the past few steps. Observe thta it only parses the file if there has been a change or if the relevant event(s) actually fire.