Differences Between API v3 and v4¶
APIv4 is broadly similar to APIv3. Both are designed for reading and writing data. Both use entities, actions, and parameters. However, APIv4 is specifically a breaking-change which aims to reduce ambiguity and improve flexibility and consistency.
This document walks through a list of specific differences. As you consider them, it may help to have a concrete example expressed in both APIv3 and APIv4:
APIv3 | APIv4 |
---|---|
Procedural-array style:1: $res = civicrm_api3('Contact', 'get', [ 2: 'sequential' => 1, 3: 'check_permissions' => 0, // default 4: 'first_name' => 'Bob', 5: 'return' => 'id,display_name', 6: 'options' => [ 7: 'limit' => 2, 8: 'offset' => 2, 9: ], 10: ]); 11: 12: foreach ($res['values'] as $row) { 13: echo $row['display_name']; 14: } |
Procedural-array style:1: $result = civicrm_api4('Contact', 'get', [ 2: 'checkPermissions' => FALSE, 3: 'where' => [['first_name', '=', 'Bob']], 4: 'select' => ['id', 'display_name'], 5: 'limit' => 2, 6: 'offset' => 2, 7: ]); 8: 9: foreach ($result as $row) { 10: echo $row['display_name']; 11: } 1: $result = \Civi\Api4\Contact::get() 2: ->setCheckPermissions(FALSE) 3: ->addWhere(['first_name', '=', 'Bob']) 4: ->addSelect(['id', 'display_name']) 5: ->setLimit(2) 6: ->setOffset(2) 7: ->execute(); 8: 9: foreach ($result as $row) { 10: echo $row['display_name']; 11: } |
Input¶
APIv4 reflects the ongoing efforts present through the lifecycle of APIv3 toward uniform and discreet input parameters.
For a little history... If you used early versions of APIv3, you might have written some code like this:
civicrm_api3('Contact', 'get', array(
'check_permissions' => 0,
'first_name' => 'Elizabeth',
'return' => 'id,display_name',
'rowCount' => 1000,
'offset' => 2,
));
You may notice that there are no subordinate arrays -- everything goes into one flat list of parameters. As the system grew, this became a bit awkward:
- What happens if you want to filter on a field named
return
oraction
orrowCount
? - How do you ensure that the same option gets the same name across all entities (
rowCount
vslimit
)? - Why does
first_name
use snake_case whilerowCount
uses lowerCamelCase? - Why is
Contact.get
the only API to supportrowCount
?
Over time, APIv3 evolved so that this example would be more typical:
civicrm_api3('Contact', 'get', [
'check_permissions' => FALSE,
'first_name' => 'Elizabeth',
'return' => ['id','display_name'],
'options' => ['limit' => 1000, 'offset' => 2],
]);
Observe:
- The
options
adds a place where you can define parameters without concern for conflicts. - The new generation of
options
are more standardized - they often have generic implementations that work with multiple entities/actions. - The top-level still contains a mix of option fields (like
return
) and data or query fields (likefirst_name
). - The old options at the top-level are deprecated but still around.
APIv4 presented an opportunity to break backward compatibility and thereby become more consistent. In APIv4, a typical call would look like:
civicrm_api4('Contact', 'get', [
'checkPermissions' => FALSE,
'where' => [['first_name', '=', 'Elizabeth']],
'select' => ['id', 'display_name'],
'limit' => 1000,
'offset' => 2,
]);
Key things to note:
- The
options
array is completely gone. The params array is the list of options. - Most items in the params array have shared/generic implementations - ensuring consistent naming and behavior for every api entity.
- The data fields (e.g.
id
,display_name
, andfirst_name
) no longer appear at the top. They always appear beneath some other param, such aswhere
orselect
. - In APIv3, it was possible (but deprecated) to call the API using a lower-case entity name (eg activity.get). This behavior has been removed in APIv4 and using lower-case entity names will cause an error. Use the proper CamelCase representation of the Entity name when making API calls.
API Wrapper¶
- APIv4 supports two notations in PHP:
- Procedural/array style:
civicrm_api4('Entity', 'action', $params)
- Object-oriented style:
\Civi\Api4\Entity::action()->...->execute()
- Procedural/array style:
- When using OOP style in an IDE, most actions and parameters can benefit from auto-completion and type-checking.
$checkPermissions
always defaults toTRUE
. In APIv3, the default depended on the environment (TRUE
in REST/Javascript;FALSE
in PHP).- Instead of APIv3's
sequential
param, a more flexibleindex
controls how results are returned. In traditional style is is the 4th parameter to the api function:- Passing a string will index all results by that key e.g.
civicrm_api4('Contact', 'get', $params, 'id')
will index by id. - Passing a number will return the result at that index e.g.
civicrm_api4('Contact', 'get', $params, 0)
will return the first result and is the same as\Civi\Api4\Contact::get()->execute()->first()
.-1
is the equivalent of$result->last()
.
- Passing a string will index all results by that key e.g.
- When chaining API calls together, back-references to values from the main API call must be explicitly given (discoverable in the API Explorer).
Actions¶
- For
get
, the defaultlimit
has changed. If you send an API call without an explicit limit, then it will return all records. (In v3, it would silently apply a default of 25.) However, if you use the API Explorer, it will recommend a default limit of 25. - The
create
action is now only used for creating new items (no more implicit update by passing an id to v3create
). - The
save
action in v4 is most similar to v3'screate
, with the difference that it takes an array of one or more records instead of a single record. Like APIv3'screate
, it infers the action based on the presence ofid
in each record. Update
andDelete
can be performed on multiple items at once by specifying awhere
clause, vs a single item by id in v3. Unlike v3, they will not complain if no matching items are found to update/delete and will return an empty result instead of an error.getsingle
is gone, use$result->first()
orindex
0
.getoptions
is no longer a standalone action, but part ofgetFields
.replace
requires at least one value; if there are no values to replace, usedelete
.
Output¶
Result
is anArrayObject
rather than a plainarray
.- In PHP, you can iterate over the
ArrayObject
(foreach ($myResult as $record)
), or you can call methods like$result->first()
or$result->indexBy('foo')
. - By default, results are indexed sequentially (
0,1,2,3,...
like APIv3'ssequential => 1
). You may optionally index byid
,name
, or any other field, as in:- (Procedural-style; use
$index
parameter):civicrm_api4('Contact', 'get', [], 'id')
- (OOP-style; use
indexBy()
method):\Civi\Api4\Contact::get()->execute()->indexBy('id')
- (Procedural-style; use
- Custom fields are refered to by name rather than id. E.g. use
constituent_information.Most_Important_Issue
instead ofcustom_4
.