Skip to content

CiviCRM Entities

An "entity" in CiviCRM is a persistent object (typically a sql table), with the following properties:

  • A database table: In most cases, there is a 1-to-1 relationship between entities and tables.
  • Or other storage medium: Some entities are stored elsewhere, for example Afforms are stored as files.
  • Fields: Typically the columns of the database table, although extra fields (e.g. a read-only calculated value) are supported by APIv4. The list of fields can be modified via the civi.entity.fields event.
  • An API: Most entities have a corresponding api for CRUD, metadata and other actions.

Tools

Declaration

Every entity in CiviCRM is declared via either:

  1. An .entityType.php file loaded via entity-types-php mixin (recommended for most entities).
  2. Or hook_civicrm_entityTypes (advanced use, for dynamic or generated entities, e.g. ECK).

Note: at the lowest level, hook_civicrm_entityTypes is the only way for extensions to declare entities. But the extension mixin entity-types-php typically handles this automatically; it finds and loads all .entityType.php files and passes their contents to the hook. If you have an advanced use-case and need to implement hook_civicrm_entityTypes yourself, the data you pass to the hook will be an array in the same format as that of an .entityType.php file described below, keyed by the name of the entity, plus the extra key 'module` which must contain the full_name of your extension.

Definition

The entityType definition array can contain the following keys:

* Denotes a required value

  • 'name*' (string) - unique entity name, capitalized CamelCase (e.g. "SearchDisplay")
  • 'module*' (string) - name of extension declaring the entity (only required when using hook_civicrm_entityTypes, the mixin passes this automatically)
  • 'table' (string|null) - a SQL table name (e.g. "civicrm_search_display") (required for all entities with a sql table)
  • 'class' (string|null) - a PHP DAO class (e.g."CRM_Search_DAO_SearchDisplay") - used for backward compatibility when converting existing entities from xml/dao to entityType.php
  • 'getInfo' (callback():array) - Function returning entity metadata such as:
    • title (ts(string))
    • title_plural (ts(string))
    • description (ts(string))
    • log (boolean) If FALSE, prevents change logging in basic logging mode but has no effect on sites in advanced/trigger mode logging
    • add (string)
    • icon (string)
    • label_field (string)
  • 'getPaths' (callback():array) - Function returning an array of zero or more paths, each keyed by the action that the path is used for. The most common actions are 'add', 'view', 'update' and 'delete'. Less common actions include 'browse', 'preview', 'copy' and 'detach'. Paths may include square-bracket tokens such as [id] or [price_set_id].
  • 'getIndices' (callback():array) - Function returning an array of sql indices.
  • 'getFields' (callback():array) - Function returning an array of field definitions, keyed by field name, with possible values:
    • title (ts(string))
    • description (ts(string))
    • sql_type (string) for example, 'int', 'int(10) unsigned', 'bigint', 'tinyint', 'varchar(30)', 'datetime', 'decimal', 'text', 'longtext'
    • input_type (string) one of:
      • ChainSelect
      • CheckBox
      • Date
      • Email
      • EntityRef (Autocomplete entity) see also specify entity_reference key below.
      • File
      • Hidden
      • Location (Address location)
      • Number may use the following input_attrs keys: step
      • Radio
      • RichTextEditor
      • Select
      • Select Date may use the following input_attrs keys: time date start_date_years end_date_years
      • Text may use the following input_attrs keys: text_length
      • TextArea may use the following input_attrs keys: note_rows note_columns
      • Url
    • data_type (string) One of the following.
      • Blob
      • Boolean
      • Date
      • Email
      • Enum
      • Float
      • Int
      • Link
      • Mediumblob
      • Money
      • String
      • Text
      • Time
      • Timestamp
    • required (boolean)
    • localizable (boolean)
    • serialize (int) One of the CRM_Core_DAO::SERIALIZE_* constants, with a preference towards CRM_Core_DAO::SERIALIZE_JSON
    • default (mixed)
    • usage (array)
    • input_attrs (array)
    • pseudoconstant (array) See old XML documentation for details
    • permission (array)
    • entity_reference for use with EntityRef type, has the following keys:
      • entity e.g. "Email" or "Contact"
      • key the field on the entity being referenced that matches this field’s value
      • on_delete (CASCADE or SET NULL).
  • 'fields_callback' (deprecated) (array[function($class, &$fields)]) callbacks to modify legacy DAO::fields. See civi.entity.fields event for replacement.
  • 'links_callback' (deprecated): (array[function($class, &$links)] callbacks to modify legacy DAO::getReferenceColumns. See civi.entity.fields event for replacement.

Pseudoconstant array

Pseudoconstant settings tell the code how to determine the valid options for the field value.

Shared option: prefetch bool. Should the options for these fields be pre-fetched for UI elements. This defaults to TRUE but can be set to FALSE where it is likely there will be many options.

The options must be specified by using one of the following three methodologies:

Using the civicrm_option_value table

With this methodology, the acceptable field values are taken from rows in the civicrm_option_value for a given OptionGroup.

Example use in a something.entityType.php file. Note that the keyColumn line could be omitted since the default is 'value' anyway.

<?php
return [
  ...
  'getInfo' => fn() => [
     'hat_colour_id' => [
       'title' => E::ts('Color of hat'),
       ...
       'pseudoconstant' => [
         'optionGroupName' => 'hat_color',
         'key_column' => 'value',
       ]
     ]
   ],
  ];

Using an arbitrary table

With this methodology, the acceptable field values are taken from key_column in table, with some extra settings that make it different from your typical foreign key.

  • table the name of the table to use
  • key_column as above, the name of the column that stores the value being referenced.
  • label_column the column that stores the human readable variant name.
  • name_column Optional. The column in the referenced table which contains a machine-readable name of the value.
  • condition Optional. Extra SQL to add in a WHERE clause that will further limit the possible options

Example:

<?php
return [
  ...
  'getInfo' => fn() => [
     'campaign_id' => [
       'title' => E::ts('Campaign'),
       ...
       'pseudoconstant' => [
         'table' => 'civicrm_campaign',
         'key_column' => 'id',
         'name_column' => 'iso_code',
         'label_column' => 'full_name',
         'condition' => 'parent_id IS NULL',
       ]
     ]
   ],
  ];

name_column

In some cases, key_column will reference a column containing integers and name_column will reference a column containing values like "Individual". Setting name_column in these cases allows us to use specify "Individual" when making API calls with the colon syntax, e.g.
->addWhere('campaign_id:name', '=', 'defund_billionaires')

Using a callback function

Take the acceptable field values from a PHP callback function.

<?php
return [
  ...
  'getInfo' => fn() => [
     'hat_colour' => [
       'title' => E::ts('Color of hat'),
       ...
       'pseudoconstant' => [
         'callback' => 'get_the_hat_colours',
       ]
     ]
   ],
  ];

Elsewhere, e.g. in yourextension.php

<?php
/**
 * Callback providing options for hat_colour values.
 *
 *
 */
function get_the_hat_colours(): array {
  return [
    // <value> => <label>
    'ff0000' => 'Red'
    'blue' => 'Blue'
  ];
}

Typically, you might implement the function as a static public method on a class, in which case you would specify the callback value like CRM_OAuth_BAO_OAuthClient::getProviders.

Modification

In rare cases, you may find it necessary to modify the field metadata of an entity. This is achieved by implementing a callback for the civi.entity.fields event (available since CiviCRM v6.7.0).

Drawbacks / Limitations

  • Modifying field metadata does not affect the sql table structure. Your extension would need to do that separately e.g. as part of its install/upgrade code.
  • You'd need to carefully monitor future updates to the entity you are modifying to ensure continued compatibility with your changes.

Consider alternatives first

  • Instead of adding a new column to a core field, consider creating custom fields.
  • Changes to field labels can be achieved using string replacements.
  • If it's sufficient to change the metadata at the API level, implement an Api4 SpecProviderInterface.

Example implementation

After inserting a new column named purge_date into the civicrm_mailing table, this adds metadata which makes it possible to read/write to the column in the CiviCRM API:

AutoService

This example uses the AutoService helper which is ideal for easily registering event listeners in an extension.

/**
 * @service
 * @internal 
 */
class PurgeDateHandler extends AutoService implements EventSubscriberInterface {

  public static function getSubscribedEvents(): array {
    return [
      '&civi.entity.fields::Mailing' => 'onMailingFields',
    ];
  }

  public function onMailingFields(string $entity, array &$fields) {
    $fields['purge_date'] = [
      'title' => E::ts('Auto-Purge Date'),
      'sql_type' => 'timestamp',
      'input_type' => 'Select Date',
      'description' => E::ts('Date/time when this mailing should be automatically deleted'),
      'default' => NULL,
      'input_attrs' => [
        'format_type' => 'activityDateTime',
      ],
    ];
  }

}