Drupal Design Configuration Management - NCIOCPL/cgov-digital-platform GitHub Wiki

Overview

We will manage our configuration with the features module. Features provides a UI and API for taking different site building components from modules with exportables and bundling them together in a single feature module. A feature module is like any other Drupal module except that it contains additional information in its info file so that configuration can be checked, updated, or reverted programmatically.

Features will be used to manage configuration for "features" for the profile. This will provide a more streamlined way to export changes to configuration managed my your modules. The other benefit of features is that it will allow configuration updates to be imported once the repository moves from install to update.

As noted in Module Design, a module should only contain the code and configuration required by that feature.

Features Gotchas

  • No configuration translation support. We don't enable config_translate anyway.
  • If a module is not installed and you try and export, it will remove your configs. Don't do it.
  • We do not have it configured yet to completely handle additions of things like display views for a bundle. It will work in some cases, and not in others.
  • Dependency analysis and guessing through the UI can go horribly wrong.

Steps to Enable a brand new module

  1. Create your module
  2. Add a new file to the module called <modulename>.features.yml (NOTE: This is not in config install!)
    1. This file just contain:
excluded:
  - user.role.authenticated
  - user.role.content_author
  - user.role.content_editor
  - user.role.advanced_editor
  - user.role.layout_manager
  - user.role.site_admin
  - user.role.admin_ui
  - user.role.content_previewer
  - user.role.blog_manager
  - user.role.image_manager
  - user.role.pdq_importer

  1. Clear Caches
  2. Make all your config changes through the UI
  3. Use the features UI to add your configs.

Steps to Enable Features for a legacy Module

  1. Add a new file to the module called <modulename>.features.yml (NOTE: This is not in config install!)
    1. This file should contain
excluded:
  - user.role.authenticated

  1. Clear Caches
  2. Export feature to sync config: drush features:export <modulename>
    • When it prompts you, "The extension already exists at ", answer yes
  3. Review configuration changes and dependency updates. Resolve conflicts if circular dependencies are introduced. Most configs it will just order things correctly, quote strings that need to be quoted, remove quotes from things that are not to be quoted. Displays are the things that will change the most.
    • Things to look for:
      • Did it remove the enforced dependency from a field.storage. config?
        • It will do this if your module was missing a dependency
      • Did it remove an entire config?
        • there was probably a missing dependency from the info.yml
  4. Final Test
    1. Commit your changes
    2. blt cgov:reinstall
    3. Export feature to sync config: drush features:export <modulename>
    4. git status -- There should be NO pending changes.

How to re-export a module

It depends on if you are modifying existing configs, or new ones...

Modifying existing configs

  1. Modify your configs through the UI.
  2. Run drush features:export <module> - you should see changes to your configs in your git status

Modifying new configs

  1. Modify your configs through the UI. Two Options
  • Option 1. 2. Run drush features:add <module> <config> -- so this is a rough approximation of how it should work derived by reading the docs, which poorly document this. YMMV.
  • Option 2. Use the features UI. I tried this once, it changed 200+ configs, I did not try it again. We need to refine what our exclusions are for the project to exclude items shipped with core and third parties that we do not override.

Exceptions to Configuration Files

There are a number of cases where we cannot, or should not, set options in the configuration files directly. This is because circular dependencies will be introduced, OR the config that is being updated no longer allows us to separate functional pieces.

Entity Reference (ER) Fields

When attaching an ER field to an entity (field.field...yml) the UI forces you to select the bundle types that are allowed. Doing so makes the ER field dependent on those bundles, and that is problematic as it can lead to circular dependencies.

Purge Configurations

Features cannot handle multiple modules with the same configuration. We have two different cgov_caching modules one for when there is Akamai, one when there is not. These need the same config files, but with different contents. As such we cannot manage these configs with features. A helper has been created, \Drupal\CgovPlatform\Cache\CgovPurgeConfigInstaller, to support configuration changes from the hook_install and any hook_update_N functions.

Roles

Roles have permissions, which will be wiped out by features if the config is imported. Always create roles in hook_install.

Role Permissions

Role permissions should be attached to roles in the module.install file with hook_install. The Cgov tools has a helper function that can help with this. An example of what to do in the hook is below.

function my_module_install() {
  $siteHelper = \Drupal::service('cgov_core.tools');

  $perms = [
    'role1' => [
      'permission 1',
      'permission 2',
    ],
    'role2' => [
      'permission 3',
    ],
  ];

  $siteHelper->addRolePermissions($perms);
}

If you have a Kernel test that uses permissions, then you will need to install your module and its dependencies. Normally we just install configs and schemas, but that does not execute the install hook. Below is a simple example:

<?php
namespace Drupal\Tests\my_module\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
 * Test my module.
 *
 * @group cgov
 * @group cgov_site
 */
class MyModuleTest extends KernelTestBase {
  use NodeCreationTrait;
  use UserCreationTrait;
  /**
   * {@inheritdoc}
   */
  public static $modules = [
    'system',
    'user',
  ];
  /**
   * Use our own profile instead of one from the standard distribution.
   *
   * @var string
   */
  protected $profile = 'cgov_site';

  /**
   * Users with role permissions to be tested.
   *
   * @var array
   */
  protected $users = [];

  /**
   * {@inheritdoc}
   */
  protected function setUp() {
    static::$configSchemaCheckerExclusions = CgovSchemaExclusions::$configSchemaCheckerExclusions;
    parent::setUp();
    // These are special and cannot be installed as a dependency
    // for this module. So we have to install their bits separately.
    $this->installEntitySchema('user');
    $this->installSchema('system', ['sequences']);
    $this->installConfig(['system', 'user']);
    // Install core and its dependencies.
    // This ensures that the install hook will fire, which sets up
    // the permissions for the roles we are testing below.
    \Drupal::service('module_installer')->install(['my_module']);
  }

  /**
   * Tests the module.
   */
  public function testMyModule() {
    $this->assertEquals(TRUE, TRUE);
  }
}

Workflow

The workflow configuration (workflow.workflow.*.yml) contain the list of entities that participate in that workflow. This introduces circular dependencies into our configurations when creating modular features. So workflow configurations should never contain the list of entities, and instead, content types should attach themselves to the workflow on module installation.

function my_module_install() {
  $siteHelper = \Drupal::service('cgov_core.tools');
    // editorial_workflow is our "normal" workflow. There are more specialized workflows,
    // if your module uses a specialized workflow, then enter its machine name as the
    // second argument.
    $siteHelper->attachContentTypeToWorkflow('my_content_type', 'editorial_workflow'); 
}
⚠️ **GitHub.com Fallback** ⚠️