Skip to content

CRM Contact Method Entity

Overview

The crm_contact_method entity represents additional information attached to CRM contacts, such as email addresses, telephone numbers, and postal addresses. This is a flexible entity system that allows contacts to have multiple instances of different types of contact methods.

Key Features

  • Revisionable: Full revision tracking with timestamps and user information
  • Type-based: Three main types - address, email, and telephone
  • Type Classification: Each detail can be classified with types like "home", "work", "mobile", etc.
  • Status Management: Each detail can be enabled/disabled
  • Access Control: Inherits access permissions from parent contact

Entity Relationships

Contact details belong to CRM contacts and are classified by both contact method type and method detail type, providing a flexible classification system.

Entity Structure

classDiagram
  class crm_contact_method {
    +int id [PK]
    +uuid uuid
    +int revision_id
    +string type --> crm_contact_method_type.id
    +int detail --> crm_method_detail.id
    +bool status
    +int crm_contact --> crm_contact.id
    +int created [timestamp]
    +int changed [timestamp]
    +address_field address [bundle: address]
    +email_field email [bundle: email]
    +telephone_field telephone [bundle: telephone]
  }

  class crm_contact_method_revision {
    +int revision_id [PK]
    +uuid uuid
    +int id
    +string type
    +int detail --> crm_method_detail.id
    +bool status
    +int created [timestamp]
    +int changed [timestamp]
    +int revision_uid --> users.uid
    +int revision_timestamp [timestamp]
    +string revision_log
  }

  class crm_contact_method_type {
    +string id [PK]
    +string label
    +string description
    +bool locked
  }

  class crm_method_detail {
    +string id [PK]
    +string label
    +string description
    +array bundles
    +bool negate
  }

  class crm_contact {
    +int id [PK]
    +string type
    +string name
  }


  crm_contact_method  --|>  crm_contact_method_revision : "has revisions"
  crm_contact_method  --|>  crm_contact_method_type : "contact method type"
  crm_contact_method  --|>  crm_method_detail : "classified by"
  crm_contact_method  --|>  crm_contact : "belongs to"

  class AddressContactMethod {
    +address_field address
    +label() string
  }

  class EmailContactMethod {
    +email_field email
    +label() string
  }

  class TelephoneContactMethod {
    +telephone_field telephone
    +label() string
  }


  AddressContactMethod --|> crm_contact_method : "address"
  EmailContactMethod --|> crm_contact_method : "email"
  TelephoneContactMethod --|> crm_contact_method : "telephone"

Contact Method Types

The CRM Contact Method entity supports three main types, each designed for specific types of contact information:

Address (address)

Stores postal/physical addresses using Drupal's Address field module.

  • Type ID: address
  • Field: address (Address field type)
  • Class: Drupal\crm\Entity\ContactMethod\AddressContactMethod
  • Configuration: Customized to hide name fields (given, additional, family, organization)
  • Required: Yes (address field is required)

Example Address Detail:

type: address
detail: home  # or work, billing, etc.
address:
  country_code: US
  administrative_area: CA
  locality: San Francisco
  postal_code: "94102"
  address_line1: "123 Main St"
  address_line2: "Apt 4B"

Email (email)

Stores email addresses using Drupal's Email field module.

  • Type ID: email
  • Field: email (Email field type)
  • Class: Drupal\crm\Entity\ContactMethod\EmailContactMethod
  • Required: No
  • Validation: Email format validation

Example Email Detail:

type: email
detail: work  # or home, main, other, etc.
email: "john.doe@example.com"

Telephone (telephone)

Stores telephone numbers using Drupal's Telephone field module.

  • Type ID: telephone
  • Field: telephone (Telephone field type)
  • Class: Drupal\crm\Entity\ContactMethod\TelephoneContactMethod
  • Required: No
  • Validation: Telephone format validation

Example Telephone Detail:

type: telephone
detail: mobile  # or home, work, fax, etc.
telephone: "+1-555-123-4567"

Method Detail Types

📖 Full Documentation: For comprehensive information about method detail types, see the Method Detail Entity documentation.

Method detail types provide semantic classification for contact methods, allowing you to specify the context or purpose of each detail (e.g., "home" vs "work" email).

Available Method Detail Types

The following method detail types are available by default:

Type ID Label Description Applicable Bundles
home Home Personal/home contact information All bundles
work Work Business/work contact information All bundles
main Main Primary contact information All bundles
other Other Alternative contact information All bundles
mobile Mobile Mobile telephone number telephone only
fax Fax Fax telephone number telephone only
billing Billing Billing address address only

Contact Method Type Restrictions

Method detail types can be configured to apply only to specific contact method types:

  • Global Types (home, work, main, other): Available for all contact method types
  • Telephone-Specific (mobile, fax): Only available for telephone type
  • Address-Specific (billing): Only available for address type

Configuration

Method details are configured in crm_method_detail configuration entities:

# config/optional/crm.crm_method_detail.mobile.yml
id: mobile
label: Mobile
description: ''
bundles:
  - telephone
negate: false

The bundles array specifies which contact method types this detail type applies to. An empty array means it applies to all types.

Fields Reference

Base Fields

All contact method entities have the following base fields:

Field Name Type Description Required Revisionable
id Integer Unique entity identifier Auto No
uuid UUID Universal unique identifier Auto No
revision_id Integer Current revision ID Auto No
type String Contact method type (address, email, telephone) Auto No
detail Entity Reference Reference to crm_method_detail No Yes
status Boolean Whether the detail is active Yes (default: TRUE) Yes
crm_contact Entity Reference Reference to parent crm_contact Yes No
created Timestamp Creation timestamp Auto No
changed Timestamp Last modified timestamp Auto No

Revision Fields

Contact details are fully revisionable with the following revision metadata:

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

Bundle-Specific Fields

Address Type Fields

Field Name Type Settings Required
address Address Country list: All, Name fields hidden Yes

Address Field Configuration:

field_overrides:
  givenName:
    override: hidden
  additionalName:
    override: hidden
  familyName:
    override: hidden
  organization:
    override: hidden

Email Type Fields

Field Name Type Settings Required
email Email Standard email validation No

Telephone Type Fields

Field Name Type Settings Required
telephone Telephone Standard telephone validation No

Access Control

Contact detail access control is handled by the ContactMethodAccessControlHandler class, which implements a hierarchical permission system based on the parent contact.

Permission Model

Contact detail permissions are inherited from the parent contact entity:

  1. Administrative Access: Users with administer crm permission have full access
  2. Inherited Access: Contact detail operations inherit permissions from the parent contact
  3. No Orphan Details: Contact details without a parent contact are denied access

Operation Permissions

Operation Required Permission Fallback
view Parent contact view permission Contact view access
update Parent contact update permission Contact edit access
delete Parent contact update permission Contact edit access
create Any of: create any crm contact, edit any crm contact, administer crm -

Access Control Logic

<?php

declare(strict_types=1);

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
use Drupal\crm\Entity\ContactMethod;

/**
 * Example: Check access to a contact method programmatically.
 *
 * @param int $contact_method_id
 *   The contact method entity ID.
 * @param string $operation
 *   The operation to check (view, update, delete).
 * @param \Drupal\Core\Session\AccountInterface|null $account
 *   The user account to check access for (defaults to current user).
 *
 * @return bool
 *   TRUE if access is allowed, FALSE otherwise.
 */
function check_contact_method_access(int $contact_method_id, string $operation, ?AccountInterface $account = NULL): bool {
  if ($account === NULL) {
    $account = \Drupal::currentUser();
  }

  // Load the contact method entity.
  $contact_method = ContactMethod::load($contact_method_id);

  if ($contact_method === NULL) {
    return FALSE;
  }

  // Check access using the entity access system.
  $access_result = $contact_method->access($operation, $account, TRUE);

  return $access_result->isAllowed();
}

/**
 * Example: Access control logic from ContactMethodAccessControlHandler.
 *
 * This demonstrates how access control works internally for contact methods.
 */
function example_access_control_logic(ContactMethod $entity, string $operation, AccountInterface $account): AccessResult {
  // Administrative users have full access.
  if ($account->hasPermission('administer crm')) {
    return AccessResult::allowed()->cachePerPermissions();
  }

  // Load the parent contact.
  $contact = $entity->get('crm_contact')->entity;

  if ($contact === NULL) {
    // No parent contact = deny access.
    return AccessResult::forbidden('Contact method must have a parent contact.')
      ->addCacheableDependency($entity);
  }

  // View access inherits from parent contact.
  if ($operation === 'view') {
    return $contact->access($operation, $account, TRUE);
  }

  // Edit/Delete access requires parent contact update permission.
  if ($operation === 'update' || $operation === 'delete') {
    return $contact->access('update', $account, TRUE);
  }

  // Default deny.
  return AccessResult::neutral();
}

Key Points:

  • Contact methods inherit access permissions from their parent contact
  • Administrative users with administer crm permission have full access
  • View operations check parent contact's view permission
  • Update and delete operations check parent contact's update permission
  • Orphaned contact methods (without a parent) are denied access

Security Features

  • Parent Contact Validation: Details must belong to a valid contact
  • Cascading Permissions: Detail permissions automatically follow contact permissions
  • Administrative Override: CRM administrators can access all details
  • No Direct Permissions: Contact details don't have standalone permissions

API Usage Examples

Creating Contact Methods

Creating an Email Detail

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// Create an email contact method.
$email_detail = ContactMethod::create([
  'type'        => 'email',
  'detail'      => 'work',  // Reference to crm_method_detail entity.
  'status'      => TRUE,
  'crm_contact' => $contact_id,
  'email'       => 'john.doe@example.com',
]);

try {
  $email_detail->save();
  \Drupal::logger('my_module')->info('Created email contact method with ID @id', ['@id' => $email_detail->id()]);
}
catch (\Exception $e) {
  \Drupal::logger('my_module')->error('Failed to create email contact method: @message', ['@message' => $e->getMessage()]);
}

Creating an Address Detail

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// Create an address contact method.
$address_detail = ContactMethod::create([
  'type'        => 'address',
  'detail'      => 'billing',
  'status'      => TRUE,
  'crm_contact' => $contact_id,
  'address'     => [
    'country_code'        => 'US',
    'administrative_area' => 'CA',
    'locality'            => 'San Francisco',
    'postal_code'         => '94102',
    'address_line1'       => '123 Main Street',
    'address_line2'       => 'Suite 400',
  ],
]);

try {
  $address_detail->save();
  \Drupal::logger('my_module')->info('Created address contact method with ID @id', ['@id' => $address_detail->id()]);
}
catch (\Exception $e) {
  \Drupal::logger('my_module')->error('Failed to create address contact method: @message', ['@message' => $e->getMessage()]);
}

Creating a Telephone Detail

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// Create a telephone contact method.
$phone_detail = ContactMethod::create([
  'type'        => 'telephone',
  'detail'      => 'mobile',
  'status'      => TRUE,
  'crm_contact' => $contact_id,
  'telephone'   => '+1-555-123-4567',
]);

try {
  $phone_detail->save();
  \Drupal::logger('my_module')->info('Created telephone contact method with ID @id', ['@id' => $phone_detail->id()]);
}
catch (\Exception $e) {
  \Drupal::logger('my_module')->error('Failed to create telephone contact method: @message', ['@message' => $e->getMessage()]);
}

Loading Contact Methods

Load All Details for a Contact

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// Load all contact methods for a specific contact.
$storage = \Drupal::entityTypeManager()->getStorage('crm_contact_method');
$details = $storage->loadByProperties(['crm_contact' => $contact_id]);

if (empty($details)) {
  \Drupal::logger('my_module')->info('No contact methods found for contact @id', ['@id' => $contact_id]);
  return;
}

foreach ($details as $detail) {
  $method_type = $detail->bundle();

  // Get the detail type label (e.g., "work", "home").
  $detail_entity = $detail->get('detail')->entity;
  $detail_type = $detail_entity !== NULL ? $detail_entity->label() : 'Unknown';

  $value = '';
  switch ($method_type) {
    case 'email':
      $value = $detail->get('email')->value ?? '';
      break;

    case 'telephone':
      $value = $detail->get('telephone')->value ?? '';
      break;

    case 'address':
      $address = $detail->get('address')->first();
      if ($address !== NULL) {
        $value = $address->getAddressLine1() . ', ' . $address->getLocality();
      }
      break;
  }

  \Drupal::logger('my_module')->info('Detail: @detail_type @method_type - @value', [
    '@detail_type' => $detail_type,
    '@method_type' => $method_type,
    '@value' => $value,
  ]);
}

Load Details by Contact Method Type

<?php

declare(strict_types=1);

// Load only email details for a contact.
$storage = \Drupal::entityTypeManager()->getStorage('crm_contact_method');
$email_details = $storage->loadByProperties([
  'crm_contact' => $contact_id,
  'type'        => 'email',
  'status'      => TRUE,
]);

if (empty($email_details)) {
  \Drupal::logger('my_module')->info('No active email addresses found for contact @id', ['@id' => $contact_id]);
}

Load Details by Method Detail Type

<?php

declare(strict_types=1);

// Load all "work" details for a contact.
$storage = \Drupal::entityTypeManager()->getStorage('crm_contact_method');
$work_details = $storage->loadByProperties([
  'crm_contact' => $contact_id,
  'detail'      => 'work',
  'status'      => TRUE,
]);

if (empty($work_details)) {
  \Drupal::logger('my_module')->info('No work contact methods found for contact @id', ['@id' => $contact_id]);
}

Updating Contact Methods

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// Update an existing contact method.
$detail = ContactMethod::load($detail_id);

if ($detail === NULL) {
  \Drupal::logger('my_module')->warning('Contact method @id not found.', ['@id' => $detail_id]);
  return;
}

// Update the status.
$detail->set('status', FALSE);

// Update the detail type.
$detail->set('detail', 'home');

// Update the specific field based on bundle type.
switch ($detail->bundle()) {
  case 'email':
    $detail->set('email', 'new.email@example.com');
    break;

  case 'telephone':
    $detail->set('telephone', '+1-555-987-6543');
    break;

  case 'address':
    $detail->set('address', [
      'country_code'        => 'US',
      'administrative_area' => 'NY',
      'locality'            => 'New York',
      'postal_code'         => '10001',
      'address_line1'       => '456 Broadway',
    ]);
    break;
}

// Save with a new revision.
$detail->setNewRevision(TRUE);
$detail->setRevisionLogMessage('Updated contact method');

try {
  $detail->save();
  \Drupal::logger('my_module')->info('Updated contact method @id', ['@id' => $detail->id()]);
}
catch (\Exception $e) {
  \Drupal::logger('my_module')->error('Failed to update contact method: @message', ['@message' => $e->getMessage()]);
}

Querying Contact Methods

Using Entity Query

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// Query for active telephone details with mobile type.
$query = \Drupal::entityQuery('crm_contact_method')
  ->condition('type', 'telephone')
  ->condition('detail', 'mobile')
  ->condition('status', TRUE)
  ->accessCheck(TRUE);

$detail_ids = $query->execute();

if (empty($detail_ids)) {
  \Drupal::logger('my_module')->info('No active mobile phone numbers found.');
  return;
}

$details = ContactMethod::loadMultiple($detail_ids);

foreach ($details as $detail) {
  $phone = $detail->get('telephone')->value;
  \Drupal::logger('my_module')->info('Mobile phone: @phone', ['@phone' => $phone]);
}

Complex Queries with Joins

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// Find all email details for contacts with addresses in a specific city.
$database = \Drupal::database();
$query = $database->select('crm_contact_method', 'cd');
$query->join('crm_contact_method__address', 'cda', 'cd.id = cda.entity_id');
$query->fields('cd', ['id'])
  ->condition('cd.type', 'email')
  ->condition('cd.status', TRUE)
  ->condition('cda.address_locality', 'San Francisco');

try {
  $result = $query->execute()->fetchCol();

  if (empty($result)) {
    \Drupal::logger('my_module')->info('No email addresses found for contacts in San Francisco.');
    return;
  }

  $details = ContactMethod::loadMultiple($result);

  foreach ($details as $detail) {
    $email = $detail->get('email')->value;
    \Drupal::logger('my_module')->info('Found email: @email', ['@email' => $email]);
  }
}
catch (\Exception $e) {
  \Drupal::logger('my_module')->error('Database query failed: @message', ['@message' => $e->getMessage()]);
}

Working with Revisions

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

$storage = \Drupal::entityTypeManager()->getStorage('crm_contact_method');

// Load a specific revision.
$revision_id = 123;
$revision = $storage->loadRevision($revision_id);

if ($revision === NULL) {
  \Drupal::logger('my_module')->warning('Revision @id not found.', ['@id' => $revision_id]);
  return;
}

// Get revision information.
$revision_user = $revision->getRevisionUser();
$revision_time = $revision->getRevisionCreationTime();
$revision_log = $revision->getRevisionLogMessage();

\Drupal::logger('my_module')->info('Revision @id by @user at @time: @log', [
  '@id' => $revision->getRevisionId(),
  '@user' => $revision_user !== NULL ? $revision_user->getDisplayName() : 'Unknown',
  '@time' => date('Y-m-d H:i:s', $revision_time),
  '@log' => $revision_log ?? 'No log message',
]);

// Load all revisions for a contact method.
$detail = ContactMethod::load($detail_id);

if ($detail === NULL) {
  \Drupal::logger('my_module')->warning('Contact method @id not found.', ['@id' => $detail_id]);
  return;
}

$revision_ids = $storage->revisionIds($detail);

foreach ($revision_ids as $rid) {
  $revision = $storage->loadRevision($rid);

  if ($revision === NULL) {
    continue;
  }

  $user = $revision->getRevisionUser();
  $user_name = $user !== NULL ? $user->getDisplayName() : 'Unknown';

  \Drupal::logger('my_module')->info('Revision: @id by @user at @time', [
    '@id' => $revision->getRevisionId(),
    '@user' => $user_name,
    '@time' => date('Y-m-d H:i:s', $revision->getRevisionCreationTime()),
  ]);
}

Common Pitfalls and Best Practices

Always Set the Parent Contact

Contact methods must have a valid parent contact:

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// WRONG: Missing parent contact.
$email = ContactMethod::create([
  'type' => 'email',
  'email' => 'test@example.com',
]);
$email->save(); // This may fail or create orphaned data!

// CORRECT: Always set the parent contact.
$email = ContactMethod::create([
  'type' => 'email',
  'email' => 'test@example.com',
  'crm_contact' => $contact_id,
  'status' => TRUE,
]);

Validate Email and Phone Formats

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// Validate email before creating.
$email_address = 'invalid-email';

if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) {
  \Drupal::logger('my_module')->error('Invalid email address: @email', [
    '@email' => $email_address,
  ]);
  return;
}

$email = ContactMethod::create([
  'type' => 'email',
  'email' => $email_address,
  'crm_contact' => $contact_id,
  'status' => TRUE,
]);

// Run entity validation before saving.
$violations = $email->validate();
if ($violations->count() > 0) {
  foreach ($violations as $violation) {
    \Drupal::logger('my_module')->error('Validation error: @message', [
      '@message' => $violation->getMessage(),
    ]);
  }
  return;
}

try {
  $email->save();
}
catch (\Exception $e) {
  \Drupal::logger('my_module')->error('Failed to save contact method: @message', [
    '@message' => $e->getMessage(),
  ]);
}

Check Access Before Operations

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

$contact_method = ContactMethod::load($detail_id);

if ($contact_method === NULL) {
  \Drupal::logger('my_module')->warning('Contact method @id not found.', ['@id' => $detail_id]);
  return;
}

$current_user = \Drupal::currentUser();

// Check if user has permission to update this contact method.
if (!$contact_method->access('update', $current_user)) {
  \Drupal::logger('my_module')->warning('User @uid does not have permission to update contact method @id', [
    '@uid' => $current_user->id(),
    '@id' => $detail_id,
  ]);
  return;
}

// Safe to proceed with update.
$contact_method->set('status', FALSE);
$contact_method->save();

Handle Bundle-Specific Fields Correctly

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

$contact_method = ContactMethod::load($detail_id);

if ($contact_method === NULL) {
  return;
}

// WRONG: Assuming field exists without checking bundle.
$email = $contact_method->get('email')->value; // Fatal error if not email type!

// CORRECT: Check bundle first.
switch ($contact_method->bundle()) {
  case 'email':
    if ($contact_method->hasField('email')) {
      $value = $contact_method->get('email')->value;
    }
    break;

  case 'telephone':
    if ($contact_method->hasField('telephone')) {
      $value = $contact_method->get('telephone')->value;
    }
    break;

  case 'address':
    if ($contact_method->hasField('address')) {
      $address_field = $contact_method->get('address')->first();
      if ($address_field !== NULL) {
        $value = $address_field->getAddressLine1();
      }
    }
    break;
}

Security Considerations

Access Control Inheritance

Contact methods inherit access control from their parent contact. This means:

  • Data Leakage Prevention: Users cannot access contact methods unless they can access the parent contact
  • Cascading Permissions: Changing contact permissions automatically affects all its contact methods
  • Orphan Protection: Contact methods without valid parent contacts are denied access
<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// Always verify the user has access to the parent contact.
$contact_method = ContactMethod::load($detail_id);

if ($contact_method === NULL) {
  return;
}

$contact = $contact_method->get('crm_contact')->entity;

if ($contact === NULL) {
  \Drupal::logger('my_module')->error('Contact method @id has no parent contact.', [
    '@id' => $detail_id,
  ]);
  return;
}

$current_user = \Drupal::currentUser();

if (!$contact->access('view', $current_user)) {
  \Drupal::logger('my_module')->warning('Access denied to contact @cid for user @uid', [
    '@cid' => $contact->id(),
    '@uid' => $current_user->id(),
  ]);
  return;
}

// Now safe to work with the contact method.

Sanitizing Output

When displaying contact method data, always sanitize output:

<?php

declare(strict_types=1);

use Drupal\Component\Utility\Html;

$email = $contact_method->get('email')->value;

// WRONG: Direct output without sanitization.
print '<div>' . $email . '</div>'; // XSS risk!

// CORRECT: Use proper sanitization.
print '<div>' . Html::escape($email) . '</div>';

// BETTER: Use render arrays.
$build = [
  '#type' => 'html_tag',
  '#tag' => 'div',
  '#value' => $email,
];

Performance Optimization

Bulk Loading Contact Methods

<?php

declare(strict_types=1);

use Drupal\crm\Entity\ContactMethod;

// INEFFICIENT: N+1 query problem.
$contact_ids = [1, 2, 3, 4, 5];
foreach ($contact_ids as $contact_id) {
  $details = \Drupal::entityTypeManager()
    ->getStorage('crm_contact_method')
    ->loadByProperties(['crm_contact' => $contact_id]);

  foreach ($details as $detail) {
    // Process each detail...
  }
}

// EFFICIENT: Single query.
$storage = \Drupal::entityTypeManager()->getStorage('crm_contact_method');
$query = $storage->getQuery()
  ->condition('crm_contact', $contact_ids, 'IN')
  ->condition('status', TRUE)
  ->accessCheck(TRUE);

$detail_ids = $query->execute();
$all_details = ContactMethod::loadMultiple($detail_ids);

// Group by contact for processing.
$grouped = [];
foreach ($all_details as $detail) {
  $contact_id = $detail->get('crm_contact')->target_id;
  $grouped[$contact_id][] = $detail;
}

Caching Considerations

<?php

declare(strict_types=1);

// Contact methods are cached with cache tags.
// When a contact method changes, related caches are invalidated.

// Example: Custom cache with proper tags.
$cid = 'my_module:contact_methods:' . $contact_id;
$cache = \Drupal::cache()->get($cid);

if ($cache === FALSE) {
  $details = \Drupal::entityTypeManager()
    ->getStorage('crm_contact_method')
    ->loadByProperties(['crm_contact' => $contact_id]);

  $data = [];
  $cache_tags = ['crm_contact:' . $contact_id];

  foreach ($details as $detail) {
    $data[] = [
      'type' => $detail->bundle(),
      'value' => $detail->label(),
    ];

    // Add cache tags for each contact method.
    $cache_tags[] = 'crm_contact_method:' . $detail->id();
  }

  \Drupal::cache()->set($cid, $data, \Drupal\Core\Cache\CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
}
else {
  $data = $cache->data;
}

Administrative URLs

  • Contact Methods List: /admin/content/crm/detail
  • Contact Method Types: /admin/structure/crm/contact-detail-types
  • Method Details: /admin/structure/crm/method-detail