From 70be69e231b5fc67e2af0530ea016ade54d25e0f Mon Sep 17 00:00:00 2001
From: Tim Otten
+ * An offset to check for.
+ *
+ * Whether a offset exists
+ * @link http://php.net/manual/en/arrayaccess.offsetexists.php
+ * @param mixed $offset
+ * The return value will be casted to boolean if non-boolean was returned.
+ */
+ public function offsetExists($offset) {
+ return array_key_exists($offset, $this->data);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to retrieve
+ * @link http://php.net/manual/en/arrayaccess.offsetget.php
+ * @param mixed $offset
+ * The offset to retrieve. + *
+ * @return mixed Can return all value types. + */ + public function offsetGet($offset) { + return $this->data[$offset]; + } + + /** + * (PHP 5 >= 5.0.0)+ * The offset to assign the value to. + *
+ * @param mixed $value+ * The value to set. + *
+ * @return void + */ + public function offsetSet($offset, $value) { + $this->data[$offset] = $value; + } + + /** + * (PHP 5 >= 5.0.0)+ * The offset to unset. + *
+ * @return void + */ + public function offsetUnset($offset) { + unset($this->data[$offset]); + } + + /** + * (PHP 5 >= 5.0.0)+ * The return value is cast to an integer. + */ + public function count() { + return count($this->data); + } + + +} \ No newline at end of file diff --git a/Civi/API/Kernel.php b/Civi/API/Kernel.php index 91e55881cc..9740466574 100644 --- a/Civi/API/Kernel.php +++ b/Civi/API/Kernel.php @@ -72,6 +72,8 @@ class Kernel { */ $apiProvider = NULL; + // TODO Define alternative calling convention makes it easier to construct $apiRequest + // without the ambiguity of "data" vs "options" $apiRequest = $this->createRequest($entity, $action, $params, $extra); try { @@ -126,16 +128,100 @@ class Kernel { * @param string $action * @param array $params * @param mixed $extra - * @return array the request descriptor + * @return array the request descriptor; keys: + * - version: int + * - entity: string + * - action: string + * - params: array (string $key => mixed $value) [deprecated in v4] + * - extra: unspecified + * - fields: NULL|array (string $key => array $fieldSpec) + * - options: \CRM_Utils_OptionBag derived from params [v4-only] + * - data: \CRM_Utils_OptionBag derived from params [v4-only] + * - chains: unspecified derived from params [v4-only] */ public function createRequest($entity, $action, $params, $extra) { - $apiRequest = array(); - $apiRequest['entity'] = \CRM_Utils_String::munge($entity); - $apiRequest['action'] = \CRM_Utils_String::munge($action); + $apiRequest = array(); // new \Civi\API\Request(); $apiRequest['version'] = civicrm_get_api_version($params); $apiRequest['params'] = $params; $apiRequest['extra'] = $extra; $apiRequest['fields'] = NULL; + + if ($apiRequest['version'] <= 3) { + // APIv1-v3 munges entity/action names, which means that the same name can be written + // multiple ways. That makes it harder to work with. + $apiRequest['entity'] = \CRM_Utils_String::munge($entity); + $apiRequest['action'] = \CRM_Utils_String::munge($action); + } + else { + // APIv4 requires exact entity/action name; deviations should cause errors + if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $entity)) { + throw new \API_Exception("Malformed entity"); + } + if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $action)) { + throw new \API_Exception("Malformed action"); + } + $apiRequest['entity'] = $entity; + $apiRequest['action'] = $action; + } + + // APIv1-v3 mix data+options in $params which means that each API callback is responsible + // for splitting the two. In APIv4, the split is done systematically so that we don't + // so much parsing logic spread around. + if ($apiRequest['version'] >= 4) { + $options = array(); + $data = array(); + $chains = array(); + foreach ($params as $key => $value) { + if ($key == 'options') { + $options = array_merge($options, $value); + } + elseif ($key == 'return') { + if (!isset($options['return'])) { + $options['return'] = array(); + } + $options['return'] = array_merge($options['return'], $value); + } + elseif (preg_match('/^option\.(.*)$/', $key, $matches)) { + $options[$matches[1]] = $value; + } + elseif (preg_match('/^return\.(.*)$/', $key, $matches)) { + if ($value) { + if (!isset($options['return'])) { + $options['return'] = array(); + } + $options['return'][] = $matches[1]; + } + } + elseif (preg_match('/^format\.(.*)$/', $key, $matches)) { + if ($value) { + if (!isset($options['format'])) { + $options['format'] = $matches[1]; + } + else { + throw new \API_Exception("Too many API formats specified"); + } + } + } + elseif (preg_match('/^api\./', $key)) { + // FIXME: represent subrequests as instances of "Request" + $chains[$key] = $value; + } + elseif ($key == 'debug') { + $options['debug'] = $value; + } + elseif ($key == 'version') { + // ignore + } + else { + $data[$key] = $value; + + } + } + $apiRequest['options'] = new \CRM_Utils_OptionBag($options); + $apiRequest['data'] = new \CRM_Utils_OptionBag($data); + $apiRequest['chains'] = $chains; + } + return $apiRequest; } diff --git a/tests/phpunit/Civi/API/KernelTest.php b/tests/phpunit/Civi/API/KernelTest.php new file mode 100644 index 0000000000..1240aaabbd --- /dev/null +++ b/tests/phpunit/Civi/API/KernelTest.php @@ -0,0 +1,103 @@ + $requestParams, 1 => $expectedOptions, 2 => $expectedData, 3 => $expectedChains) + $cases[] = array( + array('version' => 4), // requestParams + array(), // expectedOptions + array(), // expectedData + array(), // expectedChains + ); + $cases[] = array( + array('version' => 4, 'debug' => TRUE), // requestParams + array('debug' => TRUE), // expectedOptions + array(), // expectedData + array(), // expectedChains + ); + $cases[] = array( + array('version' => 4, 'format.is_success' => TRUE), // requestParams + array('format' => 'is_success'), // expectedOptions + array(), // expectedData + array(), // expectedChains + ); + $cases[] = array( + array('version' => 4, 'option.limit' => 15, 'option.foo' => array('bar'), 'options' => array('whiz' => 'bang'), 'optionnotreally' => 'data'), // requestParams + array('limit' => 15, 'foo' => array('bar'), 'whiz' => 'bang'), // expectedOptions + array('optionnotreally' => 'data'), // expectedData + array(), // expectedChains + ); + $cases[] = array( + array('version' => 4, 'return' => array('field1', 'field2'), 'return.field3' => 1, 'return.field4' => 0, 'returnontreally' => 'data'), // requestParams + array('return' => array('field1', 'field2', 'field3')), // expectedOptions + array('returnontreally' => 'data'), // expectedData + array(), // expectedChains + ); + $cases[] = array( + array('version' => 4, 'foo' => array('bar'), 'whiz' => 'bang'), // requestParams + array(), // expectedOptions + array('foo' => array('bar'), 'whiz' => 'bang'), // expectedData + array(), // expectedChains + ); + $cases[] = array( + array('version' => 4, 'api.foo.bar' => array('whiz' => 'bang')), // requestParams + array(), // expectedOptions + array(), // expectedData + array('api.foo.bar' => array('whiz' => 'bang')), // expectedChains + ); + $cases[] = array( + array( + 'version' => 4, + 'option.limit' => 15, + 'options' => array('whiz' => 'bang'), + 'somedata' => 'data', + 'moredata' => array('woosh'), + 'return.field1' => 1, + 'return' => array('field2'), + 'api.first' => array('the first call'), + 'api.second' => array('the second call'), + ), // requestParams + array('limit' => 15, 'whiz' => 'bang', 'return' => array('field1', 'field2')), // expectedOptions + array('somedata' => 'data', 'moredata' => array('woosh')), // expectedData + array('api.first' => array('the first call'), 'api.second' => array('the second call')), // expectedChains + ); + return $cases; + } + + /** + * @param $inputParams + * @param $expectedOptions + * @param $expectedData + * @param $expectedChains + * @dataProvider v4options + */ + function testCreateRequest_v4Options($inputParams, $expectedOptions, $expectedData, $expectedChains) { + $kernel = new Kernel(NULL); + $apiRequest = $kernel->createRequest('MyEntity', 'MyAction', $inputParams, NULL); + $this->assertEquals($expectedOptions, $apiRequest['options']->getArray()); + $this->assertEquals($expectedData, $apiRequest['data']->getArray()); + $this->assertEquals($expectedChains, $apiRequest['chains']); + } + + /** + * @expectedException \API_Exception + */ + function testCreateRequest_v4BadEntity() { + $kernel = new Kernel(NULL); + $kernel->createRequest('Not!Valid', 'create', array('version' => 4), NULL); + } + + /** + * @expectedException \API_Exception + */ + function testCreateRequest_v4BadAction() { + $kernel = new Kernel(NULL); + $kernel->createRequest('MyEntity', 'bad!action', array('version' => 4), NULL); + } +} \ No newline at end of file -- 2.25.1