На сайте всегда (или почти всегда) возникает задача управления настройками вроде телефон, email, какие то текстовые элементы, вроде копирайта, адрес организации и т.п.
С точки зрения данных — требуется объект в терминах шаблонов проектирования — синглтон. Т.к. нам нужен всего один экземпляр.
Из готовых модулей для реализации этой концепции можно использовать модуль config_pages.
Другой подход так же традиционный — это создание модуля с конфигурацией, где описываются нужные нам поля. Далее выполняется создание и программирование кастомной формы для редактирования этой конфигурации. И уже в коде — это использование config API, для доступа к непосредственным данным.
Такую конфигу можно экспортировать/импортировать, что может быть как достоинством, так и проблемой.
План Б: нода + сервис
Я иногда использую третий подход, который позволяет избежать работы над программированием формы (а также настройки маршрута, и прав доступа к этой форме). Он заключается в том, что мы создаём дополнительный тип публикации, где объявляем, используя конструктор, нужные поля.
Код всё равно кое-какой придется написать, т.к. доступ к данным их этой публикации я оформляю в виде сервиса.
Администратором создаётся всего одна публикация этого типа, а редакторы ограничиваются в праве удалять или создавать их, но могут при этом редактировать уже существующие.
Далее более подробно.
Первый шаг
Создаём новый тип публикации, объявляем поля — пусть это будет публикация с машинным именем constants, и содержит поле field_email, где мы будем хранить публичный емайл адрес для контактов.
Второй шаг
Создание сервиса. Лучше оформить этот код в виде независимого кастомного модуля (для примера my_constants).
my_constants.info.yml
| 
					 1 2 3 4 5 6  | 
						name: 'My Constants' type: module description: 'Contacts and other constants for the website' core_version_requirement: ^10 || ^11 package: Custom version: 1.0  | 
					
Сервис объявляется в файле my_constants.services.yml
| 
					 1 2 3  | 
						services:   my_constants.get_constants:     class: Drupal\my_constants\Constants  | 
					
Здесь указано, что код сервиса my_constants.get_constants должен быть реализован в виде класса в файле /src/Constants.php. Если это не какой то особенный сервис, то для реализации нет нужды расширять существующий класс или интерфейс, мы имеем дело с обычным классом.
Обычно я делаю так, чтобы сервис позволял читать нужные мне данные из нашей уникальной ноды. Он выполняет всю рутину по поиску ноды, чтению её полей, подготовке данных и кешированию.
Вот пример кода сервиса.
/src/Constants.php
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58  | 
						<?php namespace  Drupal\my_constants; use Drupal\Core\Cache\Cache; class Constants {   const CID = 'singleton_data';   protected $email = '';   public function __construct()   {     $cached = $this->getFromCache();     if ($cached === FALSE) {       // load singleton_contacts node       $nids = \Drupal::entityQuery('node')         ->condition('type', 'contacts')         ->accessCheck(false)         ->range(0, 1)         ->execute();       if (count($nids)) {         $nid = array_pop($nids);         $node = \Drupal\node\Entity\Node::load($nid);         $this->email = $node->field_mail->value;         $this->saveToCache($nid);       }     } else {       $this->email = $cached->email;     }   }   private function getFromCache()   {     if ($cache = \Drupal::cache()->get(self::CID)) {       return $cache->data;     }     return false;   }   private function saveToCache(int $nid) {     $data = new \stdClass();     $data->email = $this->email;     \Drupal::cache()->set(self::CID, $data, Cache::PERMANENT, ['node:' . $nid]);   }   public function getMail(): string   {     return $this->email;   } }  | 
					
CID — это ключ кеша в таблице cache_default.
Третий шаг — настройка
Ограничиваем права редакторов на создание и удаление публикаций этого типа, но выдать разрешение на редактирование (раздел user persmissions в админке).
Далее администратор создаёт одну публикацию, так мы укладываемся в концепцию singleton.
Четвертый шаг — использование
При создании кеша я использую тег node:NID, чтобы при изменении констант, редактировании публикации, сервис пересоздавал свои данные. Публичный метод getMail(), можно использовать для получения емейла из созданных нами констант сайта.
К примеру в теме:
| 
					 1 2 3 4 5 6 7  | 
						/**  * Implements theme_preprocess_page().  */ function THEME_preprocess_page(&$vars): void {   $service = \Drupal::service('my_constants.get_constants');   $vars['public_mail'] = $service->getMail(); }  | 
					
Преимущества и недостатки
На мой взгляд — меньше рутины, проще добавить и утилизировать поля, не используются лишние контриб. модули, мы используем базовые возможности drupal из коробки.
Минусом может быть то, что константы не являются часть codebase, как если бы они были в конфигурации сайта и могли бы быть импортированы/экспортированы, вместе с конфиг файлами. Но эти данные — по сути контент, и они не должны быть частью codebase.
Иногда поля удобнее сразу рендерить, это делается штатно через сервис renderer:
| 
					 1 2  | 
						$render_array = $node->field_email->view(); $this->email= \Drupal::service('renderer')->renderRoot($render_array);  | 
					
