From 0f643fb2c7553ba4d6256b2ccc6ef1f7cb7d74cd Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Thu, 20 Mar 2014 21:24:41 -0700 Subject: [PATCH] CRM-14370 - civicrm_api, CiviUnitTestCase - Move main logic to Civi\API\Kernel Also: Allow test-classes to use different API handlers (eg $apiKernel->run() instead of civicrm_api()) Conflicts: Civi/Core/Container.php --- Civi/API/Kernel.php | 226 ++++++++++++++++++++ Civi/Core/Container.php | 15 ++ api/api.php | 162 +------------- tests/phpunit/CiviTest/CiviUnitTestCase.php | 24 ++- 4 files changed, 260 insertions(+), 167 deletions(-) create mode 100644 Civi/API/Kernel.php diff --git a/Civi/API/Kernel.php b/Civi/API/Kernel.php new file mode 100644 index 0000000000..785e0ff264 --- /dev/null +++ b/Civi/API/Kernel.php @@ -0,0 +1,226 @@ + + */ + protected $apiProviders; + + function __construct($dispatcher, $apiProviders = array()) { + $this->apiProviders = $apiProviders; + $this->dispatcher = $dispatcher; + } + + /** + * @param string $entity + * type of entities to deal with + * @param string $action + * create, get, delete or some special action name. + * @param array $params + * array to be passed to function + * @param null $extra + * + * @return array|int + */ + public function run($entity, $action, $params, $extra) { + $apiRequest = array(); + $apiRequest['entity'] = \CRM_Utils_String::munge($entity); + $apiRequest['action'] = \CRM_Utils_String::munge($action); + $apiRequest['version'] = civicrm_get_api_version($params); + $apiRequest['params'] = $params; + $apiRequest['extra'] = $extra; + + $apiWrappers = array( + \CRM_Utils_API_HTMLInputCoder::singleton(), + \CRM_Utils_API_NullOutputCoder::singleton(), + \CRM_Utils_API_ReloadOption::singleton(), + \CRM_Utils_API_MatchOption::singleton(), + ); + \CRM_Utils_Hook::apiWrappers($apiWrappers, $apiRequest); + + try { + require_once ('api/v3/utils.php'); + require_once 'api/Exception.php'; + if (!is_array($params)) { + throw new \API_Exception('Input variable `params` is not an array', 2000); + } + _civicrm_api3_initialize(); + $errorScope = \CRM_Core_TemporaryErrorScope::useException(); + // look up function, file, is_generic + $apiRequest += _civicrm_api_resolve($apiRequest); + if (strtolower($action) == 'create' || strtolower($action) == 'delete' || strtolower($action) == 'submit') { + $apiRequest['is_transactional'] = 1; + $transaction = new \CRM_Core_Transaction(); + } + + // support multi-lingual requests + if ($language = \CRM_Utils_Array::value('option.language', $params)) { + _civicrm_api_set_locale($language); + } + + _civicrm_api3_api_check_permission($apiRequest['entity'], $apiRequest['action'], $apiRequest['params']); + $fields = _civicrm_api3_api_getfields($apiRequest); + // we do this before we + _civicrm_api3_swap_out_aliases($apiRequest, $fields); + if (strtolower($action) != 'getfields') { + if (empty($apiRequest['params']['id'])) { + $apiRequest['params'] = array_merge(_civicrm_api3_getdefaults($apiRequest, $fields), $apiRequest['params']); + } + //if 'id' is set then only 'version' will be checked but should still be checked for consistency + civicrm_api3_verify_mandatory($apiRequest['params'], NULL, _civicrm_api3_getrequired($apiRequest, $fields)); + } + + // For input filtering, process $apiWrappers in forward order + foreach ($apiWrappers as $apiWrapper) { + $apiRequest = $apiWrapper->fromApiInput($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']) { + _civicrm_api3_validate_fields($apiRequest['entity'], $apiRequest['action'], $apiRequest['params'], $fields); + + $result = isset($extra) ? $function($apiRequest['params'], $extra) : $function($apiRequest['params']); + } + else { + return civicrm_api3_create_error("API (" . $apiRequest['entity'] . ", " . $apiRequest['action'] . ") does not exist (join the API team and implement it!)"); + } + + // For output filtering, process $apiWrappers in reverse order + foreach (array_reverse($apiWrappers) as $apiWrapper) { + $result = $apiWrapper->toApiOutput($apiRequest, $result); + } + + if (\CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { + if ($result['is_error'] === 0) { + return 1; + } + else { + return 0; + } + } + if (!empty($apiRequest['params']['format.only_id']) && isset($result['id'])) { + return $result['id']; + } + if (\CRM_Utils_Array::value('is_error', $result, 0) == 0) { + _civicrm_api_call_nested_api($apiRequest['params'], $result, $apiRequest['action'], $apiRequest['entity'], $apiRequest['version']); + } + if (function_exists('xdebug_time_index') + && \CRM_Utils_Array::value('debug', $apiRequest['params']) + // result would not be an array for getvalue + && is_array($result) + ) { + $result['xdebug']['peakMemory'] = xdebug_peak_memory_usage(); + $result['xdebug']['memory'] = xdebug_memory_usage(); + $result['xdebug']['timeIndex'] = xdebug_time_index(); + } + + return $result; + } catch (PEAR_Exception $e) { + if (\CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { + return 0; + } + $error = $e->getCause(); + if ($error instanceof DB_Error) { + $data["error_code"] = DB::errorMessage($error->getCode()); + $data["sql"] = $error->getDebugInfo(); + } + if (!empty($apiRequest['params']['debug'])) { + if (method_exists($e, 'getUserInfo')) { + $data['debug_info'] = $error->getUserInfo(); + } + if (method_exists($e, 'getExtraData')) { + $data['debug_info'] = $data + $error->getExtraData(); + } + $data['trace'] = $e->getTraceAsString(); + } + else { + $data['tip'] = "add debug=1 to your API call to have more info about the error"; + } + $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest); + if (!empty($apiRequest['is_transactional'])) { + $transaction->rollback(); + } + return $err; + } + catch (\API_Exception $e) { + if (!isset($apiRequest)) { + $apiRequest = array(); + } + if (\CRM_Utils_Array::value('format.is_success', \CRM_Utils_Array::value('params', $apiRequest)) == 1) { + return 0; + } + $data = $e->getExtraParams(); + $data['entity'] = \CRM_Utils_Array::value('entity', $apiRequest); + $data['action'] = \CRM_Utils_Array::value('action', $apiRequest); + $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest, $e->getCode()); + if (\CRM_Utils_Array::value('debug', \CRM_Utils_Array::value('params', $apiRequest)) + && empty($data['trace']) // prevent recursion + ) { + $err['trace'] = $e->getTraceAsString(); + } + if (!empty($apiRequest['is_transactional'])) { + $transaction->rollback(); + } + return $err; + } + catch (\Exception $e) { + if (\CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { + return 0; + } + $data = array(); + $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest, $e->getCode()); + if (!empty($apiRequest['params']['debug'])) { + $err['trace'] = $e->getTraceAsString(); + } + if (!empty($apiRequest['is_transactional'])) { + $transaction->rollback(); + } + return $err; + } + + } +} \ No newline at end of file diff --git a/Civi/Core/Container.php b/Civi/Core/Container.php index a53af70aff..b2388f4c3a 100644 --- a/Civi/Core/Container.php +++ b/Civi/Core/Container.php @@ -64,6 +64,12 @@ class Container { )) ->setFactoryService(self::SELF)->setFactoryMethod('createEventDispatcher'); + $container->setDefinition('civi_api_kernel', new Definition( + '\Civi\API\Kernel', + array(new Reference('dispatcher')) + )) + ->setFactoryService(self::SELF)->setFactoryMethod('createApiKernel'); + return $container; } @@ -74,4 +80,13 @@ class Container { $dispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher(); return $dispatcher; } + + /** + * @param \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher + * @return \Civi\API\Kernel + */ + public function createApiKernel($dispatcher) { + $kernel = new \Civi\API\Kernel($dispatcher, array()); + return $kernel; + } } diff --git a/api/api.php b/api/api.php index 1c68039ad1..d0aa4653e9 100644 --- a/api/api.php +++ b/api/api.php @@ -22,167 +22,7 @@ * @return array|int */ function civicrm_api($entity, $action, $params, $extra = NULL) { - $apiRequest = array(); - $apiRequest['entity'] = CRM_Utils_String::munge($entity); - $apiRequest['action'] = CRM_Utils_String::munge($action); - $apiRequest['version'] = civicrm_get_api_version($params); - $apiRequest['params'] = $params; - $apiRequest['extra'] = $extra; - - $apiWrappers = array( - CRM_Utils_API_HTMLInputCoder::singleton(), - CRM_Utils_API_NullOutputCoder::singleton(), - CRM_Utils_API_ReloadOption::singleton(), - CRM_Utils_API_MatchOption::singleton(), - ); - CRM_Utils_Hook::apiWrappers($apiWrappers,$apiRequest); - - try { - require_once ('api/v3/utils.php'); - require_once 'api/Exception.php'; - if (!is_array($params)) { - throw new API_Exception('Input variable `params` is not an array', 2000); - } - _civicrm_api3_initialize(); - $errorScope = CRM_Core_TemporaryErrorScope::useException(); - // look up function, file, is_generic - $apiRequest += _civicrm_api_resolve($apiRequest); - if (strtolower($action) == 'create' || strtolower($action) == 'delete' || strtolower($action) == 'submit') { - $apiRequest['is_transactional'] = 1; - $transaction = new CRM_Core_Transaction(); - } - - // support multi-lingual requests - if ($language = CRM_Utils_Array::value('option.language', $params)) { - _civicrm_api_set_locale($language); - } - - _civicrm_api3_api_check_permission($apiRequest['entity'], $apiRequest['action'], $apiRequest['params']); - $fields = _civicrm_api3_api_getfields($apiRequest); - // we do this before we - _civicrm_api3_swap_out_aliases($apiRequest, $fields); - if (strtolower($action) != 'getfields') { - if (empty($apiRequest['params']['id'])) { - $apiRequest['params'] = array_merge(_civicrm_api3_getdefaults($apiRequest, $fields), $apiRequest['params']); - } - //if 'id' is set then only 'version' will be checked but should still be checked for consistency - civicrm_api3_verify_mandatory($apiRequest['params'], NULL, _civicrm_api3_getrequired($apiRequest, $fields)); - } - - // For input filtering, process $apiWrappers in forward order - foreach ($apiWrappers as $apiWrapper) { - $apiRequest = $apiWrapper->fromApiInput($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']) { - _civicrm_api3_validate_fields($apiRequest['entity'], $apiRequest['action'], $apiRequest['params'], $fields); - - $result = isset($extra) ? $function($apiRequest['params'], $extra) : $function($apiRequest['params']); - } - else { - return civicrm_api3_create_error("API (" . $apiRequest['entity'] . ", " . $apiRequest['action'] . ") does not exist (join the API team and implement it!)"); - } - - // For output filtering, process $apiWrappers in reverse order - foreach (array_reverse($apiWrappers) as $apiWrapper) { - $result = $apiWrapper->toApiOutput($apiRequest, $result); - } - - if (CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { - if ($result['is_error'] === 0) { - return 1; - } - else { - return 0; - } - } - if (!empty($apiRequest['params']['format.only_id']) && isset($result['id'])) { - return $result['id']; - } - if (CRM_Utils_Array::value('is_error', $result, 0) == 0) { - _civicrm_api_call_nested_api($apiRequest['params'], $result, $apiRequest['action'], $apiRequest['entity'], $apiRequest['version']); - } - if (function_exists('xdebug_time_index') - && CRM_Utils_Array::value('debug', $apiRequest['params']) - // result would not be an array for getvalue - && is_array($result) - ) { - $result['xdebug']['peakMemory'] = xdebug_peak_memory_usage(); - $result['xdebug']['memory'] = xdebug_memory_usage(); - $result['xdebug']['timeIndex'] = xdebug_time_index(); - } - - return $result; - } - catch(PEAR_Exception $e) { - if (CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { - return 0; - } - $error = $e->getCause(); - if ($error instanceof DB_Error) { - $data["error_code"] = DB::errorMessage($error->getCode()); - $data["sql"] = $error->getDebugInfo(); - } - if (!empty($apiRequest['params']['debug'])) { - if(method_exists($e, 'getUserInfo')) { - $data['debug_info'] = $error->getUserInfo(); - } - if(method_exists($e, 'getExtraData')) { - $data['debug_info'] = $data + $error->getExtraData(); - } - $data['trace'] = $e->getTraceAsString(); - } - else{ - $data['tip'] = "add debug=1 to your API call to have more info about the error"; - } - $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest); - if (!empty($apiRequest['is_transactional'])) { - $transaction->rollback(); - } - return $err; - } - catch (API_Exception $e){ - if(!isset($apiRequest)){ - $apiRequest = array(); - } - if (CRM_Utils_Array::value('format.is_success', CRM_Utils_Array::value('params',$apiRequest)) == 1) { - return 0; - } - $data = $e->getExtraParams(); - $data['entity'] = CRM_Utils_Array::value('entity', $apiRequest); - $data['action'] = CRM_Utils_Array::value('action', $apiRequest); - $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest, $e->getCode()); - if (CRM_Utils_Array::value('debug', CRM_Utils_Array::value('params',$apiRequest)) - && empty($data['trace']) // prevent recursion - ) { - $err['trace'] = $e->getTraceAsString(); - } - if (!empty($apiRequest['is_transactional'])) { - $transaction->rollback(); - } - return $err; - } - catch(Exception $e) { - if (CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { - return 0; - } - $data = array(); - $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest, $e->getCode()); - if (!empty($apiRequest['params']['debug'])) { - $err['trace'] = $e->getTraceAsString(); - } - if (!empty($apiRequest['is_transactional'])) { - $transaction->rollback(); - } - return $err; - } + return \Civi\Core\Container::singleton()->get('civi_api_kernel')->run($entity, $action, $params, $extra); } /** diff --git a/tests/phpunit/CiviTest/CiviUnitTestCase.php b/tests/phpunit/CiviTest/CiviUnitTestCase.php index cd8d3dd560..f28677abd2 100644 --- a/tests/phpunit/CiviTest/CiviUnitTestCase.php +++ b/tests/phpunit/CiviTest/CiviUnitTestCase.php @@ -680,6 +680,18 @@ class CiviUnitTestCase extends PHPUnit_Extensions_Database_TestCase { $this->assertEquals($result, $expected, "api result array comparison failed " . $prefix . print_r($result, TRUE) . ' was compared to ' . print_r($expected, TRUE)); } + /** + * A stub for the API interface. This can be overriden by subclasses to change how the API is called. + * + * @param $entity + * @param $action + * @param $params + * @return array|int + */ + function civicrm_api($entity, $action, $params) { + return civicrm_api($entity, $action, $params); + } + /** * This function exists to wrap api functions * so we can ensure they succeed & throw exceptions without litterering the test with checks @@ -706,7 +718,7 @@ class CiviUnitTestCase extends PHPUnit_Extensions_Database_TestCase { case 'getcount' : return $this->callAPISuccessGetCount($entity, $params, $checkAgainst); } - $result = civicrm_api($entity, $action, $params); + $result = $this->civicrm_api($entity, $action, $params); $this->assertAPISuccess($result, "Failure in api call for $entity $action"); return $result; } @@ -730,7 +742,7 @@ class CiviUnitTestCase extends PHPUnit_Extensions_Database_TestCase { 'version' => $this->_apiversion, 'debug' => 1, ); - $result = civicrm_api($entity, 'getvalue', $params); + $result = $this->civicrm_api($entity, 'getvalue', $params); if($type){ if($type == 'integer'){ // api seems to return integers as strings @@ -761,7 +773,7 @@ class CiviUnitTestCase extends PHPUnit_Extensions_Database_TestCase { 'version' => $this->_apiversion, 'debug' => 1, ); - $result = civicrm_api($entity, 'getsingle', $params); + $result = $this->civicrm_api($entity, 'getsingle', $params); if(!is_array($result) || !empty($result['is_error']) || isset($result['values'])) { throw new Exception('Invalid getsingle result' . print_r($result, TRUE)); } @@ -790,7 +802,7 @@ class CiviUnitTestCase extends PHPUnit_Extensions_Database_TestCase { 'version' => $this->_apiversion, 'debug' => 1, ); - $result = civicrm_api($entity, 'getcount', $params); + $result = $this->civicrm_api($entity, 'getcount', $params); if(!is_integer($result) || !empty($result['is_error']) || isset($result['values'])) { throw new Exception('Invalid getcount result : ' . print_r($result, TRUE) . " type :" . gettype($result)); } @@ -835,7 +847,7 @@ class CiviUnitTestCase extends PHPUnit_Extensions_Database_TestCase { 'version' => $this->_apiversion, ); } - $result = civicrm_api($entity, $action, $params); + $result = $this->civicrm_api($entity, $action, $params); $this->assertAPIFailure($result, "We expected a failure for $entity $action but got a success"); return $result; } @@ -1582,7 +1594,7 @@ class CiviUnitTestCase extends PHPUnit_Extensions_Database_TestCase { 'contact_id.1' => $contactId, 'group_id' => 1, ); - civicrm_api('GroupContact', 'Delete', $params); + $this->civicrm_api('GroupContact', 'Delete', $params); } /** -- 2.25.1