How to create a Field Formatter in Drupal

By mourad-zitouni, 11 December, 2021

Today I will create a filed formatter, we will use it to display a media image as a url.

Let's create a custom module, we start by defining info file image_formatter.info.yml:

name: Image Formatter
type: module
description: 'Field formatter for media image.'
core_version_requirement: ^8.9 || ^9
dependencies:
  - drupal:media

Note that we added a dependency for media module. We can enable the module on the back office (admin/modules) or using drush (drush en image_formatter).

Now that our module is defined, we will create the formatter class, the file goes to src/Plugin/Field/FieldFormatter.php :

<?php
/**
* @file
* Contains \Drupal\image_formatter\Plugin\field\formatter\ImageUrlFormatter.
*/

namespace Drupal\image_formatter\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;

/**
 * Plugin implementation of the 'image_url' formatter.
 *
 * @FieldFormatter(
 *   id = "image_url",
 *   label = @Translation("Image url"),
 *   field_types = {
 *     "entity_reference"
 *   }
 * )
 */
class ImageUrlFormatter extends FormatterBase {
}

In the annotations we defined the id, label of the formatter, and the field types on which the formatter will be applicable (entity_reference in our example).

The class ImageUrlFormatter extends FormatterBase class, we will implement viewElements method for the display of the URL, for this we need to inject entityTypeManager so we can load the media file:

<?php
/**
* @file
* Contains \Drupal\image_formatter\Plugin\field\formatter\ImageUrlFormatter.
*/

namespace Drupal\image_formatter\Plugin\Field\FieldFormatter;


use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Plugin implementation of the 'image_url' formatter.
 *
 * @FieldFormatter(
 *   id = "image_url",
 *   label = @Translation("Image url"),
 *   field_types = {
 *     "entity_reference"
 *   }
 * )
 */
class ImageUrlFormatter extends FormatterBase implements ContainerFactoryPluginInterface {

  /**
   * @var EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * @var StreamWrapperManagerInterface
   */
  protected $streamWrapperManager;

  /**
   * ImageUrlFormatter constructor.
   * @param $plugin_id
   * @param $plugin_definition
   * @param FieldDefinitionInterface $field_definition
   * @param array $settings
   * @param $label
   * @param $view_mode
   * @param array $third_party_settings
   * @param EntityTypeManagerInterface $entity_type_manager
   * @param StreamWrapperManagerInterface $stream_wrapper_manager
   */
  public function __construct(
    $plugin_id,
    $plugin_definition,
    FieldDefinitionInterface $field_definition,
    array $settings,
    $label,
    $view_mode,
    array $third_party_settings,
    EntityTypeManagerInterface $entity_type_manager,
    StreamWrapperManagerInterface $stream_wrapper_manager
  ) {

    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
    $this->entityTypeManager = $entity_type_manager;
    $this->streamWrapperManager = $stream_wrapper_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $plugin_id,
      $plugin_definition,
      $configuration['field_definition'],
      $configuration['settings'],
      $configuration['label'],
      $configuration['view_mode'],
      $configuration['third_party_settings'],
      $container->get('entity_type.manager'),
      $container->get('stream_wrapper_manager'),
    );
  }

  /**
   * {@inheritdoc}
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
  }
}

Here we added the constructor of the class and we injected entity type manager service and stream wrapper manager. If you are not familiar with dependency injection, here is a link to know more.

Second step we will prepare the default settings, the settings form and the settings summary:

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings()
  {
    return [
      'image_style' => '',
    ] + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $image_styles = image_style_options(FALSE);
    $element['image_style'] = array(
      '#title' => t('Image style'),
      '#type' => 'select',
      '#default_value' => $this->getSetting('image_style'),
      '#empty_option' => t('None (original image)'),
      '#options' => $image_styles,
    );
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = array();
    $image_styles = image_style_options(FALSE);

    // Unset possible 'No defined styles' option.
    unset($image_styles['']);

    // Styles could be lost because of enabled/disabled modules that
    // defines their styles in code.
    $image_style_setting = $this->getSetting('image_style');
    if (isset($image_styles[$image_style_setting])) {
      $summary[] = t('Image style: @style', array('@style' => $image_styles[$image_style_setting]));
    } else {
      $summary[] = t('Original image');
    }
    return $summary;
  }

This will let the user select the image style to apply to the media image on the manage display page of the content type, and get the summary of the settings selected. Next, we add the implementation of viewElements method:

  /**
   * {@inheritdoc}
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];
    $image_style_setting = $this->getSetting('image_style');

    // Check if Image style is required.
    /** @var  ImageStyleInterface $image_style */
    $image_style = !empty($image_style_setting) ? $this->entityTypeManager->getStorage('image_style')->load($image_style_setting) : NULL;

    foreach ($items as $delta => $item) {

      // Get the media item.
      $media_id = $item->getValue()['target_id'];

      /** @var MediaInterface $media_item */
      $media_item = $this->entityTypeManager->getStorage('media')->load($media_id);

      // Get the image file.
      $file_id = $media_item->field_media_image->getValue()[0]['target_id'];

      /** @var FileInterface $file */
      $file = $this->entityTypeManager->getStorage('file')->load($file_id);

      // Get the URL.
      $uri = $file->getFileUri();

      $wrapper = $this->streamWrapperManager->getViaUri($uri);
      $url = $image_style ? $image_style->buildUrl($uri) : Url::fromUri($wrapper->getExternalUrl())->toString();

      // Output.
      $elements[$delta] = [
        '#type' => 'inline_template',
        '#template' => '{{ url }}',
        '#context' => [
          'url' => parse_url($url,
            PHP_URL_PATH
          )
        ],
      ];
    }
    return $elements;
  }

All steps are commented here, we get the image style applied in settings, then load media image file, apply the image style if any, and get the url (relative to drupal path), finally we return the output as inline_template.

We can now enable the formatter in the Manage display for any reference to an image in content type/ paragraphs,...

Comments