How to create a service in Drupal

It has been a while since I didn’t post on my blog. I’m trying to get back on it these days.

In this example I will create a Drupal 8/9 service to add a custom Drush command.

The command will sanitize user passwords in the database.

I will start by creating the services file:

services:
  mymodule_drush_commands.commands:
    class: \Drupal\mymodule_drush_commands\Commands\MyCustomCommands

Here I defined the service and the corresponding class.

Now I will add the code for the class we created (folder “src/Commands”), we extend the DrushCommands class:

<?php

namespace Drupal\mymodule_drush_commands\Commands;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drush\Commands\DrushCommands;
use Drupal\Core\Site\Settings;

class MyCustomCommands extends DrushCommands {

  const DOMAIN_PATTERN = '#^(http|https):\/\/(?<name>.*\D)#';

  /**
   *
   */
  const DOMAIN_NAME = [
    'dev' => 'develop',
    'rel' => 'staging',
    'prep' => 'preprod',
  ];


  protected $database;
  protected $passwordHasher;
  protected $settings;
  protected $environment;

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

public function __construct($database, $passwordHasher, Settings $settings, EntityTypeManagerInterface $entity_type_manager) {
    $this->database = $database;
    $this->passwordHasher = $passwordHasher;
    $this->settings = $settings;
    $this->entityTypeManager = $entity_type_manager;

    }
  }

We should now inject the services we are using here (database, password, settings, entity type manager) by adding to the services yaml file as class arguments :

services:
  tra_drush_commands.commands:
    class: \Drupal\tra_drush_commands\Commands\TraCustomCommands
    arguments: ['@database', '@password', '@settings', '@entity_type.manager']
tags:
      - { name: drush.command }

I also added a tag to the service created, here is a more detailed explanation about tags.

Then I will start working on the main task which is to sanitise passwords.

First we will get the environment base url from settings (in the controller of the class we made):

$base_url = $this->settings->get('base_url', FALSE);
    if(preg_match(static::DOMAIN_PATTERN, $base_url, $matches)) {
      $this->environment = $matches['name'] === 'dev' ? 'dev' : array_search($matches['name'], static::DOMAIN_NAME);
    }

We also check if domain is matching one of our list (dev, staging,…) to be sure we are not on a production environment.

Finally we add the code for the sanitize command:

/**
   * Drush command to sanitize passwords.
   *
   * @command mymodule_commands:sanitize
   * @aliases mymodule-san
   * @usage mymodule_commands:sanitize
   */
  public function sanitize() {
    $message = 'Environment not suitable for password sanitization.';
    if ($this->environment !== false) {
      $users = $this->database->select('users_field_data', 'u')->fields('u', ['uid', 'name'])->condition('u.uid', 0, '>')->execute()->fetchAll();

      foreach ($users as $user) {
        $password = sprintf('%s@%s', strtolower(str_replace(' ', '', $user->name)), static::DOMAIN_NAME[$this->environment]);
        $hash = $this->passwordHasher->hash($password);

        $query = $this->database->update('users_field_data')
          ->fields(['pass' => $hash])
          ->condition('uid', $user->uid, '=')
          ->execute();
      }
      $message = 'User passwords sanitized.';
    }

    $this->output()->writeln($message);
  }

This code updates all user passwords with a pattern we defined : “username@enviroment”.