(dev/core#873) civi.api.prepare - Allow dynamic wrappers
authorTim Otten <totten@civicrm.org>
Wed, 10 Apr 2019 02:16:47 +0000 (19:16 -0700)
committerTim Otten <totten@civicrm.org>
Sat, 13 Apr 2019 22:26:56 +0000 (15:26 -0700)
commit9abe1c3b0acd7fa42ec394ab23d689a4052474ca
tree22b959350a122f973abfc781f731434f252e8aa6
parent8a0e977784f86a3a81b8c17aba2e0e283d3e4b12
(dev/core#873) civi.api.prepare - Allow dynamic wrappers

Overview
--------

Allow extensions to dynamically wrap an API. There are some existing mechanisms which sort-of allow wrapping, but
this enables additional use-cases.

Before
------

There are a few techniques for wrapping or overriding an API, but none is suitable to my current use-case. Limitations:

* `hook_civicrm_apiWrappers` - This allows registering things before and after the API call, but it does not allow
   changing the underlying API call.
* `civi.api.{authorize,prepare,respond}` events - Same as above. These are a bit more nuanced/fine-grained, but still does not allow changing
* `civi.api.resolve` event with `AdhocProvider` - This allows you to swap an API completely, but it doesn't allow you
  to delegate back to the original API call (if you've got nothing to add).

After
------

One may subscribe to `civi.api.prepare` and then call the `wrapApi()` helper:

```php
function onPrepare($event) {
  if ($event->getApiRequestSig() === '3.widget.frobnicate') {
    $event->wrapApi(function($apiRequest, $continue){
      echo "Hello\n";
      $r = $continue($apiRequest);
      echo "Goodbye\n";
      return $r;
    });
  }
}
```

Key characteristics:

* The wrapper only applies if you register it specifically for the given API call.
* The wrapper allows you to defer to the original implementation (`$continue`).
* The wrapper allows you to perform logic before and after.
* The wrapper allows you to *conditionally* replace -- you might call `$continue` or something entirely different.

The style here is more event-oriented, but you can see the same concept in OOP systems, such as PHP's function-override notation.
This would be analogous:

```php
class MyChild extends MyParent {
  function frobnicate($arg1) {
    echo "Hello\n";
    $r = parent::frobnicate($arg1);
    echo "Goodbye\n";
    return $r;
  }
}
```
Civi/API/Event/Event.php
Civi/API/Event/PrepareEvent.php
Civi/API/Kernel.php
Civi/API/Provider/WrappingProvider.php [new file with mode: 0644]
tests/phpunit/Civi/API/Event/PrepareEventTest.php [new file with mode: 0644]