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