Add generic interface + base class for upgrade paths that require config changes

Created on 28 April 2025, 5 days ago

Problem/Motivation

When we make changes to config entities/schema it requires the following:

1. A post_update hook that uses ConfigEntityUpdater to batch update all existing config entities of whatever type is being changed. This will update exported config for existing sites when core is updated to a new version.
2. A pre_save hook that does the same thing as 1 and also throws a deprecation message. This is for contrib modules, profiles, etc which have config in config/install (or test modules) so they know their exported config also needs to be updated.

The emerging pattern is to do the following:

1. Create a FooConfigUpdater service (e.g BlockConfigUpdater for block config entities)
This service is responsible for checking whether a config entity needs to be updated, doing the update, and throwing the deprecation error if the entity needed updating. This must be a service so the class property to flag that deprecations are enabled persists from the post_update hook into the pre_save hook.

The post_update hook then simply calls a needsFooBarUpdate method on the FooConfigUpdater service and if that returns TRUE, the ConfigEntityUpdater class will save the config entity, which will then flow into the pre_save hook. The pre_save hook then does the actual config changes.

This pattern has worked quite well and is currently in use by a number of different core issues:

1. Deprecating block_content block settings https://git.drupalcode.org/project/drupal/-/merge_requests/6953/diffs
2. Updating EntityViewDisplay config https://git.drupalcode.org/project/drupal/-/merge_requests/11941/diffs
3. Updating image fields https://git.drupalcode.org/project/drupal/-/merge_requests/5544/diffs

And quite a few more.

We should standardise these classes with an interface + base class to make them easier to implement and reduce boilerplate.

Proposed resolution

Add an ConfigUpdaterInterface with the following methods:
public function setDeprecationsEnabled(bool $enabled): void
public function updateConfigEntity(ConfigEntityInterface $entity): bool

Add a ConfigUpdaterBase abstract class with the following code:

  /**
   * Flag determining whether deprecations should be triggered.
   */
  protected bool $deprecationsEnabled = TRUE;

  /**
   * Stores which deprecations were triggered.
   */
  protected array $triggeredDeprecations = [];

  /**
   * Sets the deprecations enabling status.
   *
   * @param bool $enabled
   *   Whether deprecations should be enabled.
   */
  public function setDeprecationsEnabled(bool $enabled): void {
    $this->deprecationsEnabled = $enabled;
  }

Remaining tasks

Agree on implementation
Decide where these should live
Decide how much test coverage (if any) we need

API changes

New ConfigUpdater APIs

📌 Task
Status

Active

Version

11.0 🔥

Component

base system

Created by

🇦🇺Australia acbramley

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @acbramley
  • 🇬🇧United Kingdom catch

    +1, while the pattern works well, I think if you counted the number of people who know all the different bits of the pattern and why they're required, you'd not run out of toes, maybe even have some fingers left - config updates aren't needed that regularly and we had a lot of updates that failed to implement this properly in Drupal 8/9/10.

Production build 0.71.5 2024