April 03, 2009

Using Drupal Module Install Scripts to Simplify Site Deployments

One of the best features in Drupal is the ability to configure Drupal from within the Web UI. While this allows quick manipulation from the front end, regardless of technical expertise, it can quickly become a nightmare when migrating modified data between local, development, test, and production servers. Going through the process of updating all of these settings manually during each deployment is not only time consuming but extremely error prone, especially if you have multiple developers working simultaneously on separate machines.

Luckily, Drupal provides functionality to create module install scripts to make much of this process automated and repeatable. At first, it may seem like you're spending a lot of time writing these update scripts but, trust me, the time is well spent. You will save significantly more time and frustration when it comes time to deploy these changes.

Using install scripts can also improve your Q/A process. With the scripts in place you can load up a production database at any time and quickly run the updates to ensure that everything will run smoothly when you actually deploy to the live site. Install scripts also allow you to revert your database easily if anything should go wrong. Once you write he scripts, you don't have to think about database updates, all you need to do is point your browser to update.php and Drupal handles the rest for you!

Creating .install Files
To create an install file for your module simply create a file named module_name.install and put it in the same directory as module_name.module (along with the module_name.info file). In this file you can define functions that are triggered when a module is installed, uninstalled, enabled, disabled, or updated.

Hooks Available To .install Files

This sample file on the Drupal API site is a good source for .install file functionality.

Enabling / Disabling Modules
Install scripts are a perfect place for automatically enabling or disabling modules. By keeping this functionality in update functions you avoid visiting the module page every time you add a new module. Here's how to do it:

Enable Modules

function my_module_update_2201 {
  module_rebuild_cache();
  $mods = array('logintoboggan', 'custom_module');
  drupal_install_modules($mods);
  /* This will enable modules named logintoboggan and custom_module */
}

Disable Modules

function my_module_update_2201 {
  module_rebuild_cache();
  $mods = array('logintoboggan', 'custom_module', 'forum');
  module_disable($mods);
  /* This will disable modules named logintoboggan, custom_module, and forum */
}

If you are enabling new modules run update.php a second time to ensure that all the appropriate update scripts are run.

Setting System Variables
Nearly all of Drupal's module configuration options are stored in Drupal variables. This includes everything from the site name to email templates. Most custom modules also utilize these variables to store user configurable settings. To see all the currently stored variables take a look at the variables database table.

To programmatically set a variable, simply call variable_set(). This function accepts two arguments: name and value. The value argument can be either a string or array which will be automatically serialized for storage and unserialized when variable_get() is called. You can use this function to set new variables or modify existing ones.

Here are some examples:

function my_module_update_2201 {
  variable_set('site_email', 'new_email@domain.com');
  variable_set('my_module_email_settings', array('to' => 'email@databasepublish.com', 'from' => 'from@email.com'));
}

Custom SQL statements
Occasionally you'll need to run updates that the standard Drupal API can't handle. This is no problem. You can put anything you want in update scripts including custom SQL statements. Drupal provides a special database query function called update_sql() to handle update queries. If I wanted to publish all previously unpublished story nodes, I could add the following update function to my install file:

function my_module_update_2201 {
  $ret = array();
  $ret[] = update_sql("UPDATE {node} SET status=1 WHERE type='story' AND status=0");
  return $ret;
}

update_sql() will return a status message that will print on the update confirmation page indicating if it was successful or not. Please note, update_sql() does not accept additional arguments in a sprintf format like db_query(). All arguments should go directly in the SQL statement, like so:

function my_module_update_2201 {
  $ret = array();
  $ret[] = update_sql("UPDATE {node} SET status=1 WHERE type=". $type ." AND status=0");
  return $ret;
}

update_sql() can accept any valid SQL statement.

Using Devel Macro To Manipulate CCK Fields
While it's easy to define a content types from within modules, you unfortunately can't do the same with CCK groups and fields. Currently, you have to write complicated update scripts to achieve this. This will change when the Field API is released (finally!) with Drupal 7, but for right now the Devel Macro module will automate much of this scripting for you.

For those unfamiliar with the Devel Macro module, it's a module that can record and playback Drupal form submissions. This is extremely useful when you need to run an update on something that doesn't have a solid API or requires an enormous amount of coding that can more easily be handled in a few form submissions. CCK certainly falls into this category.

Using the Devel Macro module is pretty straightforward. You begin recording, make your modifications via standard Drupal form submissions, and finally export the macro array. This macro array can then be placed in an update function.

If I wanted to add a new CCK field I would do the following:

Here's an example of an update script that uses a Devel Macro script to create a new CCK field:

function my_module_update_2101() {
  $cck_path = drupal_get_path("module", "Content");
  include_once($cck_path ."/content_admin.inc");
  include_once($cck_path ."/content_crud.inc");
  include_once($cck_path ."/content.module");
  include_once(drupal_get_path("module", "devel") ."/macro.module");
 
  /* Paste entire macro array here */
  $macro[0]['form_id'] = '_content_admin_field_add_new';
  //... (truncated)
 
  // submit the form
  drupal_execute_macro($macro);
  content_clear_type_cache();
  return array();
}

Best Practices For Running Devel Macro Imports From Update Scripts:

Setting Permissions
There is currently no API methods to add or remove permissions in Drupal. Fortunately the useful Permission API module to simplifies this. With this module installed you'll be able to easily add or revoke permissions for your roles in your update scripts. For example:

function my_module_update_3100() {
  // Grant all permissions to the site admin role<br />
  $rid = db_result(db_query("SELECT rid FROM {role} r WHERE r.name = '%s'", 'site_admin'));
  permissions_grant_all_permissions($rid);

  // Grant all permissions defined in the forum module to the editor role
  $rid = db_result(db_query("SELECT rid FROM {role} r WHERE r.name = '%s'", 'editor'));
  permissions_grant_all_permissions_by_module($rid, $module);

  // Grant specific permissions to the authenticated user role
  $rid = db_result(db_query("SELECT rid FROM {role} r WHERE r.name = '%s'", 'authenticated user'));
  $new_permissions = array('access comments', 'post comments', 'post comments without approval');
  permissions_grant_permissions($rid, $new_permissions);

  // Revoke specific permissions from the authenticated user role
  $rid = db_result(db_query("SELECT rid FROM {role} r WHERE r.name = '%s'", 'authenticated user'));
  $new_permissions = array('access comments', 'post comments', 'post comments without approval');
  permissions_revoke_permissions($rid, $new_permissions);

  $ret[] = array('success' => TRUE, 'query' => 'Permissions successfully updated.');
  return $ret;
}

I would argue that Drupal deployments should involve little to no manual interaction for database changes. It's just too risky and time consuming. Budgeting the extra time up-front to write thorough install scripts is a major step in achieving this. It can be the difference of a full days effort to deploy to the staging and production environments to literally a few minutes for each build.

While install scripts can automate most database changes, it's not the only method available to you in Drupal. My next post will explain how to use standard hooks to define things like views, blocks, menu callbacks, and validation all from code in your modules. Defining these items in code instead of the database will further alleviate any manual action when deploying changes.

Posted at 11:35 am by Jeff Rigby

Technorati Tags:Technorati Tags:
Useful post - thanks. Just a quick note to say that in the "Setting System Variables" example, you set a variable called "site_email".... I think you meant this to be "site_mail"
Note: "Macro module is now no longer packaged with devel." Now at http://drupal.org/project/macro
Thanks, Jeff. This was quite a useful article to me. I've heard of other modules that assist with deployment (like the 'patterns' module). Do you have any experience with any other technique, and would you say that technique you describe here is still the best course of action? Thanks! David

More Blogs From Author:
Request a Consultation

DPCI In The News

Article by Jill Ambroz of Folio Magazine on the rise of the open-source Web Content Management System as a way for publishers to deliver content to their sites. > more

Alltop, all the top stories