From 5bc7c754b9bcde10c913b79912ff42a1c320717d Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 27 Mar 2015 00:00:11 -0700 Subject: [PATCH] _civicrm_api3_basic_array_get - Helper for simple array-backed APIs Suppose you have a small/mid-sized list of records (from JSON, XML, YAML, etc) and want to expose an API for filtering them. You could make a new table and new DAO/BAO and then call _civicrm_api3_basic_get, but _civicrm_api3_basic_array_get would be so much simpler. --- CRM/Utils/Array.php | 21 ++++++ api/v3/utils.php | 55 ++++++++++++++ tests/phpunit/api/v3/UtilsTest.php | 113 +++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) diff --git a/CRM/Utils/Array.php b/CRM/Utils/Array.php index fa658f6881..66af443f11 100644 --- a/CRM/Utils/Array.php +++ b/CRM/Utils/Array.php @@ -866,4 +866,25 @@ class CRM_Utils_Array { return $arrayDiff; } + /** + * Given a 2-dimensional matrix, create a new matrix with a restricted list of columns. + * + * @param array $matrix + * All matrix data, as a list of rows. + * @param array $columns + * List of column names. + * @return array + */ + public static function filterColumns($matrix, $columns) { + $newRows = array(); + foreach ($matrix as $pos => $oldRow) { + $newRow = array(); + foreach ($columns as $column) { + $newRow[$column] = CRM_Utils_Array::value($column, $oldRow); + } + $newRows[$pos] = $newRow; + } + return $newRows; + } + } diff --git a/api/v3/utils.php b/api/v3/utils.php index cab2d4cc31..af33724230 100644 --- a/api/v3/utils.php +++ b/api/v3/utils.php @@ -2266,3 +2266,58 @@ function _civicrm_api3_field_value_check(&$params, $fieldName) { } return array($fieldValue, $op); } + +/** + * Like _civicrm_api3_basic_get, but data is backed by a simple array + * instead of a DAO/BAO. This is useful for small/mid-size data loaded + * from JSON or XML documents. + * + * @param array $params + * API parameters. + * @param array $records + * List of all records. + * @param string $idCol + * The property which defines the ID of a record + * @param array $fields + * List of filterable fields. + * @return array + */ +function _civicrm_api3_basic_array_get($entity, $params, $records, $idCol, $fields) { + $options = _civicrm_api3_get_options_from_params($params, TRUE, $entity, 'get'); + // TODO // $sort = CRM_Utils_Array::value('sort', $options, NULL); + $offset = CRM_Utils_Array::value('offset', $options); + $limit = CRM_Utils_Array::value('limit', $options); + + $matches = array(); + + $currentOffset = 0; + foreach ($records as $record) { + if ($idCol != 'id') { + $record['id'] = $record[$idCol]; + } + $match = TRUE; + foreach ($params as $k => $v) { + if (in_array($k, $fields) && $record[$k] !== $v) { + $match = FALSE; + break; + } + } + if ($match) { + if ($currentOffset >= $offset) { + $matches[$record[$idCol]] = $record; + } + if ($limit && count($matches) >= $limit) { + break; + } + $currentOffset++; + } + } + + $return = CRM_Utils_Array::value('return', $options, array()); + if (!empty($return)) { + $return['id'] = 1; + $matches = CRM_Utils_Array::filterColumns($matches, array_keys($return)); + } + + return civicrm_api3_create_success($matches, $params); +} diff --git a/tests/phpunit/api/v3/UtilsTest.php b/tests/phpunit/api/v3/UtilsTest.php index 1d6b73872d..c5a90b75da 100644 --- a/tests/phpunit/api/v3/UtilsTest.php +++ b/tests/phpunit/api/v3/UtilsTest.php @@ -314,4 +314,117 @@ class api_v3_UtilsTest extends CiviUnitTestCase { $this->assertEquals('HTML', $result['values']['preferred_mail_format']['options']['HTML']); } + public function basicArrayCases() { + $records = array( + array('snack_id' => 'a', 'fruit' => 'apple', 'cheese' => 'swiss'), + array('snack_id' => 'b', 'fruit' => 'grape', 'cheese' => 'cheddar'), + array('snack_id' => 'c', 'fruit' => 'apple', 'cheese' => 'cheddar'), + array('snack_id' => 'd', 'fruit' => 'apple', 'cheese' => 'gouda'), + array('snack_id' => 'e', 'fruit' => 'apple', 'cheese' => 'provolone'), + ); + + $cases[] = array( + $records, + array('version' => 3), // params + array('a', 'b', 'c', 'd', 'e'), // expected results + ); + + $cases[] = array( + $records, + array('version' => 3, 'fruit' => 'apple'), // params + array('a', 'c', 'd', 'e'), // expected results + ); + + $cases[] = array( + $records, + array('version' => 3, 'cheese' => 'cheddar'), + array('b', 'c'), + ); + + return $cases; + } + + /** + * Make a basic API (Widget.get) which allows getting data out of a simple in-memory + * list of records. + * + * @param $records + * The list of all records. + * @param $params + * The filter criteria + * @param array $resultIds + * The records which are expected to match. + * @dataProvider basicArrayCases + */ + public function testBasicArrayGet($records, $params, $resultIds) { + $params['version'] = 3; + + $kernel = new \Civi\API\Kernel(new \Symfony\Component\EventDispatcher\EventDispatcher()); + + $provider = new \Civi\API\Provider\AdhocProvider($params['version'], 'Widget'); + $provider->addAction('get', 'access CiviCRM', function ($apiRequest) use ($records) { + return _civicrm_api3_basic_array_get('Widget', $apiRequest['params'], $records, 'snack_id', array('snack_id', 'fruit', 'cheese')); + }); + $kernel->registerApiProvider($provider); + + $r1 = $kernel->run('Widget', 'get', $params); + $this->assertEquals(count($resultIds), $r1['count']); + $this->assertEquals($resultIds, array_keys($r1['values'])); + $this->assertEquals($resultIds, array_values(CRM_Utils_Array::collect('snack_id', $r1['values']))); + $this->assertEquals($resultIds, array_values(CRM_Utils_Array::collect('id', $r1['values']))); + + $r2 = $kernel->run('Widget', 'get', $params + array('sequential' => 1)); + $this->assertEquals(count($resultIds), $r2['count']); + $this->assertEquals($resultIds, array_values(CRM_Utils_Array::collect('snack_id', $r2['values']))); + $this->assertEquals($resultIds, array_values(CRM_Utils_Array::collect('id', $r2['values']))); + + $r3 = $kernel->run('Widget', 'get', $params + array('options' => array('offset' => 1, 'limit' => 2))); + $slice = array_slice($resultIds, 1, 2); + $this->assertEquals(count($slice), $r3['count']); + $this->assertEquals($slice, array_values(CRM_Utils_Array::collect('snack_id', $r3['values']))); + $this->assertEquals($slice, array_values(CRM_Utils_Array::collect('id', $r3['values']))); + } + + public function testBasicArrayGetReturn() { + $records = array( + array('snack_id' => 'a', 'fruit' => 'apple', 'cheese' => 'swiss'), + array('snack_id' => 'b', 'fruit' => 'grape', 'cheese' => 'cheddar'), + array('snack_id' => 'c', 'fruit' => 'apple', 'cheese' => 'cheddar'), + ); + + $kernel = new \Civi\API\Kernel(new \Symfony\Component\EventDispatcher\EventDispatcher()); + $provider = new \Civi\API\Provider\AdhocProvider(3, 'Widget'); + $provider->addAction('get', 'access CiviCRM', function ($apiRequest) use ($records) { + return _civicrm_api3_basic_array_get('Widget', $apiRequest['params'], $records, 'snack_id', array('snack_id', 'fruit', 'cheese')); + }); + $kernel->registerApiProvider($provider); + + $r1 = $kernel->run('Widget', 'get', array( + 'version' => 3, + 'snack_id' => 'b', + 'return' => 'fruit', + )); + $this->assertAPISuccess($r1); + $this->assertEquals(array('b' => array('id' => 'b', 'fruit' => 'grape')), $r1['values']); + + $r2 = $kernel->run('Widget', 'get', array( + 'version' => 3, + 'snack_id' => 'b', + 'return' => array('fruit', 'cheese'), + )); + $this->assertAPISuccess($r2); + $this->assertEquals(array('b' => array('id' => 'b', 'fruit' => 'grape', 'cheese' => 'cheddar')), $r2['values']); + + $r3 = $kernel->run('Widget', 'get', array( + 'version' => 3, + 'cheese' => 'cheddar', + 'return' => array('fruit'), + )); + $this->assertAPISuccess($r3); + $this->assertEquals(array( + 'b' => array('id' => 'b', 'fruit' => 'grape'), + 'c' => array('id' => 'c', 'fruit' => 'apple'), + ), $r3['values']); + } + } -- 2.25.1