hook_civicrm_apiWrappers¶
Legacy Hook
This hook implements a legacy API Wrapper interface. It may be deprecated in a future version of CiviCRM.
Summary¶
This hook allows you to add, override, or remove methods to be called before and after API calls — and to modify either the parameters or the result of the call.
Notes¶
Introduced in CiviCRM 4.4.0.
Use caution when overriding or removing methods.
Definition¶
hook_civicrm_apiWrappers(&$wrappers, $apiRequest)
Parameters¶
API_Wrapper[]
$wrappers - an array of objects which implement theAPI_Wrapper
interface.array|Civi\Api4\Generic\AbstractAction
$apiRequest
- contains keys 'entity', 'action', and params; see APIv3 Usage.
Returns¶
- Void
Wrapper class¶
The API_Wrapper
interface specifies two methods:
-
fromApiInput($apiRequest)
- Allows for modifcation of API parameters before the request is executed. Should return $apiRequest (possibly modified). -
toApiOutput($apiRequest, $result)
- Allows for modification of the result before it is returned. Should return a (possibly modified) $result array.
These methods will be called for every API call unless the hook_civicrm_apiWrappers()
implementation
conditionally registers the object. One way to optimize this is to check for the API Entity in
hook_civicrm_apiWrappers()
and to check for the API action in the wrapper methods.
Example¶
In the file where hooks are implemented, e.g., path/to/myextension/myextension.php
:
/**
* Implements hook_civicrm_apiWrappers().
*/
function myextension_civicrm_apiWrappers(&$wrappers, $apiRequest) {
// The APIWrapper is conditionally registered so that it runs only when appropriate
if ($apiRequest['entity'] == 'Contact' && $apiRequest['action'] == 'create') {
if ($apiRequest['version'] == '3') {
$wrappers[] = new CRM_Myextension_API3Wrappers_Contact();
}
elseif ($apiRequest['version'] == '4') {
$wrappers[] = new CRM_Myextension_API4Wrappers_Contact();
}
}
}
Note
We need to handle requests that might be from API version 3 or 4. In this example we have identified different classes to handle the different versions. You could handle the difference in one class if you prefer.
We then create files for our two classes:
path/to/myextension/CRM/Myextension/API3Wrappers/Contact.php
path/to/myextension/CRM/Myextension/API4Wrappers/Contact.php
API3 version¶
class CRM_Myextension_API3Wrappers_Contact implements API_Wrapper {
/**
* Conditionally changes contact_type parameter for the API request.
*/
public function fromApiInput($apiRequest) {
if ('Invalid' == CRM_Utils_Array::value('contact_type', $apiRequest['params'])) {
$apiRequest['params']['contact_type'] = 'Individual';
}
return $apiRequest;
}
/**
* Munges the result before returning it to the caller.
*/
public function toApiOutput($apiRequest, $result) {
if (isset($result['id'], $result['values'][$result['id']]['display_name'])) {
$result['values'][$result['id']]['display_name_munged'] = 'MUNGE! ' . $result['values'][$result['id']]['display_name'];
unset($result['values'][$result['id']]['display_name']);
}
return $result;
}
}
API4 version¶
class CRM_Myextension_API4Wrappers_Contact implements API_Wrapper {
/**
* Conditionally changes contact_type parameter for the API request.
*/
public function fromApiInput(Civi\Api4\Generic\AbstractAction $apiRequest) {
$params = $apiRequest->getParams();
if ('Invalid' === $params['contact_type'] ?? NULL) {
$apiRequest->addValue('contact_type', 'Individual');
}
return $apiRequest;
}
/**
* Munges the result before returning it to the caller.
*/
public function toApiOutput(Civi\Api4\Generic\AbstractAction $apiRequest, $result) {
if (isset($result['id'], $result['values'][$result['id']]['display_name'])) {
$result['values'][$result['id']]['display_name_munged'] = 'MUNGE! ' . $result['values'][$result['id']]['display_name'];
unset($result['values'][$result['id']]['display_name']);
}
return $result;
}
}
Note
$api4Request->addValue()
is appropriate because we are creating a record. You may need different calls to adjust other API requests.
Migrating away from this hook¶
This hook is deprecated in favour of using more flexible Symfony event listeners to achieve what you want.
This hook provides an onion-like middleware pattern where each wrapper added is the first to alter the input and the last to alter the output. If you need this style of wrapper, see EventPrepareTest.php which implements this functionality by replacing the API provider object with a special wrapper class that delegates calling the API to your callback code.
However, often you don't need this onion-like before and after - often you only used toApiOutput
or fromApiInput
but not both. In which case you can instead just add a listener to the civi.api.prepare
or civi.api.respond
as needed to do your work.
For help understanding Symfony events see Hooks in Symfony.
To implement this form of hook in an Symfony the following example maybe useful:
- Define your hook listener.
/**
* Implements hook_civicrm_config().
* Most of the magic is in the CRM_Example_APIWrapper class
*/
function example_civicrm_config(&$config) {
// Bind our wrapper for API Events
Civi::dispatcher()->addListener('civi.api.prepare', ['CRM_Example_APIWrapper', 'PREPARE'], -100);
Civi::dispatcher()->addListener('civi.api.respond', ['CRM_Example_APIWrapper', 'RESPOND'], -100);
}
- Implement function that actually manages the event processing e.g.
/**
* Implements an API Wrapper to signal the membership creation preHook that
* we're currently inside of a payment transaction.
*/
class CRM_Example_APIWrapper {
/**
* Callback to wrap completetransaction API calls.
*/
public static function PREPARE ($event) {
$request = $event->getApiRequestSig(); //getApiRequestSig() returns '<api version>.<entity>.<action>', all lower case
switch($request) {
// Wrap completetransaction in the v3 API.
// Doesn't exist yet in the v4 API.
case '3.contribution.completetransaction':
$event->wrapAPI(['CRM_Example_APIWrapper', 'completeTransaction']);
break;
}
}
public static function RESPOND($event) {
$request = $event->getApiRequestSig();
$apiRequest = $event->getApiRequest();
$result = $event->getResponse();
switch($request) {
case '3.membership.create':
$this->membershipRespond($event->getApiKernel(), $apiRequest['params'], $result, $apiRequest['action'], $apiRequest['entity'], $apiRequest['version']);
$event->setResponse($result);
break;
case '4.membership.create':
$result = $this->membershipRepondV4($apiRequest, $result);
$event->setResponse($result);
break;
}
}
/**
* <insert appropriate docs here>
* @param array $apiRequest
* @param array $callsame - function callback see \Civi\Api\Provider\WrappingProvider
*/
public function completeTransaction($apiRequest, $callsame) {
// Do some work for the contribution.completetransaction api call
// make sure that you then have this as the final line
return $callsame($apiRequest)
}
/**
* Call any nested api calls.
*
* TODO: We don't really need this to be a separate function.
* @param \Civi\API\Kernel $apiKernel
* @param $params
* @param $result
* @param $action
* @param $entity
* @param $version
* @throws \Exception
*/
public function membershipRespond($apiKernel, &$params, &$result, $action, $entity, $version) {
$result['test_field'] = 1;
// Do some other work as applicable
}
public function membershipRepondV4($apiRequest, $result) {
foreach ($result as &$membership) {
$membership['test_field'] = 1;
}
return $result;
}
}