As of 28th November 2014, the video subscription has been shutdown. All videos are available for FREE to watch.

Upgrading Code Snippets Module to Drupal 8: Creating a Custom Field

With Drupal 8 on the horizon, I decided it was time to start upgrading a contributed module that I maintain called Code Snippets to Drupal 8.

The module in Drupal 7 allows you to store code examples/snippets in a field. It ships with a custom field called "Snippets field" and it renders three form elements, description, source code and syntax highlighting mode (what programming language).

But now it's time to upgrade the module to Drupal 8.

In this tutorial, I'll show you how I created a "basic" custom field in Drupal 8. I won't go into detail about PSR-0, annotations or plugins or this tutorial will be huge.

Instead, I'll add links to other websites that explain the concept further.

That being said, if you're looking for detailed documentation on the Field API in Drupal 8, check out the following series:

UPDATE: D7 to D8 upgrade: fields, widgets and formatters (Thanks to RdeBoer)

In Drupal 8, fields are not implemented using hooks like they are in Drupal 7. Instead, they are created using Drupal 8's new Plugin API. This means that instead of implementing hooks, we define a class for a widget, formatter and field item. Most Drupal 7 field hooks like hook_field_schema, hook_field_is_empty and more; are now methods in classes.

Step 1: Implement Field Item

The first bit of work we need to do is define a field item class called SnippetsItem that extends the ConfigFieldItemBase class.

1. In Drupal 8 classes are loaded using PSR-0.

So, to define the SnippetsItem class, we need to create a SnippetsItem.php file and place it in <module>/lib/Drupal/snippets/Plugin/Field/FieldType/SnippetsItem.php

/**
 * @file
 * Contains \Drupal\snippets\Plugin\Field\FieldType\SnippetsItem.
 */
 
namespace Drupal\snippets\Plugin\Field\FieldType;
 
use Drupal\Core\Field\ConfigFieldItemBase;
use Drupal\field\FieldInterface;

Then in the file we add a namespace Drupal\snippets\Plugin\Field\FieldType and two use statements: Drupal\Core\Field\ConfigFieldItemBase and Drupal\field\FieldInterface.

2. Now we need to define the actual field details like the field id, label, default widget and formatter etc.. This the equivalent of implementing hook_field_info in Drupal 7.

In Drupal 8 a lot, if not all, of the info hooks have been replaced by annotations.

/**
 * Plugin implementation of the 'snippets' field type.
 *
 * @FieldType(
 *   id = "snippets_code",
 *   label = @Translation("Snippets field"),
 *   description = @Translation("This field stores code snippets in the database."),
 *   default_widget = "snippets_default",
 *   default_formatter = "snippets_default"
 * )
 */
class SnippetsItem extends ConfigFieldItemBase { }

So instead of implementing hook_field_info, we define the field as an annotation inside of a comment above the class.

The annotation attributes are quite self-explanatory. Just make sure that the default_widget and default_formatter reference the widget and formatter annotation ID and not the class.

If you want to learn more about annotations, check out the Annotations-based plugins documentation page on drupal.org.

3. Now that we have our field item class, we need to define a few methods. The first one we'll look at is schema().

In Drupal 7, when you create a custom field you define its schema using hook_field_schema. In Drupal 8, we define the schema by adding a schema() method to the SnippetsItem class.

/**
 * {@inheritdoc}
 */
public static function schema(FieldInterface $field) {
  return array(
    'columns' => array(
      'source_description' => array(
        'type' => 'varchar',
        'length' => 256,
        'not null' => FALSE,
      ),
      'source_code' => array(
        'type' => 'text',
        'size' => 'big',
        'not null' => FALSE,
      ),
      'source_lang' => array(
        'type' => 'varchar',
        'length' => 256,
        'not null' => FALSE,
      ),
    ),
  );
}

4. Now we need to add the isEmpty() method and define what constitutes an empty field item. This method is the same as implementing hook_field_is_empty in Drupal 7.

/**
 * {@inheritdoc}
 */
public function isEmpty() {
  $value = $this->get('source_code')->getValue();
  return $value === NULL || $value === '';
}

5. The final method we'll add to the class is the getPropertyDefinitions() method.

/**
 * {@inheritdoc}
 */
static $propertyDefinitions;
 
/**
 * {@inheritdoc}
 */
public function getPropertyDefinitions() {
  if (!isset(static::$propertyDefinitions)) {
    static::$propertyDefinitions['source_description'] = array(
      'type' => 'string',
      'label' => t('Snippet description'),
    );
    static::$propertyDefinitions['source_code'] = array(
      'type' => 'string',
      'label' => t('Snippet code'),
    );
    static::$propertyDefinitions['source_lang'] = array(
      'type' => 'string',
      'label' => t('Snippet code language'),
    );
  }
  return static::$propertyDefinitions;
}

This method is used to define the type of data that exists in the field values. The "Snippets field" has just three values: description, code and language. So I just added those values to the method as strings.

Go to the How Entity API implements Typed Data API documentation on drupal.org to learn more about this.

Click here to see the whole file.

Step 2: Implement Field Widget

Now that we've defined the field item, let's create the field widget. We need to create a class called SnippetsDefaultWidget that extends the WidgetBase class.

1. So create a SnippetsDefaultWidget.php file and add it to <module>/lib/Drupal/snippets/Plugin/Field/FieldWidget/SnippetsDefaultWidget.php.

/**
 * @file
 * Contains \Drupal\snippets\Plugin\Field\FieldWidget\SnippetsDefaultWidget.
 */
 
namespace Drupal\snippets\Plugin\Field\FieldWidget;
 
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;

Make sure the file namespace is Drupal\snippets\Plugin\Field\FieldWidget and add the following two use statements: Drupal\Core\Field\FieldItemListInterface and Drupal\Core\Field\WidgetBase.

2. Next, we need to define the widget using an annotation. This is the equivalent of using hook_field_widget_info in Drupal 7.

/**
 * Plugin implementation of the 'snippets_default' widget.
 *
 * @FieldWidget(
 *   id = "snippets_default",
 *   label = @Translation("Snippets default"),
 *   field_types = {
 *     "snippets_code"
 *   }
 * )
 */
class SnippetsDefaultWidget extends WidgetBase { }

Just a heads up, make sure that the field_types attribute in the annotation references the field types using their ID. For this module, it is snippets_code because we added id = "snippets_code", to the @FieldType annotation.

3. And finally, we need to define the actual widget form. We do this by adding a formElement() method to the SnippetsDefaultWidget class. This method is the same as using hook_field_widget_form in Drupal 7.

/**
 * {@inheritdoc}
 */
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) {
 
  $element['source_description'] = array(
        '#title' => t('Description'),
        '#type' => 'textfield',
        '#default_value' => isset($items[$delta]->source_description) ? $items[$delta]->source_description : NULL,
      );
  $element['source_code'] = array(
        '#title' => t('Code'),
        '#type' => 'textarea',
        '#default_value' => isset($items[$delta]->source_code) ? $items[$delta]->source_code : NULL,
      );
  $element['source_lang'] = array(
        '#title' => t('Source language'),
        '#type' => 'textfield',
        '#default_value' => isset($items[$delta]->source_lang) ? $items[$delta]->source_lang : NULL,
      );
  return $element;
}

Click here to see the whole file.

Step 3: Implement Field Formatter

The final piece to the puzzle, is the field formatter, and we create it by defining a class called SnippetsDefaultFormatter that extends the FormatterBase class.

1. Create a SnippetsDefaultFormatter.php file and add it to <module>/lib/Drupal/snippets/Plugin/Field/FieldFormatter/SnippetsDefaultFormatter.php.

/**
 * @file
 * Contains \Drupal\snippets\Plugin\field\formatter\SnippetsDefaultFormatter.
 */
 
namespace Drupal\snippets\Plugin\Field\FieldFormatter;
 
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;

Make sure the file namespace is Drupal\snippets\Plugin\Field\FieldFormatter and add the following use statements: Drupal\Core\Field\FieldItemListInterface and Drupal\Core\Field\FormatterBase.

2. Next, we need to define the formatter as an annotation. The same as we did for the widget and field type, this is the equivalent of using hook_field_formatter_info.

/**
 * Plugin implementation of the 'snippets_default' formatter.
 *
 * @FieldFormatter(
 *   id = "snippets_default",
 *   label = @Translation("Snippets default"),
 *   field_types = {
 *     "snippets_code"
 *   }
 * )
 */
class SnippetsDefaultFormatter extends FormatterBase { }

3. Now the only thing left to do is add the viewElements() method and define the actual field formatter. Again, this method is the same as using hook_field_formatter_view in Drupal 7.

/**
 * {@inheritdoc}
 */
public function viewElements(FieldItemListInterface $items) {
  $elements = array();
  foreach ($items as $delta => $item) {
    // Render output using snippets_default theme.
    $source = array(
      '#theme' => 'snippets_default',
      '#source_description' => check_plain($item->source_description),
      '#source_code' => check_plain($item->source_code),
    );
 
    $elements[$delta] = array('#markup' => drupal_render($source));
  }
 
  return $elements;
}

One thing to take note of is that I'm using a custom template called snippets_default to render the snippets before it is displayed by the formatter.

The reason for this is I didn't want to put a lot of logic or HTML code in the viewElements() method.

Click here to see the whole file.

Conclusion

As stated earlier the biggest change in Drupal 8 is that fields are created using the Plugin API and not hooks. Once you understand that, the concept of creating a field is very similar to Drupal 7. A lot of the methods in Drupal 8 match the hooks in Drupal 7.

If you want to test out Code Snippets, download the 8.x-dev release and give it a go.

Tags: 

Ivan is the founder of Web Wash and spends most of his time consulting and writing about Drupal. He's been working with Drupal for 6 years and has successfully completed several large Drupal projects in Australia.

Connect: Twitter drupal.org LinkedIn App.net

Comments

Submitted by Berdir on

Looks great. Great to see entity/field stuff being covered too, not just the usual candidates like routing :)

I'm working on documenting Entity Field API here: https://drupal.org/developing/api/entity

I'm not at the point where I can document this (want to write an overview of content entities and (configurable) fields first, but this looks like a great example to have below such a not-yet existing page.

Are you interested in converting this to a child page there? You could just put it directly below the entity page there, I'll move it around when the parents are there. I'm sure it's ok to reference back to your blog to document where it came from.

Submitted by qdelance on

Thanks for your time and tutorials.
Just submitted a patch to track D8 changes.

Add new comment