_civicrm_api3_basic_array_get - Helper for simple array-backed APIs
authorTim Otten <totten@civicrm.org>
Fri, 27 Mar 2015 07:00:11 +0000 (00:00 -0700)
committerTim Otten <totten@civicrm.org>
Tue, 14 Jul 2015 04:00:05 +0000 (21:00 -0700)
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
api/v3/utils.php
tests/phpunit/api/v3/UtilsTest.php

index fa658f68818a0234d45d8e13eb79ba594ebe19ca..66af443f11523f5c5b9e6f63b3da6f75dc8753c2 100644 (file)
@@ -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;
+  }
+
 }
index cab2d4cc31b5d6aca38110ab7af6791e716fc9a6..af337242301761db7f00f0852e16078f010dedfd 100644 (file)
@@ -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);
+}
index 1d6b73872dff85269754198acff11e15e9ad01d0..c5a90b75dae3d3d3c43d3dbf455738ef863338ce 100644 (file)
@@ -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']);
+  }
+
 }