return $this->apiRequest;
}
+ /**
+ * Create a brief string identifying the entity/action. Useful for
+ * pithy matching/switching.
+ *
+ * Ex: if ($e->getApiRequestSig() === '3.contact.get') { ... }
+ *
+ * @return string
+ * Ex: '3.contact.get'
+ */
+ public function getApiRequestSig() {
+ return mb_strtolower($this->apiRequest['version'] . '.' . $this->apiRequest['entity'] . '.' . $this->apiRequest['action']);
+ }
+
}
*/
namespace Civi\API\Event;
+use Civi\API\Provider\WrappingProvider;
/**
* Class PrepareEvent
return $this;
}
+ /**
+ * Replace the normal implementation of an API call with some wrapper.
+ *
+ * The wrapper has discretion to call -- or not call -- or iterate with --
+ * the original API implementation, with original or substituted arguments.
+ *
+ * Ex:
+ *
+ * $event->wrapApi(function($apiRequest, $continue){
+ * echo "Hello\n";
+ * $continue($apiRequest);
+ * echo "Goodbye\n";
+ * });
+ *
+ * @param callable $callback
+ * The custom API implementation.
+ * Function(array $apiRequest, callable $continue).
+ * @return PrepareEvent
+ */
+ public function wrapApi($callback) {
+ $this->apiProvider = new WrappingProvider($callback, $this->apiProvider);
+ return $this;
+ }
+
}
list($apiProvider, $apiRequest) = $this->resolve($apiRequest);
$this->authorize($apiProvider, $apiRequest);
- $apiRequest = $this->prepare($apiProvider, $apiRequest);
+ list ($apiProvider, $apiRequest) = $this->prepare($apiProvider, $apiRequest);
$result = $apiProvider->invoke($apiRequest);
return $this->respond($apiProvider, $apiRequest, $result);
* @param array $apiRequest
* The full description of the API request.
* @return array
+ * [0 => ProviderInterface $provider, 1 => array $apiRequest]
* The revised API request.
*/
public function prepare($apiProvider, $apiRequest) {
/** @var \Civi\API\Event\PrepareEvent $event */
$event = $this->dispatcher->dispatch(Events::PREPARE, new PrepareEvent($apiProvider, $apiRequest, $this));
- return $event->getApiRequest();
+ return [$event->getApiProvider(), $event->getApiRequest()];
}
/**
--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\API\Provider;
+
+use Civi\API\Events;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * A wrapping provider overrides an existing API. It has discretion to pass-through
+ * to the original API (0 or many times) or to substitute with entirely different
+ * behavior.
+ *
+ * The WrappingProvider does yield any metadata of its own. It's primarily
+ * intended for dynamically decorating an existing API.
+ */
+class WrappingProvider implements ProviderInterface {
+
+ /**
+ * @var callable
+ * Function($apiRequest, callable $continue)
+ */
+ protected $callback;
+
+ /**
+ * @var ProviderInterface
+ */
+ protected $original;
+
+ /**
+ * WrappingProvider constructor.
+ * @param callable $callback
+ * @param \Civi\API\Provider\ProviderInterface $original
+ */
+ public function __construct($callback, \Civi\API\Provider\ProviderInterface $original) {
+ $this->callback = $callback;
+ $this->original = $original;
+ }
+
+ public function invoke($apiRequest) {
+ // $continue = function($a) { return $this->original->invoke($a); };
+ $continue = [$this->original, 'invoke'];
+ return call_user_func($this->callback, $apiRequest, $continue);
+ }
+
+ public function getEntityNames($version) {
+ // return $version == $this->version ? [$this->entity] : [];
+ throw new \API_Exception("Not support: WrappingProvider::getEntityNames()");
+ }
+
+ public function getActionNames($version, $entity) {
+ // return $version == $this->version && $this->entity == $entity ? [$this->action] : [];
+ throw new \API_Exception("Not support: WrappingProvider::getActionNames()");
+ }
+
+}
--- /dev/null
+<?php
+namespace Civi\API\Event;
+
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Civi\API\Events;
+use Civi\API\Kernel;
+
+/**
+ */
+class PrepareEventTest extends \CiviUnitTestCase {
+ const MOCK_VERSION = 3;
+
+ /**
+ * @var \Symfony\Component\EventDispatcher\EventDispatcher
+ */
+ public $dispatcher;
+
+ /**
+ * @var \Civi\API\Kernel
+ */
+ public $kernel;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->dispatcher = new EventDispatcher();
+ $this->kernel = new Kernel($this->dispatcher);
+ }
+
+ public function getPrepareExamples() {
+ $apiCall = ['Widget', 'frobnicate', ['id' => 98, 'whimsy' => 'green', 'version' => self::MOCK_VERSION]];
+
+ $exs = [];
+
+ $exs[] = ['onPrepare_null', $apiCall, [98 => 'frob[green]']];
+ $exs[] = ['onPrepare_wrapApi', $apiCall, [98 => 'frob[go green] and frob[who green]']];
+
+ return $exs;
+ }
+
+ /**
+ * @param string $onPrepare
+ * Name of a function (within this test class) to register for 'civi.api.prepare' event.
+ * @param array $inputApiCall
+ * @param array $expectResult
+ * @dataProvider getPrepareExamples
+ */
+ public function testOnPrepare($onPrepare, $inputApiCall, $expectResult) {
+ $this->dispatcher->addListener(Events::PREPARE, [$this, $onPrepare]);
+ $this->kernel->registerApiProvider($this->createWidgetFrobnicateProvider());
+ $result = call_user_func_array([$this->kernel, 'run'], $inputApiCall);
+ $this->assertEquals($expectResult, $result['values']);
+ }
+
+ /**
+ * Create an API provider for entity "Widget" with action "frobnicate".
+ *
+ * @return \Civi\API\Provider\ProviderInterface
+ */
+ public function createWidgetFrobnicateProvider() {
+ $provider = new \Civi\API\Provider\AdhocProvider(self::MOCK_VERSION, 'Widget');
+ $provider->addAction('frobnicate', 'access CiviCRM', function ($apiRequest) {
+ return civicrm_api3_create_success([
+ $apiRequest['params']['id'] => sprintf("frob[%s]", $apiRequest['params']['whimsy']),
+ ]);
+ });
+ return $provider;
+ }
+
+ /**
+ * Baseline - run API call without any manipulation of the result
+ *
+ * @param \Civi\API\Event\PrepareEvent $e
+ */
+ public function onPrepare_null(PrepareEvent $e) {
+ // Nothing to do!
+ }
+
+ /**
+ * Wrap the API call. The inputs are altered; the call is run twice; and
+ * the results are combined.
+ *
+ * @param \Civi\API\Event\PrepareEvent $e
+ */
+ public function onPrepare_wrapApi(PrepareEvent $e) {
+ if ($e->getApiRequestSig() === '3.widget.frobnicate') {
+ $e->wrapApi(function($apiRequest, $continue) {
+ $apiRequestA = $apiRequest;
+ $apiRequestB = $apiRequest;
+ $apiRequestA['params']['whimsy'] = 'go ' . $apiRequestA['params']['whimsy'];
+ $apiRequestB['params']['whimsy'] = 'who ' . $apiRequestB['params']['whimsy'];
+ $resultA = $continue($apiRequestA);
+ $resultB = $continue($apiRequestB);
+ $result = [];
+ // Concatenate the separate results and form one result.
+ foreach (array_keys($resultA['values']) as $id) {
+ $result[$id] = $resultA['values'][$id] . ' and ' . $resultB['values'][$id];
+ }
+ return civicrm_api3_create_success($result);
+ });
+ }
+ }
+
+}