Object-Oriented API (OOAPI) is an extension for CiviCRM that provides an object-oriented wrapper for the native APIv3.
Its features include:
- Object-oriented syntax
- More concise than the native APIv3
- Automatic caching to minimise database calls
- An interface that encourages name-based look-ups
- Strict error-checking
- Modelling of parent-child relationships between entities
- Hides some of the native APIv3's bugs and inconsistencies
- Additional features, e.g. the ability to record a contact being added to a group at a specific date
- Provides API for BAOs & DAOs not available in the native APIv3
- Easily extensible to new BAOs & DAOs
// If the contact has a London address, add them to the newsletter.
$contact = CRM_API_Contact::getSingle($contactId);
foreach ($contact->getAddresses() as $address) {
if ($address->city === 'London')
$contact->updateGroupStatus('Newsletter', 'Added', $dateTime);
}
Note that it is helpful to have some familiarity with CiviCRM's native API in order to understand how this extension works.
The extension is licensed under AGPL-3.0.
- PHP v7.0+
- CiviCRM v5.7
This extension has not yet been published for installation via the web UI.
Sysadmins and developers may download the .zip
file for this extension and install it with the command-line tool cv.
cd <extension-dir>
cv dl uk.org.caat.crm.api@https://bitbucket.org/caatuk/uk.org.caat.crm.api/get/3df7e898b4c5.zip
Sysadmins and developers may clone the Git repo for this extension and install it with the command-line tool cv.
git clone https://bitbucket.org/caatuk/uk.org.caat.crm.api.git
cv en api
Each type of entity has its own class. The name of each class is the native API's entity name prefixed by 'CRM_API_', e.g. CRM_API_Contact, CRM_API_Activity, CRM_API_ContributionSoft, etc.
Note: The EntityTag, GroupContact and CustomValue entity types from the native APIv3 do not have corresponding classes in OOAPI. Instead, their functionality is provided by methods of classes that can have tags, groups and custom fields (see below).
public static function create($params, $cache = NULL)
- params: An array of fields and values for the new entity
- cache: TRUE to cache the entity in memory for quicker retrieval; FALSE to not cache; omit for the class's default cache setting
An OOAPI object representing the newly created entity.
$contact = CRM_API_Contact::create([
contact_type => 'Individual',
first_name => 'Mikhail',
last_name => 'Bakunin'
]);
public function update($params, $always = TRUE)
- params: An array of fields and their new values
- always: FALSE to only write to the database if the cached fields have changed
$contact->update([
'birth_date' => '1814-05-30',
'deceased_date' => '1876-07-01',
'is_deceased' => TRUE
]);
public function delete($permanent = TRUE)
- permanent: FALSE if the entity is to be moved to the trash (available for contacts only)
$address->delete();
public static function get($params = [], $cache = NULL, $readFromCache = TRUE)
- params: An array of fields and values to look up matching entities. May include an options array in order to specify sort order or limit the number of entities returned.
- cache: TRUE to cache retrieved entities in memory for quicker retrieval; FALSE to not cache; NULL for the class's default cache setting
- readFromCache: FALSE to ignore the cache and look up entities in the database
An array of OOAPI objects representing any matching entities.
Whereas the native API limits the number of records returned by default, OOAPI does not. A limit may be specified by including an options array in the params, as for the native APIv3.
$contacts = CRM_API_Contact::get(['last_name' => 'Bakunin']);
public static function getSingle($params = [], $required = TRUE, $cache = NULL, $readFromCache = TRUE)
- params: An array of fields and values or an integer ID or a value of the class's default string look-up field (if there is one) with which to uniquely identify an entity
- required: TRUE to throw an exception if no matching entity is found; FALSE to return NULL if no matching entity is found
- cache: TRUE to cache retrieved entities in memory for quicker retrieval; FALSE to not cache; NULL for the class's default cache setting
- readFromCache: FALSE to ignore the cache and look up entities in the database
An OOAPI object representing the matching entity, or NULL if there is no matching entity
$contact = CRM_API_Contact::getSingle($contactId);
public function refresh()
If an entity has been updated in the database without using OOAPI then any OOAPI objects referring to the entity may be out of sync. This function reloads an object's fields from the database.
$contact->refresh();
public function isDeleted()
TRUE if the object represents an entity that no longer exists; FALSE otherwise
if ($address->isDeleted()) ...
public static function getObject($fields, $cache = NULL)
This method constructs an OOAPI object from a BAO, a DAO or an array of fields. It is particularly useful in the post hook.
- fields: A BAO, DAO or array of fields with which to initialise the object
- cache: TRUE to cache the entity in memory for quicker retrieval; FALSE to not cache; omit for the class's default cache setting
An OOAPI object with the specified field values
function myextension_civicrm_post($op, $objectName, $objectId, &$objectRef) {
if ($objectName === 'Individual' && $op === 'edit') {
$contact = CRM_API_Contact::getObject($objectRef);
...
public static function getFromQuery($query, $params = [], $cache = NULL)
- query: An SQL query, which must return all the entity's fields including id
- params: The values for any variables in the query (in the same format as for CRM_Core_DAO::executeQuery)
- cache: TRUE to cache the entity in memory for quicker retrieval; FALSE to not cache; omit for the class's default cache setting
An array of OOAPI objects representing all entities selected by the query.
$emails = CRM_API_Email::getFromQuery("
SELECT * FROM civicrm_email
WHERE email NOT REGEXP '[[:alnum:]\\._-]+@[[:alnum:]\\._-]+'
");
An entity's fields can be accessed as PHP properties. For example:
if (isset($contact->first_name))
$firstName = $contact->first_name;
Field values are returned using appropriate PHP types. String fields are returned as PHP strings, integer fields are returned as PHP integers, floating point fields are returned as PHP floats, boolean fields are returned as PHP booleans, array fields are returned as arrays, and date/time fields are returned as DateTime objects. They may also be set using the same types.
This allows the fields to be operated on intuitively withour having to convert them first. It also allows for function overloading, e.g. where a function does one thing if passed an integer ID value or another thing if passed a string.
(By comparison, the native APIv3 returns many fields as strings when they are not actually strings.)
public function fields()
an array containing all of the entity's field names and values
foreach ($contact->fields() as $fieldName => $fieldValue) ...
Custom field values can be accessed using the custom field name.
$contact = CRM_API_Contact::create([
'contact_type' => 'Individual',
'first_name' => 'Mikhail',
'last_name' => 'Bakunin',
'Favourite_Colour' => 'Black'
]);
Custom fields can also be set individually (unlike ordinary fields, which are read-only and can only be set using the update() method):
$contact->Favourite_Colour = 'red';
Custom field sets that allow multiple records are slightly different. The values are returned as arrays:
foreach ($contact->Job_Titles as $recordId => $jobTitle) ...
But they cannot be set using the same notation. Instead, each field has its own add and update methods. In the methods below, replace CUSTOMFIELD with the name of the custom field.
public function addCUSTOMFIELD($values)
- values: An array of values to be added to the custom field.
$contact->addQualifications(['Computer Science BSc', 'Fine Art MA']);
public function updateCUSTOMFIELD($values)
- values: An array mapping record IDs to new field values
$contact->updateQualifications([$recordId => 'Computer Science BEng']);
Custom fields whose values are selected from a pre-defined list using a Select or Radio control have an associated option group that stores the possible values. This function returns the option group used by such a custom field.
public function getOptionGroup()
A CRM_API_OptionGroup object representing the custom field's option group
$colourOptionGroup = $colourCustomField->getOptionGroup();
public function tag($tag)
- tag: A tag object, ID or name
$contact->tag('Major Donor');
public function untag($tag)
- tag: A tag object, ID or name
$contact->untag('Major Donor');
public function hasTag($tag)
- tag: A tag object, ID or name
if ($contact->hasTag('Major Donor')) ...
public function updateGroupStatus($group, $status, $dateTime = NULL)
- group: A group object, ID or title
- status: One of 'Added', 'Removed' or 'Pending'
- dateTime: A DateTime object or string specifying the date of the status update (defaults to the current date)
$contact->updateGroupStatus('Newsletter', 'Added', '2010-11-18');
public function inGroup($group, $status = 'Added', $readFromCache = TRUE)
- group: A group object, ID or title
- status: One of 'Added', 'Removed' or 'Pending'
- readFromCache: FALSE to ignore the cache and look up membership in the database
A boolean, indicating whether the entity has the specified status in the specified group
if ($contact->inGroup('Newsletter')) ...
OOAPI models parent-child relationships between entities. For each relationship there are methods for parent entities to access their children and for child entities to access their parents. The names of these methods depend on the type of the parent/child entities being accessed, as shown in the table below.
Parent entity | Child entity | Child->parent methods | Parent->child methods |
---|---|---|---|
Contact | Address | Contact | Address(es) |
Contact | Contact | Email(s) | |
Contact | Phone | Contact | Phone(s) |
Contact | Note | Contact | Note(s) |
Contact | Contribution | Contact | Contribution(s) |
Contact | ContributionSoft | Contact | SoftCredit(s) |
Contact | ContributionRecur | Contact | RecurringContribution(s) |
Contact | Participant | Contact | Participant(s) |
Contribution | ContributionSoft | Contribution | SoftCredit(s) |
Mailing | MailingGroup | Mailing | Group(s) |
Mailing | MailingRecipient | Mailing | Recipient(s) |
Mailing | MailingJob | Mailing | Job(s) |
MailingJob | MailingEventQueue | Job | EventQueue(s) |
MailingEventQueue | MailingEventDelivered | Queue | Delivery / Deliveries |
CustomGroup | CustomField | Group | Field(s) |
OptionGroup | OptionValue | Group | Value(s) |
Country | StateProvince | Country | StateProvince(s) |
In the methods listed below, replace the words PARENT and CHILD(REN) with the names from columns three and four of the table above, e.g. getCHILDREN() becomes getAddresses() or getEmails(), and so on.
public function getPARENT($required = TRUE, $cache = NULL, $readFromCache = TRUE)
- required: TRUE to throw an exception if there isn't a parent; FALSE to return NULL if there isn't a parent
- cache: TRUE to cache retrieved parent in memory for quicker retrieval; FALSE to not cache; NULL for the class's default cache setting
- readFromCache: FALSE to ignore the cache and look up entities in the database
An OOAPI object representing the parent entity
$optionGroup = $optionValue->getGroup();
public function getCHILD($field = NULL, $value, $required = TRUE)
- field: The field used to look up the child entity (May be omitted to use the relationship's default string look-up field or default integer look-up field, depending on value.)
- value: Uniquely identifies the child entity
- required: TRUE to throw an exception if the specified value doesn't match a child entity; FALSE to return NULL if the specified value doesn't match a child entity
An OOAPI object representing the matching child entity, or NULL if there is no matching child entity
$webTypeOptionGroup = CRM_API_OptionGroup::getSingle('website_type');
$webTypeOptionValue = $webTypeOptionGroup->getValue('value', $websiteTypeId);
public function getCHILDREN()
An array of OOAPI objects representing all the entity's children.
foreach ($optionGroup->getValues() as $optionValue) ...
public function createCHILD($params, $cache = NULL)
- params: An array of fields and values for the new child entity
- cache: TRUE to cache the entity in memory for quicker retrieval; FALSE to not cache; omit for the class's default cache setting
An OOAPI object representing the newly created child entity.
$email = $contact->createEmail(['email' => 'b.durruti@riseup.net']);
public function updateCHILD($field = NULL, $value, $params)
- field: The field used to look up the child entity (May be omitted to use the relationship's default string look-up field or default integer look-up field, depending on value.)
- value: Uniquely Identifies the child to be updated
- params: An array of fields and their new values
$prefixOptionGroup = CRM_API_OptionGroup::getSingle('individual_prefix');
$prefixOptionGroup->updateValue('Dr.', ['label' => 'Dr']);
public function deleteCHILD($field = NULL, $value, $required)
- field: The field used to look up the child entity (May be omitted to use the relationship's default string look-up field or default integer look-up field, depending on value.)
- value: Uniquely Identifies the child to be deleted
- required: TRUE to throw an exception if there isn't a matching child entity to delete; FALSE to do nothing if there isn't a matching child entity to delete
$prefixOptionGroup = CRM_API_OptionGroup::getSingle('individual_prefix');
$prefixOptionGroup->deleteValue('Lord');
public function deleteCHILDREN()
$contact->deleteAddresses();
OOAPI facilitates the use of names to identify entities. For example:
$activityTypeOptionGroup = CRM_API_OptionGroup::getSingle('activity_type');
$activityTypeId = $activityTypeOptionGroup->getValue('Meeting')->value;
This makes the code easier to read than using hard-coded ID numbers. (It is bad practice to use literal ID numbers in code.)
When any error is encountered, an exception is thrown. These can be caught and handled in the calling code. Converting the exception to a string will reveal the stack trace.
When CiviCRM is in debug mode, OOAPI will check that fields returned by the native API match the parameters passed in, and it will throw an exception if they do not.
By default, entities are cached in memory when they are encountered. This makes repeated look-ups more efficient. However, if a large amount of data is being processed, caching may need to be managed so that it doesn't take up too much memory.
Some methods accept a $cache argument that prevents the method from caching the entity. There are some methods specifically for managing caching:
public function cache()
Puts an entity in the cache (if it isn't there already).
$contact->cache();
public function uncache()
Removes an entity and its child entities from the cache. (The object's field values are still accessible.)
$contact->uncache();
public static function cacheAll()
Gets and caches all entities of a specific type
CRM_API_Country::cacheAll();
public static function uncacheAll()
Removes all entities of a specific type and their children from the cache
CRM_API_Activity::uncacheAll();
final public static function uncacheAllContactData()
Removes all entities that contain contact data from the cache. When batch-processing contacts, call this function after processing each contact in order to prevent the cache from hogging memory.
CRM_API_Entity::uncacheAllContactData();
public static function setCacheByDefault($cacheByDefault)
- cacheByDefault: TRUE to cache by default; FALSE to not cache by default
CRM_API_Activity::setCacheByDefault(FALSE);
public static function diagnostics()
The number of cached entities of each type
foreach (CRM_API_Entity::diagnostics() as $class => $numCached) ...
All entity classes (e.g. CRM_API_Contact) are derived from the abstract base class CRM_API_Entity. It is this class that does most of the work.
Entity classes that can have custom data are derived from CRM_API_ExtendableEntity. Entity classes that can be tagged are derived from CRM_API_TaggableExtendableEntity.
It is fairly simple to add new OOAPI classes. There must be a database table and a DAO or BAO for the entity type, both named according to CiviCRM convention. An OOAPI class needs to be created to wrap the DAO/BAO.
For example, if the database table is called my_extension_my_entity and the DAO class is CRM_MyExtension_DAO_MyEntity then the OOAPI class should be defined as follows:
class CRM_MyExtension_API_MyEntity extends CRM_API_Entity {
protected static $properties;
protected static function initProperties() {
static::$properties = new CRM_API_EntityType([
'displayFields' => ['my_label_field'],
'dbTablePrefix' => 'my_extension'
]);
}
}
CRM_MyExtension_API_MyEntity::init();
The CRM_API_EntityType constructor takes a single argument - an array of optional properties, including:
- defaultStringLookup: The name of a field that the getSingle function will use if passed a string value
- displayFields: An array of names of fields that will be included in an entity's string representation (for diagnostic purposes)
- dbTablePrefix: The prefix of the underlying database table. (Defaults to 'civicrm'.)
- fieldsByType: Field names grouped by PHP data type. (Only necessary for fields whose type cannot be inferred from the underlying database table.)
- lookups: An array of names of fields that can be used to uniquely identify entities of this type. (Corresponds to unique keys in the underlying database table. Used for caching.)
This extension is built to use the native APIv3's core actions as much as possible, but it does also directly access a few core functions and database tables. This means that if CiviCRM's underlying implementation changes, the extension may need to be rewritten.
The get and getSingle functions only match entities using the database's '=' operator. They need to be augmented so that other operators can be specified, e.g. '<', 'IS NULL', etc, as in the native APIv3.