Building reusable features

The Features module has recently become a core part of every project we work on. The ability to extract configuration from the database and turn it into real code is a huge benefit. Lately, we've been finding we want to be able to reuse features on multiple sites, but rarely have the exact same requirements for different sites.

Roger López
#Development | Posted

The Features module has recently become a core part of every project we work on. The ability to extract configuration from the database and turn it into real code is a huge benefit. Lately, we've been finding we want to be able to reuse features on multiple sites, but rarely have the exact same requirements for different sites. Lets use the example of a simple blog feature. This feature defines a content type and view. We deploy this feature on Site A and Site B. Site B needs to display 20 posts per page, rather than 10, which is defined by the view in the feature. We then modify the view on Site B to display 20 items per page, and the feature is now overridden. This presents at least a couple of problems. First, we have lost the benefit of maintaining our features in code. After doing the work to extract the db configurations into a feature, we have just put it back in the database. This change is not tracked anywhere but in the database, or perhaps in a wiki or issue. Second, we have essentially patched the feature. Maintaining a patched module makes updating that module more complicated and error-prone. Maintaining an overridden feature is just as bad. If at some point we need to update the feature, we would need to revert and manually re-apply all the overrides. Like so many other things in Drupal, almost every Features component is alterable. CTools exportables provide an alter hook for its exportables, and as of http://drupal.org/node/693024 Features now provides an alter hook for its faux exportables. Using these alter functions, we can provide configuration settings for our features in a way to doesn't override the feature. To accomplish this, we will be adding code to the module file of the feature, in this case tha_blog.module.

/** * Implementation of hook_views_default_views_alter(). */ function tha_blog_views_default_views_alter(&$views) { if (isset($views['blog'])) { $items_per_page = $views['blog']->pager['items_per_page']; $views['blog']->pager['items_per_page'] = variable_get('tha_blog_items_per_page', $items_per_page); } }

The first thing to do is to implement the specific alter function we need. In this case, we want to modify the view named blog. This is accomplished in hook_views_default_views_alter(). This function takes a reference to all of the exported views objects found by views. We start by making sure that blog view is available, and then simply set the value of the items_per_page with a variable_get. Notice that we are passing the original value as the default for variable_get. This way, if no variable is set, the default value from the exported view is used. Now that we are altering the view with a variable, we can provide an interface for updating this value.

/** * Implementation of hook_menu(). */ function tha_blog_menu() { $items['admin/settings/tha-blog'] = array( 'title' => 'Blog', 'page callback' => 'drupal_get_form', 'page arguments' => array('tha_blog_admin_settings'), 'access arguments' => array('administer site configuration'), ); return $items; }

/** * Form API callback for admin/settings/blog. */function

tha_blog_admin_settings() { // Load the default, un-altered views from the callback for the default value. features_include_defaults(array('views')); $views = tha_blog_views_default_views(); $items_per_page = $views['blog']->pager['items_per_page']; $form['tha_blog_items_per_page'] = array( '#title' => t('Items per page'), '#type' => 'textfield', '#default_value' => variable_get('tha_blog_items_per_page', $items_per_page), ); return system_settings_form($form); } This is pretty standard code for creating a settings page and form. The one important bit is at the top of tha_blog_admin_settings(). Notice that we are loading the default view from the feature to get the default value for the setting. This is similar to what is being done in the alter hook, so that default value is always set in the view that is exported by features. So what have we accomplished? We can now override the value of the items per page in the blog view without overriding the feature. Furthermore, we can also export a new feature that encapsulates this change. Since the configuration is held in a variable, we can use strongarm to export that variable into a feature of its own. For example, we can have a feature called siteb_blog that has a dependency on tha_blog and a variable to override the items_per_page.

 

Roger López