APIv4 Architecture¶
This guide is intended for anyone wishing to extend the CiviCRM API itself, either in core or in their own extension.
Example extension
As you read this, you can also follow-along with a live demo in the API Explorer by installing the APIv4 Example Extension.
API Entity Classes¶
Every API entity is a class which inherits from \Civi\Api4\Generic\AbstractEntity
. Each class serves the following purposes:
-
Declare the API entity by its existence.
- You can declare a new API (e.g. FooBar) simply by placing the class
\Civi\Api4\FooBar
in the/Civi/Api4
directory of your extension, as long as it inherits from\Civi\Api4\Generic\AbstractEntity
.
- You can declare a new API (e.g. FooBar) simply by placing the class
-
Provide factory functions for that entity's Actions.
-
A
getInfo
function which returns an array of metadata about the entity. In most casesgetInfo
from the base class does not need to be overridden. -
An optional function
permissions
. This function should contain any specific permissions or overrides to themeta
ordefault
permissions necessary for the entity. The function must return an array in a suitable format. The following example would allow theget
action to anyone withaccess CiviEntity
permission, and also shows how to overridemeta
anddefault
permissions:return [ // for meta actions e.g. getActions, getFields etc 'meta' => [<some permission>] // To be used for all actions where there isn't a specific permission set 'default' => [<some permission>], 'get' => ['access CiviEntity'], ];
Further information on permissions is defined in the security chapter.
API Action Classes¶
Every API action is a class which inherits from \Civi\Api4\Generic\AbstractAction
. It has two functions:
-
Store the parameters of the API call.
- Every
protected
class var is considered a parameter (unless it starts with an underscore). - The
$version
parameter refers to the API version (4) and cannot be changed. - The
$_entityName
and$_actionName
vars are set when the Action object is constructed (by the Entity class factory fn, see above). The underscore makes them hidden parameters as they cannot be changed. - Adding a
protected
var to your Action named e.g.$thing
will automatically:- Provide a getter/setter (via
__call
MagicMethod) namedgetThing()
andsetThing()
. - Expose the param in the API Explorer (be sure to add a doc-block as it displays in the help panel).
- Require a value for the param if you add the
@required
annotation.
- Provide a getter/setter (via
- Every
-
Define a
_run()
function to execute the call and return the results.- The
_run()
function is invoked byexecute()
- It gets passed a
Result
object into which it places an array of values to be returned.
- The
The Result Class¶
An API Result
is a PHP ArrayObject. It has three functions:
- Store the results of the API call (accessible via ArrayAccess).
- Store metadata like the Entity & Action names.
- Some actions extend the Result object to store extra metadata. For example
BasicReplaceAction
returns\Civi\Api4\Result\ReplaceResult
which includes the additional$deleted
property to list any items deleted by the operation.
- Some actions extend the Result object to store extra metadata. For example
- Provide convenience methods like
$result->first()
and$result->indexBy($field)
.
Class Inheritance¶
To reduce code duplication and enforce consistency, APIv4 uses PHP class inheritance extensively. Compare these (slightly simplified) examples:
APIv3 Website.php | APIv4 Website.php |
---|---|
function civicrm_api3_website_get($params) { return _civicrm_api3_basic_get('Website', $params); } function civicrm_api3_website_create($params) { return _civicrm_api3_basic_create('Website', $params); } function civicrm_api3_website_delete($params) { return _civicrm_api3_basic_delete('Website', $params); } |
namespace Civi\Api4; class Website extends Generic\DAOEntity { } |
Website is a typical CRUD API using generic functions to perform actions on the civicrm_website
table.
The v3 file needed a function for each action, resulting in a lot of duplicate code across API files.
By taking advantage of class inheritance, APIv4 reduces this boilerplate to nothing; factory functions for the standard set of actions are inherited from Civi\Api4\Generic\DAOEntity
.
Entity Base Classes¶
There are three ways to implement an APIv4 entity:
- Extend DAOEntity: for entities with a database table and
DAO
class. E.g. the Contact API entity corresponds to theCRM_Conatact_DAO_Contact
class and thecivicrm_contact
database table. They extend fromCivi\Api4\Generic\DAOEntity
and thus inherit the standard set of DAO actions. - Extend BasicEntity: to start with a basic set of CRUD actions,
use
Civi\Api4\Generic\BasicEntity
as the base class. You do not need a DAO or even a database table for this type of entity; just supply a php function to read, write and delete records and the API will handle CRUD functionality. - Extend AbstractEntity: for the most flexible type of entity use the
\Civi\Api4\Generic\AbstractEntity
class directly. This gives you the flexibility to implement any actions, using any datasource. These APIs are not required to implement any action exceptGetFields
. See the APIv4 example extension for a working demonstration.
Action Class Hierarchy¶
To standardize parameter names and reduce code duplication, each action class inherits from an abstract parent.
DAO Actions¶
All DAO entities provide factory functions for
DAOGetFieldsAction
,
DAOGetAction
,
DAOCreateAction
,
DAOUpdateAction
,
DAOSaveAction
,
DAODeleteAction
, and
BasicReplaceAction
.
The Get action uses schema metadata to query the database and perform joins with other DAO entities. The Create, Delete, Save & Update actions call the core BAO methods to ensure hooks are invoked when writing to the database.
In most cases the action classes are used as-is, but it's also possible to override a DAO action to add/modify parameters or functionality.
It is also possible to add other ad-hoc actions to any entity;
e.g. Contact::getChecksum
is an ad-hoc action added to the Contact entity.
Building Your Own Actions¶
You can add arbitrary actions to any entity simply by defining a class which extends AbstractAction
or one of the Basic actions.
AbstractAction¶
AbstractAction
is the base class for all API actions. Every action must, at minimum, extend this class.
For example, the Example::random()
action extends this class directly.
Your custom action will define parameters as protected class properties, and implement a _run
function to execute the action.
Before extending AbstractAction
directly, consider if your action could benefit from the features provided by one of the Basic actions:
Basic Actions¶
These classes provide a framework for building custom API actions.
They are all used by the Civi\Api4\Generic\BasicEntity
class.
See the Example entity for a working demonstration.
Each basic action is implemented by supplying a callback function to perform logic and return data; the basic action class handles details like validating param input and formatting & filtering the result.
Basic Actions are designed to be used in 1 of 2 ways:
- Your Entity's action function can construct the basic action directly, passing the callback into the constructor.
-
You can override the basic action class (e.g. for customizing the parameters). See
Example::create()
.-
BasicGetAction
: Used to build actions whose purpose is fetching records. Parameters areselect
,where
,offset
,limit
, andorderBy
. Its built-in array-query engine automatically filters/sorts the raw data returned by your callback according to the parameters. Normally your callback can simply return an array of all existing records, but if performance is a concern, the functions_itemsToGet()
and_isFieldSelected()
can help you optimize your callback to only return results that are needed. -
BasicCreateAction
: Used to create a new record. Parameters arevalues
. Values will be passed into the callback viawriteRecord
, or you can override thewriteRecord
method in your custom create class. SeeExample::create()
. -
BasicUpdateAction
: Used to update records based on a search query. Parameters arevalues
,reload
,where
,offset
,limit
, andorderBy
. Internally callsGet
to obtain records to update, so your entity must implement a get action. Each will be passed into the callback viawriteRecord
, or you can override thewriteRecord
method in your custom update class. -
BasicSaveAction
: Used to create or update multiple records. Parameters arerecords
,defaults
, andreload
. Each will be passed into the callback viawriteRecord
, or you can override thewriteRecord
method in your custom save class. -
BasicBatchAction
: Used to perform an action on records based on a search query. Use this class, for example, to implement Delete. Parameters arewhere
,offset
,limit
, andorderBy
. Internally callsGet
to obtain records, so your entity must implement a get action. Each will be passed into the callback viadoTask
, or you can override thedoTask
method in your custom batch class.Delete
actions can implement theSoftDelete
trait and default to marking records as "trashed" rather than permanently deleting them. -
BasicReplaceAction
: Used to replace a set of records. Parameters arerecords
,default
,reload
,where
,offset
,limit
, andorderBy
. Internally callsGet
to obtain records andSave
to write them, so your entity must implement those actions. -
BasicGetFieldsAction
: Metadata action returns a list of fields for your entity.
-