Drupal field types

Drupal has many parts that can zombify you. For me, the worst threats are the APIs about entities and fields. Just thinking about it slo-w-s  m--e  d--o--w---n.   B--r--a---i----n----s!

Where was I? Oh, yes. The entity API and its fellow Horsemen of the Zombie Apocalypse.

Why the Horsemen make no sense

OK, so, I read about this "entity" thing, new for D7. I thought to myself, "Self, I know what an entity is. It's a database concept. Like in entity-relationship diagrams."

Poor fool.

Then I read about the second Horseman, bundles. "Huh. I don't get that. Is it a subtype?"

Then fields. "I know fields. Some of my best friends are fields." Then instances. "WTF? An instance is stored in a column in a row. Right? RIGHT?!?"

My brain was shutting down. I staggered about in a most zombific fashion.

After a reboot, I realized that entities ain't entities, and fields ain't fields.

Entities, bundles, fields, and instances have their own Drupaly definitions.

How to learn?

Where to start on our voyage of discovery? I've decided to start with field types, because:

  • They're at the bottom of the food chain. You can learn about field types, without learning about entities and bundles.
  • If you've used Drupal 6, you're already seen field types. In CCK. So you can use your experience with the old to understand the new.

Let's start by understanding the goal, that is, how our code will look to humans.

Using a decompicity field type

Zombies decompose. Decompicity is the decomposition state of a corpse. Decompicity has four levels, according to the International Standards-Shmandards Organization (ISSO):

  • Fresh
  • Rotten
  • Putrescent
  • Skeletal

We're going to code a decompicity field type. Let's see how humans would use it, when it's done.

A zombie content type

The human creates a zombie content type:

Content type

Figure 1. Content type

There are two elements: Name and Body. The user wants to add a field for decompicity. S/he installs the module decompfield, which adds a field type for decompicity. (We're going to write decompfield.) Then s/he adds a field to the zombie content type:

Add field to content type

Figure 2. Adding a field

Here are the field types available:

Field types

Figure 3. Available field types

Decompicity is just another field type, like File or Integer.

The human clicks Save, and enters some field settings:

Field settings

Figure 4. Field settings

Making zombies

Now a user creates some zombie nodes:

Adding a zombie

Figure 5. Adding a zombie

Setting the decompicity is just like using any other field with a dropdown.

Here's the node:

Viewing the node

Figure 6. Viewing the node

The user adds two more zombies, Wilma and Barney. Wilma is fresh. Barney's decompicity is unknown, so the user leaves the field set at "(No value)".

The field type even works with the Views module. Here's a view of the zombies:

Zombie list

Figure 7. Zombie list

Here's part of the view's design screen:

View design

Figure 8. View design

Decompicity looks just like any other field.

What is a field type, exactly?

I don't know "exactly." How about approximately?

A field type is a definition of, er, a type of data. Examples are Integer, File, Text, and Decompicity.

You don't use field types directly. Instead, you create fields with them. A field is a specific use of a field type. For example, one field type - Integer - can be used to create fields to store:

  • The number of toes you have.
  • Your height.
  • Today's month.
  • The number of hats your mother has.

... and many other things. The code for the Integer field type is written once, and used many times by different fields.

Look at this again:

Add field to content type

Figure 2 (again). Adding a field

Here, the user is creating a field. You can see it in the title: Add new field. The field's name is field_decompicity; look at the second column. The field field_decompicity is of the field type Decompicity (third column).

Each field type (Integer, Filed, Decompicity, whatever) has three important attributes:

  • Input: how does data get into fields with this field type? Drupal jargon: widget.
  • Data storage: how do fields of this field type store their values? Drupal jargon: field storage engine.
  • Output: how do fields of this field type show their values to the user? Drupal jargon: formatter.

When we write the module for the Decompicity field type, we add code for each one.

So, we're going to write a module called decompfield. The module will create a field type called Decompicity. We'll write code for (input) widget(s), output formatter(s), and (sort of) to store the data. When we're done, users will be able to use the Decompicity field type, just as they use the Integer and Text field types.

Coding the Decompicity field type

To grok this article, you must know a little about modules and hooks. The unhooked can still learn Useful Things. But expect brain blur.

The module decompfield creates the field type. Giving the module and the field type different names makes it clearer which name goes where in the code. I got this idea from the book Drupal 7 Module Development. In fact, much of the code in this article was adapted from that book.

Here's decompfield.info:

  1. name = Decompicity field
  2. description = State of decomposition: fresh, rotten, putrescent, skeletal
  3. package = Drupal Zombie
  4. core = 7.x
  5. files[] = decompfield.module

Figure 9. The .info file tells Drupal about the module

The rest of the code is in decompfield.module.

Tell Drupal about the field type

We need to tell Drupal's field system about the new field type, so it can, for example, add it to the list of available types:

Field types

Figure 3 (again). Available field types

Here's the code:

  1. /*
  2.  * Implements hook_field_info.
  3.  */
  4. function decompfield_field_info() {
  5.   //Tell Drupal about the field type.
  6.   return array(
  7.     'decompicity' => array(
  8.       'label' => t('Decompicity'),
  9.       'description' => t(
  10.           'This field stores a decompicity level: fresh, rotten, putrescent, skeletal.'),
  11.       'default_widget' => 'decompicity_select',
  12.       'default_formatter' => 'decompicity_value_name',
  13.     ),
  14.   );
  15. }

Figure 10. Tell Drupal about the field type

As usual, you create a nested array of doom (NAD). There's one element in the array, with the key decompicity (line 7). That element is an array. There's a label and description, and default widget and formatter. Their names (decompicity_select and decompicity_value_name) must match code we'll define later.

decompfield_field_info()returns only the most basic data about the field type. Drupal needs to know more. That's in other hooks.

Tell Drupal how to store data for fields that use this field type

Here's the code:

  1. /*
  2.  * Implements hook_field_schema.
  3.  */
  4. function decompfield_field_schema($field) {
  5.   //Tell Drupal how to store field data.
  6.   //Declare array to return.
  7.   $schema = array();
  8.   $schema['columns']['decompicity'] = array(
  9.     'type' => 'int',
  10.     'not null' => TRUE, //Handle empty values explicitly.
  11.   );
  12.   return $schema;
  13. }

Figure 11. Tell Drupal how the field type stores data

If you're storing data in an SQL database, you don't have to do much. Create a NAD with the data type - the database data type. That's int.

How did I know to use $schema['columns']['decompicity'] and not just $schema['decompicity']? I aped an example from Drupal 7 Module Development. Have examples on hand. You'll need them.

Line 10 says that decompicity cannot be null. What if the user is entering data for Julie-the-zombie, and doesn't know her decompicity? I found it easier to add an explicit value to handle "don't know." More later.

Tell Drupal how users enter data for fields that use this field type

You need to define some widgets. There are two steps:

  • Tell Drupal what widgets you are going to provide.
  • Give Drupal the code for each widget.

That's a common pattern in Drupal. One hook to give Drupal metadata, that describes the thing you want to make. Another hook to give Drupal the code that makes that thing.

Here's the first one, telling Drupal about our single widget:

  1. /*
  2.  * Implements hook_field_widget_info.
  3.  */
  4. function decompfield_field_widget_info() {
  5.   //Tell Drupal about input widget(s) for the field type.
  6.   //There is only one widget, called decompicity_select.
  7.   return array(
  8.     'decompicity_select' => array(
  9.       'label' => t('Select list'),
  10.       'description' => t(
  11.           'User selects value from a dropdown list.'),
  12.       'field types' => array('decompicity'),
  13.       'behaviors' => array(
  14.         'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  15.         'default value' => FIELD_BEHAVIOR_DEFAULT,
  16.       ),
  17.     ),
  18.   );
  19. }

Figure 12. Declaring a widget to Drupal

Each widget has one entry in the NAD. The index of the entry is the name of the widget. decompicity_select, in this case. (You saw that name before, in Figure 10.) Line 12 tells Drupal what field types this widget is for.

What's that "behaviors" stuff on lines 13 through 16? It has to do with:

  • How this widget type handles multiple values.
  • Whether there is a default value for fields of this type.

Widgets and multiple values

Multiple values has to do with the "Number of values" setting:

Field settings

Figure 4 (again). Field settings

If the user wants more than one decompicity for something, how many widgets do they get? Let's say that elephant zombies decompose at three different rates: a rate for the legs, a rate for the trunk, and a rate for the body. Line 14 ...

'multiple values' => FIELD_BEHAVIOR_DEFAULT,

... says that there should be a separate widget for each decomposition rate.

Of course there will be one widget for each value. What else could there be?

Here's one widget - a set of checkboxes - that can have more than one value:

One widget, many values

Figure 13. One widget, many values

This widget has two values: Auburn Hills, and Rochester.

Isn't that more than one widget?

As far as the browser is concerned, yes. But we're writing code for Drupal's Field API. A widget is whatever your code creates. So if your code creates a group of checkboxes and tells Drupal that the group is a single widget, Drupal treats the entire group as one widget.

It's what your code does that matters. You get to decide what a widget is.

Widget default

One more line from Figure 12 to talk about. Line 15 ...

'default value' => FIELD_BEHAVIOR_DEFAULT,

... tells Drupal that our widget can have a default value.

Implementing the widget

We've told Drupal what widgets we want. Just one. Now we need to give the code for the widget.

  1. /*
  2.  * Implements hook_field_widget_form.
  3.  */
  4. function decompfield_field_widget_form(&$form, &$form_state, $field,
  5.     $instance, $langcode, $items, $delta, $element) {
  6.   if ($instance['widget']['type'] == 'decompicity_select') {
  7.     //Create a form element for the field type.
  8.     $element['decompicity'] = array(
  9.       '#type' => 'select',          //Dropdown.
  10.       '#title' => t('Decompicity'),
  11.       '#options' => array(
  12.         0 => t('(No value)'),
  13.         1 => t('Fresh'),
  14.         2 => t('Rotten'),
  15.         3 => t('Putrescent'),
  16.         4 => t('Skeletal'),
  17.         ),
  18.       '#default_value' => isset($items[$delta]['decompicity'])
  19.           ? $items[$delta]['decompicity']
  20.           : 0,
  21.     );
  22.   }
  23.   return $element;
  24. }

Figure 14. Widget creation

There's only one widget: decompicity_select. You can see the name in line 6. The code makes a NAD with one element, with the index decompicity (line 8). It's a <select> dropdown, with five options.

The user sees names in the dropdown:

Adding decompicity

(Part of) Figure 5 (again). Adding a zombie

But what gets stored by Drupal is a number, like 2. Each option has a numeric index (lines 12 to 16), since we told Drupal to store decompicity data as integers. Here's that code again. Look at line 9:

  1. /*
  2.  * Implements hook_field_schema.
  3.  */
  4. function decompfield_field_schema($field) {
  5.   //Tell Drupal how to store field data.
  6.   //Declare array to return.
  7.   $schema = array();
  8.   $schema['columns']['decompicity'] = array(
  9.     'type' => 'int',
  10.     'not null' => TRUE, //Handle empty values explicitly.
  11.   );
  12.   return $schema;
  13. }

Figure 11 (again). Tell Drupal how the field type stores data

That's common for widgets and formatters. The data is stored one way (e. g., numbers), but the widgets and formatters show it a different way (e. g., text).

Default confusion

Here are lines 18 to 20:

18.      '#default_value' => isset($items[$delta]['decompicity'])
19.          ? $items[$delta]['decompicity']
20.          : 0

This is confusing, because Drupal is using the word "default" in a strange way. Here, the default value is the value the widget first has on an edit page. That's usually not the same as the default value of the field for new records.

Huh? Suppose the user created Bert-the-zombie, and gave him a decompicity of 1 (Fresh). Then the user edits Bert-the-zombie. What should decompicity be when the user first sees the edit page? 1 (Fresh), of course. That's what the user entered before. So the default value of the widget when edit page shows should be 1.

So, our code says to look to see if decompicity field already has a value (line 18). If it does, use that value as the default for the decompicity widget (line 19). If the field does not have a value, use 0 (line 20) as the default value for the widget.

Confusing? Yep. Default-for-field and default-for-widget are different concepts. It's clearer if you think of default-for-widget as initial-display-value-for-widget.

One last piece of code...

Tell Drupal how to display data for fields that use this field type

"Formatters" are the things that display field data. Formatters are for output, widgets for input.

As with widgets, we need to tell Drupal two things:

  • What formatters we will provide.
  • The code for each one.

Here's the code for the first one, the metadata, telling Drupal about the formatters we will be coding:

  1. /*
  2.  * Implements hook_field_formatter_info.
  3.  */
  4. function decompfield_field_formatter_info() {
  5.   //Tell Drupal about the field type's output formatter(s).
  6.   //There is only one: decompicity_value_name.
  7.   return array(
  8.     'decompicity_value_name' => array(
  9.       'label' => t('Value name'),
  10.       'field types' => array('decompicity'),
  11.     ),
  12.   );
  13. }

Figure 15. Declaring a formatter to Drupal

The NAD is really a SAD in this case. Simple array of doom. There's just one formatter, called decompicity_value_name.

Here's the code for the formatter:

  1. /*
  2.  * Implements hook_field_formatter_view.
  3.  */
  4. function decompfield_field_formatter_view($obj_type, $object, $field,
  5.     $instance, $langcode, $items, $display) {
  6.   $element = array();
  7.   if ($display['type'] == 'decompicity_value_name') {
  8.     //Could be multiple cardinality - so loop.
  9.     foreach ($items as $delta => $item) {
  10.       //Figure out the value name.
  11.       switch ( $item['decompicity'] ) {
  12.         case 0:
  13.           $value_name = '(No value)';
  14.           break;
  15.         case 1:
  16.           $value_name = 'Fresh';
  17.           break;
  18.         case 2:
  19.           $value_name = 'Rotten';
  20.           break;
  21.         case 3:
  22.           $value_name = 'Putrescent';
  23.           break;
  24.         case 4:
  25.           $value_name = 'Skeletal';
  26.           break;
  27.       }
  28.       //Translate the value name.
  29.       $output = t('@value', array('@value' => $value_name));
  30.       //Create a markup element for the value name.
  31.       $element[$delta] = array('#markup' => $output);
  32.     } //end for
  33.   }
  34.   return $element;
  35. }

Figure 16. Code for the formatter

Line 7 tests which formatter we are using. Why test that, since we only have one formatter? Because we could add another in the future. Maybe one that uses pictures of the different decomposition levels.

Lines 11 to 27 map a decompicity value from the database (e. g., 2) to text (Rotten). Line 31 creates a markup element with that text.

Here's line 9:

foreach ($items as $delta => $item) {

Why is there a for loop? Because a field of type decompicity might have more than one value. Remember the elephant. It has three different values for decompicity, and we need to format each one.

Elephant: You're sick, you know that?

That's all

Field types are used when people add fields to content types and such. You can make your own field types. Tell Drupal:

  • How to input the data (widgets).
  • How to store the data.
  • How to output the data (formatters).

Use the Field API to make field types, and users will be able to do Cool Things. Like make views that use your field types.