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 DrupalsnippetsPluginFieldFieldTypeSnippetsItem.
*/
namespace DrupalsnippetsPluginFieldFieldType;
use DrupalCoreFieldConfigFieldItemBase;
use DrupalfieldFieldInterface;
Then in the file we add a namespace DrupalsnippetsPluginFieldFieldType
and two use statements: DrupalCoreFieldConfigFieldItemBase
and DrupalfieldFieldInterface
.
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 DrupalsnippetsPluginFieldFieldWidgetSnippetsDefaultWidget.
*/
namespace DrupalsnippetsPluginFieldFieldWidget;
use DrupalCoreFieldFieldItemListInterface;
use DrupalCoreFieldWidgetBase;
Make sure the file namespace is DrupalsnippetsPluginFieldFieldWidget
and add the following two use statements: DrupalCoreFieldFieldItemListInterface
and DrupalCoreFieldWidgetBase
.
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 DrupalsnippetsPluginfieldformatterSnippetsDefaultFormatter.
*/
namespace DrupalsnippetsPluginFieldFieldFormatter;
use DrupalCoreFieldFormatterBase;
use DrupalCoreFieldFieldItemListInterface;
Make sure the file namespace is DrupalsnippetsPluginFieldFieldFormatter
and add the following use statements: DrupalCoreFieldFieldItemListInterface
and DrupalCoreFieldFormatterBase
.
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.
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.
Thanks a lot. 🙂
I added this tutorial to https://drupal.org/developing/api/entity, as a child page: https://drupal.org/node/2128865
Fantastic that so much D8 documentation is available early in the piece.
Here’s another one: https://drupal.org/node/1985716
Thanks for the link. I added it to the tutorial.
Meanwhile, things changed a bit – thus for the field type to work with d8 HEAD you’ll need to make use of classes for defining the field item properties. There is an updated variant over at https://drupal.org/node/2128865
Thanks for the tutorial! 🙂
Thanks for the update.
Thanks for your time and tutorials.
Just submitted a patch to track D8 changes.
Thanks, I’ll check it out.
It’s a good tutorial, but, can you tell me. How to add this custom field element to a custom form? I have a lot of time trying.
Hi Jean,
Have a look a the link below to learn how to add elements to forms.
https://www.drupal.org/docs/8/api/form-api/introduction-to-form-api
Cheer,
Ivan