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 two purposes:
-
Declare the API entity by its existance.
- 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.
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)
.
If you need to use array functions such as is_array
on the result you will need to call getArrayCopy()
first i.e. $result->getArrayCopy()
which basically copies the ArrayObject into an actual Array.
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
.
DAO vs Ad-hoc Entities¶
There are two categories of APIv4 entities: standard (aka DAO) entities, and ad-hoc entities.
- Standard entities correspond to 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. - Ad-hoc entities extend the
\Civi\Api4\Generic\AbstractEntity
class directly. They have the flexibility to implement any actions, using any datasource. They 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 (standard) Actions¶
All standard 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. 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. 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. -
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.