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

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

Field types registry

The functions used to access properties about available field types are moved to methods on the FieldTypePluginManager:

field_info_field_types() \Drupal::service('plugin.manager.field.field_type')->getDefinitions()
field_info_field_types($type) \Drupal::service('plugin.manager.field.field_type')->getDefinition($type)
field_info_field_settings($type) \Drupal::service('plugin.manager.field.field_type')->getDefaultStorageSettings($type)
field_info_instance_settings($type) \Drupal::service('plugin.manager.field.field_type')->getDefaultFieldSettings($type)

Field types implementations

The plugin classes implementing field types are directly the "field item" classes used by "field value" objects in Drupal 8's new Entity API: the class providing the 'image' field type is \Drupal\image\Plugin\Field\FieldType\ImageItem, and it is the class of the object holding the field value at $node->field_image[0].
In short: providing a field type is providing a type of "field value", and the code in the various methods is the value object specifying its own behavior.

(in more detail, the "field type" plugins are automatically added as "data type" plugins through a "plugin derivative", and the objects are only ever instantiated as "typed data" objects rather than as "field type plugins").

The biggest conceptual shift from Drupal 7 is that field item structures are no longer dumb arrays with raw data. They are classed objects, and each field type has full control over what happens with its field values. Instead of massaging field item arrays from the outside (e.g. hook_field_load()), they can provide computed field property classes and implement methods on the item and item list classes to act when something happens. Handbook documentation: http://drupal.org/node/2112677

More information about the Entity API in Drupal 8 can be found in the handbook.

To port existing field types, create a class in a file like '{module}/src/Plugin/Field/FieldType/{FieldType}.php. The class must implement Drupal\Core\Field\FieldItemInterface (in most cases, you will want to extend the Drupal\Core\Field\ConfigFieldItemBase base class).

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

hook_field_info() Replaced by annotation-based plugin discovery, using the \Drupal\Core\Field\Annotation\FieldType annotation class

+ FieldItemInterface::defaultStorageSettings() / FieldItemInterface::defaultFieldSettings() (static methods)
hook_field_schema() FieldItemInterface::schema() (static method)
hook_field_settings_form() FieldItemInterface::storageSettingsForm()
hook_field_instance_settings_form() FieldItemInterface::fieldSettingsForm()
hook_field_is_empty() ComplexDataInterface::isEmpty()
hook_field_presave() FieldItemInterface::preSave()
hook_field_insert() FieldItemInterface::insert()
hook_field_update() FieldItemInterface::update()
hook_field_delete() FieldItemInterface::delete()
hook_field_delete_revision() FieldItemInterface::deleteRevision()
hook_field_validate() TypedDataInterface::getConstraints() (field validation moves on top of the existing Entity validation with Symfony constraints)
hook_field_load() Removed. Field types can add computed properties or methods to their field item classes instead. See https://drupal.org/node/2112677.
hook_field_prepare_view() Removed, in favour of prepareView() on Formatters. Field types that implemented specific prepare_view steps with this hook should consider providing a base prepareView() implementation in an abstract base formatter class instead.
hook_field_prepare_translation() Dropped - was only related to the old (node-only) translation model.
hook_field_prepare_translation_alter() Dropped - was only related to the old (node-only) translation model.

Field types can also specifiy a custom class to use for the field item list by specifying the list_class property in the @FieldType annotation. By default, \Drupal\Core\Field\FieldItemList is used for fields.

Contrary to the old D7 hooks above, the methods do not receive the parent entity or the langcode of the field values as parameters. If needed, those can be accessed by the getEntity() and getLangcode() methods on the FieldItemList and FieldItem classes.

If a field type doesn't want to be available in the "Add new field" UI, it can specify 'no_ui' = TRUE in its definition (typically in the @FieldType annotation). Such field types can only be used for >base fields, or added programmatically.

Code example:

Example for the email field.

Drupal 7

/**
 * Implements hook_field_info().
 */
function email_field_info() {
  return array(
    'email' => array(
      'label' => 'Email',
      'description' => t('This field stores and renders email addresses.'),
      'default_widget' => 'email_textfield',
      'default_formatter' => 'email_default',
      'property_type' => 'text',
    ),
  );
}

/**
 * Implements hook_field_schema().
 */
function email_field_schema($field) {
  return array(
    'columns' => array(
      'email' => array(
        'type' => 'varchar',
         'length' => 255,
         'not null' => FALSE,
      ),
    ),
  );
}

// ... other field API hooks ...

Drupal 8


namespace Drupal\telephone\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;

/**
 * Plugin implementation of the 'telephone' field type.
 *
 * @FieldType(
 *   id = "telephone",
 *   label = @Translation("Telephone number"),
 *   description = @Translation("This field stores a telephone number in the database."),
 *   default_widget = "telephone_default",
 *   default_formatter = "string"
 * )
 */
class TelephoneItem extends FieldItemBase {

  /**
   * Definitions of the contained properties.
   *
   * @var array
   */
  static $propertyDefinitions;

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return array(
      'columns' => array(
        'value' => array(
          'type' => 'varchar',
          'length' => 256,
          'not null' => FALSE,
        ),
      ),
    );
  }

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $property_definitions['value'] = DataDefinition::create('string')
      ->setLabel(t('Telephone number'));
    return $property_definitions;
  }

}

Impacts: 
Module developers

Comments

xjm’s picture

hook_field_load() -> PrepareCacheInterface::getCacheData(). The "field item" class needs to explicitly state "implements PrepareCacheInterface" for the method to be called. Only called when preparing values for the cache.

Uh... what now? How does that explain how to convert hook_field_load() to D8? From D7:

Define custom load behavior for this module's field types.

Unlike most other field hooks, this hook operates on multiple entities. The $entities, $instances and $items parameters are arrays keyed by entity ID. For performance reasons, information for all available entity should be loaded in a single query where possible.

Note that the changes made to the field values get cached by the field cache for subsequent loads. You should never use this hook to load fieldable entities, since this is likely to cause infinite recursions when hook_field_load() is run on those as well. Use hook_field_formatter_prepare_view() instead.

Make changes or additions to field values by altering the $items parameter by reference. There is no return value.

https://api.drupal.org/api/drupal/modules%21field%21field.api.php/functi...

m.stenta’s picture

Some explanation courtesy of berdir in #drupal-contribute regarding hook_field_load() in Drupal 7, and how to achieve the same thing(s) now in Drupal 8:

the way core (text.module) used that hook is to pre-calculate stuff so that it can be put into the field cache. that's what the change note documents (how to prepare something for field cache). The other part is actually doing some computing on a field item, this now works with computed properties, which are accessed on-demand

... It's the conceptual change from dumb-array-structure that you need to massage from the outside (in hooks/callbacks) to look as you need it to D8, where you have an actual class, and you can control how things work when you are accessed

you only need getCacheData() if you want to add whatever thing you compute to the field cache, so that it only has to be done when the entity is initially loaded, see TextItemBase::getCacheData().

Also worth noting: in order to implement getCacheData() within a field item class (a child class of FieldItemBase), that class also needs to implement PrepareCacheInterface.

m.stenta’s picture

I fleshed out the handbook page for computed properties: http://drupal.org/node/2112677

Hopefully it's all correct. Feel free to update if there are any mistakes. I set the status of it to "Needs technical review".

mradcliffe’s picture

getPropertyDefinitions() needs to return an array of DataDefinition objects.

#2112239: Convert base field and property definitions