11

I had this question in mind along all the last week: When I should create a service or an utility function?

In the Drupal Core we have both Services and Utility functions, but I can't find the distinction between them (when I need to create a service or when I need to create an utility function).

I will take as example the Modules Weight module where I have the InternalFunctions class.

<?php

namespace Drupal\modules_weight\Utility;

class InternalFunctions {

  public static function prepareDelta($weight) {
    $delta = 100;

    $weight = (int) $weight;

    if ($weight > $delta) {
      return $weight;
    }

    if ($weight < -100) {
      return $weight * -1;
    }

    return $delta;
  }


  public static function modulesList($force = FALSE) {
    $modules = [];
    $installed_modules = system_get_info('module');

    $config_factory = \Drupal::service('config.factory');

    if ($force) {
      $show_system_modules = TRUE;
    }
    else {
modules.
      $show_system_modules = $config_factory->get('modules_weight.settings')->get('show_system_modules');
    }

    $modules_weight = $config_factory->get('core.extension')->get('module');

    foreach ($installed_modules as $filename => $module_info) {
      if (!isset($module_info['hidden']) && ($show_system_modules || $module_info['package'] != 'Core')) {
        $modules[$filename]['name'] = $module_info['name'];
        $modules[$filename]['description'] = $module_info['description'];
        $modules[$filename]['weight'] = $modules_weight[$filename];
        $modules[$filename]['package'] = $module_info['package'];
      }
    }
    uasort($modules, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);

    return $modules;
  }

}

In this class I have two static functions but they are both utility functions or prepareDelta() is an utility function and modulesList() should be in another class and have a service?

The only difference that I found at this time is that inside the namespace Drupal\Component\Utility (where you will see a lot of utility fucntions) none of them use inside a service and usually a service use another services inside (I don't have review all the services to validate this).

So, when I should create a service or an 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." – Adrian Cid Almaguer Nov 28 '17 at 18:52
  • That's what I was thinking about Utility methods, but for services sometimes I think that is more a consideration. – Adrian Cid Almaguer Nov 28 '17 at 18:54
  • I think a lot of it comes down to what the class does, and what it needs to have made available to it in order to operate. Take the Unicode class in core - that's a static utility class, not a service, because it doesn't have any dependencies, and doesn't need to maintain any state. If it required a service dependency, the DI pattern would require it to be converted to a service, and you'd use the singleton (or factory-generated) instance from the container when you needed it. Otherwise you can just use the static class it when it makes sense. – Clive Nov 28 '17 at 20:07
  • As such, I would create a service if you expect other modules (or other developers) to interact with that code doesn't ring true to me. If that were the case, Unicode would be a service by design, and it doesn't really need to be. Don't forget utility classes can just as easily, more easily in some respects, be used by other modules and other code in your own module. But that all depends on your own perspective/experience as a developer, it'll mostly come down to common sense learnt the hard way – Clive Nov 28 '17 at 20:11
  • 1
    @NoSssweat But Unicode is a Drupal class that contains only static methods! The fact that the core devs chose to implement it as a static class, rather than a service, probably means something don't you think? A utility class doesn't really need to be overwritten, by its nature - it does some things, if those things aren't what you want, you write your own class instead. Remember the sort of things that traditionally live in utility classes are one-shot, "I do this and nothing else" types of methods, that don't need input except a set of parameters – Clive Nov 28 '17 at 20:18
5

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.

  • Thanks for the answer, yesterday I changed a little the module code (because I made some commits) and I create the modules_weight service. I have the service because this can be used by others modules and is now general, you can get all the modules or only the core modules list. But in the module this list can be affected by the value inside the show_system_modules config variable, so I made another function that takes this var and then call the services, but reading your answer it seems that the modulesList function should not be static. – Adrian Cid Almaguer Nov 29 '17 at 15:14
  • In this case do you thing that the modulesList function should be inside the service or on another class with a constructor with the dependency injection? – Adrian Cid Almaguer Nov 29 '17 at 15:15
  • I think you you can put it in the same service and declare getModulesList() as a protected method. – 4k4 Nov 29 '17 at 15:52
  • but the point is that if somebody wants to use getModuleList() will not be possible and modulesList() have access to a variable that only is important for the module. Maybe adding modulesList() as another method and adding in the description that use a module config variable? – Adrian Cid Almaguer Nov 29 '17 at 16:02
  • I would only make one of the two methods public. Perhaps you can set a default value $force = NULL, so that you know if someone wants to override the config value with a FALSE. – 4k4 Nov 29 '17 at 16:17
7

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

  • Thanks, do you have some references to add to your answer? – Adrian Cid Almaguer Nov 28 '17 at 19:27
  • @AdrianCidAlmaguer added. – No Sssweat Nov 28 '17 at 19:35
  • 1
    Thanks, now this references can help others users (and me too) ;-) – Adrian Cid Almaguer Nov 28 '17 at 19:36
  • you should create a service if it's something you see yourself using again in different files of your module Why would a service be more useful (or better practice) than having a utility class which is used several times in the same module? (to clarify: I'm not arguing, but there doesn't seem to be any difference in that context specifically. I'd love to hear why you think a service makes more sense) – Clive Nov 28 '17 at 19:41
  • 1
    Yeah it's an interesting one @NoSssweat. IMO it's guided by higher-level principles than Drupal or Symfony. I think you apply good, standard, class design to your code, and then slot the results into whatever framework you're using at the time by whatever method makes sense for that class – Clive Nov 28 '17 at 20:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.