Drupal Commerce Blog

What's happening in the world of Drupal Commerce.

Upgrade paths between Drupal 8 module versions

Over time, modules update their shipped configuration such as rules, views, node types. Users expect to get these updates when they update the modules. For example, Drupal Commerce 1.x provides over eleven default views and rules. From release to release, these Views and Rules were enhanced with new features or bug fixes reported by the community.

Take the order management view. In a future release of Drupal Commerce, we may add a newly exposed filter to the view. In Drupal 7 the view would automatically be updated unless it was overridden. That is not the case in Drupal 8.

Drupal 8 introduces the Configuration Management system. While robust, it changes how we work with the configuration in comparison to Drupal 7. Instead of a module owning configuration, configuration is now owned by the site. When a module is installed, its configuration is imported. On module update, no configuration updates are performed. Instead, the module must write an update hook to perform one of the following updates:

  • Install new configuration (e.g. the module now ships with an additional view)
  • Update existing configuration (e.g. the module has added a new field to an existing view)
  • Delete removed configuration (e.g. the module has removed an old view. Rare)

When updating existing configuration, we need to check whether it was modified by the user. In most cases (such as the “updated view” one), we want to only perform the update if the configuration was not user-modified. The update operation is usually called a “revert” since it involves reverting the installed configuration back to the version shipped with the module.

We decided to solve this set of use cases while preparing for the Commerce beta release. To do so, we’ve created a new ConfigUpdater class. It allows you to import, revert, or delete an array of configuration names. Before a configuration object is reverted or deleted its hash is compared to see if it has been modified since installation. The design was influenced by the Configuration Update module but tweaked to better satisfy the update hook context.

Here’s a full usage example:

<?php
/**
 * Placed in commerce.post_update.php
 */
function commerce_post_update_example() {
 
$config_updater = \Drupal::service('commerce.config_updater');

 
// The 'views.view.commerce_products' view was updated in the module, and
  // should be updated on the site unless the user has already modified it.
 
$first_result = $config_updater->revert(['views.view.commerce_products']);

 
// The 'commerce_product.commerce_product_type.default' product type was
  // updated in the module and should be updated on the site regardless
  // of whether the user has already modified it
 
$second_result = $config_updater->revert(['commerce_product.commerce_product_type.default'], FALSE);

 
// The 'system.action.commerce_publish_product' action was removed from the
  // module, and should be removed from the site.
 
$third_result = $config_updater->delete(['system.action.commerce_publish_product']);

 
// Let's pretend this is a more generic replacement for the old publish action.
 
$fourth_result = $config_updater->import(['system.action.commerce_superpublish_product']);

 
$message = '';
 
// Each updater call allows multiple config names, and returns a result
  // with success and failure messages. Merge them, and show them to the user.
 
$success_results = array_merge(
   
$first_result->getSucceeded(),
   
$second_result->getSucceeded(),
   
$third_result->getSucceeded(),
   
$fourth_result->getSucceeded()
  );
 
$failure_results = array_merge(
   
$first_result->getFailed(),
   
$second_result->getFailed(),
   
$third_result->getFailed(),
   
$fourth_result->getFailed()
  );

  if (
$success_results) {
   
$message = t('Succeeded:');
   
$message .= '';
    foreach (
$success_results as $success_message) {
     
$message .= '' . $success_message . '';
    }
   
$message .= '';
  }
  if (
$failure_results) {
   
$message .= t('Failed:');
   
$message .= '';
    foreach (
$failure_results as $failure_message) {
     
$message .= '' . $failure_message . '';
    }
   
$message .= '';
  }

  return
$message;
}
?>

See the original issue for more context: https://www.drupal.org/node/2677906

We think that this functionality will be needed by many contrib modules, so we’ve opened a core issue: https://www.drupal.org/node/2684081

Matt Glaman
Posted: Apr 22, 2016