Добавить к сущности базовое поле - entityDefinitionUpdateManager


134

Начиная с Drupal 8.7.0 автоматические обновления схем сущностей и полей больше недоступны. Теперь, когда хранилище для данных или сущности нужно создать, обновить или удалить, необходимо прописать это явно, используя Update API и Entity Definition Update Manager.

Для примера, добавим к нодам новое базовое поле is_frz_tasks.

Файл
MYMODULE.install
use \Drupal\Core\Field\BaseFieldDefinition;

function MYMODULE_install() {
  $field_storage_definition = BaseFieldDefinition::create('boolean')
    ->setLabel('Label is_frz_tasks')
    ->setDescription('is_frz_tasks')
    ->setRevisionable(FALSE)
    ->setTranslatable(FALSE)
    ->setDisplayOptions('form', [
      'type' => 'boolean_checkbox',
      'settings' => [
        'display_label' => TRUE,
      ],
    ])
    ->setDisplayConfigurable('form', TRUE);

  \Drupal::entityDefinitionUpdateManager()
    ->installFieldStorageDefinition('is_frz_tasks', 'node', 'node', $field_storage_definition);
}

Теперь нужно сообщить системе информацию о новом поле

Файл
MYMODULE.module
use \Drupal\Core\Field\BaseFieldDefinition;

/**
 * Implements hook_entity_base_field_info().
 */
function MYMODULE_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) {
  $fields = [];
  if ($entity_type->id() === 'node') {
    $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
    if ($field_is_frz_tasks = $definition_update_manager->getFieldStorageDefinition('is_frz_tasks', 'node')) {
      $fields['is_frz_tasks'] = $field_is_frz_tasks;
    }
    return $fields;
  }
}

После включения модуля в таблице node_field_data будет добавлено наше новое поле, также оно будет добавлено на форму редактирвоания ноды.

Остается еще один нюанс - правильно обработать удаление модуля. Для этого используем  hook_uninstall.

Файл
MYMODULE.install
function MYMODULE_uninstall() {
  $entityTypeId = 'node';
  $fieldName = 'is_frz_tasks';

  $entityDefinitionUpdateManager = \Drupal::entityDefinitionUpdateManager();
  if ($field_is_frz_tasks = $entityDefinitionUpdateManager->getFieldStorageDefinition($fieldName, $entityTypeId)) {
    $entityDefinitionUpdateManager->uninstallFieldStorageDefinition($field_is_frz_tasks);
  }
}

И вот тут не все так просто. Проблема возникает, если в новом поле уже сохранены какие-то данные. Поле не удаляется сразу, а становится в очередь на удаление, и очищается только по крону. А сама процедура удаления вылетает с ошибкой: 

The field is_frz_tasks has already been deleted and it is in the process of being purged.

В общем, пока обходным решением является предварительная очистка данных поля. Дополняем обработку в hook_uninstall():

function MYMODULE_uninstall() {
  $entityTypeId = 'node';
  $fieldName = 'is_frz_tasks';

  $entityTypeManager = \Drupal::entityTypeManager();
  $entityDefinitionUpdateManager = \Drupal::entityDefinitionUpdateManager();

  $entityDefinition = $entityTypeManager->getDefinition($entityTypeId);
  $entityStorage = $entityTypeManager->getStorage($entityTypeId);

  $tableName = $entityStorage->getDataTable() ?: $entityStorage->getBaseTable();
  $tableRevision = $entityStorage->getRevisionDataTable() ?: $entityStorage->getRevisionTable();

  // clear data
  $database = \Drupal::database();
  if ($database->schema()->fieldExists($tableName, $fieldName)) {
    $database->update($tableName)
      ->fields([$fieldName => NULL])
      ->execute();
  }
  if ($entityDefinition->isRevisionable() && $database->schema()->fieldExists($tableRevision, $fieldName)) {
    $database = \Drupal::database();
    $database->update($tableRevision)
      ->fields([$fieldName => NULL])
      ->execute();
  }

  if ($field_is_frz_tasks = $entityDefinitionUpdateManager->getFieldStorageDefinition($fieldName, $entityTypeId)) {
    $entityDefinitionUpdateManager->uninstallFieldStorageDefinition($field_is_frz_tasks);
  }
}

Теперь все отрабатывает корректно.

Для проверки повторно переустанавливал модуль  

drush dre MYMODULE. 

Заполнял поле, затем снова переустанавливал

Какие еще могут возникнуть проблемы:

Что бы обработать очередь полей на удаление:

drush cron

или:

drush php-eval 'field_purge_batch(1000);'

Если по какой-то причине из БД исчезли таблицы вида field_deleted_data_XXX, но информация  в системе о них осталась, то вы получите ошибку БД:

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'field_deleted_data_XXX' doesn't exist: SELECT DISTINCT "t"."entity_id" AS "entity_id"
FROM "field_deleted_data_XXX" "t"
WHERE "bundle" = :db_condition_placeholder_0

Тут мне помогло только одно - в БД вручную необходимо создать таблицу:

CREATE TABLE `field_deleted_data_XXX` (   
`bundle` varchar(128) CHARACTER SET ascii NOT NULL DEFAULT '',
`deleted` tinyint(4) NOT NULL DEFAULT '0',
`entity_id` int(10) unsigned NOT NULL,
`revision_id` int(10) unsigned NOT NULL,
`langcode` varchar(32) CHARACTER SET ascii NOT NULL DEFAULT '',
`delta` int(10) unsigned NOT NULL,
`content_translation_source_value` varchar(12) CHARACTER SET ascii NOT NULL,
PRIMARY KEY (`entity_id`,`deleted`,`delta`,`langcode`),
KEY `bundle` (`bundle`),
KEY `revision_id` (`revision_id`) );

и затем запустить очистку 

drush php-eval 'field_purge_batch(1000);'

Вспомогательный класс для работы полями (рекомендую для ознакомления): error84

Drupal Drupal 9 — Статьи проdrush
Добавить комментарий
Может быть интересно

Порядок действий для установки Solr на сервере с Centos 7

4
Модуль Migrate это фреймворк для миграции (импорта) данных в Drupal из любых источников.
1
Снова возвращаемся к migrate. Довольно удобный фреймворк для импорта данных в Друпал. Один из распространенных форматов источника для импорта - CSV. Поддерживается migrate из коробки. Описание и примеры работы с классом MigrateSourceCSV можно найти на drupal.org.
2
Те, кто использует модуль Double field могли заметить, что в текстовой области отсутствует редактор. Бывают случаи, когда для удобства наполнения он просто необходим.
1
Данный модуль просто незаменим, если на Вашем сайте реализована мультиязычность или на Вашем сайте используются панели. Multilingual Panels позволяет создавать/менять настройки панели для различных языков.
1