20 Maggio, 2014 | Di Wellnet

Come utilizzare il Service Container in Drupal 8 - Parte 1

Come utilizzare il Service Container in Drupal 8 - Parte 1

La maggiore novità introdotta in Drupal 8 è che molte delle sue funzionalità fanno uso di componenti definiti nel framework Symfony. Tra questi, quello che riveste un ruolo chiave dal punto di vista del riutilizzo del codice e delle prestazioni è il Service Container. Una moderna applicazione web scritta in PHP fa un largo uso di classi per gestire tutta la logica di business. Una singola classe può essere vista anche come un servizio a livello globale che permette di fare qualcosa: spedire mail agli utenti, prelevare dati dal database oppure ricevere una lista di tweet da Twitter. Ed è qui che entra in gioco il Service Container, un oggetto molto utile che ci permette di gestire tutti i servizi della nostra applicazione. In modo particolare aiuta ad istanziare correttamente i servizi che la compongono preoccupandosi di gestire automaticamente le dipendenze, il tutto attraverso un meccanismo chiamato Dependency Injection.

 

I servizi del core di drupal 8

In drupal 8 tutti i servizi sono definiti nel file core.service.yml all’interno della root. Si tratta di un file YAML che contiene per ogni servizio la sua rispettiva configurazione. In particolare, oltre al nome del servizio è possibile definire la classe di appartenenza (class), i servizi da cui dipende (arguments), e in aggiunta eventuali tag, calls, metodi e class factory ad esso collegati.

 

Creare un modulo Drupal 8 che usa il Service Container

 

Prima di procedere è bene ricordare che Drupal 8 si affida allo standard PSR-4 relativo ai package PHP che utilizzano i namespaces per fare l’autoload delle classi. Occorre quindi fare attenzione alla disposizione dei file all’interno delle cartelle del nostro modulo custom. Per maggiori dettagli su PSR-4 è bene dare un’occhiata qui https://drupal.org/node/2156625, dove viene spiegato come disporre correttamente il tutto.

 

Panoramica sui files di configurazione

my_module.routing.yml: contiene i settaggi relative alle rotte del sito (ovvero cosa avviene quando viene richiesto un particolare url)

my_module.services.yml: contiene i settaggi relativi ai servizi custom creati all’interno del nostro modulo.

my_module.info.yml: contiene informazioni relative al nostro modulo (analogo a quello di Drupal 6 & 7).

 

Utilizzare un servizio del core all’ interno del nostro .module

Esempio 1: Consumare un servizio all'interno di un .module

Mostrare a video l’host name del visitatore utilizzando il metodo service() fornito dalla classe Drupal. All’interno del nostro servicecontainer_example.module inseriremo l’hook user_login() che ci permette di fare qualcosa quando l’utente esegue un login. Definiamo l’oggetto Drupal sul quale invocheremo il metodo service per recuperare un servizio definito nel core (es. current_user). Successivamente sull’oggetto che rappresenta l’utente corrente chiamiamo il metodo definito nella classe AccountProxy (vedi core.services.yml) e mostriamo a video il risultato tramite un comodo drupal_set_message.

Il contenuto del nostro file servicecontainer_example.module sarà il seguente:

<?php
/**
  * Implements hook_user_login().
  *
  */
function servicecontainer_example_user_login($account) {
    $current_user = \Drupal::service(‘current_user’);
    $host_name = $current_user—>getHostname();
    drupal_set_message(t('Your host name is: @hostname',array('@hostname'=>$hostname)));
 
  }
?>

Esempio 2: Creazione di un servizio custom

Innanzitutto occorre creare la classe che andrà a rappresentare il nostro servizio. L’esempio mostra la creazione di una classe Newsletter che funge da banale servizio di Newsletter per la nostra applicazione e che estende la classe PhpMail fornita dal core di Drupal.

<?php
namespace Drupal\servicecontainer_example\Classes;
 
use Drupal\Core\Mail\Plugin\Mail\PhpMail;
 
class Newsletter extends PhpMail {
 protected $recipients;
 
  /**
   * Set array of recipients.
   *
   * @param $recipients
   */
  public function setRecipients($recipients) {
    $this->recipients = $recipients;
  }
 
  /**
   * Return array of recipients.
   *
   * @return mixed
   */
  public function getRecipients() {
    return $this->recipients;
  }
 
  /**
   * Send mail to every recipient.
   *
   */
  public function sendNewsletter($subject, $message) {
    foreach ($this->recipients as $recipient) {
      mail($recipient, $subject, $message, NULL, NULL);
    }
  }
 
}
 
?>

Successivamente andremo a definire i settaggi del servizio all’interno del file my_module.service.ymldel nostro modulo. Il modulo dell’esempio si chiama servicecontainer_example, per cui il nostro file yaml relativo ai servizi da noi definiti sarà servicecontainer_example.services.yml e conterrà il seguente codice:

services:
  newsletter:
    class: Drupal\servicecontainer_example\Classes\Newsletter

Routing e Controller

Il controller, in un architettura del tipo MVC, è l’elemento che si occupa di gestire le richieste e fornire le risposte al client. Di solito vengono inseriti i metodi (le “actions" del sito) in corrispondenza delle rotte definite nel file di routing (servicecontainer_example.routing.yml).

Ad esempio poniamo che all’ url examples/servicecontainer_example/consume_custom_service del nostro sito vogliamo far partire un servizio di newsletter, il file di routing del nostro modulo (servicecontainer_example.routing.yml) avrà il seguente codice:

servicecontainer_example.consume_custom_service:
  path: 'examples/servicecontainer_example/consume_custom_service'
  defaults:
    _content: '\Drupal\servicecontainer_example\Controller\ServiceContainerExampleController::consumeCustomService'
  requirements:
    _access: 'TRUE'

servicecontainer_example.consume_custom_service è il nome della rotta, path indica l’url, default la cartella dove si trova il controller,e subito dopo la classe del controller seguita da :: “metodo che vogliamo invocare”. Ovviamente si possono definire tutte le rotte che si vogliono.

È buona norma che il Controller estenda la classe ControllerBase, in questo modo si può e si deve fare l’override del metodo statico create; esso contiene al suo interno la direttiva new attraverso la quale è possibile passare i servizi che verranno usati nei metodi direttamente al costruttore del controller.

Il nostro controllore ServiceContainerExampleController.php avrà il seguente codice:

<?php
namespace Drupal\servicecontainer_example\Controller;
 
use Drupal\Core\Controller\ControllerBase;
use Drupal\servicecontainer_example\Classes\Newsletter;
use Symfony\Component\DependencyInjection\ContainerInterface;
 
/**
 * Class ServiceContainerExampleController
 * @package Drupal\servicecontainer_example\Controller
 */
class ServiceContainerExampleController extends ControllerBase {
 
 /**
   * @var use Drupal\servicecontainer_example\Classes\Newsletter
   */
  protected $newsletter;
 
 /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container->get('newsletter'));
  }
 
  /**
   * Construct for ServiceContainerExampleController.
   *
   * @param Newsletter $newsletter
   */
    public function __construct(Newsletter $newsletter) {
    $this->newsletter = $newsletter;
  }
}
?>

Ed ora aggiungiamo il metodo che si occuperà di consumare il nostro servizio di Newsletter. Come potete vedere dal codice qui sotto, il servizio newsletter è richiamato tramite $this in quanto viene settato direttamente nel costruttore del controller. A questo punto, è possibile invocare i metodi della classe Newsletter e quindi di impostare i destinatari e spedire infine la nostra newsletter.

<?php
 /**
   * This method consume method of a a new service (newsletter) inside this module: class Newsletter
   *
   */
  public function consumeCustomService() {
   $newsletter = $this->newsletter;
    // Set default values: recipients, subject and message by using methods of class Newsletter
    $recipients = array('recipient1@my_domain.it', 'recipient2@my_domain.com');
    $newsletter->setRecipients($recipients);
    $subject = 'Newsletter Test';
    $message = 'Hi, this is a test.';
 
    // Consume our service: launch method for send newsletter to recipients.
    $newsletter->sendNewsletter($subject, $message);
 
    // Return a confirm message
    $build = array(
      '#markup' => t('Mail sent to: @recipients',
        array(
          '@recipients' => implode(',', $recipients),
        )
      ),
    );
 
    return $build;
  }
?>

A breve verrà rilasciata la seconda parte del tutorial, nella quale vedremo altri esempi più avanzati. In particolare l’attenzione sarà rivolta alla possibiltà di creare dei servizi factory e di manipolare il contenuto del container attraverso un passo di compilazione.

**Note: il codice sopra riportato potrebbe non funzionare con il prossimo rilascio di Drupal 8.0-alpha12 (nei prossimi giorni), in quanto parte del core è in continua evoluzione, anche se non manca molto alla prima beta.

Wellnet
Wellnet

Wellnet è una nuova realtà nel panorama delle agenzie digitali italiane: 70 expertise complementari e integrate, con sedi a Milano, Torino Cuneo e Modena, frutto della fusione di tre realtà di successo preesistenti.