FieldTypes, FieldWidgets and FieldFormatters

Last updated on
25 March 2017

Overview

Drupal 8 ships with a big library of base classes which allow you to work with your very own content. When it comes to content entities you want to use Fields. It is important to understand Fields as that is where your entities store their data.

FieldTypes

Core field types:

Custom field types

Whenever you want to represent data in a way Drupal doesn't provide; you might want to create a new field type for your data.

Let's say you have a content entity which holds sensitive data. The creator of this content can allow specific users to access the entity via a password which differs for every user. If we talk in database tables you want to create something like this:

| entity_id | uid | password      |
-----------------------------------
| 1         | 1   | 'helloworld'  |
| 1         | 2   | 'goodbye'     |

As you can see our entity with the id 1 has two different passwords for two different users. So how can we implement it in Drupal without having to create the table all by hand? We create a new field type.

Because Drupal implements the field logic as Plugin, we always have a base class that we can inherit from to make it work in Drupal. For a new field type you want to create the following folder structure in your module:
modules/custom/MODULENAME/src/Plugin/Field/FieldType
It is quite a long path any a little bit annoying but it makes it easier for Drupal to handle all the different functions that can coexist in your modules.

For our example, we create the file EntityUserAccessField.php

    /**
     * @file
     * Contains \Drupal\MODULENAME\Plugin\Field\FieldType\EntityUserAccessField
     */
     
    namespace Drupal\MODULENAME\Plugin\Field\FieldType;
     
    use Drupal\Core\Field\FieldItemBase;
    use Drupal\Core\Field\FieldStorageDefinitionInterface;
    use Drupal\Core\TypedData\DataDefinition;
     
    /**
     * @FieldType(
     *   id = "entity_user_access",
     *   label = @Translation("Entity User Access"),
     *   description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
     * )
     */
     
    class EntityUserAccessField extends FieldItemBase {
      /**
       * {@inheritdoc}
       */
      public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
        //ToDo: Implement this.
      }
     
      /**
       * {@inheritdoc}
       */
      public static function schema(FieldStorageDefinitionInterface $field_definition) {
        //ToDo: Implement this.
      }
    }

As you can see a field type looks very similar to a content entity. Actually, there is no real difference between those two but this is a topic for another node ;)

First of all, we have an Annotation for our field type

  • @FieldType: This calls the Annotation class FieldType from the Drupal library
  • id: This is the machine name of our field type so we can reuse it. Be sure not to override any predefined names from php and such.
  • label: This is can be a user readable translation for the machinename.
  • description: If the label is not enough you can also add a description for the field type.

Second, our class extends FieldItemBase which makes us implement two methods so we can use this field type properly:

  • propertyDefinitions(): This method is similar to a baseFieldDefinition of a content entity (it is not the same!). We can define data which appears on entity forms where this field type is being used.
  • schema(): On entities, this method is deprecated but we still have it on fields. This method should implement the data representation of the field in a database. It can differ from the properties.

Because it is not that obvious what to write down on these methods let us add some code to them for the convenience.

public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
  $properties['uid'] = DataDefinition::create('integer')
      ->setLabel(t('User ID Reference'))
      ->setDescription(t('The ID of the referenced user.'))
      ->setSetting('unsigned', TRUE);

  $properties['password'] = DataDefinition::create('string')
      ->setLabel(t('Password'))
      ->setDescription(t('A password saved in plain text. That is not save dude!'));

  $properties['created'] = DataDefinition::create('timestamp')
    ->setLabel(t('Created Time'))
    ->setDescription(t('The time that the entry was created'));

    // ToDo: Add more Properties.
 
    return $properties;
}

It is also possible to save the user id via a DataReferenceDefinition this might be covered here in the future.

public static function schema(FieldStorageDefinitionInterface $field_definition) {
  $columns = array(
    'uid' => array(
      'description' => 'The ID of the referenced user.',
      'type' => 'int',
      'unsigned' => TRUE,
    ),
    'password' => array(
      'description' => 'A plain text password.',
      'type' => 'varchar',
      'length' => 255,
    ),
    'created' => array(
      'description' => 'A timestamp of when this entry has been created.',
      'type' => 'int',
    ),

    // ToDo: Add more columns.
  );
 
  $schema = array(
    'columns' => $columns,
    'indexes' => array(),
    'foreign keys' => array(),
  );

  return $schema;
}

The schema() is necessary to make Drupal know about how to save our data. The schema columns need to be a subset of the properties defined in propertyDefinitions().

We now have a whole new field type created. It does not have any logic on it how to handle any data input but it can be used on any content entity as a field. If you want you can use it as a baseField on an entity:

public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
  // Some fields above.
 
  $fields['entity_user_access'] = BaseFieldDefinition::create('entity_user_access')
    ->setLabel(t('Entity User Access'))
    ->setDescription(t('Specify passwords for any user that want to see this entity.'))
    ->setCardinality(-1); // Ensures that you can have more than just one member
 
  // Even more fields below.
 
  return $fields;
}
  • BaseFieldDefinition::create(): You have to use the machine name of the field type in the create()
  • setCardinality(-1): The cardinality represents the amount of field data one entity can have. E.g. if we write a 2 in it only two users could access the entity, 3 would be 3 users and so on. -1 represents infinite users.

FieldWidget

If you have custom data you might event want custom representation of this data. Widgets are used to represent how the user can input this custom data on forms. E.g

  • if an integer has to be submitted in the form but the user can only check a checkbox
  • if you want autocompletion for your data
  • if password input has to be done via a special gui

et cetera.

A field widget in Drupal can be found under
modules/custom/MODULENAME/src/Plugin/Field/FieldWidget
which is a very long path too. At this point, it should be clear why Drupal uses this style to separate .php files. It is very easy to overlook which files belong where.

We create a field widget in EntityUserAccessWidget.php

/**
 * @file
 * Contains \Drupal\MODULENAME\Plugin\Field\FieldWidget\EntityUserAccessWidget
 */
 
namespace Drupal\MODULENAME\Plugin\Field\FieldWidget;
 
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
 
/**
 * Plugin implementation of the 'entity_user_access_w' widget.
 *
 * @FieldWidget(
 *   id = "entity_user_access_w",
 *   label = @Translation("Entity User Access - Widget"),
 *   description = @Translation("Entity User Access - Widget"),
 *   field_types = {
 *     "entity_user_access",
 *   },
 *   multiple_values = TRUE,
 * )
 */
 
class EntityUserAccessWidget extends WidgetBase {
  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    // ToDo: Implement this.
  }
}

Did you notice already? Drupal 8 uses this style of code over and over again if you want to implement features. There is an annotation and a base class you have to inherit. Yay, Drupal can use it!

  • @FieldWidget: Specifies the annotation class
  • id: The machine name of the widget
  • field_types: An array of field type machine names which can use this widget
  • multiple_values: Default is FALSE. If it's true this will allow you to submit more than one value on an entity form

If you now want to use this widget with your field type you have to edit the annotation of the field type like this

    // ...

    /**
     * @FieldType(
     *   id = "entity_user_access",
     *   label = @Translation("Entity User Access"),
     *   description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
     *   default_widget = "entity_user_access_w",
     * )
     */
     
    // ...

Yeah all done! No, wait nothing happens yet because we have to implement the formElement() in our widget.

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
  $element['userlist'] = array(
    '#type' => 'select',
    '#title' => t('User'),
    '#description' => t('Select group members from the list.'),
    '#options' => array(
       0 => t('Anonymous'),
       1 => t('Admin'),
       2 => t('foobar'),
       // This should be implemented in a better way!
     ),
  );

$element['passwordlist'] = array(
    '#type' => 'password',
    '#title' => t('Password'),
    '#description' => t('Select a password for the user'),
  );
 
  return $element;
}

If you now open a form with this widget then you will see at least two input fields. One is a select for the user and the other is a password field. If you want to implement how the data is being saved then you have to implement validation methods on this widget or on the entity form. See Drupal 8 Form API for more info.

By now you have done a most of the work regarding a custom field. If you don't understand what is going on then just try out the code or have a look at the core modules for deeper knowledge on the topic.

FieldFormatters

The last thing that is missing is the representation of the data in the so called view mode of an entity - by the way the widget is the form mode. This is most common if you call an entity via yourdrupalpage.com/myentity/1/view

As there is not so much to talk about here we will come directly to the code. Under modules/custom/MODULENAME/src/Plugin/Field/FieldFormatter create EntityUserAccessFormatter.php

    /**
     * @file
     * Contains \Drupal\MODULENAME\Plugin\Field\FieldFormatter\EntityUserAccessFormatter.
     */
     
    namespace Drupal\MODULENAME\Plugin\Field\FieldFormatter;
     
    use Drupal\Core\Field\FieldItemListInterface;
    use Drupal\Core\Field\FormatterBase;
     
    /**
     * Plugin implementation of the 'entity_user_access_f' formatter.
     *
     * @FieldFormatter(
     *   id = "entity_user_access_f",
     *   label = @Translation("Entity User Access - Formatter"),
     *   description = @Translation("Entity User Access - Formatter"),
     *   field_types = {
     *     "entity_user_access",
     *   }
     * )
     */
     
    class EntityUserAccessFormatter extends FormatterBase {
      /**
       * {@inheritdoc}
       */
      public function viewElements(FieldItemListInterface $items, $langcode) {
        $elements = array();
     
        foreach ($items as $delta => $item) {
          $elements[$delta] = array(
            'uid' => array(
              '#markup' => \Drupal\user\Entity\User::load($item->user_id)->getUsername(),
            ),
            // Add more content
          );
        }
     
        return $elements;
      }
    }

This example has a very similar annotation as the widget so we do not need to talk about it that much. The viewElements() just shows the username of the saved user id of our field type. The access implementation has to be done in the entity. So this implementation will show all the user names which have a password on the entity.