Drupal staging and deployment tips: It’s all code
Posted: - Modified: | drupalAs I talk to more and more developers about practices for working with Drupal, I get the idea that the staging and deployment process adopted by my team isn’t widespread.
Many developers make their changes directly through the web-based interface of a testing server, or even on the production site itself. I think that’s both tedious and scary. =)
Putting all of our behavior-related changes in update function in the install file makes it easier to merge changes and repeatedly test upgrades. Editorial changes (fixing typos, etc.) can happen on the site, but if it’s behavior-related code, it should be in the code repository. In moments of weakness, we’ve made web-based changes to our site, and we almost always regret those–either right away because things broke, or when we try to reconstruct our changes.
The Module Developer’s Guide documents how to write .install files for Drupal 5 and Drupal 6, but doesn’t go into much detail about what else you can put in the update functions. Maybe that’s why many developers use install files only for database-related changes. But you can do so much more: creating nodes, adding permissions, enabling other modules, and so on.
Watch out for these potential pitfalls:
- $user is set to the superuser (uid = 1) if you run update.php after logging in as the superuser, but if $access_check is set to FALSE in update.php, then $user will be null. That means that if you’re creating nodes or doing other things that check user_access or node_access, you should temporarily switch to the superuser.
Here are some general tips on how to find out the programmatic equivalent of a web-based action:
- Find the form or form_submit function that processes the action and see if there are any API functions you can call to produce the same effect. If you can’t find an API function, consider writing one.
- If you can’t write an API function, use the Macro module (part of the Devel module for Drupal 5) to record the form submission, and then use drupal_execute to run the recorded macro, OR
- Directly manipulate the database, making sure to call any hooks necessary.
And here are some examples of programmatically doing things:
Setting a variable
variable_set(‘yourvariable’, ‘yourvalue’);
Enabling modules
include_once(‘includes/install.inc’);
module_rebuild_cache();
drupal_install_modules(array(‘module1’, ‘module2’));
Disabling modules
db_query(“UPDATE {system} SET status=0 WHERE type=’module’ AND name=’%s'”, ‘modulename’);
Creating nodes
global $user;
$old_user = $user;
$user = user_load(array(‘uid’ => 1));
$session = session_save_session();
$session_save_session(FALSE);
// Do the work
$node = new stdClass();
$node->type = ‘page’;
$node->title = ‘Title’;
$node->body = ‘Body’;
node_save($node);
// Restore the user
$user = $old_user;
session_save_session($session);
Deleting nodes
global $user;
$old_user = $user;
$user = user_load(array(‘uid’ => 1));
$session = session_save_session();
session_save_session(FALSE);
// Do the work
node_delete($nid);
// Restore the user
$user = $old_user;
session_save_session($session);
Updating the list of blocks
global $theme_key;
$theme_key = ‘yourtheme’;
_block_rehash();
Convenience functions for working with permissions
function _add_permissions($roles, $permissions) { $ret = array(); foreach ($roles as $rid) { if (is_numeric($rid)) { $role = db_fetch_array(db_query("SELECT rid, name FROM {role} WHERE rid=%d", $rid)); } else { $role = db_fetch_array(db_query("SELECT rid, name FROM {role} WHERE name='%s'", $rid)); } $role_permissions = explode(', ', db_result(db_query('SELECT perm FROM {permission} WHERE rid=%d', $role['rid']))); $role_permissions = array_unique(array_merge($role_permissions, $permissions)); db_query('DELETE FROM {permission} WHERE rid = %d', $role['rid']); db_query("INSERT INTO {permission} (rid, perm) VALUES (%d, '%s')", $role['rid'], implode(', ', $role_permissions)); $ret[] = array('success' => true, 'query' => "Added " . implode(', ', $permissions) . ' permissions for ' . $role['name']); } return $ret; } function _remove_permissions($roles, $permissions) { $ret = array(); foreach ($roles as $rid) { if (is_numeric($rid)) { $role = db_fetch_array(db_query("SELECT rid, name FROM {role} WHERE rid=%d", $rid)); } else { $role = db_fetch_array(db_query("SELECT rid, name FROM {role} WHERE name='%s'", $rid)); } $role_permissions = explode(', ', db_result(db_query('SELECT perm FROM {permission} WHERE rid=%d', $role['rid']))); $role_permissions = array_diff($role_permissions, $permissions); db_query('DELETE FROM {permission} WHERE rid = %d', $role['rid']); db_query("INSERT INTO {permission} (rid, perm) VALUES (%d, '%s')", $role['rid'], implode(', ', $role_permissions)); $ret[] = array('success' => true, 'query' => "Removed " . implode(', ', $permissions) . ' permissions for ' . $role['name']); } return $ret; }
Use update functions for all of your behavioral changes. Use source code control. Write regression tests. These practices won’t take all the challenges out of Drupal development, but they certainly make it less stressful–and more fun. =)