Drupal 8: Configuration Schema

Конфигурации в Drupal 8 являются неотъемлимой частью системы. Вы можете создавать собственные конфигурации, править, экспортировать и импортировать уже существующие. Это не требует особо никаких действий.

Если вы решили создать свои конфигурации, например, для своего модуля, то это тоже не требует особо никаких телодвижений. Но есть такая вещь, как * Configuration Schema*, которая позволяет объявлять схему ваших конфигураций, примерно так, как это работает с БД. Вы можете опускать данную часть при работе с конфигами, но вы теряете некоторые возможности, порой, крайней критические. * Желательно объявлять схему для всех своих конфигураций*.

Есть отличная шпаргалка и можете даже не тратить время на прочтение статьи и быстро пробежаться глазами. Если там будут возникать вопросы или трудности, вот уже тогда можно будет прочитать и дальше.

Для чего нужны схемы конфигураций:

  • Самая важная причина — они позволяют делать вашу конфигурацию мультиязычной ( читай: переводимой). На основе этих схем будут генерироваться формы для переводов ваших конфигураций. Например, при помощи данной особенности работает перевод для текста модуля FZ152.
  • Также схема позволяет описать, какое значение, какого типа, и в случае ввода некорректных данных они будут автоматически сконвертированы в нужный тип для конфигурации. Таким образом, не получиться записать строку, там где ожидается булевое значение конфигурацией, что также спасает от дальнейших ошибок из-за некорректных данных.
  • Позволяет понимать как работают те или иные конфигурации, в особенности крайне крупные и зависимые от других. Куча ключей в дефолтном конфигурационном файле может совершенно ни о чем не говорить, конфигурационная схема позволяет четко определить что и как ожидает конфиг конкретного типа.

Если вы собрались писать свои схемы конфигураций, рекомендую поставить модуль Configuration inspector. Он добавляет интерфейс, который позволяет просматривать конфигурации, типы их данных, какие есть значения, дерево конфигурации, сырые значения и генерирует форму, чтобы проверить как друпал понимает эти данные. Он позволит вам увидеть ошибки в схемах, и более наглядно посмотреть как видит её друпал.

user.settings

Структура scheme файла

Схема файлы сами являются конфигурациями описываемые на YAML. Они находятся в config дериктории модуля, который описывает схему конфигураций, только в дополнительной папке schema (config/schema). Если вы с модулем поставляете конфиги по умолчанию, то вы их создаете в config/install или config/optional, так что всё очень похоже.

Название файла схем имеет следующий формат: [NAME].schema.yml (вы можете делать по файлу на каждый свой конфиг, или собрать всё в одном файле, это как вам удобнее). Обычно вместо [NAME] подставляется название модуля.

Внутри данного файла описывается структура(ы) для конфигураций данного же модуля. Например, выдернем tour.schema.yml из модуля tour, что в ядре ( можете прочитать про Tour API отдельно):

# Schema for the configuration files of the Tour module.

tour.tour.*:
  type: config_entity
  label: 'Tour settings'
  mapping:
    id:
      type: string
      label: 'ID'
    label:
      type: label
      label: 'Label'
    module:
      type: string
      label: 'Providing module'
    routes:
      type: sequence
      label: 'Route settings'
      sequence:
        type: route
        label: 'Route'
    tips:
      type: sequence
      label: 'Tips'
      sequence:
        type: tour.tip.[plugin]
        label: 'Tour tip'

tour.tip:
  type: mapping
  label: 'Tour tip'
  mapping:
    id:
      type: string
      label: 'ID'
    plugin:
      type: string
      label: 'Plugin'
    label:
      type: label
      label: 'Label'
    weight:
      type: integer
      label: 'Weight'
    location:
      type: string
      label: 'Location'
    attributes:
      type: sequence
      label: 'Attributes'
      sequence:
        type: string
        label: 'Attribute'

tour.tip.text:
  type: tour.tip
  label: 'Textual tour tip'
  mapping:
    body:
      type: text
      label: 'Body'

Конфиг хоть и большой, но достаточно комплексный и охватывает многие аспекты схем.

Туры по сайту при помощи Tour API объявляются через конфиг сущности. Поэтому, не удивительно что у этих конфигураций есть собственная схема описывающая что и как хранить.

Первые ключи в конфиг схемах — это названия конфигураций к которым они относятся или просто какая-то структура, которая будет в дальнейшем использовать внутри других. Если вы не читали про Tour API, то конфиги туров имеют следующий формат для имени: tour.tour.[TOUR_NAME].yml. Так как каждый тур имеет свой TOUR_NAME, а следовательно, и файл конфигурации, мы не можем написать схему под каждый из них, поэтому в названиях конфигов схемы можно использовать *. Таким образом tour.tour.* охватывает все конфигурации формата tour.tour.[TOUR_NAME].

Далее уже описываются вложенные данные конфигурации, их типы и т.д.

Если забегать наперёд, то описание tour.tour.* охватывает все конфигурации для туров. Внутри этих конфигураций есть tips, что является множественным значением. А тип получается при помощи tour.tip.[plugin]. Данный тип всегда ( из коробки) будет превращаться в tour.tip.text (почему и как ниже), который в свою очередь, повторно использует тип tour.tip. Это такая вот вложенная схема: tour.tour.* > tour.tip.text > tour.tip.

Свойства

Внутри конфигурации идёт описание всех данных внутри конфигурации с использованием свойств:

  • type: Тип конфигурации или данных.
  • label: Метка для значения конфигурации.
  • translatable: Булевое значение, определяющее может ли данное значение конфигурации быть переводимым или нет. Не все типы могут быть переводимыми, например, Boolean не может быть переведен, с другой стороны как некоторые, переводимые по умолчанию: text, label и date_format.
  • translation context: Строка являющаяся контекстом для перевода. Всё как с обычными переводами и t().
  • nullable: Булевое значение определяющее, может ли данное значение отсутствовать. По умолчанию (false) все значения требуются и не могут быть пустыми.
  • class: Класс отвечающий за реализацию парсинга. Используется только при объявлении своих базовых типов данных, это мы затрагивать не будем.
  • langcode: Для того чтобы конфигурация была переводимой, необходимо явно указывать данное значение.
  • Специфичные значения для определенных типов:
    • mapping: Может использоваться не только с type = mapping, но и с другими. Позволяет задать вложенную структуру ключ-значение по принципу ассоциативных массивов. Ключи известны.
    • sequence: Аналогично mapping, тоже может быть использовать с другими типами, не обязательно с одноименным и больше похож на обычный массив с индексами. Ключи неизвестны/не важны.

Если вернуться к примеру выше, то там видно, что все конфигурации формата tour.tour.* - являются конфигурационными сущностями, mapping сразу в нём же, указывает какие вложенные значения может иметь конфиг: id с лейблом ID и типом string, метка, с одноименными типами и меткой, module (string), который хранит название модуля которому принадлежит конфиг, routes (sequence) в котором будет массив из путей на которым будет работать тур, каждый элемент которого, должен быть типа route и tips (sequence) - это подсказки, которые тоже идут в виде массива но уже типа tour.tip.[plugin] — который объявлен чуть ниже и мы рассмотрим такую конструкцию чуть позже.

Типы конфигураций и их значений

Из коробки уже объявлены все базовые типы, при необходимости, можно объявлять свои. Пройдемся по тем что есть в ядре (core.data_types.schema.yml):

  • undefined: Позволяет указать что данный тип не определен. Не имеет никакого смысла указывать данный тип явно. Проще данное значение просто не описывать так как оно не несет в себе смысла.
  • ignore: Используется когда тип данных определить невозможно. При этом, не рекомендуется использовать данный тип, а лучше объявить свои структуры\типы и ссылаться на них.
  • boolean
  • email (= string, тут больше о семантике)
  • integer
  • timestamp
  • float
  • string
  • uri (= string)
  • mapping — простое соответствие ключ-значение (где элементы имеют свои типы). Похоже на ассоциативные массивы. Как упоминалось выше: массив где ключи известны;
  • sequence — это почти тоже самое что и mapping, только отличие в том, что это подразумевает последовательность. Иными словами, если mapping имеет жесткую структуру дерева указанную в нем, то sequence может иметь несколько вложенных данных определенного формата. Похоже на обычные массивы. Где ключ - какое-то число, а уже значение имеет определенную структуру. Как упоминалось выше: массив, где ключи неизвестны и значений может быть множество.;
  • label: (= string) Имеет свойство translatable: true;
  • path: Внутренний путь для Drupal, также просто типа string;
  • text: Аналогично label, только подразумевает большой объем текста ( textfield). Имеет свойство translatable: true;
  • date_format: Аналогично label, но также установлен контекст translation context: 'PHP date format';
  • config_entity — для конфиг сущностей, как ясно из названия;
  • config_object — для общих и глобальных конфигураций (сам по себе является обычным mapping объявляенный как тип конфиг схемы);
  • color_hex (= string)

В общем случае для самого первого уровня объявления конфигураций обычно используется либо config_entity или config_object, а для всех внутренних данных конфигурации прочие типы. Config entity - для конфигурационных сущностей, а Config object для всех прочих конфигураций на сайте.

config_object также добавляет langcode, что позволяет сделать конфигурацию мультиязычной, попутно он добавляет default_config_hash для хранения хэша конфигурации.

Повторное использование типов данных

Это уже было упомянуто выше, например так работает config_object — это просто объявленная схема, которая является типом mapping, указывает что у него есть langcode и _core, _core в свою очередь ссылается на _core_config_info, а _core_config_info добавляет default_config_hash (string).

Возможно уже стало ясно, что мы можем объявить свои "типы" на лету через конфиги. Так объявлены многие типы указанные выше, например label, path, text. Если значения для вашего типа не требуют какой-то специальной обработки в PHP для конвертации из одного типа в другой, и могут опираться на базовые типа string, boolean, integer и так далее, то свои типы проще, лучше и быстрее объявлять прямо в схеме аналогичным образом.

Например в ядре есть такое объявление в core.data_types.schema.yml:

# Mail text with subject and body parts.
mail:
  type: mapping
  label: 'Mail'
  mapping:
    subject:
      type: label
      label: 'Subject'
    body:
      type: text
      label: 'Body'

Наипростейшая схема которая объявлена для того чтобы её можно было повторно использовать в дальнейшем. Можно это называть как тип mail. Сам mail является типом mapping и имеет вложенные ключи subject типа label и body типа text.

Если вы хотите сделать конфиг, например, my_config.mail у которого должны быть subject и body, вам не нужно описывать это заново, вы можете сослаться на уже описанный:

my_config:
  type: config_object
  label: 'My configuration'
  mail:
    type: mail

Без повторного использования, выглядела бы эта схема следующим образом:

my_config:
  type: config_object
  label: 'My configuration'
  mail:
    type: mapping
    label: 'Mail'
    mapping:
      subject:
        type: label
        label: 'Subject'
      body:
        type: text
        label: 'Body'

Динамически ссылки

Одну динамическую ссылку можно увидеть уже в самом первом примере: type: tour.tip.[plugin].

Всего их есть три типа:

  • [type]: Позволяет ссылаться данные из конфига. Вместо type можно подставлять значение (ключ) откуда возьмутся значения. Если опираться на пример с Tour и type: tour.tip.[plugin]. То тип будет сформирован на основе конфигурации тура из ключа plugin, который будет находиться внутри последовательности (массива). Если посмотреть на объявление туров, то там видно, что plugin для подсказки из коробки может быть только типа plugin: text. Таким образом, строка type: tour.tip.[plugin] превращается в type: tour.tip.text. А это точное совпадение с tour.tip.text в самом низу tour.schema.yml, что является повторным использованием данных. По сути, данная динамическая ссылка нужна для разбиение на различные типы и повторное использование.
  • [%key]: Позволяет получить ключ.
  • [%parent]: Позволяет сослаться на родителя. Можно использовать даже так [%parent.%parent.type].

Как правило, они используются для формирования корректного типа данных для каждого элемента sequence. Поэтому они опираются на ключи, значения или же родительские данные относительно самой последовательности.

По данным особенностям рекомендую ещё раз обратиться к шпаргалке. Там это визуально продемонстрировано.

Включение переводов для конфигураций

Конфигурации, как упоминалось выше, могут быть переводимы. Возможно, вам потребуется включить для этого модуль Configuration Translation из ядра, если он ещё не включен. Иначе конфигурации не будут переводимыми ни при каких условиях.

Для того чтобы ваши конфигурации стали переводимыми, в конфиге должно быть значение langcode, в котором будет храниться язык перевода, а также данные с translatable: true. Это также, уже описывалось выше. Но этого будет недостаточно, помимо этого, надо также явно указать что конфигурации определенного типа являются переводимыми.

Для того чтобы ваши конфигурации появились в интерфейсе на перевод, необходимо в корне модуля создать файл [MODULENAME].config_translation.yml примерно следующего содержания:

my_configuration_name:
  title: 'My config settings'
  base_route_name: my_route_to_config_form
  names:
    - my_config.first
    - my_config.second
  • my_configuration_name для конфиг сущностей должен соответствовать названию сущности, для обычных конфигов название конфигурации. Оно не обязательно должно быть одноименным переводимой конфигурации, главное чтобы было уникальным.
    • title: Это имя, показываемое на странице переводов конфигураций
    • base_route_name: Название роута для которого будет прикреплен интерфейс перевода. Может быть обычной страницей, так и конфиг формой.
    • names: (опционально) Название конфигураций для которых нужно включить переводы.
    • weight: (опционально) Вес, по умолчанию 20.
    • list_controller: (опционально) Название класса который будет отвечать за генерацию списка конфигураций. Скорее всего, это для генерации names через класс, если кто в курсе, поделитесь опытом. В ядре вообще ничего по этому нет. Что самое странное, данный параметр даже не используется в ConfigNamesMapper, что по сути, делает его не рабочим и похоже что это вырезали и забыли потереть документацию в исходнике. ?

При таком объявлении не получится сделать динамическую генерацию для конфигов. Если вам это требуется, или вы хотите поправить данные, для этого есть два хука: hook_config_translation_info() и hook_config_translation_info_alter().

Стандарты схема файлов

У данных файлов даже есть свои небольшие стандарты:

  • Очень желательно добавлять комментарий для каких конфигураций(ии) данная схема.
  • Избегать ненужных и бессмысленных комментариев.
  • Использовать одинарные кавычки для строковых значений.
  • Всегда использовать одинарные кавычки для label, даже если там одно слово.
  • Для типов и ключей не использовать кавычки. По умолчанию они всегда строки, не допускающие пробелов.
  • Целые числа (integer) также оборачиваются в одинарные кавычки так как конвертируются в строку для хранения.
  • Метки можно объявлять не всем типам данным, но те что будут переводимы - обязательно.

API

С программной стороны есть API, который позволяет получить данные схемы по их конфигам:

// Получение схемы конфигурации system.maintenance
$definition = \Drupal::service('config.typed')->getDefinition('system.maintenance');
// Получение схемы конфигурации для конфигурации image.style.medium и его эффекта.
// Так вы получите схему для image.effect.*, а не image.style.*.
$effects = \Drupal::service('config.typed')->get('image.style.medium')->get('effects.bddf0d06-42f9-4c75-a700-a33cafa25ea0.data')->getDataDefinition();
// Также можно получить ещё больше информации.
// Получаем все эффекты для стиля изображений medium.
$effects = \Drupal::service('config.typed')->get('image.style.medium')->get('effects');
// Получаем ключ (uuid) первого эффекта, так как это массив.
$first_uuid = key($effects->getValue());
// Получаем данные первого эффекта
$data = $effects->get($first_uuid)->get('data');
// Получает тип и значение для ширины эффекта. Тип возьмется из схемы, а значение из
// акутального конфиг файла.
$data->get('width')->getPluginId();
$data->get('width')->getValue();

Пример

Далее по тексту подразумевается что модуль в котором пишется код - dummy.

На самом деле не так всё сложно, как может показаться! Поэтому лучше испробовать это на практике, так как это будет намного понятнее и проще.

В качестве примера мы создадим форму, которая будет редактировать и сохранять конфигурацию под названием dummy.message. Эта конфигурация будет содержать информацию о "сообщении", а затем оно будет выводиться на всех страницах сайта.

Конфигурация будет иметь следующие значения:

  • status: В нём мы будем хранить, выводить данное сообщение или нет.
  • type: Тип сообщения.
  • subject: Заголовок сообщения.
  • body: Тело сообщения.

А также будет переводимой.

Начнём, мы с объявления схемы конфигурации. Для этого создаем файл dummy.schema.yml в config/schema и описываем:

config/schema/dummy.schema.yml
# Configuration schema for dummy message.

dummy.message:
  type: config_object
  label: 'Dummy message'
  mapping:
    status:
      type: boolean
      label: 'Status of message'
    type:
      type: string
      label: 'Message type'
    subject:
      type: label
      label: 'Subject of message'
    body:
      type: text
      label: 'Body of message'

Обратите внимание, мы не указываем нигде langcode в схеме, так как данное значение объявлено в config_object и нам незачем дублировать его. Об этом объяснялось в разделе про повторное использование данных.

Ничего сложного тут нет. Обращу лишь внимание на то, что subject и body у нас таких типов, что они по умолчанию будут переводимы. В связке с langcode, который добавит config_object, а также label и text оно станет переводимым, явно мы это нигде не указываем, так как нет необходимости.

Мы также предоставим конфигурацию по умолчанию вместе с модулем.

config/install/dummy.message.yml
status: false
type: 'status'
subject: 'Hello World!'
body: 'This is default configuration message.'
langcode: 'en'

Также, чтобы конфигурация стала переводимой, недостаточно указать langcode и translatable, нужно явно указать что данная конфигурация переводима. Для этого в корне модуля создается файл [MODULENAME].config_translation.yml:

dummy.config_translation.yml
dummy.message:
  title: 'Dummy message settings'
  base_route_name: dummy.message.settings
  names:
    - dummy.message

Схема есть, конфиг есть, теперь время объявить конфигурационную форму для того чтобы всё это править.

src/Form/DummyMessageSettings.php
<?php

namespace Drupal\dummy\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Configure dummy settings for this site.
 */
class DummyMessageSettings extends ConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'dummy_message_settings';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return ['dummy.message'];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['status'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Active'),
      '#default_value' => $this->config('dummy.message')->get('status'),
    ];

    $form['type'] = [
      '#type' => 'select',
      '#title' => $this->t('Message type'),
      '#default_value' => $this->config('dummy.message')->get('type'),
      '#options' => [
        'status' => $this->t('Status message'),
        'warning' => $this->t('Warning message'),
        'error' => $this->t('Error message'),
      ],
    ];

    $form['subject'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Subject'),
      '#default_value' => $this->config('dummy.message')->get('subject'),
    ];

    $form['body'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Body'),
      '#default_value' => $this->config('dummy.message')->get('body'),
    ];

    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->config('dummy.message')
      ->set('status', $form_state->getValue('status'))
      ->set('type', $form_state->getValue('type'))
      ->set('subject', $form_state->getValue('subject'))
      ->set('body', $form_state->getValue('body'))
      ->save();
    parent::submitForm($form, $form_state);
  }

}

Добавляем роут для данной формы

dummy.routing.yml
dummy.message.settings:
  path: '/dummy-message'
  defaults:
    _title: 'Dummy message settings'
    _form: 'Drupal\dummy\Form\DummyMessageSettings'
  requirements:
    _permission: 'administer site configuration'

И описываем вывод сообщения через препроцессор страницы.

dummy.module
<?php

/**
 * @file
 * File for custom hooks and functions.
 */

use Drupal\Core\Render\Markup;

/**
 * Implements hook_preprocess_HOOK().
 */
function dummy_preprocess_page(&$variables) {
  $dummy_message = \Drupal::config('dummy.message');
  if ($dummy_message->get('status')) {
    $subject = $dummy_message->get('subject');
    $body = $dummy_message->get('body');
    $message = Markup::create("<h3>{$subject}</h3> {$body}");

    switch ($dummy_message->get('type')) {
      case 'status':
        \Drupal::messenger()->addStatus($message);
        break;

      case 'warning':
        \Drupal::messenger()->addWarning($message);
        break;

      case 'error':
        \Drupal::messenger()->addError($message);
        break;
    }
  }
}

Если включить модуль, то наши сообщения не будут появляться, так как в конфиге по умолчанию они отключены. Мы можем перейти на /dummy-message и включить, а также изменить какие нужно значения.

Warning

Если вы поставили Configuration inspector, то можно зайти на страницу /admin/config/development/configuration/inspect и найти данную конфигурацию. И посмотреть как видит её Drupal.

List Tree

А если перейти в перевод конфигураций /admin/config/regional/config-translation, то можно обнаружить на перевод нашу конфигурацию Dummy message settings.

Translation

Перейдя на русскую версию сайта, сообщение будет переведено!

Ru

Ссылки

Комментарии

Павел
пн, 30/07/2018 - 15:30

Спасибо, что делитесь своими знаниями. До прочтения этой статьи не мог понять как перевести меню своего модуля.

Андрей
вт, 10/09/2019 - 23:42

Вот, постала предо мною как бы простая задача, а я деталей ее реализации не знаю досконально - создать перевод на немецкий язык для конфигурации вьюса.

Базовый (англ) конфиг лежит в /module_name/config/install

Перевод лежит в /module_name/config/install/language/de

В переводе установлен langcode: de, переведен тайтл вьюса на немецкий, лейба дефолтного дисплея и еще переведены следующие элементы:

  • submit_button: Apply
  • reset_button_label: Reset
  • exposed_sorts_label: 'Sort by'
  • sort_asc_label: Asc
  • sort_desc_label: Desc

Создал schema-файл и закидал туда структуру, которая по древовидности соответствует конфиге, чтобы добраться к каждому переводу. Так ли это надо делать вообще? views.view.watch_more_videos: type: config_object mapping: label: type: label label: 'View Title' display: type: string label: 'View Display list' default: type: string label: 'Default View Display' display_options: type: string label: 'Default display options' title: type: label label: 'Display Title'

Niklan
ср, 11/09/2019 - 06:58

Схемы надо описывать для своих конфигураций. Views сам описывает схему для своих конфигов. В случае с вьюсами, просто перевести нужный вью через интерфейс и этого хватит.

Учитывая что вы уже пишите что перевели, значит схемы есть, без них бы возможности переводить не появилось.

Андрей
ср, 11/09/2019 - 11:34

Не, то я сам сделал копию и перевел вручную. Сайт устанавливается из консоли сразу на немецком языке (указан параметр) и надо ему дать эту конфигу уже переведенную. Механизма перевода вьюса нету на проекте и не будет. Я вообще и не знаю даже - нужен ли тут вообще файл конфиг схемы? Но без него, наверное, установка не подтянула переводы немецкие, значит таки нужно его внедрять.

Niklan
ср, 11/09/2019 - 11:48

Я ещё раз обращаю внимание, что схемы у вьюса прописаны.

Для того чтобы они начали работать, нужно чтобы, в данном случае, был активен модуль config_translation (из ядра). Если сайт моноязычный, то просто скорректировать на нужный язык любым удобным способом и не париться.

Андрей
ср, 11/09/2019 - 16:50

config_translation включен. Точнее тут ситуация такая - модуль кастомный должен иметь английскую и немецкую конфигу вьюса. Сайт проинстален был с одним лишь немецким языком и больше языков добавленных нет. Базовая конфигурация вьюса должна быть английская и лежит в "module_name/config/install", а в папке "module_name/config/install/language/de" лежит немецкий ее конфиг. При установке модуля всегда тянется английский конфиг (на немецкий интерфейс). Как заставить при инстале модуля втянуться тому конфигу, язык которого соответствует дефолтному языку сайта? Немецкий конфиг импортится только, когда я его вытаскиваю из языковой папки в каталог module_name/config/install вместо английского.

Niklan
чт, 12/09/2019 - 07:35

Такие кейсы не пробовал, не подскажу. Возможно, самым быстрым способом будет глянуть где-то в контрибе, или скачать features, экспорнуть на двух языках и посмотреть какую структуру он соберет.

xandeadx
Домашняя страница пн, 11/01/2021 - 17:10

Дополню — чтобы при включённом модуле Config translation на странице настроек вашего модуля появился таб для их перевода, нужно самостоятельно создать дефолтный таб:

# dummy.links.task.yml

dummy.message.settings
  route_name: dummy.message.settings
  base_route: dummy.message.settings
  title: 'Settings'
Niklan
ср, 16/06/2021 - 07:10

Можно, конечно.

/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
$config_factory = \Drupal::service('config.factory');
$config_ids = $config_factory->listAll();

Добавить комментарий

Поддерживается Markdown
Поделиться