¿Cuándo debo crear un servicio o una función de utilidad?

11

Tuve esta pregunta en mente durante toda la última semana: ¿Cuándo debería crear un servicio o una función de utilidad?

En Drupal Core tenemos funciones tanto de Servicios como de Utilidades, pero no puedo encontrar la distinción entre ellas (cuando necesito crear un servicio o cuando necesito crear una función de utilidad).

Tomaré como ejemplo el módulo Módulos Peso donde tengo la clase 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;
  }

}

En esta clase tengo dos funciones estáticas, pero ambas son funciones de utilidad o prepareDelta()es una función de utilidad y modulesList()deberían estar en otra clase y tener un servicio.

La única diferencia que encontré en este momento es que dentro del espacio de nombres Drupal \ Component \ Utility (donde verá muchas funciones de utilidad) ninguno de ellos usa dentro de un servicio y generalmente un servicio usa otros servicios dentro (no revise todos los servicios para validar esto).

Entonces, ¿cuándo debo crear un servicio o una función de utilidad?

Adrian Cid Almaguer
fuente
Ken Rickard del canal #contribute de Slack Drupal dice: "Crearía un servicio si espera que otros módulos (u otros desarrolladores) interactúen con ese código. Los métodos de utilidad son solo atajos privados para usted".
Adrian Cid Almaguer
Eso es lo que estaba pensando sobre los métodos de utilidad, pero para los servicios a veces creo que es más una consideración.
Adrian Cid Almaguer
Creo que gran parte se reduce a lo que hace la clase y a lo que necesita haber puesto a su disposición para poder operar. Tome la Unicodeclase en el núcleo: es una clase de utilidad estática, no un servicio, porque no tiene dependencias y no necesita mantener ningún estado. Si requería una dependencia de servicio, el patrón DI requeriría que se convirtiera en un servicio, y usaría la instancia singleton (o generada de fábrica) del contenedor cuando lo necesitara. De lo contrario, puede simplemente usela clase estática cuando tenga sentido.
Clive
Como tal, crearía un servicio si espera que otros módulos (u otros desarrolladores) interactúen con ese código que no me parece verdadero. Si ese fuera el caso, Unicodesería un servicio por diseño, y realmente no es necesario. No olvide que las clases de utilidades pueden ser utilizadas, más fácilmente, en algunos aspectos, por otros módulos y otro código en su propio módulo. Pero todo eso depende de su propia perspectiva / experiencia como desarrollador, en su mayoría se reducirá al sentido común aprendido de la manera difícil
Clive
2
@NoSssweat But Unicode es una clase de Drupal que contiene solo métodos estáticos. El hecho de que los desarrolladores principales eligieron implementarlo como una clase estática, en lugar de un servicio, probablemente significa algo, ¿no crees? Una clase de utilidad realmente no necesita ser sobrescrita, por su naturaleza: hace algunas cosas, si esas cosas no son lo que quieres, debes escribir tu propia clase. Recuerde que el tipo de cosas que tradicionalmente viven en las clases de utilidad son los métodos de una sola vez, "Hago esto y nada más", que no necesitan entrada excepto un conjunto de parámetros
Clive

Respuestas:

6

En general servicios de uso. Consulte la siguiente publicación de blog cuando esté bien usar funciones de utilidad estáticas:

Entonces, ¿nunca usas estática?

Pues no, hay casos de uso válidos. Una es que si tiene una lista de elementos estáticos predefinidos puede ayudar a reducir la memoria, ya que estará en el nivel de clase y no en ningún caso.

Otros casos son métodos de utilidad que no requieren dependencias externas, por ejemplo, un método slugify.

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

El método slug solo tiene un comportamiento muy bien definido. Es fácil tener en cuenta el comportamiento en las pruebas unitarias y no me preocuparía demasiado cuando vea esta llamada.

Estos métodos pueden incluso probarse en unidades, ya que no requieren inicialización.

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

(La cantidad de código estático ahora en Drupal se debe a la transición del código de procedimiento D7, así que no use Drupal en el estado actual como ejemplo).


Sobre el ejemplo de la pregunta, el resto de la clase de utilidad (no se muestra en la pregunta)

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

}

llama al servicio propio del módulo en un contenedor estático:

\Drupal::service('modules_weight')

Esto probablemente se deba a que la clase de utilidad se usa en código de procedimiento heredado. En el código OOP esto no es necesario, aquí debe inyectar el servicio directamente.

4k4
fuente
Gracias por la respuesta, ayer cambié un poco el código del módulo (porque hice algunos commits) y creé el servicio modules_weight. Tengo el servicio porque otros módulos lo pueden usar y ahora es general, puede obtener todos los módulos o solo la lista de módulos principales. Pero en el módulo, esta lista puede verse afectada por el valor dentro de la variable de configuración show_system_modules, por lo que hice otra función que toma esta var y luego llamo a los servicios, pero al leer su respuesta parece que la función modulesList no debería ser estática.
Adrian Cid Almaguer
En este caso, ¿cree que la función modulesList debe estar dentro del servicio o en otra clase con un constructor con la inyección de dependencia?
Adrian Cid Almaguer
Creo que puede ponerlo en el mismo servicio y declarar getModulesList () como método protegido.
4k4
pero el punto es que si alguien quiere usar getModuleList () no será posible y modulesList () tendrá acceso a una variable que solo es importante para el módulo. ¿Tal vez agregar modulesList () como otro método y agregar la descripción que usa una variable de configuración del módulo?
Adrian Cid Almaguer
Solo haría público uno de los dos métodos. Tal vez pueda establecer un valor predeterminado $force = NULL, para saber si alguien quiere anular el valor de configuración con un FALSO.
4k4
8

Ken Rickard del canal #contribute de Slack Drupal dice: "Crearía un servicio si espera que otros módulos (u otros desarrolladores) interactúen con ese código. Los métodos de utilidad son solo atajos privados para usted".

Sí, lo bueno de los servicios es que cualquiera puede sobrescribirlos. Entonces, si desea dar a otras personas la capacidad de personalizar un fragmento de código en particular. Consulte Alteración de servicios existentes, prestación de servicios dinámicos .

Además, debe hacer que sea un servicio si necesita hacer una prueba simulada para pruebas de unidad PHP. Consulte Servicios e inyección de dependencias en Drupal 8 , consulte Unidad de prueba de clases de Drupal más complicadas .

Q & A:

Prueba de unidad de servicio

Escribir pruebas unitarias para un método que llama a métodos estáticos de otra clase

No Sssweat
fuente
Gracias, ¿tiene algunas referencias para agregar a su respuesta?
Adrian Cid Almaguer
@AdrianCidAlmaguer agregado.
No Sssweat
1
Gracias, ahora estas referencias pueden ayudar a otros usuarios (y a mí también) ;-)
Adrian Cid Almaguer
debe crear un servicio si es algo que se ve a sí mismo usando nuevamente en diferentes archivos de su módulo ¿Por qué un servicio sería más útil (o mejor práctica) que tener una clase de utilidad que se usa varias veces en el mismo módulo? (para aclarar: no estoy discutiendo, pero no parece haber ninguna diferencia en ese contexto específicamente. Me encantaría saber por qué crees que un servicio tiene más sentido)
Clive
1
Sí, es interesante @NoSssweat. En mi opinión, se rige por principios de nivel superior que Drupal o Symfony. Creo que aplica un diseño de clase bueno y estándar a su código, y luego coloca los resultados en cualquier marco que esté usando en ese momento por cualquier método que tenga sentido para esa clase
Clive