Skip to content

Relationship Statistics

CRM maintains per-contact counts of relationships by type in the relationship_statistics field on contacts. Statistics update when relationships are created, updated, or deleted without saving a new contact revision.

Service

Property Value
Service ID crm.relationship_statistics
Class Drupal\crm\Service\RelationshipStatisticsService
Interface Drupal\crm\Service\RelationshipStatisticsInterface

Key methods

  • increment(ContactInterface $contact, string $type_key) — increase count for a type
  • decrement(ContactInterface $contact, string $type_key) — decrease count for a type
  • recalculateAll(int $batch_size = 100) — rebuild all contact statistics

Statistics are written directly to crm_contact__relationship_statistics (and revision table when applicable), similar to comment statistics on nodes.

Hooks and maintenance

RelationshipHooks updates statistics on relationship insert, update, and delete. If counts drift after bulk imports or manual database changes, recalculate:

ddev drush crm:recalculate-statistics

Optional batch size:

ddev drush crm:recalculate-statistics --batch-size=50

Alias: crm-rs.

Access

Stored counts cover every relationship type, with no access checks. Viewers see counts only for relationship types they may view. A type is viewable when the account has administer crm, view any crm relationship, or view any {type} crm relationship. Each output path filters on read:

Output Behavior
RelationshipStatisticsFormatter Skips types the viewer cannot view. Adds the user.permissions cache context.
RelationshipStatisticsType Views filter When exposed, offers and applies only viewable types; hidden exposed defaults are dropped. Configured values and the admin form are unchanged. Adds the user.permissions cache context.
Serialization (REST and other serializers) RelationshipStatisticsNormalizer drops items for types the viewer cannot view. Checks $context['account'] when set, else the current user. Adds the user.permissions cache context to the serialization cacheability.
JSON:API Hides the whole field unless the viewer may view every relationship type.

All four use getViewableBundles() on crm.relationship_access_query_alter (RelationshipAccessQueryAlterInterface). It returns NULL when the account may view every type, or else the list of viewable type IDs. The $account argument is optional and defaults to the current user.

JSON:API does not use custom normalizers, so it cannot filter field items per type. RelationshipStatisticsAccessHooks implements hook_entity_field_access_alter() instead: on JSON:API routes it forbids view access to relationship_statistics for restricted accounts. The result varies by permissions and route name (route.name). It uses an alter hook because core keys hook_entity_field_access() results by module, so a second CRM implementation would overwrite UserMappedFieldAccessHooks.

RelationshipStatisticsNormalizer (crm.normalizer.relationship_statistics, priority 20) is registered by CrmServiceProvider only when Serialization is enabled.

Tests:

  • tests/src/Kernel/Normalizer/RelationshipStatisticsNormalizerTest.php — normalizer filtering and JSON:API field access
  • tests/src/Kernel/Plugin/Field/FieldFormatter/RelationshipStatisticsFormatterTest.php and tests/src/Unit/Plugin/Field/FieldFormatter/RelationshipStatisticsFormatterTest.php
  • tests/src/Kernel/Plugin/views/RelationshipStatisticsViewsTest.php and tests/src/Unit/Plugin/views/filter/RelationshipStatisticsTypeTest.php

Views integration

  • Filter: RelationshipStatisticsType — filter contacts by relationship type counts
  • Field: RelationshipStatisticsFormatter — display statistics on contact views

The formatter shows only relationship types the viewer may view. The filter limits exposed choices the same way (see Access).

See Views integration.

Field type

RelationshipStatisticsItem stores type key and count pairs on the contact entity. The computed data is exposed to Views through ContactViewsData.