Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Plugins

A plugin is a class that adds one more variant of something to the system: one more block, one more way to output a field, one more text filter. The block from the previous lesson is a plugin. All plugins are built the same way, so once you understand one kind, the others are easy to write.

How Drupal finds plugins

Every kind of plugin has three things:

  • a folder inside src/Plugin where the classes live;
  • an attribute with the description: machine name, label;
  • a base class with the methods to implement.

A plugin does not have to be registered in YAML files. On drush cr Drupal scans the src/Plugin folders of all modules and remembers the classes with a fitting attribute.

A field formatter

A formatter decides how a field value is output. Let us write a formatter that shows the reading time instead of the article text. Its folder is src/Plugin/Field/FieldFormatter. Create it and the file by hand:

// web/modules/custom/blog/src/Plugin/Field/FieldFormatter/ReadingTimeFormatter.php
namespace Drupal\blog\Plugin\Field\FieldFormatter;
 
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;
 
#[FieldFormatter(
    id: 'blog_reading_time',
    label: new TranslatableMarkup('Reading time'),
    field_types: ['text_with_summary', 'text_long'],
)]
class ReadingTimeFormatter extends FormatterBase
{
    public function viewElements(FieldItemListInterface $items, $langcode): array
    {
        $elements = [];
 
        foreach ($items as $delta => $item) {
            $minutes = \Drupal::service('blog.reading_time')->minutes((string) $item->value);
 
            $elements[$delta] = [
                '#markup' => $this->t('Reading time: @min min.', ['@min' => $minutes]),
            ];
        }
 
        return $elements;
    }
}

The field_types entry of the attribute lists the field types the formatter suits: long text and text with a summary. The viewElements() method receives all values of the field and returns a render array for each. The time is calculated by the blog.reading_time service from the lesson about services. For brevity it is taken statically; in real code it is passed through create(), as in the block.

Using it

Run drush cr and open Structure → Content types → Article → Manage display. In the Body row the Format list now has a "Reading time" option. Choose it for the Teaser view mode and save — in the /blog list a line like "Reading time: 3 min." appears under the article title instead of the text.

Attributes and annotations

Before Drupal 10.2 plugins were described not with attributes but with annotations — special comments above the class:

// web/modules/custom/blog/src/Plugin/Field/FieldFormatter/ReadingTimeFormatter.php
 
/**
 * @FieldFormatter(
 *   id = "blog_reading_time",
 *   label = @Translation("Reading time"),
 *   field_types = {"text_with_summary", "text_long"}
 * )
 */
class ReadingTimeFormatter extends FormatterBase
{
}

This entry does the same as the attribute above. Older examples and modules use exactly this form. In Drupal 10 and 11 annotations still work, but new code is written with attributes.

Kinds of plugins

PluginFolder in src/PluginPurpose
BlockBlockBlocks
FieldFormatterField/FieldFormatterOutput of a field value
FieldWidgetField/FieldWidgetThe input in the edit form
FieldTypeField/FieldTypeA new field type
FilterFilterA text format filter
QueueWorkerQueueWorkerA task queue worker
ConditionConditionA condition for showing blocks

Drush creates a skeleton for any of them: drush generate shows the list, for example drush generate plugin:field:formatter.

Common mistake

A plugin is not found when the file path does not match the plugin kind: a formatter put into src/Plugin/FieldFormatter or src/Field/FieldFormatter is invisible to Drupal — it must be exactly src/Plugin/Field/FieldFormatter. The second cause is a duplicate id: two plugins with the same machine name overwrite each other. Start the id with the module name.

Thanks for reading!

Donate to the site — we work hard for you.