/api4

CiviCRM api version 4

Primary LanguagePHPOtherNOASSERTION

CiviCRM API Version 4

Design Principles

  • TDD - tests come first; writing the tests will inform design decisions.
  • Massively Scalable - built from the outset with a view to support massive scales via asynchronous queues, efficient caching, job prioritisation etc.
  • Clean - leave all the legacy cruft in v3 and start with a clean slate.
  • Consistent - uniformity between all entities as much as possible, minimize oddities.
  • Strict - ditch the aliases, unique names, camelCase conversions, and alternate syntaxes. Params will only be accepted in one format.
  • OOP - use classes in the \Civi namespace - minimize boilerplate via class inheritance/traits.
  • Discoverable - params are self-documenting through fluent style and api reflection; no undocumented params
  • Doable - prioritize new features based on impact and keep scope proportionate to developer capacity.

Input

$params array will be organized into categories, expanding on the "options" convention in v3:

Add complex limiting filters to get and various updating actions using addWhere() and addClause().

The addWhere() method takes filter "triples" [$fieldName, $operator, $criteria]. Operators are one of the basic SQL operators:

  • '=', '<=', '>=', '>', '<', 'LIKE', "<>", "!=",
  • "NOT LIKE", 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN',
  • 'IS NOT NULL', or 'IS NULL'.
// fluent style
\Civi\Api4\Contact::get()
  ->setSelect(['id', 'sort_name'])
  ->addWhere('contact_type', '=', 'Individual')
  ->addOrderBy('sort_name', 'DESC')
  ->setCheckPermissions(TRUE)
  ->execute();

// traditional style
civicrm_api4('Contact', 'get', array(
  'select' => array('id', 'sort_name'),
  'where' => array('contact_type' => 'Individual'),
  'orderBy' => array('sort_name' => 'DESC'),
  'checkPermissions' => TRUE,
));

Using the addClause() method you can define complex logic trees. Each call to addClause() adds another node to the filter. A node may be in one of three forms, with the branch form allowing nesting:

  • leaf: [$fieldName, $operator, $criteria]
  • negated: ['NOT', $node]
  • branch: ['OR|NOT', [$node, $node, ...]]
// more complex Boolean operations
// (get participation records except for $contact_id at $event_id)
$not_first_participant_result = \Civi\Api4\Participant::get()
  ->setSelect(['id'])
  ->addClause(array('NOT',
    array('AND', array(
      array('event_id', '=', $event_id),
      array('contact_id', '=', $contact_id)))))
  ->execute();

// alternative presentation of above example:
$not_first_participant_result_via_or = \Civi\Api4\Participant::get()
  ->setSelect(['id'])
  ->addClause(array('OR', array(
      array('event_id', '!=', $event_id),
      array('contact_id', '!=', $contact_id))))
  ->execute();

Output

The php binding returns an arrayObject. This gives immediate access to the results, plus allows returning additional metadata properties.

$result = \Civi\Api4\Contact::get()->execute();

// you can loop through the results directly
foreach ($result as $contact) {}

// you can just grab the first one
$contact1 = $result->first();

// reindex results on-the-fly (replacement for sequential=1 in v3)
$result->indexBy('id');

// or fetch some metadata about the call
$entity = $result->entity; // "Contact"
$fields = $result->fields; // contact getfields

We can do the something very similar in javascript thanks to js arrays also being objects:

CRM.api4('Contact', 'get', params).done(function(result) {
  // you can loop through the results
  result.forEach(function(contact, n) {});

  // you can just grab the first one
  var contact1 = result[0];

  // or fetch some metadata about the call
  var entity = result.entity; // "Contact"
});

Creating a new action

For update operations extend the get class to allow batch operation (paging)

Upgrading from Version 3:

API4 will be a while before it is ready to take over.

These are notable changes:

  • Use $result->indexBy('id'); rather than sequential => 0.
  • getSingle is gone, use $result->first()

Feature Wishlist

Get Action

  • Joins across all FKs and pseudo FKs.

Error Handling

  • Ability to simulate an api call
  • Report on all errors, not just the first one to be thrown

The JIRA issue number is CRM-17867

Requirements

CiviCRM version 4.7.13+

Discussion

Use the mailing list http://lists.civicrm.org/lists/info/civicrm-api

Contributing

Create a pull-request, or, for frequent contributors, we can give you direct push access to this repo.

Architecture

The API use embedded magic functions to extend generic PHP OOP approaches and provide easy to use naming, autoloading and self-documentation. In order for the magic to work, coders extending the API need to use consistent paths, class names and class name-spacing.

API V4 entities have both general and specific single class actions. Specific single-class action class are named \Civi\API\V4\Entity\[$entity]\[ucfirst($action)] and generic actions \Civi\API\V4\Action\[ucfirst($action)]. Although called as static entity class methods, each action is implemented as its own class courtesy of some magic in Civi\API\V4\Entity::__callStatic().

A series of action classes inherit from the base Action class (GetActions, GetFields, Create, Get, Update, Delete).

Update actions extend the Get class allowing them to perform bulk operations.

The Action class uses the magic __call() method to set, add and get parameters. The base action execute() method calls the core civi_api_kernel service runRequest() method. Action objects find their business access objects via V3 API code.

Each action object has a _run() method that accepts a decorated arrayobject (Result) as a parameter and is accessed by the action's execute() method.

All action classes accept an entity with their constructor and use the standard PHP ReflectionClass for metadata tracking with a custom ReflectionUtils class to extract PHP comments. The metadata is available via getParams() and getParamInfo() methods. Each action object is able to report its entitiy class name (getEntity()) and action verb (getAction()).

Each action object also has an $options property and a set of methods (offsetExists(), offsetGet(), offsetSet() and offsetUnset()) that act as interface to a thisArrayStorage property.

The get action class uses a Api4SelectQuery object (based on the core SelectQuery object which uses the V3 API utilities and the CRM_Utils_SQL_Select class) to execute the query based on the action's select, where, orderBy, limit and offset parameters.

The GetActions action globs the Civi/API/V4/Entity/[ENTITY_NAME] subdirectories of the [get_include_path()](http://php.net/manual/en/function.get-include-path.php) then the Civi/API/V4/Action subdirectories for generic actions. In the event of duplicate actions, only the first is reported.

The GetFields action uses the [BAO]->fields() method.

todo: ActionObjectProvider, implements the Symfony EventSubscriberInterface (the single getSubscribedEvents() method) and the CiviCRM ProviderInterface interfaces (invoke($apiRequest), getEntityNames($version) and getActionNames($version, $entity)).

The API kernel , shared with V3 of the API, is constructed with a Symfony event dispatcher and a collection of apiProviders.

Security

Each action object has a $checkPermissions property. This is set to FALSE for calls from PHP but TRUE for calls from REST.

Tests

Tests are located in the tests directory (surprise!) To run the entire Api4 test suite go to the api4 extension directory and type phpunit4 from the command line.