Drupal 8 Field API series part 1: field formatters
Written on July 22, 2013 - 14:19
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:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">// FormatterBase class.<br></span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Core</span><span style="color: #007700">\</span><span style="color: #0000BB">Field</span><span style="color: #007700">\</span><span style="color: #0000BB">FormatterBase</span><span style="color: #007700">;<br></span><span style="color: #FF8000">// FieldItemInterface<br></span><span style="color: #007700">use </span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Core</span><span style="color: #007700">\</span><span style="color: #0000BB">Field</span><span style="color: #007700">\</span><span style="color: #0000BB">FieldItemListInterface</span><span style="color: #007700">;<br></span><span style="color: #0000BB">?></span></span>
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.
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Plugin implementation of the 'foo_formatter' formatter<br> *<br> * @FieldFormatter(<br> * id = "foo_formatter",<br> * label = @Translation("Foo formatter"),<br> * field_types = {<br> * "text",<br> * "text_long"<br> * },<br> * settings = {<br> * "trim_length" = "600",<br> * },<br>* edit = {<br>* "editor" = "form"<br>* }<br> * )<br> */<br></span><span style="color: #007700">class </span><span style="color: #0000BB">FooFormatter </span><span style="color: #007700">extends </span><span style="color: #0000BB">FormatterBase </span><span style="color: #007700">{ }<br></span><span style="color: #0000BB">?></span></span>
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.
<span style="color: #000000"><span style="color: #0000BB"><?php<br> </span><span style="color: #FF8000">/**<br> * {@inheritdoc}<br> */<br> </span><span style="color: #007700">public function </span><span style="color: #0000BB">settingsForm</span><span style="color: #007700">(array </span><span style="color: #0000BB">$form</span><span style="color: #007700">, array &</span><span style="color: #0000BB">$form_state</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$element </span><span style="color: #007700">= array();<br><br> </span><span style="color: #0000BB">$element</span><span style="color: #007700">[</span><span style="color: #DD0000">'trim_length'</span><span style="color: #007700">] = array(<br> </span><span style="color: #DD0000">'#title' </span><span style="color: #007700">=> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Trim length'</span><span style="color: #007700">),<br> </span><span style="color: #DD0000">'#type' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'number'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'#default_value' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">getSetting</span><span style="color: #007700">(</span><span style="color: #DD0000">'trim_length'</span><span style="color: #007700">),<br> </span><span style="color: #DD0000">'#min' </span><span style="color: #007700">=> </span><span style="color: #0000BB">1</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'#required' </span><span style="color: #007700">=> </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">,<br> );<br><br> return </span><span style="color: #0000BB">$element</span><span style="color: #007700">;<br> }<br></span><span style="color: #0000BB">?></span></span>
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.
<span style="color: #000000"><span style="color: #0000BB"><?php<br> </span><span style="color: #FF8000">/**<br> * {@inheritdoc}<br> */<br> </span><span style="color: #007700">public function </span><span style="color: #0000BB">settingsSummary</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">$summary </span><span style="color: #007700">= array();<br> </span><span style="color: #0000BB">$summary</span><span style="color: #007700">[] = </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Trim length: @trim_length'</span><span style="color: #007700">, array(</span><span style="color: #DD0000">'@trim_length' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">getSetting</span><span style="color: #007700">(</span><span style="color: #DD0000">'trim_length'</span><span style="color: #007700">)));<br> return </span><span style="color: #0000BB">$summary</span><span style="color: #007700">;<br> }<br></span><span style="color: #0000BB">?></span></span>
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.
<span style="color: #000000"><span style="color: #0000BB"><?php<br> </span><span style="color: #FF8000">/**<br> * {@inheritdoc}<br> */<br> </span><span style="color: #007700">public function </span><span style="color: #0000BB">viewElements</span><span style="color: #007700">(</span><span style="color: #0000BB">FieldItemListInterface $items</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$elements </span><span style="color: #007700">= array();<br><br> </span><span style="color: #0000BB">$text_processing </span><span style="color: #007700">= </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">getSetting</span><span style="color: #007700">(</span><span style="color: #DD0000">'text_processing'</span><span style="color: #007700">);<br> foreach (</span><span style="color: #0000BB">$items </span><span style="color: #007700">as </span><span style="color: #0000BB">$delta </span><span style="color: #007700">=> </span><span style="color: #0000BB">$item</span><span style="color: #007700">) {<br> if (</span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">getPluginId</span><span style="color: #007700">() == </span><span style="color: #DD0000">'text_summary_or_trimmed' </span><span style="color: #007700">&& !empty(</span><span style="color: #0000BB">$item</span><span style="color: #007700">-></span><span style="color: #0000BB">summary</span><span style="color: #007700">)) {<br> </span><span style="color: #0000BB">$output </span><span style="color: #007700">= </span><span style="color: #0000BB">$item</span><span style="color: #007700">-></span><span style="color: #0000BB">summary_processed</span><span style="color: #007700">;<br> }<br> else {<br> </span><span style="color: #0000BB">$output </span><span style="color: #007700">= </span><span style="color: #0000BB">$item</span><span style="color: #007700">-></span><span style="color: #0000BB">processed</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$output </span><span style="color: #007700">= </span><span style="color: #0000BB">text_summary</span><span style="color: #007700">(</span><span style="color: #0000BB">$output</span><span style="color: #007700">, </span><span style="color: #0000BB">$text_processing </span><span style="color: #007700">? </span><span style="color: #0000BB">$item</span><span style="color: #007700">-></span><span style="color: #0000BB">format </span><span style="color: #007700">: </span><span style="color: #0000BB">NULL</span><span style="color: #007700">, </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">getSetting</span><span style="color: #007700">(</span><span style="color: #DD0000">'trim_length'</span><span style="color: #007700">));<br> }<br> </span><span style="color: #0000BB">$elements</span><span style="color: #007700">[</span><span style="color: #0000BB">$delta</span><span style="color: #007700">] = array(</span><span style="color: #DD0000">'#markup' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$output</span><span style="color: #007700">);<br> }<br><br> return </span><span style="color: #0000BB">$elements</span><span style="color: #007700">;<br> }<br></span><span style="color: #0000BB">?></span></span>
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.
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implements hook_field_formatter_settings_summary_alter().<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">my_module_field_formatter_settings_summary_alter</span><span style="color: #007700">(&</span><span style="color: #0000BB">$summary</span><span style="color: #007700">, </span><span style="color: #0000BB">$context</span><span style="color: #007700">) {<br> </span><span style="color: #FF8000">// Append a message to the summary when an instance of foo_formatter has<br> // mysetting set to TRUE for the current view mode.<br> </span><span style="color: #007700">if (</span><span style="color: #0000BB">$context</span><span style="color: #007700">[</span><span style="color: #DD0000">'formatter'</span><span style="color: #007700">]-></span><span style="color: #0000BB">getPluginId</span><span style="color: #007700">() == </span><span style="color: #DD0000">'foo_formatter'</span><span style="color: #007700">) {<br> if (</span><span style="color: #0000BB">$context</span><span style="color: #007700">[</span><span style="color: #DD0000">'formatter'</span><span style="color: #007700">]-></span><span style="color: #0000BB">getSetting</span><span style="color: #007700">(</span><span style="color: #DD0000">'mysetting'</span><span style="color: #007700">)) {<br> </span><span style="color: #0000BB">$summary</span><span style="color: #007700">[] = </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'My setting enabled.'</span><span style="color: #007700">);<br> }<br> }<br>}<br></span><span style="color: #0000BB">?></span></span>
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
- Change record: Field formatters are now plugins
- Change record: Formatter summaries return arrays instead of strings
- Issue: Make widgets / formatters work on EntityNG Field value objects
- Documentation: Plugin API
- Documentation: How Entity API implements Typed Data API
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.