4

Let's say I use a hook with a lot of code inside. To improve readability and reusability I'd like to use helper/private methods. However, a Drupal .module file is not a class.

The idea is to split big methods from this file into more readable and reusable smaller methods.

What is the correct way to call helper/private methods from a Drupal .module file?

  • 2
    Create a class with a public method, instantiate it in your procedural code, and then have that public method call a private method? – mradcliffe Sep 26 '18 at 15:05
  • This isn’t a Drupal question really. – Kevin Sep 26 '18 at 16:08
  • Since Drupal hook system force us to use a .module file, I thought this question would be relevant here to help Drupal users who wants to improve their code for hooks. The idea is to split big methods from this file in more readable and reusable smaller methods. May be I should have named it helper methods. Anyway, if there is some good practices to handle that, I even think that could be added in the doc for hooks. – Kwadz Sep 27 '18 at 8:54
  • 3
    @Kwadz I would agree - while the concept you're talking about (SoC) is a very generic one, Drupal does have specific things in it that you should use to solve this. Specifically, create a service class, put the functionality in there, and consume it in the thinnest way possible in your hook. Then you can test the service class separately, and your hook is just a dumb proxy for the information it receives (which is arguably what you want) – Clive Sep 27 '18 at 9:28
6

I always follow Symfony standards when it comes to helper functions. I normally have one module for developer-central stuff where I add a class under src/Utils for that and then call it everywhere I need it. Also in *.module or *.theme files.

web/modules/custom/my_module/src/Utils/MyHelperFunctions.php

namespace Drupal\my_module\Utils;

/**
 * Class MyHelperFunctions.
 */
class MyHelperFunctions {

  /**
   * Get current multisite directory name.
   *
   * @return string
   *   The basename of the matching multisite directory.
   */
  public function getMultisiteAlias() {

    $site_path = \Drupal::service('site.path');
    $site      = explode('/', $site_path);

    return $site[1];
  }

}

web/modules/themes/custom/my_theme/my_theme.theme

use Drupal\my_module\Utils\MyHelperFunctions;

/**
 * Implements template_preprocess_html().
 */
function my_theme_preprocess_html(&$variables) {

  // Add multisite body class.
  $site = MyHelperFunctions::getMultisiteAlias();

  $variables['attributes']['class'][] = 'site-' . $site;
}

@NoSssweat suggested that the more Drupalitically correct way to do this would be to create a service to let that method be called by other modules via dependency injection as well.

web/modules/custom/my_module/my_module.services.yml

services:
  my_module.helper_functions:
    class: Drupal\my_module\Utils\MyHelperFunctions

And then you wouldn't need to use the namespace anymore, but just can call the service wherever you want:

\Drupal::service('my_module.helper_functions')->getMultisiteAlias();

Read more: When should I create a service or a utility function?

|improve this answer|||||
  • 1
    In my opinion this looks like OOP only on the surface, but this is actually just a global function. – ssibal Sep 27 '18 at 11:00
  • Am not friend of helper functions, but when am in need, I go for traits instead. – ssibal Sep 27 '18 at 11:20
  • 1
    This is fine, just know that the more Drupalitically correct way, more so when you know or think other modules will use it, is to create/define a service. Then the other module would call that service. – No Sssweat Oct 2 '18 at 19:57
  • @NoSssweat – Answer updated. Did you mean it like that? (I didn't know how/why to use services before you mentioned that. Edits welcome.) – leymannx Oct 4 '18 at 9:49
  • @leymannx excellent, too bad I can't up vote you again, lol. – No Sssweat Oct 5 '18 at 1:37
2

For people who prefer to use OOP, instead of using the traditional Drupal hook system, we can use the event system using Hook Event Dispatcher module which dispatches events for several Drupal core hooks.

May be all hooks will be replaced in Drupal as discussed here and here.

|improve this answer|||||
-1

Kwadz answer is the most cleanest so far, but here is another solution: In our project we create classes for hooks:

/**
 * Implementation of hook xyz.
 */
function my_module_some_hook($arguments) {
  MyHookClass::create(\Drupal::getContainer())->myMethod($arguments);
}

In your class you just need to implement ContinaterInjectionInterface, and you are good to go. You can do anything in OO now, nevertheless you can also write unit tests for it if needed.

|improve this answer|||||
  • 1
    Would you be able to improve your answer by providing the implementation of MyHookClass with some explanations? – Kwadz Sep 27 '18 at 12:03
  • This is the wrong method to instantiate a service. – kiamlaluno Dec 20 '18 at 12:30
  • 1
    And this comment does not say a lot about what would be a better method. Also why you think it's a service? It's not: The class only implements ContainerInjectionInterface. Yes it could be also instantiated with class resolver, but this is also fine in my opinion. – ssibal Jan 2 '19 at 9:53

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.