Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Share changes with your team using Features and upgrade paths

Parent Feed: 

The Features module is great for keeping configuration in code, with just a few clicks you can bundle your settings together and be sure that changes will be tracked and safely dumped to code at the next feature update.

However, often you will want to share other kinds of modifications with your team. Imagine you suddenly need to add a Taxonomy vocabulary or to enable a couple of modules your feature will be dependent on. Since your team's workflow is completely 100% database free, you cannot pass those changes by simply sharing your database. Still, you need to push those changes to other developers, who already enabled the feature you have been modifying in their development environment. How do you do that?

Make your workflow solid: meet hook_update_N()

Features are modules and modules can have their own upgrade path by implementing hook_update_N(). That's exactly what you need.

All the changes the Features module is not keeping track of must be stored in sequential implementation of the hook_update_N() to be sure that other developers will have them replicated in their database by simply visiting update.php.

Have a look at the following example: we want to add a free-tagging taxonomy vocabulary to our feature. First of all we need to enable the Taxonomy module add it as dependency in its .info file:

dependencies[] = "taxonomy"

Adding a dependency will not enable the new module on the other developers' local copy, since the feature has already been enabled and dependencies will not be rechecked. To be sure the module will be enabled we have to write an update function:

<?php
/**
* Enabling Taxonomy module.
*/
function feature_example_update_6001() {
 
$return = array();
 
drupal_install_modules(array('taxonomy'));
 
$return[] = array('success' => TRUE, 'query' => 'Enabling Taxonomy module.');
  return
$return;
}
?>

Now it's time to create our free-tagging vocabulary. Since taxonomies are still not supported by the Features module we have to find another way to put this change in code. One way to do it is to create the vocabulary in an update function:

<?php
/**
* Create "Tags" vocabulary.
*/
function feature_example_update_6002() {
 
$return = array();
 
$vocab = array(
   
'name' => 'Tags',
   
'multiple' => 0,
   
'required' => 0,
   
'hierarchy' => 0,
   
'relations' => 0,
   
'weight' => 0,
   
'nodes' => array('story' => 1),
   
'tags' => TRUE,
   
'help' => t('Enter tags related to your post.'),
  );
 
taxonomy_save_vocabulary($vocab); 
 
$return[] = array('success' => TRUE, 'query' => 'Create "Tags" vocabulary.');
  return
$return;
}
?>

We can now commit our changes. Once the other developers pull the latest version of our feature in their local environment all they need to do is to run a Drupal update by visiting update.php.

The comment on top of the update functions above plays an important role in the workflow since both Drush and update.php are actually able to display it when running drush updatedb or running update.php, giving valuable information to the other team members:

$ drush updatedb
The following updates are pending:

feature_example module              
6001 - Enabling Taxonomy module.
6002 - Create "Tags" vocabulary.

Do you wish to run all pending updates? (y/n):

Make your features database free: meet hook_install()

Storing your changes in hook_update_N() will allow your team to be always up-to-date with the latest development of your feature, making your development workflow solid and maintainable. But what happens if a new developer wants to hop in? Our rule of thumb is "No database sharing", hence the new developer needs to install our project from scratch and, still, we need to guarantee that he will get the complete latest status of the project. To do that we need to copy some of the configuration we have been placing in update functions to the hook_install():

<?php
/**
* Implementation of hook_install()
*/
function feature_example_install() {
 
$vocab = array(
   
'name' => 'Tags',
   
'multiple' => 0,
   
'required' => 0,
   
'hierarchy' => 0,
   
'relations' => 0,
   
'weight' => 0,
   
'nodes' => array('story' => 1),
   
'tags' => TRUE,
   
'help' => t('Enter tags related to your post.'),
  );
 
taxonomy_save_vocabulary($vocab); 
}
?>

Structural and development updates

As you might have noticed hook_install() copies code from the feature_example_update_6002() and not from feature_example_update_6001(). This is because the two updates have a different nature: 6002 is a structural update, meaning that it is something we must guarantee even if the feature will be installed from scratch; 6001 is a development update which only aims to upgrade an already working development copy.

When writing your upgrade paths, it's good practice to distinguish between two kinds of updates, and in the case of a structural update, make sure you copy changes to the hook_install() of your feature.

Real life examples

Upgrade and install hooks are really powerful, you can put virtually anything in there. Below we list some real life example of upgrade paths we use in our projects at Nuvole.

Add a menu and menu items

<?php
/**
* Implementation of hook_install()
*/
function master_site_install() {
 
 
// Create a custom menu called "Manage Content".
 
db_query("INSERT INTO {menu_custom} (menu_name, title, description)
            VALUES ('%s', '%s', '%s')"
,
           
'menu-content',
           
'Manage Content',
           
'Manage your site content.'); // Add "Home" menu item to Primary Links menu.
 
$item['link_title'] = t('Home');
 
$item['link_path'] = '<front>';
 
$item['menu_name'] = 'primary-links';
 
$item['weight'] = -10;
 
menu_link_save($item);
}
?>

Add OpenId to admin account

<?php
/**
* Add Nuvole OpenID to admin account.
*/
function nuvole_site_update_6004() {
 
$return = array();
 
// Delete any other association of the OpenId account to avoid conflicts.
 
$return[] = update_sql("DELETE FROM {authmap}
                          WHERE authname = 'http://nuvole.myopenid.com/'"
); 
 
// Bind OpenId account to admin user.
 
$return[] = update_sql("INSERT INTO {authmap} (uid, authname, module)
                          VALUES (1, 'http://nuvole.myopenid.com/', 'openid')"
); 
  return
$return;
}
?>

Upgrading data from OpenLayers 1.x to 2.x

<?php
/**
* Upgrade OpenLayers 1.x to 2.x: WKT data in "content_type_opera"
*/
function publicopera_site_update_6007() { // OpenLayers 2.x stores WKT values as geometry collection.
  // Update data accordingly.
 
db_query("UPDATE {content_type_opera} SET field_opera_map_openlayers_wkt =
            CONCAT('GEOMETRYCOLLECTION(', field_opera_map_openlayers_wkt, ')')"
);
  return array(array(
'success' => TRUE, 'query' => 'All WKT content updated.'));
}
?>
Author: 
Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web