CRM Method Detail Entity
Overview
The crm_method_detail entity provides semantic classification for contact methods, allowing you to specify the context or purpose of each contact method detail (e.g., "home" vs "work" email, "mobile" vs "fax" telephone).
Key Features
- Configuration Entity: Lightweight, exportable configuration entity
- Type Filtering: Can be restricted to specific contact method types (address, email, telephone)
- Negate Logic: Supports inclusive and exclusive type restrictions
- Status Management: Can be enabled or disabled
- Flexible Classification: Allows unlimited custom types to be created
Entity Purpose
Method detail types provide a secondary classification layer for contact methods. While the contact method type determines what kind of data is stored (address, email, or telephone), the method detail type provides context about how that data is used:
- Global Types (all types): home, work, main, other
- Telephone-Specific: mobile, fax
- Address-Specific: billing
Relationship to Contact Methods
Every crm_contact_method entity can reference a method detail through its detail field. The available details are automatically filtered based on the contact method's type, using the type filtering logic configured in each method detail.
Entity Structure
classDiagram
class crm_method_detail {
+string id [PK]
+string label
+string description
+array bundles
+bool negate
+bool status
}
class crm_contact_method {
+int id [PK]
+string type
+int detail --> crm_method_detail.id
+int crm_contact
}
class crm_contact_method_type {
+string id [PK]
+string label
}
%% Relationships
crm_contact_method }o--|| crm_method_detail : "classified by"
crm_contact_method }o--|| crm_contact_method_type : "contact method type"
%% Notes on filtering
note for crm_method_detail "Type Filtering Logic:\n- Empty bundles[] = applies to all types\n- negate=false: applies to listed types\n- negate=true: applies to all types EXCEPT listed"
Properties Reference
| Property | Type | Required | Description |
|---|---|---|---|
id |
String | Yes | Machine name identifier (e.g., "home", "work", "mobile") |
label |
String | Yes | Human-readable label displayed in forms and views |
description |
Text | No | Optional description of the type's purpose |
bundles |
Array | No | Array of contact method type IDs this detail type applies to |
negate |
Boolean | No | If TRUE, applies to all types EXCEPT those listed |
status |
Boolean | No | Whether the type is enabled (default: TRUE) |
Configuration Reference
Default Method Detail Types
The following method detail types are provided by default:
| Type ID | Label | Description | Applicable Types | Negate |
|---|---|---|---|---|
home |
Home | Personal/home contact information | All (empty array) | false |
work |
Work | Business/work contact information | All (empty array) | false |
main |
Main | Primary contact information | All (empty array) | false |
other |
Other | Alternative contact information | All (empty array) | false |
mobile |
Mobile | Mobile telephone number | telephone only | false |
fax |
Fax | Fax telephone number | telephone only | false |
billing |
Billing | Billing address | address only | false |
Type Filtering Logic
The bundles array and negate flag work together to control which contact method types can use each method detail type:
Case 1: Empty Bundles Array (Global Types)
When bundles is an empty array, the detail type applies to all contact method types regardless of the negate value.
id: home
label: Home
bundles: []
negate: false # Ignored when bundles is empty
Result: Available for address, email, and telephone types.
Case 2: Inclusive Type Filtering (negate = false)
When bundles contains values and negate is false, the detail type applies only to the listed types.
id: mobile
label: Mobile
bundles:
- telephone
negate: false
Result: Available only for telephone type contact methods.
Case 3: Exclusive Type Filtering (negate = true)
When bundles contains values and negate is true, the detail type applies to all types except those listed.
id: non_telephone
label: Non-Telephone
bundles:
- telephone
negate: true
Result: Available for address and email types, but not telephone.
Configuration File Examples
Method detail types are stored as configuration entities in YAML files:
Global Type Example
# config/install/crm.crm_method_detail.home.yml
langcode: en
status: true
dependencies: {}
id: home
label: Home
description: ''
bundles: []
negate: false
Type-Specific Example
# config/install/crm.crm_method_detail.mobile.yml
langcode: en
status: true
dependencies: {}
id: mobile
label: Mobile
description: ''
bundles:
- telephone
negate: false
Address-Only Type Example
# config/install/crm.crm_method_detail.billing.yml
langcode: en
status: true
dependencies: {}
id: billing
label: Billing
description: ''
bundles:
- address
negate: false
Selection Plugin
The MethodDetailSelection plugin automatically filters available method details based on the contact method's type. This ensures users only see relevant detail options when creating or editing contact methods.
Plugin ID: default:crm_method_detail
Filtering Logic:
1. Only shows enabled types (status = TRUE)
2. Checks type compatibility using the logic described above
3. Applies during entity reference field autocomplete and select lists
Administrative Interface
Admin URLs
| Page | URL | Description |
|---|---|---|
| Collection | /admin/structure/crm/method-detail |
List all method details |
| Add Form | /admin/structure/crm/method-detail/add |
Create a new method detail |
| Edit Form | /admin/structure/crm/method-detail/manage/{id} |
Edit an existing method detail |
| Delete Form | /admin/structure/crm/method-detail/manage/{id}/delete |
Delete a method detail |
Required Permissions
All method detail type operations require the administer crm permission.
Form Fields
The method detail type form includes the following fields:
Label
- Field Type: Text field
- Required: Yes
- Description: Human-readable name displayed to users
- Example: "Home", "Work", "Mobile"
Machine Name (ID)
- Field Type: Machine name
- Required: Yes
- Editable: Only when creating new types
- Generated From: Label field
- Example: "home", "work", "mobile"
Enabled
- Field Type: Checkbox
- Required: No
- Default: Checked
- Description: Disabled types are not available for selection but existing references remain intact
Description
- Field Type: Textarea
- Required: No
- Description: Optional text to explain the type's purpose
Bundles
- Field Type: Checkboxes
- Options: Dynamically loaded from
crm_contact_method_typeentities - Address
- Telephone
- Description: Select which contact method types this detail type applies to
- Note: Leave empty for global types that apply to all types
Negate
- Field Type: Checkbox
- Required: No
- Default: Unchecked (false)
- Description: If checked, this detail type applies to all types except those selected above
Form Handler
The MethodDetailForm class handles form building and submission:
Location: src/Form/MethodDetailForm.php
Key Features: - Validates machine name uniqueness - Filters empty checkbox values from bundles array - Provides contextual help text when no contact method types exist - Redirects to collection page after save
Programmatic Usage
Loading Method Detail Configuration Entities
<?php
declare(strict_types=1);
use Drupal\crm\Entity\MethodDetail;
// Load a specific method detail type.
$storage = \Drupal::entityTypeManager()->getStorage('crm_method_detail');
$method_detail = $storage->load('mobile');
if ($method_detail === NULL) {
\Drupal::logger('my_module')->warning('Method detail type "mobile" not found.');
return;
}
// Get the configuration values.
$label = $method_detail->label();
$bundles = $method_detail->get('bundles');
$negate = $method_detail->get('negate');
$status = $method_detail->get('status');
\Drupal::logger('my_module')->info('Method detail: @label (bundles: @bundles, negate: @negate)', [
'@label' => $label,
'@bundles' => implode(', ', $bundles),
'@negate' => $negate ? 'true' : 'false',
]);
Creating Method Detail Types Programmatically
<?php
declare(strict_types=1);
use Drupal\crm\Entity\MethodDetail;
// Create a new method detail type for emergency contacts.
$method_detail = MethodDetail::create([
'id' => 'emergency',
'label' => 'Emergency',
'description' => 'Emergency contact information',
'bundles' => ['telephone'],
'negate' => FALSE,
'status' => TRUE,
]);
try {
$method_detail->save();
\Drupal::logger('my_module')->info('Created method detail type: @id', ['@id' => $method_detail->id()]);
}
catch (\Exception $e) {
\Drupal::logger('my_module')->error('Failed to create method detail type: @message', [
'@message' => $e->getMessage(),
]);
}
Checking if a Detail Type Applies to a Bundle
<?php
declare(strict_types=1);
/**
* Check if a method detail type applies to a specific contact method bundle.
*
* @param string $detail_id
* The method detail type ID.
* @param string $bundle
* The contact method bundle (address, email, or telephone).
*
* @return bool
* TRUE if the detail type applies to the bundle, FALSE otherwise.
*/
function check_detail_applies_to_bundle(string $detail_id, string $bundle): bool {
$storage = \Drupal::entityTypeManager()->getStorage('crm_method_detail');
$method_detail = $storage->load($detail_id);
if ($method_detail === NULL || !$method_detail->get('status')) {
return FALSE;
}
$bundles = $method_detail->get('bundles');
$negate = $method_detail->get('negate');
// Empty bundles array = applies to all bundles.
if (empty($bundles)) {
return TRUE;
}
// Negate logic: applies to all bundles EXCEPT those listed.
if ($negate) {
return !in_array($bundle, $bundles, TRUE);
}
// Inclusive logic: applies only to listed bundles.
return in_array($bundle, $bundles, TRUE);
}
// Example usage:
if (check_detail_applies_to_bundle('mobile', 'telephone')) {
\Drupal::logger('my_module')->info('Mobile detail type applies to telephone bundle.');
}
Loading All Applicable Details for a Bundle
<?php
declare(strict_types=1);
/**
* Get all method detail types that apply to a specific bundle.
*
* @param string $bundle
* The contact method bundle (address, email, or telephone).
*
* @return array
* Array of method detail entity IDs that apply to the bundle.
*/
function get_applicable_details_for_bundle(string $bundle): array {
$storage = \Drupal::entityTypeManager()->getStorage('crm_method_detail');
$all_details = $storage->loadMultiple();
$applicable = [];
foreach ($all_details as $id => $detail) {
// Skip disabled details.
if (!$detail->get('status')) {
continue;
}
$bundles = $detail->get('bundles');
$negate = $detail->get('negate');
// Empty bundles = applies to all.
if (empty($bundles)) {
$applicable[$id] = $detail->label();
continue;
}
// Check negate logic.
$in_list = in_array($bundle, $bundles, TRUE);
if (($negate && !$in_list) || (!$negate && $in_list)) {
$applicable[$id] = $detail->label();
}
}
return $applicable;
}
// Example usage:
$telephone_details = get_applicable_details_for_bundle('telephone');
foreach ($telephone_details as $id => $label) {
\Drupal::logger('my_module')->info('Available for telephone: @label (@id)', [
'@label' => $label,
'@id' => $id,
]);
}
Configuration Examples
Creating Custom Method Detail Types
Example 1: Emergency Contact (Phone Only)
Create a method detail type for emergency contact phone numbers:
id: emergency
label: Emergency
description: 'Emergency contact telephone number'
bundles:
- telephone
negate: false
Example 2: Shipping Address (Address Only)
Create a method detail type specifically for shipping addresses:
id: shipping
label: Shipping
description: 'Address for shipping deliveries'
bundles:
- address
negate: false
Example 3: Personal (Email and Phone Only)
Create a type for personal contact methods, excluding addresses:
id: personal
label: Personal
description: 'Personal email or phone'
bundles:
- address
negate: true
This configuration would make "Personal" available for email and telephone bundles, but not for addresses.
Integration with Contact Methods
When creating or editing a contact method, the detail field automatically filters available options based on:
- The contact method's type (address, email, or telephone)
- Each method detail type's
bundlesandnegateconfiguration - The method detail type's
status(enabled/disabled)
Example Workflow
- User creates a new telephone contact method
- System checks all method detail types:
- "home" (empty bundles) ✓ Available
- "work" (empty bundles) ✓ Available
- "mobile" (bundles: [telephone]) ✓ Available
- "billing" (bundles: [address]) ✗ Not available
- User sees filtered dropdown with only: home, work, main, other, mobile, fax
Best Practices
Naming Conventions
- Use lowercase machine names with underscores:
emergency_contact, notEmergency-Contact - Keep labels short and descriptive: "Emergency" not "Emergency Contact Number"
- Use consistent terminology across your organization
Type Configuration
- Leave bundles empty for detail types that apply universally (home, work, other)
- Use specific types for context-specific detail types (mobile for telephone, billing for address)
- Avoid over-segmentation: Don't create type-specific versions of generic detail types
Negate Flag Usage
- Use
negate: truesparingly, as it can be confusing - Document the reasoning when using negate in configuration comments
- Prefer explicit positive bundle lists over negation when possible
Status Management
- Disable rather than delete types that are no longer needed
- Disabled types preserve existing data while preventing new usage
- Re-enable types if they become relevant again
Related Documentation
- CRM Contact Method Entity - Primary entity that uses method detail types
- CRM Contact Entity - Parent entity for contact methods
- Entity API Documentation - Overview of all CRM entities