Quando devo criar um serviço ou uma função de utilitário?

11

Eu tive essa pergunta em mente durante toda a última semana: Quando devo criar um serviço ou uma função de utilitário?

No Drupal Core, temos as funções Serviços e Utilitários, mas não consigo encontrar a distinção entre elas (quando preciso criar um serviço ou quando preciso criar uma função utilitária).

Tomarei como exemplo o módulo Modules Weight , onde tenho a 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;
  }

}

Nesta classe, eu tenho duas funções estáticas, mas elas são funções utilitárias ou prepareDelta()é uma função utilitária e modulesList()devem estar em outra classe e ter um serviço?

A única diferença que encontrei nesse momento é que, dentro do espaço de nome Drupal \ Component \ Utility (onde você verá muitas funções de utilidade), nenhuma delas usa dentro de um serviço e, geralmente, um serviço usa outros serviços (eu não revise todos os serviços para validar isso).

Então, quando devo criar um serviço ou uma função de utilitário?

Adrian Cid Almaguer
fonte
Ken Rickard, do canal #contribute do Slack Drupal, diz: "Eu criaria um serviço se você espera que outros módulos (ou outros desenvolvedores) interajam com esse código. Os métodos utilitários são apenas atalhos privados para você."
Adrian Cid Almaguer 28/11
Era o que eu pensava sobre os métodos Utility, mas para os serviços, às vezes, acho que isso é mais uma consideração.
Adrian Cid Almaguer 28/11
Eu acho que muito disso se resume ao que a classe faz e o que ela precisa ter disponibilizado para funcionar. Pegue a Unicodeclasse no núcleo - é uma classe de utilitário estático, não um serviço, porque não possui dependências e não precisa manter nenhum estado. Se exigisse uma dependência de serviço, o padrão de DI exigiria a conversão para um serviço e você usaria a instância singleton (ou gerada de fábrica) do contêiner quando necessário. Caso contrário, você pode apenas usea classe estática quando faz sentido.
Clive
Como tal, eu criaria um serviço se você espera que outros módulos (ou outros desenvolvedores) interajam com esse código não pareçam verdadeiros comigo. Se fosse esse o caso, Unicodeseria um serviço por design e realmente não precisa ser. Não se esqueça de que as classes de utilitário podem, com a mesma facilidade e facilidade em alguns aspectos, ser usadas por outros módulos e outro código em seu próprio módulo. Mas tudo isso depende da sua própria perspectiva / experiência como desenvolvedor, principalmente o senso comum aprendido da maneira mais difícil
Clive
2
@NoSssweat Mas Unicode é uma classe Drupal que contém apenas métodos estáticos! O fato de os desenvolvedores principais escolherem implementá-lo como uma classe estática, em vez de um serviço, provavelmente significa algo que você não acha? Uma classe de utilitário realmente não precisa ser substituída, por sua natureza - ela faz algumas coisas, se essas coisas não são o que você deseja, você escreve sua própria classe. Lembre-se do tipo de coisas que tradicionalmente vivem em classes de utilidade são os métodos de métodos "Eu faço isso e nada mais", que não precisam de entrada, exceto um conjunto de parâmetros.
Clive

Respostas:

6

Em serviços de uso geral. Consulte a seguinte postagem no blog quando estiver OK para usar funções de utilitário estático:

Então nunca use estática?

Bem, não, existem casos de uso válidos. Uma é que, se você tiver uma lista de itens pré-definidos, estática, poderá ajudar a reduzir a memória, pois ela estará no nível da classe e não em nenhum caso.

Outros casos são métodos utilitários que não requerem dependências externas, por exemplo, um método slugify.

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

O método slug faz apenas um comportamento muito bem definido. É fácil levar o comportamento em consideração nos testes de unidade e não ficaria muito preocupado quando vir essa ligação.

Esses métodos podem até ser testados em unidade, pois não requerem inicialização.

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

(A quantidade de código estático agora no Drupal é devido à transição do código processual D7, portanto, não use o Drupal no estado atual como exemplo.)


Sobre o exemplo da pergunta, o restante da classe de utilitário (não mostrado na pergunta)

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

}

chama o serviço próprio do módulo em um wrapper estático:

\Drupal::service('modules_weight')

Provavelmente, isso ocorre porque a classe de utilitário é usada no código processual herdado. No código OOP, isso não é necessário, aqui você deve injetar o serviço diretamente.

4k4
fonte
Obrigado pela resposta, ontem mudei um pouco o código do módulo (porque fiz alguns commits) e crio o serviço modules_weight. Eu tenho o serviço porque isso pode ser usado por outros módulos e agora é geral, você pode obter todos os módulos ou apenas a lista de módulos principais. Mas no módulo essa lista pode ser afetada pelo valor dentro da variável de configuração show_system_modules, então criei outra função que pega essa var e depois chama os serviços, mas ao ler sua resposta, parece que a função modulesList não deve ser estática.
Adrian Cid Almaguer 29/11
Nesse caso, a função modulesList deve estar dentro do serviço ou em outra classe com um construtor com injeção de dependência?
Adrian Cid Almaguer 29/11
Eu acho que você pode colocá-lo no mesmo serviço e declarar getModulesList () como um método protegido.
4k4 29/11/19
mas o ponto é que se alguém quiser usar getModuleList () não será possível e modulesList () terá acesso a uma variável que é importante apenas para o módulo. Talvez adicionando modulesList () como outro método e adicionando a descrição que usa uma variável de configuração do módulo?
Adrian Cid Almaguer 29/11
Eu apenas tornaria público um dos dois métodos. Talvez você possa definir um valor padrão $force = NULL, para saber se alguém deseja substituir o valor de configuração por um FALSE.
4k4 29/11/19
8

Ken Rickard, do canal #contribute do Slack Drupal, diz: "Eu criaria um serviço se você espera que outros módulos (ou outros desenvolvedores) interajam com esse código. Os métodos utilitários são apenas atalhos privados para você."

Sim, uma coisa interessante sobre serviços é que qualquer um pode substituí-los. Portanto, se você deseja dar a outras pessoas a capacidade de personalizar um pedaço de código específico. Consulte Alterando serviços existentes, fornecendo serviços dinâmicos .

Além disso, você deve torná-lo um serviço se precisar fazer o teste Mock para o teste de unidade PHP. Consulte Serviços e injeção de dependência no Drupal 8 , consulte Unidade de teste de classes Drupal mais complicadas .

Perguntas e Respostas:

Teste de unidade de serviço

Escrevendo testes de unidade para um método que chama métodos estáticos de outra classe

Sem suor
fonte
Obrigado, você tem algumas referências para adicionar à sua resposta?
Adrian Cid Almaguer 28/11
@AdrianCidAlmaguer adicionou.
Não Sssweat
1
Obrigado, agora este referências podem ajudar os usuários a outros (e eu também) ;-)
Adrian Cid Almaguer
você deve criar um serviço se é algo que se vê usando novamente em arquivos diferentes do seu módulo. Por que um serviço seria mais útil (ou melhor prática) do que ter uma classe de utilitário usada várias vezes no mesmo módulo? (para esclarecer: não estou discutindo, mas não parece haver nenhuma diferença nesse contexto especificamente. Gostaria muito de saber por que você acha que um serviço faz mais sentido)
Clive
1
Sim, é interessante @NoSssweat. A IMO é guiada por princípios de nível superior ao Drupal ou Symfony. Eu acho que você aplicar boas, design padrão, classe ao seu código, e, em seguida, encaixar os resultados em qualquer quadro que você está usando no momento por qualquer método faz sentido para essa classe
Clive