How to develop a Custom Service in Drupal 8

writing codes and database

You need to create this file inside your custom module: <my_module>.services.yml, for example mine is called maria_custom.services.yml and it looks like:

services:
  maria_custom.service:
    class: '\Drupal\maria_custom\MariaCustomService'
    arguments: ['@entity_type.manager', '@entity_field.manager', '@database', '@config.factory', '@logger.factory', '@date.formatter', '@messenger', '@session', '@user.data', '@current_user']

A Service is the class MariaCustomService which is using other services such as Entity Type Manager, Entity Field Manager, Database, Config Factory, Logger Factory, Date Formatter, Messenger, Session, User Data and Current UserAccount. This practise of re-use existing Services is called Dependency Injection and in our Custom Service class the __construct method looks like:

  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    EntityFieldManagerInterface $entity_field_manager,
    Connection $db_connection,
    ConfigFactoryInterface $config_factory,
    LoggerChannelFactoryInterface $logger,
    DateFormatter $date_formatter,
    MessengerInterface $messenger,
    Session $session,
    UserData $user_data,
    AccountInterface $account)
  {
    $this->entityTypeManager = $entity_type_manager;
    $this->entityFieldManager = $entity_field_manager;
    $this->database = $db_connection;
    $this->configFactory = $config_factory;
    $this->logger = $logger;
    $this->dateFormatter = $date_formatter;
    $this->messengerService = $messenger;
    $this->usersSession = $session;
    $this->userData = $user_data;
    $this->currentUser = $account;
    try {
      $this->nodeStorage = $this->entityTypeManager->getStorage('node');
      $this->termStorage = $this->entityTypeManager->getStorage('taxonomy_term');
      $this->fileStorage = $this->entityTypeManager->getStorage('file');
      $this->imageStyleStorage = $this->entityTypeManager->getStorage('image_style');
    } catch (PluginNotFoundException $exception) {
      throw new NotFoundHttpException("Plugin Not Found Exception");
    } catch (InvalidPluginDefinitionException $exception) {
      throw new NotFoundHttpException("Invalid Plugin Definition Exception");
    }
  }

I also define here the Node, Term, File, Image Style Storage Managers which are used on the rest of the class.

Inside this Service class I wrote reusable functions to be used from other Modules. For example this is the code to load the Preview Term data to show inside my Bootstrap 4 column element teaser's custom block:

  /**
   * Get the Taxonomy Details by Taxonomy ID.
   * @param int $tid
   *
   * @return array $term_item
   *   An associative array containing tid/taxonomy details value pairs.
   */
  public function getServiceDetails($tid)
  {
    $term_item = [];
    $term = $this->termStorage->load($tid);
    if ($term instanceof ContentEntityInterface) {
      $image_data = $this->getImageData($term, 'field_service_image');
      $term_id = $term->id();
      $term_url = $term->toUrl()->toString();

      if ($term->hasField('field_term_teaser_title')) {
        $caption = $term->field_term_teaser_title->value;
      }
      if (empty($caption)) {
        $title_parts = explode(' ', $image_data['title']);
        $caption = $title_parts[0];
      }

      if ($term->hasField('field_term_teaser')) {
        $description = strip_tags($term->field_term_teaser->value);
      }
      if (empty($description)) {
        $description = $this->getFirstWords(strip_tags($term->description->value), 137);
      }

      $term_item = array(
        "tid" => $term_id,
        'key' => 1,
        "name" => $term->label(),
        "image" => $image_data['url'],
        'caption' => $caption,
        'alt' => $image_data['alt'],
        'title' => $image_data['title'],
        'href' => $term_url,
        'description' => $description,
      );
    }

    return $term_item;
  }

In my Tags Vocabulary I created 2 custom fields (field_term_teaser and field_term_teaser_title) to store the information to Display next to the image and I have the field_service_image to store the image.

Remember to make the code as generic as possible so it can works for all ContentEntityInterface classes. For example this is the code to load the Preview Node data that it is shown inside inside my Bootstrap 4 column Node's Preview custom block:

  public function getSpecialService($nid)
  {
    $service_item = [];
    $node = $this->nodeStorage->load($nid);
    if ($node instanceof ContentEntityInterface) {
      $image_data = $this->getImageData($node, 'field_image');

      $nid = $node->id();
      $node_url = $node->toUrl()->toString();

      // Caption is the first word in the image title.
      $title_parts = explode(' ', $image_data['title']);
      $caption = $title_parts[0];

      if ($node->hasField('field_image_text_preview')) {
        $description = $node->field_image_text_preview->value;
      }

      if (empty($description) && $node->hasField('field_teaser')) {
        $description = $this->getFirstWords(strip_tags($node->field_teaser->value), 137);
      }

      $service_item = array(
        "nid" => $nid,
        'key' => 1,
        "name" => $node->label(),
        "image" => $image_data['url'],
        'caption' => $caption,
        'alt' => $image_data['alt'],
        'title' => $image_data['title'],
        'href' => $node_url,
        'description' => $description,
      );
    }

    return $service_item;
  }

In my Service Content Type I created 2 custom fields (field_term_teaser and field_image_text_preview) to store the information to Display next to the image and I have the field_image to store the image.

Both Node and Term classes implement the ContentEntityInterface Interface which means that you can create a function to load the image from both Entity from both bundles, you only need to know the name of the image custom field in my case is:

  public function getImageData(ContentEntityInterface $contentEntity, $field_name)
  {
    $image_data = [
      'url' => '/themes/maria_consulting/img/image-blank.png',
      'alt' => '',
      'title' => '',
    ];

    $field_image = FALSE;
    if ($contentEntity->hasField($field_name)) {
      /** @var \Drupal\field\Entity\FieldConfig $def */
      $def = $contentEntity->getFieldDefinition($field_name);
      if ($def->getType() == 'image') {
        /** @var \Drupal\Core\Field\FieldItemListInterface $field_image */
        $field_image = $contentEntity->get($field_name);
      }
    }

    if ($field_image && !$field_image->isEmpty()) {
      $values = $field_image->getValue();

      if (!empty($values[0])) {
        $value = $values[0];
        $image_data['alt'] = $value['alt'];
        $image_data['title'] = $value['title'];

        /** @var File $file */
        $file = $this->fileStorage->load($value['target_id']);

        $image_uri = $file->getFileUri();
        /** @var ImageStyle $image_style */
        $image_style = $this->imageStyleStorage->load('medium');
        $image_data['url'] = $image_style->buildUrl($image_uri);
      }

    }

    if (empty($image_data['title'])) {
      $image_data['title'] = $contentEntity->label();
    }

    return $image_data;
  }

This function will work on any Term and Node as long as the Entity has got a Custom Field to store the image. It returns an array with the values of the image such as width, height, alt, title, URI plus the full path of the image style medium.

1. Custom

online drupal project php coding and programming

We are specialised in Custom Module Development and more recently in custom migratation modules from Drupal 7 web site into Drupal 8.

2. Architecture

data analysis for business and finance concept

Drupal 8 Architecture is very robust, fast and Object Oriented. The Drupal Core rely on many Symfony base Components that give a great entity abstraction.

3. Mobile

phone with fresh and modern responsive design website

Drupal is ideal to build mobile web sites. I recently built a sub-theme of the Drupal Bootstrap base theme using the latest CDN Starterkit.

4. Caching

Content word on wooden cubes background digital marketing concept

Set up content types, taxonomies and blocks. Classify and group your content using taxonomies and views. Views helps to group the content..