John
John John has been developing websites and software for over 20 years. Focusing on Drupal over the last 12 years.

Drupal 8: Creating your own services

Drupal 8: Creating your own services

In a recent project there was the need to create a new class that could be used as a service in Drupal 8 as it would contain quite a few reusable functions.

Create the following directory and file structure in your custom module.

Service directory structure

Let’s take one file at a time.

src/MyModuleCharts.php

<?php

namespace Drupal\mymodule;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Datetime\DateFormatter;

/**
 * Class MyModuleCharts.
 *
 * @package Drupal\mymodule
 */
class MyModuleCharts {

  /**
   * Logger service.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $logger;

  /**
   * Entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManager
   */
  protected $entityTypeManager;

  /**
   * Date Formatter.
   *
   * @var \Drupal\Core\Datetime\DateFormatter
   */
  protected $dateFormatter;

  /**
   * The current user account.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * MyModuleCharts constructor.
   *
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger
   *   The logger service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
   *   The date formatter service.
   */
  public function __construct(
    LoggerChannelFactoryInterface $logger,
    EntityTypeManagerInterface $entity_type_manager,
    DateFormatter $date_formatter,
    AccountInterface $account) {
      $this->logger = $logger->get('mymodule_charts');
      $this->entityTypeManager = $entity_type_manager;
      $this->dateFormatter = $date_formatter;
      $this->currentUser = $account;
  }

  /**
   * Update some chart value.
   *
   * @param object $entity
   *   The entity object.
   * @param int $value
   *   The value to set.
   *
   * @throws \Exception
   */
  public function setSomeChartValue($entity, $value) {
    // Do something.
  }

  /**
   * Calculate some value to set.
   *
   * @param int $value
   *   The value set.
   * @return int
   *   The value to set after calculation.
   */
  public function calculateSomeChartValue($value) {
    // Do something.
  }
}

This is the service class that we will have our reusable functions, these functions can then be used anywhere that we load our service. I have included some dependencies via injection just for example purposes.

mymodule.services.yml

services:
  mymodule.charts:
    class: 'Drupal\mymodule\MyModuleCharts'
    arguments: ['@logger.factory', '@entity_type.manager', '@date.formatter', '@current_user']

The services YAML file is used to let Drupal know that this class is a service and can be loaded by using \Drupal::service('mymodule.charts'); if inside a .module file or injected as a dependency if working in a Controller or other Class.

Using the service class in a .module file

To load our new service in a .module we can use the following code:

/** @var \Drupal\mymodule\MyModuleCharts $mymodule_charts_service */
$mymodule_charts_service = \Drupal::service('mymodule.charts');

In the example I have used the @var inline variable type declaration /** @var \Drupal\mymodule\MyModuleCharts $mymodule_charts_service */ this is useful if you are using PHPStorm or any other PHP IDE, as it gives you autocompletion of method names on your service class.

Service autocomplete

Using the service class in a Controller via Dependency Injection

<?php

namespace Drupal\mymodule\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\mymodule\MyModuleCharts;

/**
 * Class MyModuleController
 *
 * @package Drupal\mymodule\Controller
 */
class MyModuleController extends ControllerBase implements ContainerInjectionInterface {

  /**
   * Entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Date Formatter.
   *
   * @var \Drupal\Core\Datetime\DateFormatter
   */
  protected $dateFormatter;

  /**
   * MyModule Charts service.
   *
   * @var \Drupal\mymodule\MyModuleCharts
   */
  protected $myModuleChartsService;

  /**
   * MyModuleConstructor constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
   *   The date formatter service.
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    DateFormatter $date_formatter,
    MyModuleCharts $mymodule_charts_service) {
      $this->entityTypeManager = $entity_type_manager;
      $this->dateFormatter = $date_formatter;
      $this->myModuleChartsService = $mymodule_charts_service;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('date.formatter'),
      $container->get('mymodule.charts')
    );
  }

  /**
   * Some Controller type function goes here.
   *
   * @return array
   */
  public function someControllerTypeFunction() {
    // Do something amazing here!

    // Use the myModuleChartsService here.
    $this->myModuleChartsService->calculateSomeChartValue();

    return [];
  }
}

Please leave a comment with any feedback or if you feel I have explained something incorrectly.

comments powered by Disqus