Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

Field formatters have moved from hook implementations in Drupal 7 to plugins in Drupal 8.

Formatters registry

The functions used to access properties about available formatters are moved to methods on the FormatterPluginManager:

field_info_formatter_types() \Drupal::service('plugin.manager.field.formatter')->getDefinitions()
field_info_formatter_types($type) \Drupal::service('plugin.manager.field.formatter')->getDefinition($type)
field_info_formatter_settings($type) \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings($type)

Formatter implementations

To port existing field formatters, create a class in a file like '{module}/src/Plugin/Field/FieldFormatter/{Formatter}.php. In most cases, you will want to extend the \Drupal\Core\Field\FormatterBase base class.

The following hooks are removed and are now methods in \Drupal\Core\Field\FormatterInterface:

hook_field_formatter_info() Replaced by annotation-based plugin discovery, using the \Drupal\Core\Field\Annotation\FieldFormatter annotation class. As for other plugin types, the accepted properties are documented in the annotation class.
- Some property names have changed since Drupal 7 (spaces replaces by underscores).
- The 'settings' property, specifying available settings and their default values, is moved to the FormatterInterface::defaultSettings() static method.
hook_field_formatter_settings_form() FormatterInterface::settingsForm()
hook_field_formatter_settings_summary() FormatterInterface::settingsSummary()
hook_field_formatter_prepare_view() FormatterInterface::prepareView()
hook_field_formatter_view() FormatterInterface::viewElements()

About parameters:

  • The prepareView() and viewElements() methods, that are responsible for building field output, 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:
    Drupal 7
    foreach ($items as $delta => $item) {
      $value = $item['value'];
      $format = $item['format']:
    }
    

    Drupal 8

    foreach ($items as $delta => $item) {
      $value = $item->value;
      $format = $item->format:
    }
    
  • The methods no longer receive the parent entity and langcode for the field values as separate $entity and $langcode parameters. If needed (most formatters do not need them), they can be retrieved from the $items object:
    $entity = $items->getEntity();
    $langcode = $items->getLangcode();
    

Code example

Drupal 7

/**
 * Implements hook_field_formatter_info().
 */
function number_field_formatter_info() {
  return array(
    'number_decimal' => array(
      'label' => t('Default'),
      'field types' => array('number_decimal', 'number_float'),
      'settings' =>  array(
        'thousand_separator' => '',
        'decimal_separator' => '.',
        'scale' => 2,
        'prefix_suffix' => TRUE,
      ),
    ),
    'number_unformatted' => array(
      'label' => t('Unformatted'),
      'field types' => array('number_integer', 'number_decimal', 'number_float'),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function number_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];

    if ($display['type'] == 'number_decimal') {
      $element['decimal_separator'] = array(
        '#type' => 'select',
        '#title' => t('Decimal marker'),
        '#options' => array('.' => t('Decimal point'), ',' => t('Comma')),
        '#default_value' => $settings['decimal_separator'],
      );
      $element['scale'] = array(
        '#type' => 'select',
        '#title' => t('Scale'),
        '#options' => drupal_map_assoc(range(0, 10)),
        '#default_value' => $settings['scale'],
        '#description' => t('The number of digits to the right of the decimal.'),
      );
    }

    $element['prefix_suffix'] = array(
      '#type' => 'checkbox',
      '#title' => t('Display prefix and suffix.'),
      '#default_value' => $settings['prefix_suffix'],
    );
  }

  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function number_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];

  $summary = array();
  if ($display['type'] == 'number_decimal' || $display['type'] == 'number_integer') {
    $summary[] = number_format(1234.1234567890, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
    if ($settings['prefix_suffix']) {
      $summary[] = t('Display with prefix and suffix.');
    }
  }

  return implode('<br />', $summary);
}

/**
 * Implements hook_field_formatter_view().
 */
function number_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  $settings = $display['settings'];

  switch ($display['type']) {
    case 'number_integer':
    case 'number_decimal':
      foreach ($items as $delta => $item) {
        $output = number_format($item['value'], $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
        if ($settings['prefix_suffix']) {
          $prefixes = isset($instance['settings']['prefix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['prefix'])) : array('');
          $suffixes = isset($instance['settings']['suffix']) ? array_map('field_filter_xss', explode('|', $instance['settings']['suffix'])) : array('');
          $prefix = (count($prefixes) > 1) ? format_plural($item['value'], $prefixes[0], $prefixes[1]) : $prefixes[0];
          $suffix = (count($suffixes) > 1) ? format_plural($item['value'], $suffixes[0], $suffixes[1]) : $suffixes[0];
          $output = $prefix . $output . $suffix;
        }
        $element[$delta] = array('#markup' => $output);
      }
      break;
    case 'number_unformatted':
      foreach ($items as $delta => $item) {
        $element[$delta] = array('#markup' => $item['value']);
      }
      break;
  }

  return $element;
}

Drupal 8

  • Note that the docblocks should actually use {@inheritdoc} instead of overrides or implements, we left it here so you know from which interface the methods extend
  • The annotation for field formatters is 'FieldFormatter'.

Source: NumericFormatterBase class.


use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;

/**
 * Parent plugin for decimal and integer formatters.
 */
abstract class NumericFormatterBase extends FormatterBase {

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $options = array(
      ''  => t('- None -'),
      '.' => t('Decimal point'),
      ',' => t('Comma'),
      ' ' => t('Space'),
      chr(8201) => t('Thin space'),
      "'" => t('Apostrophe'),
    );
    $elements['thousand_separator'] = array(
      '#type' => 'select',
      '#title' => t('Thousand marker'),
      '#options' => $options,
      '#default_value' => $this->getSetting('thousand_separator'),
      '#weight' => 0,
    );

    $elements['prefix_suffix'] = array(
      '#type' => 'checkbox',
      '#title' => t('Display prefix and suffix.'),
      '#default_value' => $this->getSetting('prefix_suffix'),
      '#weight' => 10,
    );

    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = array();

    $summary[] = $this->numberFormat(1234.1234567890);
    if ($this->getSetting('prefix_suffix')) {
      $summary[] = t('Display with prefix and suffix.');
    }

    return $summary;
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = array();
    $settings = $this->getFieldSettings();

    foreach ($items as $delta => $item) {
      $output = $this->numberFormat($item->value);

      // Account for prefix and suffix.
      if ($this->getSetting('prefix_suffix')) {
        $prefixes = isset($settings['prefix']) ? array_map('field_filter_xss', explode('|', $settings['prefix'])) : array('');
        $suffixes = isset($settings['suffix']) ? array_map('field_filter_xss', explode('|', $settings['suffix'])) : array('');
        $prefix = (count($prefixes) > 1) ? format_plural($item->value, $prefixes[0], $prefixes[1]) : $prefixes[0];
        $suffix = (count($suffixes) > 1) ? format_plural($item->value, $suffixes[0], $suffixes[1]) : $suffixes[0];
        $output = $prefix . $output . $suffix;
      }

      $elements[$delta] = array('#markup' => $output);
    }

    return $elements;
  }

  /**
   * Formats a number.
   *
   * @param mixed $number
   *   The numeric value.
   *
   * @return string
   *   The formatted number.
   */
  abstract protected function numberFormat($number);

}

Source: DecimalFormatter class.

/**
 * Plugin implementation of the 'number_decimal' formatter.
 *
 * The 'Default' formatter is different for integer fields on the one hand, and
 * for decimal and float fields on the other hand, in order to be able to use
 * different settings.
 *
 * @FieldFormatter(
 *   id = "number_decimal",
 *   label = @Translation("Default"),
 *   field_types = {
 *     "decimal",
 *     "float"
 *   }
 * )
 */
class DecimalFormatter extends NumericFormatterBase {

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return array(
      'thousand_separator' => '',
      'decimal_separator' => '.',
      'scale' => 2,
      'prefix_suffix' => TRUE,
    ) + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, array &$form_state) {
    $elements = parent::settingsForm($form, $form_state);

    $elements['decimal_separator'] = array(
      '#type' => 'select',
      '#title' => t('Decimal marker'),
      '#options' => array('.' => t('Decimal point'), ',' => t('Comma')),
      '#default_value' => $this->getSetting('decimal_separator'),
      '#weight' => 5,
    );
    $range = range(0, 10);
    $elements['scale'] = array(
      '#type' => 'select',
      '#title' => t('Scale', array(), array('decimal places')),
      '#options' => array_combine($range, $range),
      '#default_value' => $this->getSetting('scale'),
      '#description' => t('The number of digits to the right of the decimal.'),
      '#weight' => 6,
    );

    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  protected function numberFormat($number) {
    return number_format($number, $this->getSetting('scale'), $this->getSetting('decimal_separator'), $this->getSetting('thousand_separator'));
  }

}
Impacts: 
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done

Comments

fcedillo’s picture

fixed

here

/**
   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
   */
  public function settingsSummary() {

correct the method in the comment
...FormatterInterface::settingsSummary() instead of ...FormatterInterface::settingsForm()