From 787604ff8d42bab9d61939245948a76581440c6a Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 24 Mar 2014 15:47:56 -0700 Subject: [PATCH] CRM-14370 - API Kernel - Add ProviderInterface. Extract MagicFunctionProvider. --- Civi/API/Event/Event.php | 4 +- Civi/API/Event/ResolveEvent.php | 48 ++++++++++++++ Civi/API/Events.php | 10 ++- Civi/API/Kernel.php | 39 ++++++------ Civi/API/Provider/MagicFunctionProvider.php | 70 +++++++++++++++++++++ Civi/API/Provider/ProviderInterface.php | 37 +++++++++++ Civi/Core/Container.php | 1 + 7 files changed, 185 insertions(+), 24 deletions(-) create mode 100644 Civi/API/Event/ResolveEvent.php create mode 100644 Civi/API/Provider/MagicFunctionProvider.php create mode 100644 Civi/API/Provider/ProviderInterface.php diff --git a/Civi/API/Event/Event.php b/Civi/API/Event/Event.php index 91657fa272..2c521673c2 100644 --- a/Civi/API/Event/Event.php +++ b/Civi/API/Event/Event.php @@ -29,7 +29,7 @@ namespace Civi\API\Event; class Event extends \Symfony\Component\EventDispatcher\Event { /** - * @var object + * @var \Civi\API\Provider\ProviderInterface */ protected $apiProvider; @@ -44,7 +44,7 @@ class Event extends \Symfony\Component\EventDispatcher\Event { } /** - * @return object + * @return \Civi\API\Provider\ProviderInterface */ public function getApiProvider() { return $this->apiProvider; diff --git a/Civi/API/Event/ResolveEvent.php b/Civi/API/Event/ResolveEvent.php new file mode 100644 index 0000000000..849ade2131 --- /dev/null +++ b/Civi/API/Event/ResolveEvent.php @@ -0,0 +1,48 @@ +apiProvider = $apiProvider; + } + + /** + * @param array $apiRequest + */ + public function setApiRequest($apiRequest) { + $this->apiRequest = $apiRequest; + } +} diff --git a/Civi/API/Events.php b/Civi/API/Events.php index 36d9af45fa..4cfbd11dc9 100644 --- a/Civi/API/Events.php +++ b/Civi/API/Events.php @@ -28,7 +28,7 @@ namespace Civi\API; /** * The API kernel dispatches a series of events while processing each API request. - * For a successful API request, the sequence is AUTHORIZE => PREPARE => RESPOND. + * For a successful API request, the sequence is RESOLVE => AUTHORIZE => PREPARE => RESPOND. * If an exception arises in any stage, then the sequence is aborted and the EXCEPTION * event is dispatched. * @@ -45,6 +45,14 @@ class Events { */ const AUTHORIZE = 'api.authorize'; + /** + * Determine which API provider executes the given request. For successful + * execution, at least one listener must invoke $event->setProvider($provider). + * + * @see ResolveEvent + */ + const RESOLVE = 'api.resolve'; + /** * Apply pre-execution logic * diff --git a/Civi/API/Kernel.php b/Civi/API/Kernel.php index 2ab5acc6b6..91e55881cc 100644 --- a/Civi/API/Kernel.php +++ b/Civi/API/Kernel.php @@ -29,6 +29,7 @@ namespace Civi\API; use Civi\API\Event\AuthorizeEvent; use Civi\API\Event\PrepareEvent; use Civi\API\Event\ExceptionEvent; +use Civi\API\Event\ResolveEvent; use Civi\API\Event\RespondEvent; /** @@ -66,6 +67,11 @@ class Kernel { * @return array|int */ public function run($entity, $action, $params, $extra) { + /** + * @var $apiProvider \Civi\API\Provider\ProviderInterface|NULL + */ + $apiProvider = NULL; + $apiRequest = $this->createRequest($entity, $action, $params, $extra); try { @@ -76,38 +82,29 @@ class Kernel { $this->boot(); $errorScope = \CRM_Core_TemporaryErrorScope::useException(); - // look up function, file, is_generic - $apiRequest += _civicrm_api_resolve($apiRequest); - - if (! $this->dispatcher->dispatch(Events::AUTHORIZE, new AuthorizeEvent(NULL, $apiRequest))->isAuthorized()) { - throw new \API_Exception("Authorization failed"); + $resolveEvent = $this->dispatcher->dispatch(Events::RESOLVE, new ResolveEvent($apiRequest)); + $apiRequest = $resolveEvent->getApiRequest(); + $apiProvider = $resolveEvent->getApiProvider(); + if (!$apiProvider) { + throw new \API_Exception("API (" . $apiRequest['entity'] . ", " . $apiRequest['action'] . ") does not exist (join the API team and implement it!)"); } - $apiRequest = $this->dispatcher->dispatch(Events::PREPARE, new PrepareEvent(NULL, $apiRequest))->getApiRequest(); - - $function = $apiRequest['function']; - if ($apiRequest['function'] && $apiRequest['is_generic']) { - // Unlike normal API implementations, generic implementations require explicit - // knowledge of the entity and action (as well as $params). Bundle up these bits - // into a convenient data structure. - $result = $function($apiRequest); - } - elseif ($apiRequest['function'] && !$apiRequest['is_generic']) { - $result = isset($extra) ? $function($apiRequest['params'], $extra) : $function($apiRequest['params']); - } - else { - throw new \API_Exception("API (" . $apiRequest['entity'] . ", " . $apiRequest['action'] . ") does not exist (join the API team and implement it!)"); + if (! $this->dispatcher->dispatch(Events::AUTHORIZE, new AuthorizeEvent($apiProvider, $apiRequest))->isAuthorized()) { + throw new \API_Exception("Authorization failed", 'unauthorized'); } + $apiRequest = $this->dispatcher->dispatch(Events::PREPARE, new PrepareEvent($apiProvider, $apiRequest))->getApiRequest(); + $result = $apiProvider->invoke($apiRequest); + if (\CRM_Utils_Array::value('is_error', $result, 0) == 0) { _civicrm_api_call_nested_api($apiRequest['params'], $result, $apiRequest['action'], $apiRequest['entity'], $apiRequest['version']); } - $responseEvent = $this->dispatcher->dispatch(Events::RESPOND, new RespondEvent(NULL, $apiRequest, $result)); + $responseEvent = $this->dispatcher->dispatch(Events::RESPOND, new RespondEvent($apiProvider, $apiRequest, $result)); return $this->formatResult($apiRequest, $responseEvent->getResponse()); } catch (\Exception $e) { - $this->dispatcher->dispatch(Events::EXCEPTION, new ExceptionEvent($e, NULL, $apiRequest)); + $this->dispatcher->dispatch(Events::EXCEPTION, new ExceptionEvent($e, $apiProvider, $apiRequest)); if ($e instanceof \PEAR_Exception) { $err = $this->formatPearException($e, $apiRequest); diff --git a/Civi/API/Provider/MagicFunctionProvider.php b/Civi/API/Provider/MagicFunctionProvider.php new file mode 100644 index 0000000000..614d28833b --- /dev/null +++ b/Civi/API/Provider/MagicFunctionProvider.php @@ -0,0 +1,70 @@ + array( + array('onApiResolve', Events::W_MIDDLE), + ), + ); + } + + public function onApiResolve(\Civi\API\Event\ResolveEvent $event) { + $apiRequest = $event->getApiRequest(); + $resolved = _civicrm_api_resolve($apiRequest); + if ($resolved['function']) { + $apiRequest += $resolved; + $event->setApiRequest($apiRequest); + $event->setApiProvider($this); + $event->stopPropagation(); + } + } + + public function invoke($apiRequest) { + $function = $apiRequest['function']; + if ($apiRequest['function'] && $apiRequest['is_generic']) { + // Unlike normal API implementations, generic implementations require explicit + // knowledge of the entity and action (as well as $params). Bundle up these bits + // into a convenient data structure. + $result = $function($apiRequest); + } + elseif ($apiRequest['function'] && !$apiRequest['is_generic']) { + $result = isset($extra) ? $function($apiRequest['params'], $extra) : $function($apiRequest['params']); + } + return $result; + } + +} \ No newline at end of file diff --git a/Civi/API/Provider/ProviderInterface.php b/Civi/API/Provider/ProviderInterface.php new file mode 100644 index 0000000000..5fafe53307 --- /dev/null +++ b/Civi/API/Provider/ProviderInterface.php @@ -0,0 +1,37 @@ +addSubscriber(new \Civi\API\Subscriber\TransactionSubscriber()); $dispatcher->addSubscriber(new \Civi\API\Subscriber\I18nSubscriber()); + $dispatcher->addSubscriber(new \Civi\API\Provider\MagicFunctionProvider()); $dispatcher->addSubscriber(new \Civi\API\Subscriber\APIv3SchemaAdapter()); $dispatcher->addSubscriber(new \Civi\API\Subscriber\WrapperAdapter(array( \CRM_Utils_API_HTMLInputCoder::singleton(), -- 2.25.1