Skip to content

CRM Relationship Entity

The CRM relationship entity represents connections between two contacts in the system. It supports complex relationship modeling with features like asymmetric relationships, time-based relationships, and automatic validation.

Features

  • Revisionable: Full revision tracking with log messages and timestamps
  • Publishable: Relationships can be enabled/disabled
  • Time-bound: Relationships can have start and end dates
  • Typed: Configurable relationship types with bundles
  • Validated: Prevents self-relationships and validates contact references
  • Computed Fields: Automatic contact_a and contact_b field computation
  • Age Calculation: Automatic age calculation based on relationship duration

Entity Structure

Base Fields

Field Type Description Revisionable
id Integer Primary key No
uuid UUID Universal identifier No
revision_id Integer Current revision ID No
bundle String Relationship type machine name No
status Boolean Published/unpublished status Yes
contacts Entity Reference References exactly 2 contacts (cardinality: 2) Yes
contact_a Computed First contact in relationship No
contact_b Computed Second contact in relationship No
start_date DateTime When relationship started Yes
end_date DateTime When relationship ended Yes
age Computed Integer Age of relationship in configurable units No
created Timestamp Creation time Yes
changed Timestamp Last modification time Yes

Revision Fields

Field Type Description
revision_uid Entity Reference User who created revision
revision_timestamp Timestamp When revision was created
revision_log Text Revision log message

Relationship Types

Relationship types are configuration entities that define the nature of relationships between contacts. They support both symmetric and asymmetric relationships.

Type Properties

Property Type Description
id String Machine name
label String Human-readable name
description Text Description of relationship type
asymmetric Boolean Whether relationship roles are different
contact_type_a String Required contact type for first contact
contact_type_b String Required contact type for second contact
label_a String Label for first contact's role
label_b String Label for second contact's role

Default Relationship Types

The system includes several pre-configured relationship types:

Symmetric Relationships (asymmetric: false)

  • Spouse: Person-to-person spousal relationship
  • Sibling: Person-to-person sibling relationship
  • Partner: Person-to-person partnership
  • Parent: Person-to-person parent-child relationship

Asymmetric Relationships (asymmetric: true)

  • Employee: Person (employee) to Organization (employer)
  • Volunteer: Person (volunteer) to Organization
  • Member: Person (member) to Organization
  • Head of Household: Person (head) to Household
  • Household Member: Person (member) to Household
  • Supervised: Person (supervisee) to Person (supervisor)

Technical Implementation

Entity Class

class Relationship extends RevisionableContentEntityBase implements CrmRelationshipInterface

The entity uses several traits: - EntityChangedTrait: Automatic change tracking - EntityPublishedTrait: Published/unpublished functionality - RevisionLogEntityTrait: Revision logging capabilities

Computed Fields

The contact_a and contact_b fields are computed from the base contacts field using the RelationshipContactsItemList class. This allows:

  • Form Display: Separate autocomplete widgets for each contact
  • View Display: Individual display of each contact with role labels
  • API Access: Direct access to contacts by position

Validation

The entity includes the DifferentContacts constraint that prevents creating relationships where both contacts are the same entity.

Custom Label Generation

Relationships automatically generate descriptive labels in the format:

{RelationshipType} ({ContactA} <=> {ContactB})

For example: "Spouse (John Doe <=> Jane Doe)"

Database Schema

erDiagram
    crm_relationship {
      int id PK
      uuid uuid UK
      int revision_id FK
      string bundle
      boolean status
      datetime start_date
      datetime end_date
      timestamp created
      timestamp changed
    }

    crm_relationship_revision {
      int revision_id PK
      int id FK
      uuid uuid
      string bundle
      boolean status
      datetime start_date
      datetime end_date
      timestamp created
      timestamp changed
      int revision_uid FK
      timestamp revision_timestamp
      text revision_log
    }

    crm_relationship__contacts {
      int entity_id FK
      int revision_id FK
      int delta
      int target_id FK
    }

    crm_relationship_type {
      string id PK
      string label
      text description
      boolean asymmetric
      string contact_type_a
      string contact_type_b
      string label_a
      string label_b
    }

    crm_contact {
      int id PK
      string name
    }

    crm_relationship ||--o{ crm_relationship_revision : revisions
    crm_relationship ||--|| crm_relationship_type : bundle
    crm_relationship ||--o{ crm_relationship__contacts : contacts
    crm_relationship__contacts }o--|| crm_contact : references

Usage Examples

Creating a Relationship Programmatically

use Drupal\crm\Entity\Relationship;

$relationship = Relationship::create([
  'bundle' => 'spouse',
  'contacts' => [
    ['target_id' => $contact_a_id],
    ['target_id' => $contact_b_id],
  ],
  'start_date' => '2020-01-01',
  'status' => TRUE,
]);
$relationship->save();

Accessing Computed Fields

// Get contacts via computed fields
$contact_a = $relationship->get('contact_a')->entity;
$contact_b = $relationship->get('contact_b')->entity;

// Get relationship age
$age_years = $relationship->get('age')->value;

Working with Relationship Types

// Load a relationship type
$type = \Drupal::entityTypeManager()
  ->getStorage('crm_relationship_type')
  ->load('spouse');

// Check if asymmetric
$is_asymmetric = $type->get('asymmetric');

// Get role labels
$label_a = $type->get('label_a'); // "Spouse"
$label_b = $type->get('label_b'); // "Spouse"

Access Control

Relationships use the RelationshipAccessControlHandler which provides: - Entity-level access control - Bundle-based permissions - Administrative override with 'administer crm' permission