How to logically organize Drupal 7 features

drupal 7 logo

A brief introduction to Features

A common problem encountered while deploying updates Drupal instances lies in the fact that much of Drupal’s configuration data lives in the database rather than in code. In general, it is considered a truism that code is deployed from dev to test to production, while data is deployed from production to test and dev. This is because in any non-trivial content management system, the actual content data is being generated on the live site itself.

This presents a problem, though, when we are working on a hotfix or new feature which requires configuration changes to accompany the code. In many cases, the configuration data is very tightly bound up with other data present in the database. While for trivial features it might be feasible to simply write a module update rule that directly makes changes to configuration tables in the database, for work of any significant complexity this methodology becomes unfeasible.

The solution to this problem is the Features module, which is a Drupal contrib module. This module allows you to persist configuration items as generated code, which, when deployed, can be “reconstituted” into the proper database objects on the target system. The Features module natively supports a number of types of configuration data, and also uses a common API to allow other modules to support even more types of configuration. Here are some types of configuration data which can be persisted as deployable units of code:

  • Content types and their fields
  • Custom menus
  • Image settings
  • Language/localization settings
  • Taxonomy vocabularies
  • Text format filters
  • User permissions and roles

The following configuration types can be persisted as parts of a Feature via third-party modules:

  • Drupal variables (via the Strongarm module)
  • Field groups (via the Field Groups module)
  • Views (via the Views module)
  • Beans (via the Bean module)
  • Entity forms (via the Entity Forms module)
  • Services (via the Services module)
  • Elysia cron rules (via the Elysia Cron module)
  • Quicktabs (via the Quicktabs module)
  • Block settings (via the Features Extra module)
  • Captcha settings (via the Captcha module)
  • Conditional fields (via the Conditional Fields module)
  • CKEditor profiles (via the CKEditor module)
  • File entities (via the File Entity module)
  • Panelizer settings (via the Panelizer module)
  • Panels Mini (via the Panels Mini module)
  • Page manager pages (via the Page Manager module)
  • Fieldable panels (via the Fieldable Panels module)
  • Node, file, bean, and taxonomy term content (via the UUID Features module)
  • Workbench moderation states and transitions (via the Workbench Moderation module)

(This list is by no means exhaustive.)

Organizational patterns in the wild

These features persisted to code can be grouped together arbitrarily. Each grouped feature becomes its own module; a feature can be “reverted” (i.e. reconstituted in the database) as an atomic unit. It is not unusual to find several features existing in parallel.

Very often a feature will be created around configuration of a single content type. The feature will generally contain a definition of the content type itself, as well as all the fields attached to that content type.

Field configuration is one of the most common uses of Features. Under the hood in Drupal, a field actually consists of two separate but related entities: field bases and field instances. A field base defines a named field, what its data type and properties are, and how it is to be stored. A field instance maps an instance of a field base to a fieldable entity, most often a node. In this way, you can have a single field base, but two field instances, if you have two content types that make use of the same field.

Some developers have a preference for putting all Views in a single feature, all page manager pages in another feature, all user-permission configuration in yet another feature, etc. I intend to show why this is a suboptimal pattern, and how to organize features in a much more future-proof way.

Features and unit testing

Let’s say you’ve developed some code which interacts with a particular content type in a customized fashion, and you want to write a unit test to put your custom module through its paces. Now, what you need to know about Drupal’s testing system is that it creates a blank-slate copy of the database in which to run its tests, in which a bare minimum of setup has taken place. Unless otherwise specified, Drupal creates a new table-prefix and runs a ‘standard’ Drupal profile install within that prefix. In your test case, you can specify what modules you need to enable in order to run your tests.

If you have separated out field bases, field instances, views, user permissions, and taxonomy configuration through many different features, you will need to specify all those features as dependencies of your test. This involves importing a larger and larger amount of code into your test environment, which increases the chances that your test will incur side effects and possibly give misleading results.

Therefore, when you are creating features, you should create each one (as much as possible) as an atomic unit, containing everything you would need in order to test every nook and cranny of the logical unit you are encapsulating.

Optimized feature organization

For each content type, you should include the following in your feature:

  • The node type definition itself
  • All field instances of the node type
  • All non-shared field bases corresponding to the node’s field instances
  • All variables related to the content type (pathauto configuration, etc.)
  • User permissions related to the content type
  • Any page manager pages or handlers used to display the content type
  • Any blocks pertaining directly to the content type
  • Any views displaying the content type. (If there is an entity reference to a related content type, that content type’s feature should be listed as a dependency.)

The following should be broken out into one or more separate features:

  • Shared field bases (this feature should be a dependency of your content type’s feature)
  • Views displaying more than one content type (this feature should depend on all content types displayed)
  • Page manager pages relating to more than one content type
  • Any other persistible configurations not related to a single content type