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

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

Widgets registry

The functions used to access properties about available widgets are moved to methods on the WidgetPluginManager:

field_info_widget_types() \Drupal::service('plugin.manager.field.widget')->getDefinitions()
field_info_widget_types($type) \Drupal::service('plugin.manager.field.widget')->getDefinition($type)
field_info_widget_settings($type) \Drupal::service('plugin.manager.field.widget')->getDefaultSettings($type)

Widget implementations

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

WidgetInterface / WidgetBase

Actual widget classes implement WidgetInterface.
WidgetBase methods hold the code that existed in field_default_*() in Drupal 7:

field_default_form() WidgetBase::form()
field_default_form_errors() WidgetBase::flagErrors()
field_default_extract_form_values()
field_default_submit()
WidgetBase::submit()

WidgetInterface also includes methods replacing the old hook_field_widget_*() hooks:

hook_field_widget_settings_form() Widgetinterface::settingsForm()
hook_field_widget_form() Widgetinterface::formElement()
hook_field_widget_error() Widgetinterface::errorElement()
(complex tricks with FAPI #callbacks) (new) Widgetinterface::massageFormValues()

API changes:

- hook_field_widget_info() is replaced by annotation-based plugin discovery, using the \Drupal\Core\Field\Annotation\FieldWidget 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 WidgetInterface::defaultSettings() static method.
- hook_field_widget_settings_form(), hook_field_widget_form(), hook_field_widget_error() : replaced by WidgetInterface methods (see above)

Parameters:

  • The formElement() method, that is responsible for building the widget form element, 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 handkook.
    Simply put, FieldItemListInterface objects can be accessed like an array of items keyed by delta, and properties in each item can be accessed by simple object syntax:

    Drupal 7

    $value = $items[$delta]['value'];
    $format = $items[$delta]['format'];
    

    Drupal 8

    $value = $items[$delta]->value;
    $format = $items[$delta]->format;
    }
    
  • The method no longer receives the parent entity and langcode for the field values as separate $entity and $langcode parameters. If needed (most widgets 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_widget_info().
 */
function text_field_widget_info() {
  return array(
    'text_textfield' => array(
      'label' => t('Text field'),
      'field types' => array('text'),
      'settings' => array('size' => 60),
    ),
  );
}

/**
 * Implements hook_field_widget_settings_form().
 */
function text_field_widget_settings_form($field, $instance) {
  $widget = $instance['widget'];
  $settings = $widget['settings'];

  $form['size'] = array(
    '#type' => 'textfield',
    '#title' => t('Size of textfield'),
    '#default_value' => $settings['size'],
    '#required' => TRUE,
    '#element_validate' => array('element_validate_integer_positive'),
  );

  return $form;
}

/**
 * Implements hook_field_widget_form().
 */
function text_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $summary_widget = array();
  $main_widget = array();

  switch ($instance['widget']['type']) {
    case 'text_textfield':
      $element + array(
        '#type' => 'textfield',
        '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL,
        '#size' => $instance['widget']['settings']['size'],
        '#maxlength' => $field['settings']['max_length'],
        '#attributes' => array('class' => array('text-full')),
      );
      break;
  }

  return $element;
}

/**
 * Implements hook_field_widget_error().
 */
function text_field_widget_error($element, $error, $form, &$form_state) {
  switch ($error['error']) {
    case 'text_summary_max_length':
      $error_element = $element[$element['#columns'][1]];
      break;

    default:
      $error_element = $element[$element['#columns'][0]];
      break;
  }

  form_error($error_element, $error['message']);
}

Drupal 8


/**
 * @file
 * Definition of Drupal\text\Plugin\Field\FieldWidget\TextfieldWidget.
 */

namespace Drupal\text\Plugin\field\widget;

use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Field\FieldItemListInterface

/**
 * Plugin implementation of the 'text_textfield' widget.
 *
 * @FieldWidget(
 *   id = "text_textfield",
 *   module = "text",
 *   label = @Translation("Text field"),
 *   field_types = {
 *     "text"
 *   }
 * )
 */
class TextfieldWidget extends WidgetBase {

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return array(
      'size' => 60,
      'placeholder' => '',
    ) + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, array &$form_state) {
    $element['size'] = array(
      '#type' => 'number',
      '#title' => t('Size of textfield'),
      '#default_value' => $this->getSetting('size'),
      '#required' => TRUE,
      '#min' => 1,
    );
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) {
    $main_widget = $element + array(
      '#type' => 'textfield',
      '#default_value' => $items[$delta]->value,
      '#size' => $this->getSetting('size'),
      '#maxlength' => $this->getFieldSetting('max_length'),
      '#attributes' => array('class' => array('text-full')),
    );

    if ($this->getFieldSetting('text_processing')) {
      $element = $main_widget;
      $element['#type'] = 'text_format';
      $element['#format'] = $items[$delta]->format;
      $element['#base_type'] = $main_widget['#type'];
    }
    else {
      $element['value'] = $main_widget;
    }

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, array &$form_state) {
    return $element[$violation->arrayPropertyPath[0]];
  }
}

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: 
Not done