Utilizzo della promozione delle proprietà del costruttore PHP nei moduli personalizzati di Drupal
PHP 8 ha introdotto la promozione delle proprietà del costruttore, una funzionalità che semplifica la definizione e l'assegnazione delle proprietà di classe permettendo di dichiararle e inizializzarle direttamente nella firma del costruttore. Questo tutorial dimostra come utilizzare la promozione delle proprietà del costruttore nei moduli personalizzati Drupal (che richiedono PHP 8.0+), in particolare per semplificare l’iniezione delle dipendenze nei servizi e nei controller. Confronteremo il pattern tradizionale di Drupal (utilizzato in PHP 7 e nelle prime versioni di Drupal 9) con l'approccio moderno di PHP 8+, usando esempi completi di codice per entrambi. Alla fine, vedrai come questa sintassi moderna riduce il codice ripetitivo, rende il codice più chiaro e si allinea alle migliori pratiche attuali.
Drupal 10 (che richiede PHP 8.1+) ha iniziato ad adottare queste funzionalità moderne di PHP nel core, quindi gli sviluppatori di moduli personalizzati sono incoraggiati a fare lo stesso. Iniziamo esaminando il pattern tradizionale di iniezione delle dipendenze in Drupal, quindi lo rifattoreremo usando la promozione delle proprietà del costruttore.
Iniezione delle dipendenze tradizionale in Drupal (Pre-PHP 8)
Nei servizi e controller di Drupal, il pattern tradizionale per l’iniezione delle dipendenze prevede tre passaggi:
- 
	Dichiarare ogni dipendenza come proprietà della classe (di solito protected) con il relativo docblock.
- 
	Tipizzare ogni dipendenza nei parametri del costruttore e assegnarle alle proprietà della classe all’interno del costruttore. 
- 
	Per i controller (e alcune classi plugin), implementare un metodo statico create(ContainerInterface $container)per recuperare i servizi dal container dei servizi di Drupal e istanziare la classe.
Questo comporta una notevole quantità di codice boilerplate. Ad esempio, considera un semplice servizio personalizzato che ha bisogno della configuration factory e di un logger factory. Tradizionalmente, scriveresti:
Esempio di classe servizio tradizionale
<?php
namespace Drupal\example;
/**
 * Servizio di esempio che registra il nome del sito.
 */
class ExampleService {
  /**
   * Il servizio configuration factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;
  /**
   * Il servizio logger channel factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $loggerFactory;
  /**
   * Costruisce un oggetto ExampleService.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   La configuration factory.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   Il logger channel factory.
   */
  public function __construct(ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory) {
    $this->configFactory = $config_factory;
    $this->loggerFactory = $logger_factory;
  }
  /**
   * Registra il nome del sito come azione di esempio.
   */
  public function logSiteName(): void {
    $site_name = $this->configFactory->get('system.site')->get('name');
    $this->loggerFactory->get('example')->info('Site name: ' . $site_name);
  }
}
In questo esempio, dichiariamo due proprietà $configFactory e $loggerFactory e le assegniamo nel costruttore. Il servizio corrispondente deve anche essere definito nel file services YAML del modulo (con i servizi necessari come argomenti), ad esempio:
# example.services.yml
services:
  example.example_service:
    class: Drupal\example\ExampleService
    arguments:
      - '@config.factory'
      - '@logger.factory'
Quando Drupal istanzia questo servizio, passerà gli argomenti configurati al costruttore nell’ordine elencato.
Esempio di classe controller tradizionale
Anche i controller di Drupal possono usare l’iniezione delle dipendenze. Tipicamente, una classe controller estende ControllerBase (per metodi di utilità) e implementa l'iniezione tramite il metodo create(). Questo metodo è una factory che estrae i servizi dal container e li passa al costruttore. Ad esempio:
<?php
namespace Drupal\example\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
 * Controller per le rotte Example.
 */
class ExampleController extends ControllerBase {
  /**
   * Il servizio entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;
  /**
   * Il servizio di traduzione delle stringhe.
   *
   * @var \Drupal\Core\StringTranslation\TranslationInterface
   */
  protected $stringTranslation;
  /**
   * Costruisce un oggetto ExampleController.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   L’entity type manager.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
   *   Il servizio di traduzione.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
    $this->entityTypeManager = $entity_type_manager;
    $this->stringTranslation = $string_translation;
  }
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): self {
    return new self(
      $container->get('entity_type.manager'),
      $container->get('string_translation')
    );
  }
  /**
   * Costruisce una semplice risposta per la pagina.
   */
  public function build(): array {
    $node_count = $this->entityTypeManager->getStorage('node')->getQuery()->count()->execute();
    return [
      '#markup' => $this->t('Ci sono @count nodi nel sito.', ['@count' => $node_count]),
    ];
  }
}
Utilizzare la promozione delle proprietà del costruttore (PHP 8+) in Drupal
La promozione delle proprietà del costruttore semplifica questo pattern permettendo di dichiarare e assegnare le proprietà direttamente nella firma del costruttore. In PHP 8, puoi anteporre la visibilità (e modificatori come readonly) ai parametri del costruttore, e PHP creerà e assegnerà automaticamente le proprietà. Non è più necessario dichiarare e assegnare manualmente le proprietà – PHP lo fa per te.
Si tratta di syntactic sugar: non cambia il funzionamento dell'iniezione delle dipendenze in Drupal, ma riduce il codice da scrivere. I servizi devono comunque essere registrati nel file YAML (oppure puoi lasciare che Drupal li autowire), e nei controller serve comunque il metodo create() (a meno che non registri il controller come servizio). La differenza è solo nella scrittura della classe. Il risultato è molto meno codice boilerplate, come mostrato in una issue del core Drupal dove decine di righe sono state ridotte a poche nel costruttore.
Rifattorizziamo gli esempi usando la promozione delle proprietà del costruttore.