APIv4 Fields¶
In most cases the values
refer to the database fields by the database field name.
- In some cases the field may be qualified with a
:
denoting that data about the field is displayed - In some cases the field may be extended with a
.
to join to another entity - In some cases calculated fields will be exposed
Calculated Fields¶
Calculated Fields are fields that are determined from other data. A few important things to remember:
- they are a feature of APIv4 and so are not present in APIv3 or via BAO's.
- they are not retrieved unless specifically requested as they can be expensive.
- they are available as tokens, for example when sending email, and anywhere else APIv4 is used, such as SearchKit.
Calculated fields can be based on:
- a single field:
Contact.age
is calculated from theContact.birth_date
field and the current date. - multiple fields from the same record:
Contribution.tax_exclusive_amount
is the difference ofContribution.total_amount
andContribution.tax_amount
. - a sub-query or join using other tables:
Contribution.balance_amount
is calculated by a query on other financial tables.
Calculated Fields are specified in GetSpecProvider
files. Look at the existing ones for examples in <civi_root>/Civi/Api4/Service/Spec/Provider/*GetSpecProvider.php
The SpecProvider has 3 main parts:
- the field definition - creating a
FieldSpec
object. - the
applies()
function - limiting which entities and actions this applies to. - the SQL string - defining how to calculate the new field (but see below for Entity Refs).
For a Calculated Field to be available through the API, the SpecProvider must be made discoverable. In an extension, one way to do this is to make your SpecProvider extend the AutoService
interface, and enable the scan-classes
mixin. When developing an extension, the mixin can be enabled using civix
:
civix mixin --enable=scan-classes@1
Using scan-classes
may not be desirable for more complex/heavy extensions. Another way to make the SpecProvider discoverable is to register it individually with the CiviCRM container, with the tag spec_provider
. For example, inside your extension's implementation of hook_civicrm_container:
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
function my_ext_civicrm_container(ContainerBuilder $container) {
$class = \Civi\Api4\Service\Spec\Provider\MyFieldGetSpecProvider::class;
$container->setDefinition($class, new Definition($class))
->setPublic(TRUE)
->addTag('spec_provider');
}
Hint
When developing these Calculated Fields, use the debug feature of API4 Explorer to see the generated SQL.
Calculated Fields in FormBuilder¶
To make your calculated field available as a filter on a FormBuilder search form, include an input type. For example:
$field->setInputType('Select')
->setOptions([1 => 'One', 2 => 'Two']);
Entity Refs¶
One powerful feature of Calculated Fields is the ability to use them as Entity References if the calculated value is a foreign key. All of the referenced entity's fields can be accessed without further definition of those fields.
So for example, the Calculated Field Contact.address_primary
links to the Contact's primary address (determined by joining to the civicrm_address
table) which then provides access to Contact.address_primary.city
.
The SQL string defining the join for a reference like this moves to a file in <civi_root>/Civi/Api4/Event/Subscriber/*SchemaMapSubscriber.php
For an example, study the implementation of Contact.address_primary
in ContactGetSpecProvider.php
and ContactSchemaMapSubscriber.php
Reminder
Note that a cache clear is needed after making changes to the *SchemaMapSubscriber.php
files.
Core Calculated Fields¶
This section contains a listing of Calculated Fields in core. Use them as inspiration!
Activity.case_id
- provides the case that an activity is linked toActivity.source_contact_id
,Activity.target_contact_id
,Activity.assignee_contact_id
- contacts related to the activityAddress.proximity
- (Filter) determines whether an address is within a given distance of a location (see example)Contact.age_years
- age of a contact (based onbirth_date
) in yearsContact.groups
- (Filter) determines whether a contact belongs to any of the specified groupsContact.address_primary, address_billing, email_primary, email_billing, phone_primary, phone_billing, im_primary, im_billing
- addresses associated with a contactContribution.balance_amount
- balanceContribution.paid_amount
- amount paidContribution.tax_exclusive_amount
- total amount less the tax amountDomain.is_active
- is this the current active domainMessageTemplate.master_id
- the original version of a MessageTemplate if it has been modified
Filters vs Fields
Most calculated fields can be used in any part of the query (SELECT, ORDER BY, HAVING, etc) but fields designaged as type '(Filter)' can only be used in the WHERE clause.