Drupal 8 Field API series part 1: field formatters

The Drupal 8 cycle has entered the API freeze since the 1st of July, which means it's time to start porting modules or simply play around with the API. While there are exceptions to change the API, you can safely assume that 95% (or even more) will remain as it is today.

This is the first article which will be part of a series of changes in Field API for Drupal 8: field formatters. In case there are updates to the API, we will update the articles, but also the change records on drupal.org. Watch out for the next couple of weeks and months and hopefully you're prepared for Drupal 8 and Field API.

Plugins

Creating field formatters in Drupal 7 was done by implementing four hooks. In Drupal 8, formatters are now plugins using the new Plugin API. Hooks are replaced by methods in classes, which means that your module file will be empty if you only provide a formatter (unless you also implement one of the field formatter alter hooks). Being classes, this means that field formatters can now extend on each other. A good example in core is the image field formatter extending the file field formatter class. Discovery and class instantiation is managed by the new formatter plugin manager.

Create a file like '{your_module}/lib/Drupal/{your_module}/Plugin/field/FieldFormatter/{NameOfYourFormatter}.php. That's a lot of directories right ? Welcome to the world of PSR-0, namespaces and plugins in D8. This is most likely going to change, feel free to read, or even help along in https://drupal.org/node/1971198. Also, plugin managers can now control where plugins reside, see https://drupal.org/node/2043379, so we'll probably change this at some point.

In most cases, you will want to extend the FormatterBase class which does most of the heavy lifting for you. Following classes will usually be imported at the top of your file:

<?php
// FormatterBase class.
Drupal\Core\Field\FormatterBase;
// FieldItemInterface
use Drupal\Core\Field\FieldItemListInterface;
?>

1. hook_field_formatter_info() are now annotations

hook_field_formatter_info() is replaced by annotation-based plugin discovery, using the \Drupal\field\Annotation\FieldFormatter annotation class. As for other plugin types, the accepted properties are documented in the annotation class. Other modules can extend this by implementing hook_field_formatter_info_alter(). In core, the edit module adds the edit property so it knows which in-place editor it has to use. Note that some property names have changed since Drupal 7 (spaces replaces by underscores). This is how an annotation looks like, which is placed right above the class keyword.

<?php
/**
 * Plugin implementation of the 'foo_formatter' formatter
 *
 * @FieldFormatter(
 *   id = "foo_formatter",
 *   label = @Translation("Foo formatter"),
 *   field_types = {
 *     "text",
 *     "text_long"
 *   },
 *   settings = {
 *     "trim_length" = "600",
 *   },
*    edit = {
*      "editor" = "form"
*    }
 * )
 */
class FooFormatter extends FormatterBase { }
?>

2. hook_field_formatter_settings_form() becomes FormatterInterface::settingsForm()

Next up is to create a settingsForm() method. If you have an old settings form, you can simply move the code to this method. Settings are automatically saved and can be accessed by calling $this->getSetting('settings_key');. Remember to always start with an empty $elements array and not with the $form argument from the function arguments.

<?php
 
/**
   * {@inheritdoc}
   */
 
public function settingsForm(array $form, array &$form_state) {
   
$element = array();

   
$element['trim_length'] = array(
     
'#title' => t('Trim length'),
     
'#type' => 'number',
     
'#default_value' => $this->getSetting('trim_length'),
     
'#min' => 1,
     
'#required' => TRUE,
    );

    return
$element;
  }
?>

3. hook_field_formatter_settings_summary() becomes FormatterInterface::settingsSummary()

Settings are accessed by calling $this->getSetting('settings_key');. Another change is that the summary now needs to return an array instead of a string.

<?php
 
/**
   * {@inheritdoc}
   */
 
public function settingsSummary() {
   
$summary = array();
   
$summary[] = t('Trim length: @trim_length', array('@trim_length' => $this->getSetting('trim_length')));
    return
$summary;
  }
?>

4. hook_field_formatter_prepare_view becomes FormatterInterface::prepareView() and hook_field_formatter_view() becomes FormatterInterface::viewElements()

The first method allows you to add additional information on the items, the second is where the actual formatting happens. Settings are accessed by calling $this->getSetting('settings_key');. Also, the methods now receive the field values as a \Drupal\Core\Field\FieldItemListInterface object, rather than an $items array in Drupal 7. More information can be found about Drupal 8 Entity API and the syntax around field values in the handbook. Simply put, FieldItemListInterface objects can be accessed and iterated on like an array of items keyed by delta, and properties in each item can be accessed by simple object syntax.

<?php
 
/**
   * {@inheritdoc}
   */
 
public function viewElements(FieldItemListInterface $items) {
   
$elements = array();

   
$text_processing = $this->getSetting('text_processing');
    foreach (
$items as $delta => $item) {
      if (
$this->getPluginId() == 'text_summary_or_trimmed' && !empty($item->summary)) {
       
$output = $item->summary_processed;
      }
      else {
       
$output = $item->processed;
       
$output = text_summary($output, $text_processing ? $item->format : NULL, $this->getSetting('trim_length'));
      }
     
$elements[$delta] = array('#markup' => $output);
    }

    return
$elements;
  }
?>

Alter hooks

The alter hooks are still the same for Drupal 8, with one small API change in hook_field_formatter_settings_summary_alter() which is invoked by the Field UI module. The summary is now an array of strings instead of a single string. <br/> will be automatically inserted between the strings when the summary is displayed.

<?php
/**
 * Implements hook_field_formatter_settings_summary_alter().
 */
function my_module_field_formatter_settings_summary_alter(&$summary, $context) {
 
// Append a message to the summary when an instance of foo_formatter has
  // mysetting set to TRUE for the current view mode.
 
if ($context['formatter']->getPluginId() == 'foo_formatter') {
    if (
$context['formatter']->getSetting('mysetting')) {
     
$summary[] = t('My setting enabled.');
    }
  }
}
?>

The other two hooks are hook_field_formatter_info_alter() allowing you to make changes to the formatter definitions and hook_field_formatter_settings_form_alter() which is invoked from by the Field UI module when displaying the summary of the formatter settings for a field.

Resources

Conclusion

Writing and maintaining field formatters for Drupal 8 is not hard. In most cases, when porting, it's simply moving the contents of your old hooks to the methods in a class. In the next part, we will see how you write widget plugins in Drupal 8.

Comments

Naveen Valecha on Mon, 22/07/2013 - 14:24

Really Helpful Article.Waiting for next part

guest on Mon, 22/07/2013 - 16:22

"Create a file like '{your_module}/lib/Drupal/{your_module}/Plugin/field/formatter/{Formatter}.php."

seriously, is this a joke? 8 levels of directory structure for a simple field formatter? only a hardcore dev could possible think this is some sort of improvement.

swentel on Mon, 22/07/2013 - 16:30

Welcome to the world of PSR-0, namespaces and plugins in D8 :)

Granted, this could and should, and actually will become shorter, feel free to help along in https://drupal.org/node/1971198

I updated the article with this info as well.

Ryan on Mon, 22/07/2013 - 21:41

Honestly, I don't see what the big deal is. It's cheap to create and navigate directories in return for auto discovery and loading. It might be nice to have to create fewer directories, but as with any convention, once it's learned it doesn't really matter. We all got used to naming functions with very specific (and often obtuse) names in order to have auto discovery of alter hooks, for example.

guest on Mon, 22/07/2013 - 22:25

it's not a matter of 'cheapness' --- the longer the convention the more error prone it is. That's just human behavior. The function naming convention never exceeds 2 or 3 levels as far as I know. That's much easier to remember and far less error prone than 8 (8!).

There are scads of maintainers that do not live and breath this stuff every minute of the day and I guarantee (even after they learn it) they, as will i, will leave off a 'lib' or 'field' or mess up the case (lets not forget, it's mixed case as well) routinely. DX -----

guest on Mon, 22/07/2013 - 22:26

Good to know it's already been thought of as an issue-- hopefully they'll be some way I can help.

Chris on Mon, 22/07/2013 - 17:05

For the annotation piece, this is LITERALLY discovered inside of the comments (provided it's formatted correctly with the @ sign, etc)?

swentel on Mon, 22/07/2013 - 17:07

Yes. Instead of writing PHP in an info hook, the definition of field formatter plugins - any plugin actually in core - lives in those comments.

guest on Mon, 22/07/2013 - 22:20

wow wow wow, code behavior being dictated by comments?? I swear I'm not trying to be negative here, but this article is seriously distressing.

swentel on Mon, 22/07/2013 - 23:07

Note that annotations are not something we've invented for describing the metadata of plugins. They are widely used in the java world, although I'm not sure if it originated there. If you want more detailed info on annotations, there's a dedicated doc page on d.o: https://drupal.org/node/1882526.

Pro tip: a comma at the end will crash the parser (I know, don't ask).

Alexei Rayu on Mon, 22/07/2013 - 17:25

A very helpful article. We definitely need more of these! Will be waiting for more articles on D7 to D8 API changes.

Nick on Mon, 22/07/2013 - 19:21

Thanks for this article, look forward to seeing more in the near future!

Tim on Thu, 25/07/2013 - 11:45

Oh no, please, not annotations! This is a horrible approach! Annotations aren't debugabble and IDE's don't support them... Drupal, don't become Symfony, please!!!

Instead of annotations, there could be just a config function/array inside the class, this is simpler, more performant and bug prone... damn... (

guest on Mon, 05/08/2013 - 17:59

"Drupal, don't become Symfony, please!!!"

far too late-- it's already like 1/2 way there.

wizonesolutions on Sat, 25/10/2014 - 18:31

I'm currently reading a documentation page about Field Items, and it links to this series. It would be great to see this series as part of the "Drupal 8 APIs" book on drupal.org. Do I have your permission to re-use potentially some/all of the content for that?

swentel on Sat, 25/10/2014 - 20:19

You absolutely have :)

Note that here and there the actual information might already be outdated, I need to update the articles against the latest API (which is now finally in a more or less stable state)

Add reply

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Have you written a response to this? Let me know the URL by sending a webmention.