For some work projects we've started making all the configuration changes via update functions. These get checked into version control and from there deployed to the staging site for testing, and then eventually deployed on the production site. The nice thing about update functions is that you can test it on staging and be sure that exactly the same changes will occur on the production site.
Here's a few examples, I'll continue to update it as I get more good examples.
Installing a module
Simple one liner to enable several modules:
<?php
function foo_update_6000(&$sandbox) {
$ret = array();
drupal_install_modules(array('devel', 'devel_node_access'));
return $ret;
}
?>Batch based update to regenerate PathAuto aliases
More elaborate update that uses the BatchAPI to avoid timeouts while regenerating the path aliases for two node types:
<?php
function foo_update_6000(&$sandbox) {
$ret = array();
if (!isset($sandbox['progress'])) {
// Set the patterns
variable_set('pathauto_node_foo_pattern', 'foo/view/[nid]');
variable_set('pathauto_node_bar_pattern', 'bar/view/[nid]');
// Initialize batch update information.
$sandbox['progress'] = 0;
$sandbox['last_processed'] = -1;
$sandbox['max'] = db_result(db_query("SELECT COUNT(*) FROM {node} n WHERE n.type IN ('foo', 'bar')"));
}
// Fetch a group of node ids to update.
$nids = array();
$result = db_query_range("SELECT n.nid FROM {node} n WHERE n.type IN ('foo', 'bar') AND n.nid > %d ORDER BY n.nid", array($sandbox['last_processed']), 0, 50);
while ($node = db_fetch_object($result)) {
$nids[] = $node->nid;
}
if ($nids) {
// Regenerate the aliases for the nodes.
pathauto_node_operations_update($nids);
// Update our progress information for the batch update.
$sandbox['progress'] += count($nids);
$sandbox['last_processed'] = end($nids);
}
// Indicate our current progress to the batch update system. If there's no
// max value then there's nothing to update and we're finished.
$ret['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']);
return $ret;
}
?>Change node settings
Make a few changes to the node type settings:
<?php
function foo_update_6001() {
$ret = array();
// Change the teaser label to 'teaser text'.
$ret[] = update_sql("UPDATE content_node_field_instance SET label = 'Teaser text' WHERE field_name = 'field_teaser'");
// Change the 'description' and 'biography' labels to 'body text'.
$ret[] = update_sql("UPDATE content_node_field_instance SET label = 'Body text' WHERE field_name IN ('field_description', 'field_bio')");
// Rename the front node type 'Front Page' to 'Front Page Configuration'
$ret[] = update_sql("UPDATE node_type SET name = 'Front Page Configuration' WHERE type = 'front'");
return $ret;
}
?>Delete a bunch of views
I exported the site's views into a default views and needed to remove the existing ones from the database.
<?php
function rfy_update_6001(&$sandbox) {
$ret = array();
// Since we're shipping default views delete the versions from the database.
if (!isset($sandbox['progress'])) {
// Initialize batch update information.
$sandbox['progress'] = 0;
$sandbox['views'] = array(
'season',
// ...
'nodequeue_1',
);
$sandbox['max'] = count($sandbox['views']) - 1;
dsm($sandbox);
}
module_load_include('module', 'views');
$view_id = $sandbox['views'][$sandbox['progress']];
if ($view = views_get_view($view_id)) {
$view->delete();
$view->destroy();
}
$sandbox['progress']++;
// Indicate our current progress to the batch update system. If there's no
// max value then there's nothing to update and we're finished.
$ret['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']);
return $ret;
}
?>
MySQLDiff
There is a cool PHP app called MySQLDiff that you can run as a web app, (probably on your localhost) and it will generate SQL Diffs as statements that you can run against a DB to bring it to match a schema. It can run against databases in mysql, or database dump files. Obviously you need to parse through the results, but finding those items that some modules hide in random tables or what not can be pretty handy.
http://www.mysqldiff.org/
Not sure about Drupal 6, but
Not sure about Drupal 6, but in D5 you need to make sure modules are in the system table before you can enable a new module:
<?phpfunction rg_card_update_2() {
$ret = array();
// make sure new modules are in the system table.
module_rebuild_cache();
module_enable(array('jquery_update'));
$ret[] = array('success' => TRUE, 'query' => 'Enabled jQuery Update');
return $ret;
}
?>
Install function won't be called
If you're enabling modules like this on D5 beware that the install functions (hook_install) won't be called. This can be bad :(
I use this technique as well
it works well, and it makes deploying a changeset from dev -> stage -> live a lot easier and more accurate. This is especially helpful when you have more than 1 developer on your team who you need to coordinate deployments and changes with.
The biggest problem is that each type of update is done differently. This is kind of a pain and you have to discover the best practice for every type of change yourself. I keep a googledoc with each type and sample code of what was most effective.
Watch out for update_sql()! I wrote a blog post on it. The essence of it is that you'll naturally be inclined to use it since you're in a hook_update_N() function, but never use it if one of the DB values you are trying to set includes a curly brace, usually when it includes a PHP code block or a serialized array. For example, when updating a block's PHP assignment code, or maybe the configuration settings of a cck field. The curly brace will be escaped, and the query will update the value incorrecttly without complaint. Use db_query() for these cases instead! (which leaves curly braces alone when part of the values).
We are using this technique
We are using this technique at Economist too, with lots of success. We use the install api wizard (bad name, great module) to help with these update functions. THis works well to move changes to other developer databases as well.
This is best practice nowadays but I'm hoping this can become less onerous still (see exportables at http://agaricdesign.com/note/drupalcondc-a-paradigm-reusable-drupal-feat...)
Exportables
Moshe,
I agree about exportables.. Version control, no putting site in maintenance mode -> run update.php -> take site out of maint. mode.
Where is that install api wizard module?
Thx
http://drupal.org/project/ins
http://drupal.org/project/install_profile_api
Node Settings update
Hi,
The first couple look great, but the last one you should not alter the db directly, but instead use the cck crud functions and the node_type_save()
These call hooks and other things that your method is going to circumvent.
Gordon.