Quand dois-je créer un service ou une fonction utilitaire?

11

J'ai eu cette question en tête toute la semaine dernière: quand devrais-je créer un service ou une fonction utilitaire?

Dans le Drupal Core, nous avons à la fois des services et des fonctions utilitaires, mais je ne trouve pas la distinction entre eux (quand j'ai besoin de créer un service ou quand j'ai besoin de créer une fonction utilitaire).

Je vais prendre comme exemple le module Modules Weight où j'ai la classe InternalFunctions .

<?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;
  }

}

Dans cette classe, j'ai deux fonctions statiques, mais ce sont toutes les deux des fonctions utilitaires ou prepareDelta()une fonction utilitaire et modulesList()devraient être dans une autre classe et avoir un service?

La seule différence que j'ai trouvée à ce moment est qu'à l'intérieur de l'espace de noms Drupal \ Component \ Utility (où vous verrez beaucoup de fonctions utilitaires) aucun d'eux n'utilise à l'intérieur d'un service et généralement un service utilise un autre service à l'intérieur (je ne le fais pas avoir revu tous les services pour valider cela).

Alors, quand devrais-je créer un service ou une fonction utilitaire?

Adrian Cid Almaguer
la source
Ken Rickard de Slack Drupal #contribute channel dit: "Je créerais un service si vous vous attendez à ce que d'autres modules (ou d'autres développeurs) interagissent avec ce code. Les méthodes utilitaires ne sont que des raccourcis privés pour vous-même."
Adrian Cid Almaguer
C'est ce que je pensais des méthodes d'utilité, mais pour les services, je pense parfois que c'est plus une considération.
Adrian Cid Almaguer
Je pense que cela dépend en grande partie de ce que fait la classe et de ce qu'elle doit avoir mis à sa disposition pour fonctionner. Prenez la Unicodeclasse au cœur - c'est une classe d'utilité statique, pas un service, car elle n'a pas de dépendances et n'a pas besoin de maintenir aucun état. S'il nécessitait une dépendance de service, le modèle DI nécessiterait qu'il soit converti en service, et vous utiliseriez l'instance singleton (ou générée en usine) à partir du conteneur lorsque vous en auriez besoin. Sinon, vous pouvez simplement usela classe statique lorsque cela a un sens.
Clive
En tant que tel, je créerais un service si vous vous attendez à ce que d'autres modules (ou d'autres développeurs) interagissent avec ce code ne me semble pas vrai. Si tel était le cas, ce Unicodeserait un service par conception, et ce n'est pas vraiment nécessaire. N'oubliez pas que les classes utilitaires peuvent tout aussi bien, plus facilement à certains égards, être utilisées par d'autres modules et d'autres codes dans votre propre module. Mais tout dépend de votre propre perspective / expérience en tant que développeur, cela dépendra essentiellement du bon sens appris à la dure
Clive
2
@NoSssweat Mais Unicode est une classe Drupal qui ne contient que des méthodes statiques! Le fait que les développeurs principaux aient choisi de l'implémenter en tant que classe statique, plutôt qu'en tant que service, signifie probablement quelque chose que vous ne pensez pas? Une classe utilitaire n'a pas vraiment besoin d'être écrasée, de par sa nature - elle fait certaines choses, si ces choses ne sont pas ce que vous voulez, vous écrivez votre propre classe à la place. Rappelez-vous que le genre de choses qui vivent traditionnellement dans les classes utilitaires sont des méthodes à usage unique, "je fais ça et rien d'autre", qui n'ont besoin d'entrée que d'un ensemble de paramètres
Clive

Réponses:

6

Dans les services à usage général. Consultez le billet de blog suivant lorsqu'il est OK d'utiliser des fonctions utilitaires statiques:

Alors n'utilisez jamais d'électricité statique?

Eh bien non, il existe des cas d'utilisation valides. La première est que si vous avez une liste d'éléments prédéfinis, la statique peut aider à réduire la mémoire car elle sera au niveau de la classe et pas dans tous les cas.

D'autres cas sont des méthodes utilitaires qui ne nécessitent pas de dépendances extérieures, par exemple une méthode slugify.

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

La méthode slug ne fait qu'un comportement très bien défini. Il est facile de prendre en compte le comportement dans les tests unitaires et je ne serais pas trop inquiet en voyant cet appel.

Ces méthodes peuvent même être testées unitairement car elles ne nécessitent pas d'initialisation.

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

(La quantité de code statique maintenant dans Drupal est due à la transition du code procédural D7, donc n'utilisez pas Drupal dans l'état actuel comme exemple.)


À propos de l'exemple de la question, le reste de la classe d'utilité (non montré dans la 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;
  }

}

appelle le propre service du module dans un wrapper statique:

\Drupal::service('modules_weight')

Cela est probablement dû au fait que la classe utilitaire est utilisée dans le code procédural hérité. Dans le code POO, cela n'est pas nécessaire, vous devez ici injecter le service directement.

4k4
la source
Merci pour la réponse, hier j'ai un peu changé le code du module (car j'ai fait quelques commits) et je crée le service modules_weight. J'ai le service car il peut être utilisé par d'autres modules et est maintenant général, vous pouvez obtenir tous les modules ou seulement la liste des modules de base. Mais dans le module, cette liste peut être affectée par la valeur à l'intérieur de la variable de configuration show_system_modules, j'ai donc créé une autre fonction qui prend cette var puis appelle les services, mais en lisant votre réponse, il semble que la fonction modulesList ne devrait pas être statique.
Adrian Cid Almaguer
Dans ce cas pensez-vous que la fonction modulesList devrait être à l'intérieur du service ou sur une autre classe avec un constructeur avec l'injection de dépendance?
Adrian Cid Almaguer
Je pense que vous pouvez le mettre dans le même service et déclarer getModulesList () comme méthode protégée.
4k4
mais le fait est que si quelqu'un veut utiliser getModuleList () ne sera pas possible et modulesList () aura accès à une variable qui seule est importante pour le module. Peut-être ajouter modulesList () comme une autre méthode et ajouter dans la description qui utilisent une variable de configuration de module?
Adrian Cid Almaguer
Je ne rendrais publique qu'une des deux méthodes. Vous pouvez peut-être définir une valeur par défaut $force = NULL, afin de savoir si quelqu'un veut remplacer la valeur de configuration par un FAUX.
4k4
8

Ken Rickard de Slack Drupal #contribute channel dit: "Je créerais un service si vous vous attendez à ce que d'autres modules (ou d'autres développeurs) interagissent avec ce code. Les méthodes utilitaires ne sont que des raccourcis privés pour vous-même."

Oui, une chose intéressante à propos des services est que n'importe qui peut les remplacer. Donc, si vous souhaitez donner à d'autres personnes la possibilité de personnaliser un morceau de code particulier. Voir Modification des services existants, fourniture de services dynamiques .

De plus, vous devriez en faire un service si vous avez besoin de faire un test Mock pour les tests unitaires PHP. Voir Services et injection de dépendances dans Drupal 8 , voir Tests unitaires de classes Drupal plus compliquées .

Q & R:

Test d'unité de service

Écriture de tests unitaires pour une méthode qui appelle des méthodes statiques d'une autre classe

Pas de sueur
la source
Merci, avez-vous des références à ajouter à votre réponse?
Adrian Cid Almaguer
@AdrianCidAlmaguer ajouté.
Pas de Sssweat
1
Merci, maintenant ces références peuvent aider les autres utilisateurs (et moi aussi) ;-)
Adrian Cid Almaguer
vous devriez créer un service si c'est quelque chose que vous vous voyez utiliser à nouveau dans différents fichiers de votre module Pourquoi un service serait-il plus utile (ou meilleure pratique) que d'avoir une classe utilitaire qui est utilisée plusieurs fois dans le même module? (pour clarifier: je ne discute pas, mais il ne semble pas y avoir de différence dans ce contexte en particulier. J'adorerais savoir pourquoi vous pensez qu'un service a plus de sens)
Clive
1
Oui, c'est intéressant @NoSssweat. L'OMI est guidée par des principes de plus haut niveau que Drupal ou Symfony. Je pense que vous appliquez une bonne conception de classe standard à votre code, puis insérez les résultats dans le cadre que vous utilisez à l'époque par la méthode qui convient à cette classe
Clive