Rendering Drupal 8 fields (the right way)

Parent Feed: 

Once upon a time, we wrote an article about how to render fields on their own in Drupal 7, which was really handy because Drupal 7 wasn't always intuitive. It's common to want to display a field outside of the context of its main entity page, like showing author information in a sidebar block or in a panel, but you had to just know which functions to use. Drupal 8 has come along since then using 'grown up' things like objects and methods, which actually makes the job a little easier. So now we have this:

The short answer

$node->field_my_thing->view();

Quick reference

I'll cover these in detail below, but here are the things you might want to be doing:

  1. Render a field as if it were on the node/entity page (e.g. with the markup from the normal formatter and full field template)

Drupal 7: field_view_field(), optionally passing a view mode string, or formatter settings array.
Drupal 8: $node->field_my_thing->view(), optionally passing a view mode string, or formatter settings array.

  1. Just get a single value out of a field (i.e. raw values, usually as originally inputted)

Drupal 7: field_get_items() and then retrieve the index you want from the array.
Drupal 8: $node->field_my_thing->get(0)->value, passing just the index you want to get(). Properties other than 'value' may be available.

  1. Render a single value as if it were on the node/entity page (e.g. with the normal formatter's markup, but not all the wrappers that Drupal's field templates give you)

Drupal 7: field_view_value(), optionally passing a view mode string, or formatter settings array, but always passing the actual items array.
Drupal 8: $node->field_my_thing->get(0)->view(), passing just the index you want to get() and optionally passing a view mode string, or formatter settings array to view().

The long answer

Now that entities like nodes are properly classed objects, and fields use the fancy new Typed Data API, we don't need to care about the underlying data structure for nodes or their fields, we can just call the method to perform the operation we want! You know, just what methods are supposed to be for! You want to view a field? Just call its 'view' method.

The output will be automatically sanitised and goes through the normal formatter for the field, as well as the regular field template. You can specify whether you want it rendered as if it were in a teaser or other view mode, by passing in the view mode string, just as we did with field_view_field(). (Or you might have used something like $node->field_my_thing['und'][0]['value'] - in which case, go back and read our article for Drupal 7!)

$node->field_my_thing->view('teaser');

Or even override the formatter to be used altogether (which is handy if the field would normally be hidden in any view mode):

$node->field_my_thing->view([
  'type' => 'image',
  'label' => 'hidden',
  'settings' => array(
    'image_style' => 'larger_thumbnail',
    'image_link' => 'content',
  ),
]);

This does assume that your field ('field_my_thing') in my examples does at least exist on your node (even if it's empty). You may want to wrap the whole code in a try/catch block, just in case it might not.

For bonus points, you could load up the normal formatter settings, and tweak them:


use Drupal\Core\Entity\Entity\EntityViewDisplay;

// If you have the entity/node you want, use collectRenderDisplay() as it may
// already be statically cached, plus it goes through various alter hooks.
$display_options = EntityViewDisplay::collectRenderDisplay($node, 'teaser')
  ->getComponent('field_images');

// Otherwise - or if you intentionally want to re-use the settings from another
// unrelated entity type, bundle or display mode - just load the display config.
$display_options = EntityViewDisplay::load('pagaraph.media_pod.teaser')
  ->getComponent('field_images');

// Then tweak those display options and view the field.
$display_options['settings']['image_style'] = 'even_bigger_thumbnail';
$node->field_my_thing->view($display_options);

This all assumes you've at least loaded your node, or other entity. (It works with any content entity, like terms, paragraphs, etc!) You'd probably be putting the result of the view() method (which will be a render array) into a variable to be used in a twig template via a preprocess hook. Or maybe you're just adding it into an existing render array, like a custom block plugin. (Either way, you probably shouldn't then be rendering it into a string yourself, let Drupal do that for you.)

You can even just view a single item within a multi-value field like this, here using an 'if' condition to be a bit more graceful than a try/catch. This is the equivalent of using field_view_value() from Drupal 7, so also skips Drupal's full field template, though includes markup produced by the field's formatter:


// View the third value, as long as there is one.
if ($third = $node->field_my_thing->get(2)) {
  $output = $third->view();
}
else {
  $output = [];
}

That helps you see how you might get a single value too, with the get() method, though note that it still returns a classed object. To just get a raw value, without any wrapping markup or value processing/sanitisation, you might want to use something like this, instead of Drupal 7's field_get_items() :


$text = $node->field_my_thing->get(0)->value;

// If the field is just a single-value field, you can omit the get() part.
$value = $node->field_single_thing->value;

// Some types of field use different properties.
$url = $node->field_my_link->uri;

// You can use getValue() to get all the properties (e.g. text value + format).
$text_values = $node->field_formatted_text->getValue();

// References can be chained!
$referenced_title = $node->field_my_reference->entity->field_other_thing->value;

In Drupal 7, there was also confusion around what to do for multilingual content. In Drupal 8, as long as you've got the translation you want first, all the methods I've discussed above will get you the appropriate values for your language. To get a specific translation, use:


if ($node->hasTranslation($candidate)) {
  $node = $node->getTranslation($langcode);
}

This Rocks.

You get to use proper modern methods on a proper typed data API. Sanitisation is done for you. You don't need to care what the underlying data structure is. And you don't need to remember some magic global procedural functions, because the methods are obvious, right there on the thing you want to use them on (the field item class). If the Drupal 7 version of this was brilliant, that makes the Drupal 8 version of this even better. Brilliant-er?

Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web