A new paradigm for overriding Drupal Features



The Drupal Features module allows you to capture configuration data, such as entities, fields, variables, views, permissions, etc into code modules.  These code modules can then be placed into version control, enabled and disabled, or used on a separate Drupal installation to provide the same feature.

Mike Potter, Software Architect
#Drupal | Posted


The Drupal Features module allows you to capture configuration data, such as entities, fields, variables, views, permissions, etc into code modules.  These code modules can then be placed into version control, enabled and disabled, or used on a separate Drupal installation to provide the same feature.

Converting site-specific configuration data from using the database into using code is crucial for building complex sites across a large development team.  But one of the problems of using Features has been the lack of a good way to override an existing feature with data in a new feature.

For example, consider the use of a Drupal distribution such as OpenPublish.  OpenPublish provides several Features such as Article, Blog, Profile, etc.  If you want to change the display format of the Main Image in the OpenPublish Article feature, you can't just override it.  Instead you end up re-exporting a new version of the OP Article Feature itself.  This prevents you from upgrading OpenPublish in the future since it now contains new site-specific configuration settings.

The Features Override module attempted to solve this problem.  However it had several drawbacks.  It required each specific override to be placed into its own feature.  It also leaves the parent feature (the OP Article in this example) marked as "overridden", making it difficult to determine if there have been any newer changes to the configuration.

To implement a better Override mechanism, Features itself needs to be updated slightly.  The trick is to allow each feature to call an "alter" hook in it's exported code.  A separate Override feature can implement this alter hook to override the base configuration.

The Solution

To illustrate the solution, a sandbox module for overriding Field Display Settings has been created. It requires a patch applied to Features). This patch to Features adds the needed call to drupal_alter in the Field exportable.  This same mechanism could be added to other core Features exportable in the future.

The Features Field Override sandbox module implements a new Features exportable.  It only exports the Display and Widget settings of specified fields.  It was based upon the core Field exportable.  The primary difference is that in addition to rendering a default_hook for the feature exportable, it also renders an "alter" hook that is called by the Field exportable being overridden.

Let's look at a real example of using this module and leave the technical details for later.  First, install Features and apply the patch linked above.  Then use git to grab a copy of the Features Field Override module from the sandbox.  Enable both modules on your Drupal 7 site.

First let's create the base Field feature.  If you have created Features before, you won't see anything new here.  Go to Structure/Features and click Create Feature.  Enter a Name and Description as shown below:


Scroll down to the Edit components drop-down and select Fields.  Then select the fields you wish to export.  In this case I have selected the body, tags, and image fields of the Article content-type:


Finally, click the "Download feature" button at the bottom of the page.  This will download the new Feature as a module for you to place into your Drupal site.  Unpack the module to the normal sites/all/modules directory.  Then go to your Drupal Modules page and enable it.

Next, let's create the new Override feature.  This works just like creating any other normal Feature.  Click the New Feature button in Structure/Features and enter the name and description of your new Override feature:


Then scroll down and in the "Edit components" drop-down, select the new "Field Display Settings" option (provided by the Features Field Override sandbox module you installed earlier).  In this case, let's just override the display settings for the Body field:


Finally click the "Download feature" button and install this new module into your Drupal site.

Now we can play with these two features and see the Override magic at work.  Let's edit the display settings for the Body field to override what is provided by the base Distro Article.  Go to the Article content type in Structure/Content types.  Click "manage display" for the Article type.

When the Override feature is enabled, this alter hook gets called by the Field feature to override the display settings.  If the Override feature is disabled, the alter hook disappears and the Field reverts to it's original configuration.  Select "Teaser" from the list of Displays in the upper-right corner.  Now hide the Body field in the teaser by selecting the <Hidden> option in the Format column.  You should see something like this:


Now click Save and return to the Structure/Features page.  In the Features list, you will see that your Distro Override feature is now marked as Overridden (because the database changes no longer match what was previously exported to code).


If you have content on the front page of your website, you should notice that the Body field has been hidden from teaser views.

We have two choices at this point:  1) save the new Override to code, or 2) "revert" the database back to the previous configuration.

Click on the "Overridden" button shown in the "Distro Field Override" row to view the feature.


If you install the "Diff" module for Drupal, you can click the "Review Overrides" tab to see the exact configuration change that has been made:


The code on the left reflects the saved code in the feature on disk.  The code on the right reflects the current active configuration in the database.  Back in the View tab, if you click checkbox outlined in green in the image above to select the component you wish to change, then click the "Revert components" button, this will undo your configuration change and replace it with the original configuration stored in code.  The Body field will now be displayed in the teaser again.

However, if you click the Recreate tab you can Download a new version of the override feature and save it to your Drupal site.  Now the hidden Body field override is stored in your new Override feature.

Back in the Structure/Features page, if you disable your new Override feature and click Save, the Body field will be restored to the teaser (your original base Distro feature is now in charge).  If you enable the Override feature and click save, the Body field should disappear in the teaser again because the base Distro feature is being overridden by your new Override feature.

There you have it.  An override feature that works just like you'd expect it to.  Just select the fields you want to override and save it to a new override feature.  The base feature is left untouched and your site-specific display settings are captured in the override feature.

The code under the hood

Want to see some code?  The exported code for a sample Override feature looks something like this:

  1. /**
  2.  * Implements hook_field_settings_default().
  3.  * Normal hook_default for any Features exportable.
  4.  * Returns the configuration data we want to save
  5.  */
  6. function distro_field_override_field_settings_default() {
  7. $fields = array();
  8. // save the display settings
  9. $fields['node-article-body']['display'] = array(
  10. 'default' => array(
  11. 'label' => 'hidden',
  12. 'module' => 'text',
  13. 'settings' => array(),
  14. 'type' => 'text_default',
  15. 'weight' => '0',
  16. ),
  17. 'teaser' => array(
  18. 'label' => 'hidden',
  19. 'type' => 'hidden',
  20. 'weight' => '0',
  21. 'settings' => array(),
  22. ),
  23. );
  24. // save the widget settings
  25. $fields['node-article-body']['widget'] = array(
  26. 'weight' => '-4',
  27. 'type' => 'text_textarea_with_summary',
  28. 'module' => 'text',
  29. 'active' => 1,
  30. 'settings' => array(
  31. 'rows' => '20',
  32. 'summary_rows' => 5,
  33. ),
  34. );
  35. return $fields;
  36. }
  38. /**
  39.  * Implements hook_features_field_alter().
  40.  * This alter hook is called by the module_name_field_default hook
  41.  * of the parent Field Feature
  42.  */
  43. function distro_field_override_features_field_alter(&$data) {
  44. $fields = distro_field_override_field_settings_default();
  45. foreach ($fields as $key => $field) {
  46. if (isset($data[$key])) {
  47. $data[$key]['field_instance']['display'] = $field['display'];
  48. $data[$key]['field_instance']['widget'] = $field['widget'];
  49. }
  50. }
  51. }

In the example above, the Override feature sets the Body field to be Hidden in the teaser display.

If you look at the parent Distro feature, the default_hook for the Field feature looks like this:

  1. /**
  2.  * Implements hook_field_default_fields().
  3.  */
  4. function distro_article_field_default_fields() {
  5. $fields = array();
  7. // Exported field: 'node-article-body'
  8. $fields['node-article-body'] = array(
  9. 'field_config' => array(
  10. 'active' => '1',
  11. 'cardinality' => '1',
  12. 'deleted' => '0',
  13. 'entity_types' => array(
  14. 0 => 'node',
  15. ),
  16. 'field_name' => 'body',
  17. 'foreign keys' => array(
  18. 'format' => array(
  19. 'columns' => array(
  20. 'format' => 'format',
  21. ),
  22. 'table' => 'filter_format',
  23. ),
  24. ),
  25. 'indexes' => array(
  26. 'format' => array(
  27. 0 => 'format',
  28. ),
  29. ),
  30. 'module' => 'text',
  31. 'settings' => array(),
  32. 'translatable' => '0',
  33. 'type' => 'text_with_summary',
  34. ),
  35. 'field_instance' => array(
  36. 'bundle' => 'article',
  37. 'default_value' => NULL,
  38. 'deleted' => '0',
  39. 'description' => '',
  40. 'display' => array(
  41. 'default' => array(
  42. 'label' => 'hidden',
  43. 'module' => 'text',
  44. 'settings' => array(),
  45. 'type' => 'text_default',
  46. 'weight' => '0',
  47. ),
  48. 'teaser' => array(
  49. 'label' => 'hidden',
  50. 'module' => 'text',
  51. 'settings' => array(
  52. 'trim_length' => 600,
  53. ),
  54. 'type' => 'text_summary_or_trimmed',
  55. 'weight' => '0',
  56. ),
  57. ),
  58. 'entity_type' => 'node',
  59. 'field_name' => 'body',
  60. 'label' => 'Body',
  61. 'required' => 0,
  62. 'settings' => array(
  63. 'display_summary' => 1,
  64. 'text_processing' => '1',
  65. 'user_register_form' => FALSE,
  66. ),
  67. 'widget' => array(
  68. 'active' => 1,
  69. 'module' => 'text',
  70. 'settings' => array(
  71. 'rows' => '20',
  72. 'summary_rows' => 5,
  73. ),
  74. 'type' => 'text_textarea_with_summary',
  75. 'weight' => '-4',
  76. ),
  77. ),
  78. );
  80. // ... more fields here ...
  82. features_alter_component($fields, 'field');
  83. return $fields;
  84. }

Notice the base Field feature has the Body field set to text_summary_or_trimmed in the Teaser display.  The new call to "features_alter_component" at the end is what calls the alter-hook in the new Override module shown above.

Feel free to post to the issue queue for the Features Field Override sandbox module if you find any problems or have any suggestions.  If this method for overriding features is accepted by the community, hooks for other types of features can be added.  Somebody could also write a more general-purpose Override module that allows the user to select specific properties of specific fields, such as selecting the specific code lines to be overridden.  The small changes to Features were all that was needed to open this up to new Override module solutions from the community.  Enjoy!

Mike Potter

Mike Potter

Software Architect