Drupal 8 自定义模块 - 使用 hook_update_N 从 YML 文件安装附加配置

Drupal 8 custom modules - Installing additional configuration from YML files with a hook_update_N

我有一个自定义模块,它安装了一组特定的配置 - 这些都存储在 config/install 文件夹中,这意味着它们在安装模块时安装。

配置包括内容类型、段落、视图模式、表单模式、字段存储以及内容类型和段落附带的字段等。想法是使用此模块安装一个'feature'(博客)并跨多个站点使用它,并在我们向此功能添加更多内容时提供更新和扩展。

自从初始安装后,您无法通过 config/install 文件夹添加更多配置,我一直在尝试找到一种通过更新挂钩导入其他配置文件的方法,这是一种有效的方法:

<?php

use \Symfony\Component\Yaml\Yaml;

/**
 * Installs the file upload element
 */
function MODULE_NAME_update_8002() {

  // Is the flaw with this the fact that the order of loading configurations now
  // matters and is a little bit more difficult to deal with?
  // NOTE:  YES. If, for example, you comment out the installing of the
  // field_storage for the field_cb_file, but try to add the field_cb_file to
  // the paragraph type, the update is successful and no errors are thrown.
  // This is basically me trying to re-create the drupal configuration management
  // system, without the dependency checks, etc. What is the PROPER way of
  // importing additional configuration from a module through an update?
  //  FIXME:  

  $configs_to_install = [
    'paragraphs.paragraphs_type.cbsf_file_download',
    'field.storage.paragraph.field_cb_file',
    'field.field.paragraph.cbsf_file_download.field_cb_file',
    'field.field.paragraph.cbsf_file_download.field_cb_heading',
    'field.field.paragraph.cbsf_file_download.field_cb_icon',
    'field.field.paragraph.cbsf_file_download.field_cb_text',
    'core.entity_form_display.paragraph.cbsf_file_download.default',
    'core.entity_view_display.paragraph.cbsf_file_download.default',
  ];

  foreach ($configs_to_install as $config_to_install) {
    $path = drupal_get_path('module', 'MODULE_NAME') . '/config/update_8002/' . $config_to_install . '.yml';
    $content = file_get_contents($path);
    $parsed_yml = Yaml::parse($content);

    $active_storage = \Drupal::service('config.storage');
    $active_storage->write($config_to_install, $parsed_yml);
  }
}

但是,此方法存在缺陷,因为这意味着如果配置文件相互依赖,则必须以正确的顺序排列配置文件,并且不会检查配置文件中存在的任何依赖项。

有没有办法利用配置管理以同样的方式正确导入配置,'loop over the files'?或者指向包含所有配置文件的文件夹并安装它们?


EDIT: There are further issues with this method - even if you've ordered the files correctly in terms of dependencies, no database tables are created. The configuration is simply 'written in' as is, and no other part of Drupal seems to be made aware that new entities were created, so they cannot run any of the functions that are otherwise ran if you were to create the entities through Drupal GUI. Definitely not the recommended way of transferring more complex configuration.

我更进一步 - 有一种方法可以使用 EntityTypeManager class 创建/更新配置。


2 个链接在很大程度上帮助了我:

https://drupal.stackexchange.com/questions/164713/how-do-i-update-the-configuration-of-a-module

pwolanins 底部的答案提供了一个功能,可以更新配置(如果存在),或者直接创建配置。

https://www.metaltoad.com/blog/programmatically-importing-drupal-8-field-configurations

此页面上的代码可以更清楚地了解正在发生的事情 - 对于您想要安装的每个配置,您 运行 通过相应的存储管理器创建 YML 文件,然后创建适当的实体配置,创建所有必需的数据库表。


我最后做的是:

使用 pwolanins 代码的略微修改版本并创建通用配置更新程序函数 -

function _update_or_install_config( String $prefix, String $update_id, String $module) {
  $updated = [];
  $created = [];

  /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
  $config_manager = \Drupal::service('config.manager');
  $files = glob(drupal_get_path('module', $module) . '/config/update_' . $update_id. '/' . $prefix . '*.yml') ;
  foreach ($files as $file) {
    $raw = file_get_contents($file);
    $value = \Drupal\Component\Serialization\Yaml::decode($raw);
    if(!is_array($value)) {
      throw new \RuntimeException(sprintf('Invalid YAML file %s'), $file);
    }

    $type = $config_manager->getEntityTypeIdByName(basename($file));
    $entity_manager = $config_manager->getEntityManager();
    $definition = $entity_manager->getDefinition($type);
    $id_key = $definition->getKey('id');
    $id = $value[$id_key];

    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $entity_storage */
    $entity_storage = $entity_manager->getStorage($type);
    $entity = $entity_storage->load($id);
    if ($entity) {
      $entity = $entity_storage->updateFromStorageRecord($entity, $value);
      $entity->save();
      $updated[] = $id;
    }
    else {
      $entity = $entity_storage->createFromStorageRecord($value);
      $entity->save();
      $created[] = $id;
    }
  }

  return [
    'udpated' => $updated,
    'created' => $created,
  ];
}

我将所有 yml 文件放在文件夹 config/update_8002 中,然后利用此函数在 hook_update_N 函数中循环配置文件:

function MODULE_NAME_update_8002() {

  $configs_to_install = [
    'paragraphs.paragraphs_type.cbsf_file_download',
    'core.entity_form_display.paragraph.cbsf_file_download.default',
    'core.entity_view_display.paragraph.cbsf_file_download.default',
    'field.storage.paragraph.field_cb_file',
    'field.field.paragraph.cbsf_file_download.field_cb_file',
    'field.field.paragraph.cbsf_file_download.field_cb_heading',
    'field.field.paragraph.cbsf_file_download.field_cb_icon',
    'field.field.paragraph.cbsf_file_download.field_cb_text',
  ];

  foreach ($configs_to_install as $config_to_install) {
    _update_or_install_config('paragraphs.paragraphs_type', '8002', 'MODULE_NAME');
    _update_or_install_config('field.storage.paragraph', '8002', 'MODULE_NAME');
    _update_or_install_config('field.field.paragraph', '8002', 'MODULE_NAME');
    _update_or_install_config('core.entity_view_display.paragraph', '8002', 'MODULE_NAME');
    _update_or_install_config('core.entity_form_display.paragraph', '8002', 'MODULE_NAME');
  }

}

请注意,_update_or_install_config 函数遍历文件夹中与特定实体类型管理器匹配的所有配置 - 因此您应该只在函数中包含前缀,以及导入的所有 YML 文件同类型的配置将包含在内。