logo

Extra Block Types (EBT) - Nuova esperienza con Layout Builder❗

Extra Block Types (EBT) - tipi di blocchi stilizzati e personalizzabili: Slideshows, Tabs, Cards, Accordion e molti altri. Impostazioni integrate per sfondo, DOM Box, plugin javascript. Vivi oggi il futuro della costruzione dei layout.

Demo moduli EBT Scarica moduli EBT

❗Extra Paragraph Types (EPT) - Nuova esperienza con Paragraphs

Extra Paragraph Types (EPT) - insieme di moduli basati su paragrafi in modo analogo.

Demo moduli EPT Scarica moduli EPT

Scorri

12.15. Servizi e Dependency Injection.

29/09/2025, by Ivan

Menu

Quando usiamo Drupal e abbiamo bisogno di utilizzare il codice di un modulo contrib o di un modulo core all’interno di un modulo custom, usiamo gli hook e i services (servizi). Abbiamo già utilizzato gli hook in questo articolo:

12.11.3. Hook per lavorare con Entity.

Vediamo ora i services. Un service è un oggetto PHP. Quindi quando create una nuova classe PHP nel vostro modulo custom, è meglio strutturarla subito come service, in modo che poi il vostro codice possa essere riutilizzato in un altro modulo nel modo standard.

Drupal raccoglie tutti i services in un oggetto PHP chiamato Service Container, quindi Drupal conserva le informazioni su tutti i servizi disponibili e utilizzati in un unico posto. Potete richiamare questo oggetto e vedere quali servizi state usando:

<?php
$container = \Drupal::getContainer();
?>

https://api.drupal.org/api/drupal/core!lib!Drupal.php/function/Drupal%3A%3AgetContainer/9.2.x

Get container

Service container

Potete lavorare con questo oggetto tramite i metodi has/get/set, ma di solito aggiungiamo i servizi al container tramite i file *.services.yml nei nostri moduli.

Vediamo l’implementazione del metodo getContainer():

<?php
public static function getContainer() {
  if (static::$container === NULL) {
    throw new ContainerNotInitializedException('\\Drupal::$container is not initialized yet. \\Drupal::setContainer() must be called with a real container.');
  }
  return static::$container;
}
?>

La variabile del Service container è definita come statica, questo significa che dopo la chiamata a index.php e fino alla fine dell’elaborazione, possiamo ottenere il valore di questa variabile in qualsiasi file durante l’esecuzione: può essere qualsiasi classe o hook in un modulo o persino un file .theme del tema.

Come usare i servizi in Drupal?

Vediamo ora come usare il Service Container in Drupal. Nell’oggetto $container sono conservati gli oggetti dei servizi, il che permette di eseguire tutta la logica necessaria per la creazione di un oggetto nel costruttore e di passarcelo già pronto per l’uso nel nostro modulo custom. Ad esempio, se abbiamo bisogno di scrivere una query SQL al database, richiamiamo semplicemente l’oggetto per lavorare con il database dal Service container, e questo oggetto userà già le credenziali dal nostro file settings.php e stabilirà la connessione con MySQL quando eseguiamo la query SQL:

$query = \Drupal::database()->select('node_field_data', 'n');
$query->addField('n', 'nid');
$query->condition('n.title', 'About Us');
$query->range(0, 1);
$nid = $query->execute()->fetchField();

Se guardate l’implementazione del metodo database(), vedrete che usiamo l’oggetto del servizio database dal Service container:

https://api.drupal.org/api/drupal/core%21lib%21Drupal.php/function/Drupal%3A%3Adatabase/9.2.x

<?php
public static function database() {
  return static::getContainer()
    ->get('database');
}
?>

In questo modo colleghiamo solo le classi necessarie al nostro codice nel momento in cui servono. Per questo usiamo l’archivio centralizzato di oggetti chiamato Service container.

Come aggiungere un servizio al Service container?

Quando creiamo un file *.services.yml, Drupal carica i servizi da questi file e memorizza gli oggetti nel Service container.

https://api.drupal.org/api/drupal/core%21modules%21syslog%21syslog.services.yml/9.2.x 

core/modules/syslog/syslog.services.yml:

services:
  logger.syslog:
    class: Drupal\syslog\Logger\SysLog
    arguments: ['@config.factory', '@logger.log_message_parser']
    tags:
      - { name: logger }

Nella variabile $container si può aggiungere un servizio tramite il metodo set(), ma di solito questo viene usato per fare mocking delle dipendenze nei test:

https://www.drupal.org/docs/automated-testing/phpunit-in-drupal/mocking-entities-and-services-with-phpunit-and-mocks

Che cos’è la Dependency Injection?

Se eseguite Code Sniffer, vi restituirà un errore dicendo che Drupal::database() deve essere corretto e che bisogna richiamare il database nel costruttore della classe in cui usiamo l’oggetto dal Service container. Quando richiamate un oggetto dal Service Container nel costruttore della classe questo si chiama Dependency Injection (DI), ad esempio:

<?php

namespace Drupal\wisenet_connect\Form;

use Drupal\Core\Database\Connection;

/**
 * Implements the WisenetConfigurationForm form controller.
 *
 * This example demonstrates a simple form with a singe text input element. We
 * extend FormBase which is the simplest form base class used in Drupal.
 *
 * @see \Drupal\Core\Form\FormBase
 */
class WisenetGetCourseForm extends FormBase {

  /**
   * Active database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;


  /**
   * Constructs a WisenetGetCourseForm object.
   *
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection to be used.
   */
  public function __construct(Connection $database) {
    $this->database = $database;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('database'),
    );
  }

 ...

  /**
   * Implements course save handler.
   *
   * Function for save course data in course content type.
   */
  public function saveCourse($courses) {
     ...
      $query = $this->database->select('node__field_course_wisenet_id', 'nc');
      $query->addField('n', 'nid');
      $query->join('node_field_data', 'n', 'nc.entity_id = n.nid');
      $query->condition('nc.field_course_wisenet_id_value', $course['CourseOfferId']);
      $query->range(0, 1);

      $nid = $query->execute()->fetchField();
     ...
  }

In questo esempio la query al database serve nel form, quindi abbiamo aggiunto un metodo aggiuntivo create(), che viene usato per creare un’istanza della classe. Il metodo create() può trovarsi in varie classi e interfacce, ma ha sempre nei parametri la variabile $container di tipo ContainerInterface. Se nel metodo create() viene richiamato un oggetto dal Service container $container->get('myservice.name'), l’oggetto restituito verrà passato al costruttore __construct() come argomento del metodo (nel nostro caso $container->get('database') e l’argomento Connection $database).

Vedremo come richiamare correttamente gli oggetti dal Service container nei costruttori dei controller (Controller), blocchi (Block), form (BaseForm), form di configurazione (ConfigForm) o classi/servizi custom nelle prossime lezioni.

Dopo aver visto come collegare e usare correttamente gli oggetti dal Service container, vedremo come creare i nostri servizi.

Vedremo anche come sovrascrivere le classi dei servizi, per usare invece di una classe di un modulo contrib la classe di un modulo custom.

Perché servono il Service container e la Dependency Injection?

Potremmo usare i namespaces e collegare il codice dei moduli direttamente, creando gli oggetti delle classi di terze parti nel punto in cui ci servono senza nessun Service container. Ma questo causa problemi con l’aggiornamento del codice. Ad esempio, se dobbiamo sostituire la classe per l’invio delle email e questa classe è richiamata in 200 punti diversi. Per semplificare l’aggiornamento del codice creiamo un Service, invece di collegare i file direttamente. Ora se vogliamo inviare email tramite smtp e non più con PHP mail(), cambiamo semplicemente la classe del servizio e non il percorso della nuova classe in 200 posti.

La Dependency Injection risolve anche il problema della doppia chiamata di un servizio all’interno della stessa classe. Non serve richiamare due volte il Service container se usiamo lo stesso servizio in più metodi di una classe. Registriamo l’oggetto del servizio in una proprietà della nostra classe e usiamo il servizio da lì tramite $this->serviceName.

Naturalmente si può fare anche senza Service container e Dependency Injection, ma questi pattern uniformano il nostro codice, lo rendono più semplice e più facile da aggiornare.

Dove posso vedere il nome del servizio?

Nel nostro esempio abbiamo il servizio "database":

/**
 * {@inheritdoc}
 */
public static function create(ContainerInterface $container) {
  return new static(
    // Qui aggiungiamo il nome del servizio.
    $container->get('database'),
  );
}

ma se aggiungete un servizio da un modulo contrib/custom, può avere un nome come questo:

nome_modulo.nome_servizio

Potete controllare il nome del servizio nel file *.services.yml. Il nome del servizio non deve necessariamente iniziare con module_name.*, ma di solito è così. Ad esempio:

/**
 * {@inheritdoc}
 */
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
  return new static(
    $configuration,
    $plugin_id,
    $plugin_definition,
    $container->get('commerce_cart.cart_provider'),
    $container->get('entity_type.manager')
  );
}