Skip to content
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

(dev/core#873) civi.api.prepare - Allow dynamic wrappers #14047

Merged
merged 1 commit into from
Apr 14, 2019

Conversation

totten
Copy link
Member

@totten totten commented Apr 13, 2019

Overview

Allow extensions to dynamically wrap an API. There are some existing mechanisms which sort-of allow wrapping, but this enables additional use-cases. It is primarily expected to be used with a business-y APIs (like Mailing.submit) rather than CRUD-y APIs (like Mailing.get), but the technique can be used on any API call.

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:

function onApiPrepare($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 $continue.
  • The wrapper allows you to be conditional and to change multiplicity -- you might call $continue once... or twice... or thrice... or not at all.

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:

class MyChild extends MyParent {
  function frobnicate($arg1) {
    echo "Hello\n";
    $r = parent::frobnicate($arg1);
    echo "Goodbye\n";
    return $r;
  }
}

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;
  }
}
```
@civibot
Copy link

civibot bot commented Apr 13, 2019

(Standard links)

@eileenmcnaughton
Copy link
Contributor

So this is a PR where I feel the main reviewer action should be to ask Tim - however, this has implicitly been done :-)

Test is included. Thought has been put into architecture - @totten I'm gonna merge on the expectation you will follow up by adding docs

@eileenmcnaughton eileenmcnaughton merged commit 51cfa6b into civicrm:master Apr 14, 2019
@totten totten deleted the master-api-wrap branch April 14, 2019 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants