Drupal - When should I create a service or a utility function?

Ken Rickard from Slack Drupal #contribute channel says: "I would create a service if you expect other modules (or other developers) to interact with that code. Utility methods are just private shortcuts for yourself."

Yes, a cool thing about services is that anyone can overwrite them. So if you want to give other people the ability to customise a particular piece of code. See Altering existing services, providing dynamic services.

In addition, you should make it a service if you need to do Mock test for PHP Unit testing. See Services and dependency injection in Drupal 8, see Unit testing more complicated Drupal classes.

Q & A:

Service unit test

Writing unit tests for a method that calls static methods from another class


In general use services. See the following blog post when it is OK to use static utility functions:

So never use static?

Well no, there are valid use cases. One is that if you have a list of pre defined items static can help reduce memory since it will be on class level and not in any instances.

Other cases are utility methods which do not require outside dependencies, for instance a slugify method.

<?php
class Util
{
    public static function slug($string)
    {
        return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '_', $string)));
    }
}

The slug method only does a very well defined behavior. It is easy to take the behavior into account in unit tests and I would not be too worried when I see this call.

These methods can even be unit tested since they do not require initialization.

Source: https://stovepipe.systems/post/avoiding-static-in-your-code

(The amount of static code now in Drupal is because of the transition from procedural D7 code, so don't use Drupal in the current state as example.)


About the example from the question, the rest of the utility class (not shown in the question)

<?php

namespace Drupal\modules_weight\Utility;

/**
 * Provides module internal helper methods.
 *
 * @ingroup utility
 */
class InternalFunctions {

...

  /**
   * Return the modules list ordered by the modules weight.
   *
   * @param bool $force
   *   Force to show the core modules.
   *
   * @return array
   *   The modules list.
   */
  public static function modulesList($force = FALSE) {
    // If we don't force we need to check the configuration variable.
    if (!$force) {
      // Getting the config to know if we should show or not the core modules.
      $force = \Drupal::service('config.factory')->get('modules_weight.settings')->get('show_system_modules');
    }
    // Getting the modules list.
    $modules = \Drupal::service('modules_weight')->getModulesList($force);

    return $modules;
  }

}

calls the module own service in a static wrapper:

\Drupal::service('modules_weight')

This is probably because the utility class is used in legacy procedural code. In OOP code this is not necessary, here you should inject the service directly.