Planète

Par Artusamak
Julien Dubois

Drupal 8 : les annotations

Drupal 8 : les annotations
mer, 06/04/2016 - 09:48
Artusamak

Cet article est extrait de notre formation drupal 8 "de Drupal 7 à Drupal 8" à destination des développeurs. N'hésitez pas à nous contacter pour en savoir plus !

Une partie des hooks déclaratifs de Drupal 7 ont disparu avec Drupal 8 (les hook_quelque_chose_info() par exemple). Certains ont été remplacés par l’utilisation des fichiers YAML (hook_menu() ou hook_permission()). Pour d’autres, c’est le principe d’annotations qui a été choisi.

Une annotation est un élément de programmation permettant d’ajouter des méta-données à une structure à l'aide d'un code descriptif. Avec PHP, cela passe par l’utilisation des Docblocks. Dans Drupal 8, les annotations sont pour le moment essentiellement utilisées par les plugins, elles sont à placer juste avant la déclaration de la classe du plugin.
Voilà à quoi ressemble la déclaration d’un bloc sous Drupal 8 :

<span class="comment shell-comment token"># Plugin/Block/CustomBlock.php</span>
<span class="comment token" spellcheck="true">/**
* Declare a block.
*
* @Block(
*   id = "custom_block",
*   admin_label = @Translation("Custom block"),
* )
*/</span>

La déclaration du même block en Drupal 7 passait par un hook_block_info().

<span class="comment token" spellcheck="true">/**
* Implements hook_block_info().
*/</span>
<span class="keyword token">function</span> <span class="function token">mymodule_core_block_info</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="token variable">$blocks</span><span class="punctuation token">[</span><span class="string token">'custom_block'</span><span class="punctuation token">]</span> <span class="operator token">=</span> <span class="keyword token">array</span><span class="punctuation token">(</span>
    <span class="string token">'info'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="function token">t</span><span class="punctuation token">(</span><span class="string token">'Custom block'</span><span class="punctuation token">)</span><span class="punctuation token">,</span>
  <span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>

Bien que les annotations soient des Docblocks leur syntaxe est précise et cela pourra entraîner des erreurs si elle n’est pas valide. Une annotation est composée du nom de la classe définissant l’annotation précédé d’un @ et d’un ensemble de clés / valeurs.

Il existe quelques règles concernant les valeurs, il faut retenir que :

  • les chaînes doivent être entre guillemets : “chaînes”
  • les nombres tel quel : 42
  • les listes sont représentées par des accolades : { }
  • les booléens par : TRUE ou FALSE

Il est possible de connaître l'ensemble des propriétés d'une annotation en regardant la classe qui définie cette annotation. Par exemple, l’annotation @Block est définie dans la classe \Drupal\Core\Block\Annotation\Block se trouvant dans le fichier /core/lib/Drupal/Core/Block/Annotation/Block.php.

<span class="comment shell-comment token"># Block.php</span>
<span class="keyword token">class</span> <span class="class-name token">Block</span> <span class="keyword token">extends</span> <span class="class-name token">Plugin</span> <span class="punctuation token">{</span>
  <span class="comment token" spellcheck="true">/**
   * The plugin ID.
   *
   * @var string
   */</span>
  <span class="keyword token">public</span> <span class="token variable">$id</span><span class="punctuation token">;</span>
  <span class="comment token" spellcheck="true">/**
   * The administrative label of the block.
   *
   * @var \Drupal\Core\Annotation\Translation
   *
   * @ingroup plugin_translatable
   */</span>
  <span class="keyword token">public</span> <span class="token variable">$admin_label</span> <span class="operator token">=</span> <span class="string token">''</span><span class="punctuation token">;</span>

  <span class="comment token" spellcheck="true">/**
   * The category in the admin UI where the block will be listed.
   *
   * @var \Drupal\Core\Annotation\Translation
   *
   * @ingroup plugin_translatable
   */</span>
  <span class="keyword token">public</span> <span class="token variable">$category</span> <span class="operator token">=</span> <span class="string token">''</span><span class="punctuation token">;</span>

  <span class="comment token" spellcheck="true">/**
   * Class used to retrieve derivative definitions of the block.
   *
   * @var string
   */</span>
  <span class="keyword token">public</span> <span class="token variable">$derivative</span> <span class="operator token">=</span> <span class="string token">''</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>

On remarquera alors que chaque propriété de la classe correspond à une ligne de l'annotation pouvant être déclarée.

Par Artusamak
Julien Dubois

Drupal 8 : Hooks et événements

Drupal 8 : Hooks et événements
lun, 04/04/2016 - 10:30
Artusamak

Cet article est extrait de notre formation drupal 8 "de Drupal 7 à Drupal 8" à destination des développeurs. N'hésitez pas à nous contacter pour en savoir plus !

L’une des forces de Drupal depuis sa création est la facilité d’utilisation de son mécanisme d’extensibilité. Autrement dit, ses hooks. Implémenter un hook est très simple, il suffit de respecter une convention de nommage.

Si l’invocation de ces hooks pouvait se faire facilement dans Drupal 7, la syntaxe évolue quelque peu :

<span class="comment shell-comment token"># Cron.php</span>
<span class="keyword token">class</span> <span class="class-name token">Cron</span> <span class="keyword token">implements</span> <span class="class-name token">CronInterface</span> <span class="punctuation token">{</span>
<span class="keyword token">protected</span> <span class="token variable">$moduleHandler</span><span class="punctuation token">;</span> <span class="comment token" spellcheck="true">// The module handler service.</span>
<span class="comment token" spellcheck="true">/**
  * Invokes any cron handlers implementing hook_cron.
  */</span>
<span class="keyword token">protected</span> <span class="keyword token">function</span> <span class="function token">invokeCronHandlers</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
   <span class="comment token" spellcheck="true">// Iterate through the modules calling their cron handlers (if any):</span>
   <span class="keyword token">foreach</span> <span class="punctuation token">(</span><span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">moduleHandler</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getImplementations</span><span class="punctuation token">(</span><span class="string token">'cron'</span><span class="punctuation token">)</span> <span class="keyword token">as</span> <span class="token variable">$module</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
     <span class="comment token" spellcheck="true">// Do not let an exception thrown by one module disturb another.</span>
     <span class="keyword token">try</span> <span class="punctuation token">{</span>
       <span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">moduleHandler</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">invoke</span><span class="punctuation token">(</span><span class="token variable">$module</span><span class="punctuation token">,</span> <span class="string token">'cron'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
     <span class="punctuation token">}</span>
     <span class="keyword token">catch</span> <span class="punctuation token">(</span><span class="class-name token"><span class="punctuation token">\</span>Exception</span> <span class="token variable">$e</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
       <span class="function token">watchdog_exception</span><span class="punctuation token">(</span><span class="string token">'cron'</span><span class="punctuation token">,</span> <span class="token variable">$e</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
     <span class="punctuation token">}</span>
   <span class="punctuation token">}</span>
<span class="punctuation token">}</span>
<span class="punctuation token">}</span>

Les fonctions de module_implements(), module_invoke(), module_invoke_all() et drupal_alter(), entre autres, ont été déplacées (et renommées) dans la classe ModuleHandler. Pour les utiliser il faudra passer par le Service module_handler et appeler la méthode que vous aviez l’habitude d’appeler. Si vous ne vous rappelez plus ce qu'est un Service, je vous invite à lire le billet sur Conteneur de Services et Services.

Tous les hooks ne sont pas morts, ce sont les hooks qui permettent à d’autres modules d’étendre Drupal qui ont été renommés, l’altération d’un formulaire se passe toujours via hook_form_FORM_ID_alter(), les thèmes et modules peuvent toujours implémenter hook_preprocess_HOOK() par exemple.

Dans le chapitre suivant dédié aux annotations nous verrons qu’il y a d’autres mécanismes qui remplacent également les hook_*_info().

Hooks VS Events

Avec l’adoption de Symfony, beaucoup de choix historiques sont remis en cause. Le principe des hooks peut être implémenté en suivant plusieurs design patterns différents, seulement voilà, Symfony vient avec sa propre implémentation grâce à sa classe EventDispatcher. Lorsque l’on souhaite connaître tous les modules qui implémentent un hook, on déclenche (dispatch) un événement. Chaque module qui implémente ce hook se signale en souscrivant (subscribe) à cet événement et retourne les données appropriées.

On retrouve par exemple l’utilisation des événements dans la gestion des routes pour afficher une réponse HTML.

<span class="comment shell-comment token"># core.services.yml</span>
html_response<span class="punctuation token">.</span>subscriber<span class="punctuation token">:</span>
<span class="keyword token">class</span><span class="punctuation token">:</span> Drupal\<span class="package token">Core<span class="punctuation token">\</span>EventSubscriber<span class="punctuation token">\</span>HtmlResponseSubscriber</span>
tags<span class="punctuation token">:</span>
   <span class="operator token">-</span> <span class="punctuation token">{</span> name<span class="punctuation token">:</span> event_subscriber <span class="punctuation token">}</span>
<span class="comment shell-comment token"># HtmlResponseSubscriber.php</span>
<span class="keyword token">class</span> <span class="class-name token">HtmlResponseSubscriber</span> <span class="keyword token">implements</span> <span class="class-name token">EventSubscriberInterface</span> <span class="punctuation token">{</span>
<span class="comment token" spellcheck="true">// The HTML response attachments processor service.</span>
<span class="keyword token">protected</span> <span class="token variable">$htmlResponseAttachmentsProcessor</span><span class="punctuation token">;</span>

<span class="keyword token">public</span> <span class="keyword token">function</span> <span class="function token">__construct</span><span class="punctuation token">(</span>AttachmentsResponseProcessorInterface <span class="token variable">$html_response_attachments_processor</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
   <span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">htmlResponseAttachmentsProcessor</span> <span class="operator token">=</span> <span class="token variable">$html_response_attachments_processor</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>

<span class="keyword token">public</span> <span class="keyword token">static</span> <span class="keyword token">function</span> <span class="function token">getSubscribedEvents</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
   <span class="comment token" spellcheck="true">// Appeler la méthode onRespond lorsque l'événement KernelEvents::RESPONSE survient.</span>
   <span class="token variable">$events</span><span class="punctuation token">[</span>KernelEvents<span class="punctuation token">:</span><span class="punctuation token">:</span><span class="constant token">RESPONSE</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="punctuation token">]</span> <span class="operator token">=</span> <span class="punctuation token">[</span><span class="string token">'onRespond'</span><span class="punctuation token">]</span><span class="punctuation token">;</span>
   <span class="keyword token">return</span> <span class="token variable">$events</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>

<span class="comment token" spellcheck="true">/**
  * Processes attachments for HtmlResponse responses.
  */</span>
<span class="keyword token">public</span> <span class="keyword token">function</span> <span class="function token">onRespond</span><span class="punctuation token">(</span>FilterResponseEvent <span class="token variable">$event</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
   <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span><span class="token variable">$event</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">isMasterRequest</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
     <span class="keyword token">return</span><span class="punctuation token">;</span>
   <span class="punctuation token">}</span>

   <span class="token variable">$response</span> <span class="operator token">=</span> <span class="token variable">$event</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getResponse</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
   <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span><span class="token variable">$response</span> <span class="keyword token">instanceof</span> <span class="class-name token">HtmlResponse</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
     <span class="keyword token">return</span><span class="punctuation token">;</span>
   <span class="punctuation token">}</span>
   <span class="token variable">$event</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">setResponse</span><span class="punctuation token">(</span><span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">htmlResponseAttachmentsProcessor</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">processAttachments</span><span class="punctuation token">(</span><span class="token variable">$response</span><span class="punctuation token">)</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>
<span class="punctuation token">}</span>

On déclare un Service qui implémente l’interface EventSubscriberInterface, on y implémente la méthode getSubscribedEvents() pour indiquer à quel(s) événement(s) réagir et quelle fonction appeler lorsque l’événement survient. L’appel à ce Service se fait automagiquement uniquement si on l’a tagué comme event_subscriber.

eventdispatchercomic-fullsize.png

Par cohérence tous les hooks devraient être remplacés par les événements mais pour des questions de temps la conversion a été retardée (voir https://www.drupal.org/node/1972304). Il est fort à parier que les événements prendront le pas sur les hooks dans la prochaine version majeure de Drupal. On peut donc dire que les hooks sont morts, vive les hooks !

Par GoZ
Fabien CLEMENT

Contribuer en tant que développeur à Drupal via Drupal.org

Contribuer en tant que développeur à Drupal via Drupal.org

La contribution sur drupal.org ne se fait pas via 'pull request' comme sur github mais fonctionne encore avec des patchs. Même si quelques modules sont disponibles sur github, ils ne sont censés être que des répliques, les repos principaux et les issues devant se gérer directement sur drupal.org.

Une documentation fixe la charte de bonne utilisation de git, ce qui permet d'aider les contributeurs à apprendre à générer un patch correct et permet au mainteneur de pointer vers cette documentation si besoin.

GoZ
jeu 31/03/2016 - 19:19

Par Artusamak
Julien Dubois

Drupal 8 : L'injection de dépendance

Drupal 8 : L'injection de dépendance
jeu, 31/03/2016 - 09:08
Artusamak

Cet article est extrait de notre formation drupal 8 "de Drupal 7 à Drupal 8" à destination des développeurs. N'hésitez pas à nous contacter pour en savoir plus !

Définition

L’injection de dépendance explicite

Nous avons vu grâce au Conteneur de Services qu’il était possible de réutiliser les objets et d’interchanger leur implémentation. Toute application ayant un minimum de valeur ajoutée, il est probable que tous ces objets aient des liens entre eux. Bien souvent, on parle de dépendance(s). L’Injection de dépendance est donc un gros mot pour désigner une façon de créer les instances des objets et de lier les objets entre eux. L’Injection de dépendance est l’un des nombreux design patterns utilisés dans Drupal.

Si l’on reprend notre exemple de Service utilisé dans le chapitre précédent, nous avons vu un Service simple, prenons maintenant l’exemple d’un Service plus compliqué, qui, pour fonctionner, doit utiliser un autre Service.

L’exemple est celui du Service flood qui permet de limiter le nombre d’actions d’un utilisateur.

Voici la définition du Service, nous allons le détailler juste après.

<span class="comment shell-comment token"># core.services.yml</span>
flood<span class="punctuation token">:</span>
<span class="keyword token">class</span><span class="punctuation token">:</span> Drupal\<span class="package token">Core<span class="punctuation token">\</span>Flood<span class="punctuation token">\</span>DatabaseBackend</span>
arguments<span class="punctuation token">:</span> <span class="punctuation token">[</span><span class="string token">'@database'</span><span class="punctuation token">,</span> <span class="string token">'@request_stack'</span><span class="punctuation token">]</span>
tags<span class="punctuation token">:</span>
   <span class="operator token">-</span> <span class="punctuation token">{</span> name<span class="punctuation token">:</span> backend_overridable <span class="punctuation token">}</span>

Et voici le constructeur ainsi que la méthode register() de la classe Flood\DatabaseBackend.

<span class="comment shell-comment token"># DatabaseBackend.php</span>
<span class="keyword token">class</span> <span class="class-name token">DatabaseBackend</span> <span class="keyword token">implements</span> <span class="class-name token">FloodInterface</span> <span class="punctuation token">{</span>

<span class="comment token" spellcheck="true">/**
* The database connection used to store flood event information.
*
* @var \Drupal\Core\Database\Connection
*/</span>
<span class="keyword token">protected</span> <span class="token variable">$connection</span><span class="punctuation token">;</span>

<span class="comment token" spellcheck="true">/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/</span>
<span class="keyword token">protected</span> <span class="token variable">$requestStack</span><span class="punctuation token">;</span>

<span class="comment token" spellcheck="true">/**
* Construct the DatabaseBackend.
*
* @param \Drupal\Core\Database\Connection $connection
*   The database connection which will be used to store the flood event
*   information.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
*   The request stack used to retrieve the current request.
*/</span>
<span class="keyword token">public</span> <span class="keyword token">function</span> <span class="function token">__construct</span><span class="punctuation token">(</span>Connection <span class="token variable">$connection</span><span class="punctuation token">,</span> RequestStack <span class="token variable">$request_stack</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
<span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">connection</span> <span class="operator token">=</span> <span class="token variable">$connection</span><span class="punctuation token">;</span>
<span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">requestStack</span> <span class="operator token">=</span> <span class="token variable">$request_stack</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>

<span class="comment token" spellcheck="true">/**
* Implements Drupal\Core\Flood\FloodInterface::register().
*/</span>
<span class="keyword token">public</span> <span class="keyword token">function</span> <span class="function token">register</span><span class="punctuation token">(</span><span class="token variable">$name</span><span class="punctuation token">,</span> <span class="token variable">$window</span> <span class="operator token">=</span> <span class="number token">3600</span><span class="punctuation token">,</span> <span class="token variable">$identifier</span> <span class="operator token">=</span> <span class="keyword token">NULL</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span><span class="function token">isset</span><span class="punctuation token">(</span><span class="token variable">$identifier</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="token variable">$identifier</span> <span class="operator token">=</span> <span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">requestStack</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getCurrentRequest</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getClientIp</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
  <span class="punctuation token">}</span>
  <span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">connection</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">insert</span><span class="punctuation token">(</span><span class="string token">'flood'</span><span class="punctuation token">)</span>
    <span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">fields</span><span class="punctuation token">(</span><span class="keyword token">array</span><span class="punctuation token">(</span>
      <span class="string token">'event'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="token variable">$name</span><span class="punctuation token">,</span>
      <span class="string token">'identifier'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="token variable">$identifier</span><span class="punctuation token">,</span>
      <span class="string token">'timestamp'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="constant token">REQUEST_TIME</span><span class="punctuation token">,</span>
      <span class="string token">'expiration'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="constant token">REQUEST_TIME</span> <span class="operator token">+</span> <span class="token variable">$window</span><span class="punctuation token">,</span>
    <span class="punctuation token">)</span><span class="punctuation token">)</span>
    <span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">execute</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>
<span class="punctuation token">}</span>

Dans cet exemple la méthode register() du Service flood sauve en base de données l’événement réalisé par un utilisateur (l’utilisateur étant identifié par son IP).

On peut voir que pour sauver l’action de l’utilisateur en base de données nous avons besoin d’utiliser la connexion à la base de données et de récupérer l’IP de l’utilisateur.

Toujours dans l’esprit de garder du code facilement interchangeable, nous ne voulons pas écrire au sein d’une méthode du code qui récupérerait la connexion à la base de données directement, nous voulons que cette connexion nous soit envoyée lorsque nous instancions notre objet.
De cette façon, si la connexion se fait sur une base MySQL ou Cassandra ou est un faux objet retournant des valeurs en dur pour les tests, cela ne fait aucune différence pour nous (et il en va de même pour l’IP de l’utilisateur).

Dans le constructeur de la classe, nous récupérons la connexion à la base de données et la requête de l’utilisateur. Ces deux informations étant indispensables, on peut donc dire qu’il y a deux dépendances sur le Service flood, et ces dépendances sont injectées via le constructeur.

Lors de la définition du Service, on indique les dépendances via une arobase @ suivie du nom de la dépendance dans les arguments. Dans notre cas la base de données (@database) et la requête qui vient d’être effectuée (@request_stack).

<span class="comment shell-comment token"># core.services.yml</span>
flood<span class="punctuation token">:</span>
<span class="keyword token">class</span><span class="punctuation token">:</span> Drupal\<span class="package token">Core<span class="punctuation token">\</span>Flood<span class="punctuation token">\</span>DatabaseBackend</span>
arguments<span class="punctuation token">:</span> <span class="punctuation token">[</span><span class="string token">'@database'</span><span class="punctuation token">,</span> <span class="string token">'@request_stack'</span><span class="punctuation token">]</span>
tags<span class="punctuation token">:</span>
   <span class="operator token">-</span> <span class="punctuation token">{</span> name<span class="punctuation token">:</span> backend_overridable <span class="punctuation token">}</span>

L’Injection de dépendance se fait dans le constructeur de la classe Flood\DatabaseBackend, de cette façon :

<span class="keyword token">public</span> <span class="keyword token">function</span> <span class="function token">__construct</span><span class="punctuation token">(</span>Connection <span class="token variable">$connection</span><span class="punctuation token">,</span> RequestStack <span class="token variable">$request_stack</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
<span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">connection</span> <span class="operator token">=</span> <span class="token variable">$connection</span><span class="punctuation token">;</span>
<span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">requestStack</span> <span class="operator token">=</span> <span class="token variable">$request_stack</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>

Les deux arguments du constructeur indiquent que notre classe a besoin de ces deux objets sous peine de ne pas pouvoir fonctionner. On garde la référence à nos deux arguments en les stockant comme attributs de la classe, ce qui permet de les utiliser par la suite au sein de nos méthodes.

<span class="keyword token">public</span> <span class="keyword token">function</span> <span class="function token">register</span><span class="punctuation token">(</span><span class="token variable">$name</span><span class="punctuation token">,</span> <span class="token variable">$window</span> <span class="operator token">=</span> <span class="number token">3600</span><span class="punctuation token">,</span> <span class="token variable">$identifier</span> <span class="operator token">=</span> <span class="keyword token">NULL</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span><span class="function token">isset</span><span class="punctuation token">(</span><span class="token variable">$identifier</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="comment token" spellcheck="true">// Récupération de l'adresse IP du client depuis l'objet "requestStack".</span>
  <span class="token variable">$identifier</span> <span class="operator token">=</span> <span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">requestStack</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getCurrentRequest</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getClientIp</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
  <span class="punctuation token">}</span>
  <span class="comment token" spellcheck="true">// Utilisation de l'objet "connection" pour requêter la base de données.</span>
  <span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">connection</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">insert</span><span class="punctuation token">(</span><span class="string token">'flood'</span><span class="punctuation token">)</span>
    <span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">fields</span><span class="punctuation token">(</span><span class="keyword token">array</span><span class="punctuation token">(</span>
      <span class="string token">'event'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="token variable">$name</span><span class="punctuation token">,</span>
      <span class="string token">'identifier'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="token variable">$identifier</span><span class="punctuation token">,</span>
      <span class="string token">'timestamp'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="constant token">REQUEST_TIME</span><span class="punctuation token">,</span>
      <span class="string token">'expiration'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="constant token">REQUEST_TIME</span> <span class="operator token">+</span> <span class="token variable">$window</span><span class="punctuation token">,</span>
    <span class="punctuation token">)</span><span class="punctuation token">)</span>
    <span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">execute</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>
<span class="punctuation token">}</span>

Notre exemple nous permet d’illustrer la méthode la plus classique pour injecter des dépendances entre nos Services. Il s’agit d’une Injection de dépendance explicite par constructeur car notre objet ne peut pas fonctionner si l’on ne lui fourni pas ses dépendances. Les dépendances ne pourront pas non plus être modifiées durant la vie de l’objet (le constructeur n’étant appelé qu’une seule fois).

Il existe deux autres façons d’injecter les dépendances vers les objets, on qualifiera ces formes d’injection comme “implicite”.

L’injection de dépendance implicite

L’autre possibilité pour définir une dépendance est de passer les objets utilisés par ce que l’on appelle un setter. Il s’agit d’une méthode d’une classe qui définit (“to set” en anglais) la valeur d’un attribut. Elle est accompagnée de sa méthode inverse, le getter, qui permet de retourner la valeur de l’attribut.

Exemple avec la classe FormBase au sein de laquelle il est possible, entre autres, de définir / récupérer le chemin vers le formulaire.

<span class="comment shell-comment token"># FormBase.php</span>
<span class="keyword token">abstract</span> <span class="keyword token">class</span> <span class="class-name token">FormBase</span> <span class="keyword token">implements</span> <span class="class-name token">FormInterface</span><span class="punctuation token">,</span> ContainerInjectionInterface <span class="punctuation token">{</span>
  <span class="comment token" spellcheck="true">// The request stack.</span>
  <span class="keyword token">protected</span> <span class="token variable">$requestStack</span><span class="punctuation token">;</span> <span class="comment token" spellcheck="true">// \Symfony\Component\HttpFoundation\RequestStack.</span>
  <span class="comment token" spellcheck="true">// Gets the request object.</span>
  <span class="keyword token">protected</span> <span class="keyword token">function</span> <span class="function token">getRequest</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
    <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span><span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">requestStack</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
      <span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">requestStack</span> <span class="operator token">=</span> \<span class="package token">Drupal</span><span class="punctuation token">:</span><span class="punctuation token">:</span><span class="function token">service</span><span class="punctuation token">(</span><span class="string token">'request_stack'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
    <span class="punctuation token">}</span>
    <span class="keyword token">return</span> <span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">requestStack</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getCurrentRequest</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
  <span class="punctuation token">}</span>
  <span class="comment token" spellcheck="true">// Sets the request stack object to use.</span>
  <span class="keyword token">public</span> <span class="keyword token">function</span> <span class="function token">setRequestStack</span><span class="punctuation token">(</span>RequestStack <span class="token variable">$request_stack</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
    <span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">requestStack</span> <span class="operator token">=</span> <span class="token variable">$request_stack</span><span class="punctuation token">;</span>
    <span class="keyword token">return</span> <span class="token variable">$this</span><span class="punctuation token">;</span>
  <span class="punctuation token">}</span>
<span class="punctuation token">}</span>

Quel est l’intérêt de cette méthode comparée à l’Injection de dépendance par constructeur ?

Avec cette méthode, il devient possible d’avoir des dépendances optionnelles vers d’autres objets.
Si dans le code de votre classe il est possible de faire appel à un autre objet mais que la classe n’en a pas absolument besoin pour fonctionner, vous pouvez utilisez cette méthode pour injecter votre dépendance. Il est également possible d’utiliser cette méthode si, au cours de la vie de l’objet, la valeur de la dépendance doit changer.
Dans notre exemple, un même formulaire peut être appelé de différents endroits, on utilise donc une injection implicite pour spécifier le chemin d’où est appelé le formulaire.

Il existe une troisième méthode pour injecter des dépendances qui consiste à définir directement la valeur d’un attribut public. Nous ne détaillerons pas cette méthode car c’est une pratique peu recommandée, aucun contrôle sur les données ne pouvant être fait facilement.

L’injection de dépendances appliquée aux Services

Nous l’avons vu dans le chapitre précédent, on peut manipuler les Services via le Conteneur de Services. Dans Drupal 8, pour accéder à un Service, il va falloir passer par le Conteneur de Services.

Les choses se complexifient légèrement car, selon ce que vous implémentez, il ne sera pas possible d’accéder au Conteneur de Services de la même façon.

Cas 1 : Je développe une classe de Service

C’est le cas le plus simple qui est celui que nous avons vu précédemment avec le Service flood, vous implémentez un Service qui a des dépendances obligatoires sur d’autres Services.
Dans ce cas là, pas besoin de manipuler le conteneur directement, Drupal se charge de l’instanciation des objets pour vous, il vous suffit de déclarer le
Service et de stocker les dépendances passées au constructeur.

Déclaration du Service :

<span class="comment shell-comment token"># core.services.yml</span>
flood<span class="punctuation token">:</span>
<span class="keyword token">class</span><span class="punctuation token">:</span> Drupal\<span class="package token">Core<span class="punctuation token">\</span>Flood<span class="punctuation token">\</span>DatabaseBackend</span>
arguments<span class="punctuation token">:</span> <span class="punctuation token">[</span><span class="string token">'@database'</span><span class="punctuation token">,</span> <span class="string token">'@request_stack'</span><span class="punctuation token">]</span>
tags<span class="punctuation token">:</span>
   <span class="operator token">-</span> <span class="punctuation token">{</span> name<span class="punctuation token">:</span> backend_overridable <span class="punctuation token">}</span>

Stockage des dépendances envoyées au constructeur.

<span class="keyword token">public</span> <span class="keyword token">function</span> <span class="function token">__construct</span><span class="punctuation token">(</span>Connection <span class="token variable">$connection</span><span class="punctuation token">,</span> RequestStack <span class="token variable">$request_stack</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
<span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">connection</span> <span class="operator token">=</span> <span class="token variable">$connection</span><span class="punctuation token">;</span>
<span class="token variable">$this</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">requestStack</span> <span class="operator token">=</span> <span class="token variable">$request_stack</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>

L’utilisation du Conteneur de Services vous est transparente.

Cas 2 : J’ai besoin de passer un Service au constructeur d’une Factory

Autre cas, vous implémentez des plugins, étendez des contrôleurs d’entités ou toute autre classe faisant appel à une Factory nécessitant le Conteneur de Services.
Dans ce cas là, vous aurez à respecter le contrat des interfaces des
Factories qui implémentent l’une des méthodes create() ou createInstance().
Dans la signature de ces méthodes, vous retrouverez la présence d’un argument
$container de type \Symfony\Component\DependencyInjection\ContainerInterface.
Cet argument vous permettra alors de récupérer les
Services à transmettre au constructeur de la classe. (Nous verrons comment savoir quelle Factory appeler dans l’implémentation d’un Service de récupération des couvertures.)

Exemple de l’utilisation du Conteneur de Services.

<span class="comment shell-comment token"># CommentStorage.php</span>
<span class="keyword token">class</span> <span class="class-name token">CommentStorage</span> <span class="keyword token">extends</span> <span class="class-name token">SqlContentEntityStorage</span> <span class="keyword token">implements</span> <span class="class-name token">CommentStorageInterface</span> <span class="punctuation token">{</span>
<span class="keyword token">public</span> <span class="keyword token">static</span> <span class="keyword token">function</span> <span class="function token">createInstance</span><span class="punctuation token">(</span>ContainerInterface <span class="token variable">$container</span><span class="punctuation token">,</span> EntityTypeInterface <span class="token variable">$entity_info</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="keyword token">return</span> <span class="keyword token">new</span> <span class="class-name token">static</span><span class="punctuation token">(</span>
   <span class="token variable">$entity_info</span><span class="punctuation token">,</span>
   <span class="token variable">$container</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">get</span><span class="punctuation token">(</span><span class="string token">'database'</span><span class="punctuation token">)</span><span class="punctuation token">,</span>
   <span class="token variable">$container</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">get</span><span class="punctuation token">(</span><span class="string token">'entity.manager'</span><span class="punctuation token">)</span><span class="punctuation token">,</span>
   <span class="token variable">$container</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">get</span><span class="punctuation token">(</span><span class="string token">'current_user'</span><span class="punctuation token">)</span><span class="punctuation token">,</span>
   <span class="token variable">$container</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">get</span><span class="punctuation token">(</span><span class="string token">'cache.entity'</span><span class="punctuation token">)</span><span class="punctuation token">,</span>
   <span class="token variable">$container</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">get</span><span class="punctuation token">(</span><span class="string token">'language_manager'</span><span class="punctuation token">)</span>
<span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>
<span class="punctuation token">}</span>

Cas 3 : Le Conteneur de Services ne m’est pas directement transmis

Il se peut que vous vous retrouviez à implémenter une classe qui n’est ni un Service ni l’implémentation d’un Plugin, contrôleur d’Entité, etc. Dans ce cas là vous n’avez aucune méthode appelée par le système à laquelle est transmis le Conteneur de Services. Dans cette situation, la seule façon d’accéder à un Service est de passer par la méthode statique service() de la classe Drupal.

Exemple d’utilisation :

<span class="comment shell-comment token"># MyController.php</span>

<span class="comment token" spellcheck="true">// Récupérer le service tour depuis mon contrôleur.</span>
<span class="token variable">$tour_service</span> <span class="operator token">=</span> \<span class="package token">Drupal</span><span class="punctuation token">:</span><span class="punctuation token">:</span><span class="function token">service</span><span class="punctuation token">(</span><span class="string token">'tour'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>

C’est la solution à utiliser en dernier recours et qu’il faut tenter d’éviter aux maximum pour garder votre application découplée et donc facilement testable, refactorable.

Par Artusamak
Julien Dubois

Drupal 8 : Naviguer dans le code et comprendre les implémentations Drupal

Drupal 8 : Naviguer dans le code et comprendre les implémentations Drupal
mar, 29/03/2016 - 17:27
Artusamak

Cet article est extrait de notre formation drupal 8 "de Drupal 7 à Drupal 8" à destination des développeurs. N'hésitez pas à nous contacter pour en savoir plus !

Le plus gros challenge lorsque l’on passe d’une version majeure de Drupal à une autre est de retrouver ses repères. Qui plus est lorsqu’on les a cachés. La montée de version amène avec elle de nouveaux paradigmes qu’il faut s’approprier. Nous les avons parcourus ensemble pour que vous appreniez à les différencier et à comprendre leur fonctionnement.

Lorsque vous implémenterez une fonctionnalité, les deux questions qui vous viendront à l’esprit seront “quel modèle (pattern) dois-je implémenter ?” et “où dois-je implémenter ce modèle (pattern) ?” souvent suivies par “comment puis-je implémenter ce modèle (pattern) ?”. Avec D7 la réponse était dans la plupart des cas “il suffit d’implémenter un hook” et les fichiers d’API suffisaient comme documentation. Dans D8, les hooks ne sont plus légions et la documentation ne se trouve plus uniquement dans les fichiers d’API.

Votre expérience Drupal 8 consistera à principalement vous approprier les services existants et les types de plugins à votre disposition pour arriver à vos fins. La documentation de ces éléments se trouvera soit dans les fichiers d’API, dans la documentation des interfaces qu’implémentent les classes ou dans les classes déclarant les plugins.

Les grands types d’implémentation

Identifier un contrôleur de page

Vous cherchez à modifier un comportement sur le rendu d’une page. Le premier réflexe à adopter consiste à identifier la route qui déclare cette page. Une recherche dans les fichiers *.routing.yml sur l’attribut path devrait vous donner des informations sur le contrôleur utilisé pour rendre la page. A vous à partir de ce point de dé-construire le rendu de la page pour surcharger la partie qui convient.

Surcharger un Service

Une action que vous ferez probablement régulièrement sera la surcharge de Services. Faute de hook_alter(), le remplacement de Service vous permettra d’ajouter le comportement spécifique dont vous avez besoin de façon chirurgical. Pour trouver votre Service, une recherche se fera cette fois sur les fichiers *.services.yml. Il faudra souvent aller voir la classe qui implémente le service voire l’interface qu’elle implémente pour trouver les arguments à lui fournir et leur rôle.

Implémenter une annotation

C’est le cas lorsque vous voulez définir un plugin (pour créer un bloc, un formateur de champ.) Pour trouver les informations à fournir à l’annotation, jetez un œil à la classe définissant l’annotation. Exemple d’un bloc pour lequel la classe associée est Block/Annotation/Block :

<span class="token shell-comment comment"># Plugin/Block/CustomBlock.php</span>
<span class="token comment" spellcheck="true">/**
* Declare a block.
*
* @Block(
*   id = "custom_block",
*   admin_label = @Translation("Custom block"),
* )
*/</span>

Surcharger une entité de contenu

Si vous voulez modifier le contrôle d’accès ou le rendu d’un type d’entité de contenu, allez voir sa déclaration, vous y trouverez les handlers responsable de chacune de ces fonctionnalités au sein de l’annotation @ContentEntityType. Il est ensuite possible de les modifier via le hook_entity_type_alter().

Le découpage en classes

On pourra facilement reprocher à Drupal 8 son nombre élevé de classes mais c’est le prix à payer pour avoir une POO propre. La segmentation permet la réutilisation et le découplage fonctionnel. On veillera à utiliser une classe pour gérer la réponse à une requête (classe de contrôleur) où on préparera des données, après que le contrôle d’accès ait été fait par une autre classe avant de déléguer le rendu à une autre classe. Chacun son travail !

Par Artusamak
Julien Dubois

Drupal 8 : Conteneur de Services et Services

Drupal 8 : Conteneur de Services et Services
mar, 29/03/2016 - 16:30
Artusamak

Cet article est extrait de notre formation drupal 8 "de Drupal 7 à Drupal 8" à destination des développeurs. N'hésitez pas à nous contacter pour en savoir plus !

Les applications modernes s’appuyant sur la POO, beaucoup d’objets sont impliqués dans le cycle de vie d’une page. Afin de vous épargner la création, la réutilisation et la suppression de chacun de ces nombreux objets, Drupal 8 s’appuie sur un objet spécial de Symfony appelé Service Container qui vise à vous simplifier la manipulation de ces dits objets.
Comme son nom l’indique, le
Conteneur de Services gère des Services.

Un Service représente une fonctionnalité réutilisable à travers votre site (accès à la base de données, envoi d’un mail, traduction une chaîne, etc).
Si l’on prend l’exemple de la base de données, imaginez que vous ayez à configurer le nom d’utilisateur, le mot de passe et le nom de la base. Que vous soyez dans un environnement de production ou de développement, vous n’aurez probablement pas les mêmes valeurs. Si vous deviez saisir les nouvelles informations de connexion il vous faudrait passer par une recherche et remplacement de ces données à travers votre base de code. Cette action étant plutôt laborieuse, les
Services permettent de simplifier cette gestion. En ayant une définition centralisée et découplée, il devient plus facile de les réutiliser, de les étendre ou de les remplacer.

Exemple de l’utilisation d’une classe pour encoder quelque chose en json :

<span class="comment shell-comment token"># Controller.php</span>
<span class="keyword token">use</span> <span class="package token">Drupal<span class="punctuation token">\</span>Component<span class="punctuation token">\</span>Serialization<span class="punctuation token">\</span>Json</span><span class="punctuation token">;</span>

<span class="token variable">$json_serializer</span> <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Json</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="token variable">$json_serializer</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">encode</span><span class="punctuation token">(</span><span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">)</span><span class="punctuation token">;</span>

Conversion en Service :

<span class="comment token" spellcheck="true"># core.services.yml</span>
<span class="atrule key token">services</span><span class="punctuation token">:</span>
  <span class="atrule key token">serialization.json</span><span class="punctuation token">:</span>
    <span class="atrule key token">class</span><span class="punctuation token">:</span> Drupal\Component\Serialization\Json

Exemple d’utilisation du Service :

<span class="comment shell-comment token"># Controller.php</span>
<span class="token variable">$service</span> <span class="operator token">=</span> \<span class="package token">Drupal</span><span class="punctuation token">:</span><span class="punctuation token">:</span><span class="function token">service</span><span class="punctuation token">(</span><span class="string token">'serialization.json'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="token variable">$service</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">encode</span><span class="punctuation token">(</span><span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">)</span><span class="punctuation token">;</span>

C’est seulement lorsque l’appel au Service est fait que l’objet est créé et retourné, pas avant qu’il ne soit nécessaire. Cette instanciation tardive augmente les performances de l’application (pas de gaspillage) et surtout permet de facilement remplacer l’implémentation d’un Service, principe très important pour réaliser des tests unitaires.

Dans le quotidien de vos projets vous serez peu fréquemment amenés à créer de nouveaux Services, mais il est fort à parier que vous aurez à modifier des Services existants pour surcharger des comportements, simuler la génération de données (pour les tests notamment).

Pour cela, il vous suffit d’implémenter une classe qui étend ServiceProviderBase et qui possède une méthode alter() dans laquelle sera décrite la surcharge.

<span class="comment shell-comment token"># src/MyModuleServiceProvider.php</span>
<span class="keyword token">namespace</span> <span class="package token">Drupal<span class="punctuation token">\</span>my_module</span><span class="punctuation token">;</span>

<span class="keyword token">use</span> <span class="package token">Drupal<span class="punctuation token">\</span>Core<span class="punctuation token">\</span>DependencyInjection<span class="punctuation token">\</span>ContainerBuilder</span><span class="punctuation token">;</span>
<span class="keyword token">use</span> <span class="package token">Drupal<span class="punctuation token">\</span>Core<span class="punctuation token">\</span>DependencyInjection<span class="punctuation token">\</span>ServiceProviderBase</span><span class="punctuation token">;</span>

<span class="keyword token">class</span> <span class="class-name token">MyModuleServiceProvider</span> <span class="keyword token">extends</span> <span class="class-name token">ServiceProviderBase</span> <span class="punctuation token">{</span>
  <span class="comment token" spellcheck="true">/**
   * {@inheritdoc}
   */</span>
  <span class="keyword token">public</span> <span class="keyword token">function</span> <span class="function token">alter</span><span class="punctuation token">(</span>ContainerBuilder <span class="token variable">$container</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
    <span class="comment token" spellcheck="true">// Remplace la classe qui implémente le service "language_manager".</span>
    <span class="token variable">$definition</span> <span class="operator token">=</span> <span class="token variable">$container</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getDefinition</span><span class="punctuation token">(</span><span class="string token">'language_manager'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
    <span class="token variable">$definition</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">setClass</span><span class="punctuation token">(</span><span class="string token">'Drupal\language_test\LanguageTestManager'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
  <span class="punctuation token">}</span>
<span class="punctuation token">}</span>

Certains des Services que vous implémenterez auront besoin d’arguments pour fonctionner. Ces arguments peuvent être des paramètres. Un paramètre est une donnée variable qui peut être définie dans un fichier centralisé. Les paramètres utilisés par un Service sont entourés du symbole % pour indiquer qu’il s’agit de paramètres.

Exemple :

<span class="comment token" spellcheck="true"># module.services.yml</span>
<span class="atrule key token">services</span><span class="punctuation token">:</span>
<span class="atrule key token">twig</span><span class="punctuation token">:</span>
<span class="atrule key token">class</span><span class="punctuation token">:</span> Drupal\Core\Template\TwigEnvironment
<span class="atrule key token">arguments</span><span class="punctuation token">:</span> <span class="punctuation token">[</span><span class="string token">'@app.root'</span><span class="punctuation token">,</span> <span class="string token">'@cache.default'</span><span class="punctuation token">,</span> <span class="string token">'%twig_extension_hash%'</span><span class="punctuation token">,</span> <span class="string token">'@twig.loader'</span><span class="punctuation token">,</span> <span class="string token">'%twig.config%'</span><span class="punctuation token">]</span>

Les paramètres peuvent être définis directement dans le fichier de déclaration des services ou dans un autre fichier. Notez que si votre chaîne contient un @, il faut l’échapper en doublant l’arobase. Dans l’exemple ci-dessus vous voyez le paramètre %twig.config% dont la valeur par défaut est définie dans le fichier core.services.yml mais que vous pouvez surcharger dans votre fichier services.yml (dans sites/default).

Par flocondetoile
Adhérent

Quelques modules Drupal 8 à (re)découvrir

Bien que Drupal 8 soit encore très jeune, 4 mois après la publication de sa première version stable, les modules Drupal 8 deviennent de plus en plus nombreux et le rythme semble encore s'accélérer.  Sans doute grâce à une architecture plus robuste, un investissement conséquent de la communauté Drupal, et une adoption de Drupal 8 beaucoup plus rapide du fait d'un coeur plus complet.

Les modules à installer dès une installation fraiche de Drupal 8 deviennent de ce fait moins nombreux. Découvrons quelque uns de ces modules qui peuvent soit enrichir rapidement votre couverture fonctionnelle, soit vous faciliter l'administration de votre site Drupal 8, ou encore qui sont tout simplement indispensables.

Par admin

Compte rendu de l'assemblée générale du 23 mars 2016

Mercredi 23 mars s'est déroulé, à la maison des associations Paris III, l'assemblée générale annuelle de l'association, l'occasion de revenir sur l'année passée, d'élire un nouveau bureau et de présenter les projets de l'année à venir.

L'heure du bilan

Drupal Dev Days 2015 à Montpellier

Les drupal devs days 2015

Du 13 au 19 avril dernier, l'équipe organisatrice accueillait environ 350 personnes, sur une semaine, à Montpellier. Pour sprinter, présenter une session ou retrouver ses amis, ils étaient là, venus du monde entier. Un projet qui s'est monté pendant plus de 9 mois. C'est aussi une belle réussite culinaire :)

Une année sous le signe des communautés

Instants lors d'événements 2015

Cette année l'association a été particulièrement présente lors d'évènements divers : JDLL à Lyon, PHP Tour Luxembourg, RMLL, Drupagora, POSS (Solutions Linux), Forum PHP, JM2L, SymfonyCon Paris, ...

Ces interventions vont continuer, car l'association sera présente lors d'Agora CMS le 1er avril prochain et des JDLL le 2 et avril.

A noter aussi cette année la participation à un podcast wordpress autour des communautés.

Novembre 2015, Drupal 8 était publié et nous faisions la fête ! 5 villes ont organisé pour l'occasion une rencontre. #celebratedrupal8

Améliorations

Cette année aussi, le site s'est étoffé. Florent y avait intégré l'adhésion en ligne. Une carte des meetups en France a aussi été intégrée. Nous préparons de nouvelles mises à jour pour la suite.

Côté finances

Le bilan financier annuel est bon. L'évènement Drupal Dev Days s'est avéré positif, avec un budget initial ambitieux de plus de 50 000€. Nous avons bénéficié de dons (Drupal Dev Days Szeged, Drupal Association, Accelerate Drupal 8), ce qui nous a permis par la suite de donner à notre tours (Accelerate Drupal 8 et Drupal Dev Days Milan).

A noter toutefois que les frais de l'association sont en hausse et que pour sa stabilité l'organisation d'un évènement par an est nécessaire, quel dommage :p

Nouveau bureau et présentation des commissions

Le nouveau bureau

Petit changement dans le bureau cet année, pour le secrétaire. Vincent Maucorps laisse sa place à Florent Torregrosa, Vincent restant vice-secrétaire. Léon Cros et Anne-Sophie Picot, restent à leur poste respectif de président et trésorière.

Commissions

Nous avions déjà abordé par le passé l'idée de fédérer les actions adhérentes et de décharger le bureau. A été présenté la proposition de fonctionnement des commissions.

Soit des groupes autonomes, en charge de points précis, se relayant au bureau, pour la communication et logistique générale.

Liste rapide des commissions

Ces commissions, leurs rôles, leurs participants seront présentées sur le site dans l'espace association très prochainement.

Et maintenant ?

un drupalcamp en 2016 à Nantes

Drupal camp Nantes logo

Nous y étions en 2011, nous y revenons, le temps d'un week-end pour un camp sous le soleil du mois de Juin. Réservez vos 10, 11 et 12 juin 2016 ! Le site de l'évènement

la nouvelle communication de l'association

Logo de l'association

Nous en parlions il y a quelques semaines. Avions publié un concours de logo. L'association porte désormais de nouvelles couleurs. nous remercions tous les participants et félicitons Christophe Sadowski et Kévin Bothua pour leur logo sélectionné.

Un gros challenge de mise à jour de notre communication, nous attends donc.

les Prix Drupal France

Parce qu'il faut bien marquer le coup de temps en temps, et qu'il faut aussi profiter des bonnes occasions pour remercier les forces vives. Cette année, a été intégré le Prix Drupal France. Rien de systématique, juste l'occasion de mettre en lumière des participations.

Cette année, deux évènements ont été mis en lumière :

  • le changement de logo
  • l'organisation sans faille des Drupal Devs Days

Kévin et Christophe pour leur logo et Julien, Edouard et Anne-Sophie, pour l'organisation des DDD, ont été remerciés pour leurs participations respectives.

En page d'accueil : 
Par Artusamak
Julien Dubois

Drupal 8, rapide comme l'éclair !

Drupal 8, rapide comme l'éclair !
jeu, 24/03/2016 - 11:00
DuaelFr

Et s'il était possible d'avoir un site qui se charge instantanément ou presque avec Drupal 8 sans configuration particulière ?

Cette question taraude les développeurs depuis de nombreuses années et toutes les grandes entreprises et communautés ont déjà eu cette réflexion à propos de leurs outils et services. Certaines ont réussi à améliorer les choses par des procédés très spécifiques d'optimisation et d'autres ont créé des principes qui pourraient être appliqués à tous les projets. Aujourd'hui, l'éventail de solutions est large mais leur mise en œuvre reste souvent très complexe d'autant qu'une unique solution ne résoudra jamais tous les problèmes.

Drupal 8 ou la révolution du cache

La chose est passée relativement inaperçue côté grand public mais cela a fait grand bruit dans la communauté des développeurs Drupal et je suis persuadé que cela fera des émules dans les autres CMS. Durant la fin de son cycle de développement, lorsque l'accent a été mis sur les performances, Drupal s'est doté d'un nouveau module intégré au cœur : Dynamic Page Cache. La promesse de ce dernier était de permettre d'émuler un fonctionnement proche de celui des ESI directement dans le CMS, sans avoir besoin d'un reverse proxy (même si son usage reste possible). 

Avec Dynamic Page Cache, chaque élément significatif de la page se voit doté de métadonnées de cache qui se cumulent les unes aux autres au fur et à mesure de l'aggrégation des éléments dans la page finale pour constituer une sorte de profil de cache. Avec ce profil, le cœur est capable facilement de gérer des variantes d'un contenu en fonction de critères concrets et d'éviter au maximum d'avoir à reconstruire des élements à moins que cela ne soit nécessaire. De la même manière, ce profil de cache permet de gérer les dépendances entre les données et leur affichage pour très facilement invalider un rendu lorsque la donnée a été modifiée, sans toucher aux autres rendus constituant la même page.

Avant cela, il était fréquent de voir des pages entièrement reconstruites alors qu'une simple et minuscule donnée y avait été modifiée. Maintenant, ces ressources serveur sont économisées et on peut constater des gains de performances réellement significatifs. De même, avant cela, avoir un élément hautement dynamique sur la page, comme par exemple un bloc indiquant le nombre d'objets dans un panier d'achat, imposait de ne pas mettre en cache la page pour éviter qu'un visiteur ne voit les informations d'un autre. Désormais, ces éléments sont gérés nativement et toute la partie commune à tous les utilisateurs de la page peut être mise en cache, indépendament des portions spécifiques qui sont stockées séparément.

J'ai déjà évoqué le fonctionnement de ce système de cache en détail dans un article dans le magazine Programmez! et cela fera également l'objet d'un prochain article issu de notre formation Drupal 8 à destination des développeurs Drupal 7 donc je n'approfondirai pas ce sujet ici. Sachez cependant que le système derrière Dynamic Page Cache est à la base des deux revolutions qui suivent.

BigPipe pour les nuls

Inventée par Facebook pour améliorer les performances de leur site, cette technique s'explique très simplement mais peut-être incroyablement complexe à mettre en place. Il s'agit de fournir au visiteur d'un site une réponse la plus rapide possible, même si elle est incomplète, puis de charger les éléments restants dans un second temps. De cette façon, on améliore les performances perçues plus que les performances réelles. Le temps de chargement total de la page restera inchangé mais avec BigPipe l'utilisateur verra quelque chose apparaître sur son écran beaucoup plus rapidement. La vidéo suivante compare le rendu des deux techniques :

Si l'on reprend l'exemple d'un site e-commerce, la structure de la page sera renvoyée très rapidement car elle est commune à tous les utilisateurs et mise en cache. La partie spécifique (le bloc panier) sera chargée et affichée dans un second temps. Sans BigPipe, c'est le serveur web qui va attendre que chaque partie de la page soit générée avant d'envoyer l'ensemble au navigateur. Avec BigPipe, c'est le navigateur qui est chargé de reconstituer les divers éléments. Bien qu'il soit possible de réaliser un équivalent à l'aide de requêtes Ajax après la fin du chargement de la page, le génie de BigPipe repose sur le fait qu'il ne nécessite qu'une seule requête HTTP pour récupérer tous les éléments d'une même page, ce qui ne serait pas le cas en passant par de l'Ajax classique.

La méthode s'appuie sur la technologie Chunked Encoding disponible depuis HTTP 1.1. Celle-ci autorise le serveur à renvoyer sa réponse en plusieurs fragments de la taille qu'il souhaite. Le serveur peut donc renvoyer un premier fragment contenant la structure de la page puis ensuite renvoyer, généralement sous la forme d'un appel Javascript enrichi de données JSON, un fragment par bloc jusqu'à ce que tous soient générés.

Dans Drupal 8, grâce aux métadonnées de cache, il est aisé de savoir quelle partie de la page est suceptible de varier beaucoup et quelle autre devrait pouvoir être identique d'un utilisateur à l'autre. Avec Dynamic Page Cache, le cœur construit la page intégralement en récupérant certaines parties du cache et en générant les autres à la demande avant de renvoyer la réponse au visiteur. Avec BigPipe (intégré dans le cœur de Drupal 8.1), il utilise les mêmes métadonnées pour remplacer les parties plus longues à charger par un élément de substitution (placeholder) puis renvoie immédiatement le rendu de la page. Ensuite, une fois chaque sous-éléments prêt, il renvoie une instruction au navigateur lui indiquant de remplacer le placeholder par le code correspondant au bloc finalisé. Une fois le module activé (téléchargeable ici pour Drupal 8.0 et inclus dans le cœur de Drupal 8.1), aucune configuration supplémentaire n'est nécessaire hormis un éventuel ajustement très mineur côté serveur pour s'assurer qu'il n'utilise pas une mémoire tampon et qu'il expédie bien les éléments au navigateur dès que possible. La magie opére donc extrêmement rapidement et sans surcoût !

RefreshLess pour délivrer plus (et plus vite)

Cette fois-ci c'est de la communauté Ruby on Rails dont provient cette technique, nommée Turbolinks, qui consiste à ne recharger que les éléments changeant dans une page web lors du clic sur un lien au lieu de recharger toute la page. Elle part du principe qu'un site a généralement une structure globale identique ou presque d'une page à une autre et que celle-ci n'a pas besoin d'être rechargée par le navigateur lors du changement de page. Cela permet d'économiser les ressources à la fois du serveur et du navigateur. Le premier n'a pas à générer les parties communes de la page, le second n'a pas à reconstruire visuellement ces mêmes parties.

L'intégration des Turbolinks n'est à ce jour qu'un prototype sous la forme d'un module Drupal 8 mais son potentiel est  énorme car il se base sur les mêmes mécanismes que BigPipe pour fonctionner à savoir, vous l'aurez deviné, les métadonnées de cache ! En effet, lors du clic sur un lien interne, une requête Ajax est envoyée au serveur qui va calculer, à partir de ces métadonnées, les régions du site qui sont suceptibles d'avoir changé. Il ne génèrera et ne renverra que ces régions là sous la forme de plusieurs commandes indiquant au navigateur quelles sections remplacer. Il est très probable qu'à l'avenir ce système s'appuie aussi sur la technologie BigPipe pour renvoyer et rendre les blocs sans attendre que la génération de tous les éléments soit terminée.

Comme le montre la vidéo suivante, les premiers résultats sont tout bonnement impressionnants et très encourageants :

Conclusion

Ces deux sujets démontrent qu'une utilisation intelligente des métadonnées de cache peuvent entrainer des applications inatendues améliorant les performances de nos sites à moindre coût. Les solutions présentées ci-dessus sont activement soutenues et développées par Wim Leers pour le compte d'Acquia dont l'objectif est toujours de rendre l'écosystème Drupal plus solide et compétitif. L'aboutissement de ces techniques permettra à l'ensemble du marché de profiter de performances améliorées. Cela bénéficiera à la fois aux visiteurs mais aussi aux éditeurs des sites qui auront besoin d'une architecture serveur moins puissante et donc moins chère, sans toutefois avoir à investir la différence dans des coûts de développement.

 

 

Crédit photo © Bert Koopman

Catégorie
Par Artusamak
Julien Dubois

Drupal 8 : Point sur le monde JS

Drupal 8 : Point sur le monde JS
mer, 23/03/2016 - 10:23
Artusamak

Cet article est extrait de notre formation drupal 8 "de Drupal 7 à Drupal 8" à destination des développeurs. N'hésitez pas à nous contacter pour en savoir plus !

Le gros changement dans Drupal 8 au niveau Javascript se situe principalement dans la façon dont on va importer les fichiers Javascript.

En Drupal 7, on devait directement définir les fichiers Javascript à importer dans le fichier *.info d’un thème, d’un module ou via drupal_add_js() ou via l’attribut #attached d’un render array. Drupal 8 systématise l’usage de librairies qui existaient déjà dans Drupal 7. Cela permet de déclarer un groupe de fichiers Javascript ou CSS et de les injecter ensemble là où c’est nécessaire dans le site. Les librairies sont créées dans un fichier *.libraries.yml se plaçant à la racine du thème ou du module.

<span class="comment token" spellcheck="true"># block.libraries.yml</span>

<span class="atrule key token">drupal.block</span><span class="punctuation token">:</span>
  <span class="atrule key token">version</span><span class="punctuation token">:</span> VERSION
  <span class="atrule key token">js</span><span class="punctuation token">:</span>
    <span class="atrule key token">js/block.js</span><span class="punctuation token">:</span> <span class="punctuation token">{</span><span class="punctuation token">}</span>
  <span class="atrule key token">dependencies</span><span class="punctuation token">:</span>
    <span class="punctuation token">-</span> core/jquery
    <span class="punctuation token">-</span> core/drupal

<span class="atrule key token">drupal.block.admin</span><span class="punctuation token">:</span>
  <span class="atrule key token">version</span><span class="punctuation token">:</span> VERSION
  <span class="atrule key token">js</span><span class="punctuation token">:</span>
    <span class="atrule key token">js/block.admin.js</span><span class="punctuation token">:</span> <span class="punctuation token">{</span><span class="punctuation token">}</span>
  <span class="atrule key token">css</span><span class="punctuation token">:</span>
    <span class="atrule key token">theme</span><span class="punctuation token">:</span>
      <span class="atrule key token">css/block.admin.css</span><span class="punctuation token">:</span> <span class="punctuation token">{</span><span class="punctuation token">}</span>
  <span class="atrule key token">dependencies</span><span class="punctuation token">:</span>
    <span class="punctuation token">-</span> core/jquery
    <span class="punctuation token">-</span> core/drupal
    <span class="punctuation token">-</span> core/drupal.ajax

Un des avantages majeurs de ce nouveau système est la possibilité de définir des dépendances. Ici, lorsque la librairie drupal.block sera incluse, elle importera automatiquement jQuery et la librairie core de Drupal.

Ce que cela sous-entend et qui est une énorme évolution dans Drupal 8, c’est que jQuery ou d’autres fichiers Javascript ne sont plus importés par défaut dans les pages Drupal.
Ainsi, un Drupal standard en mode anonyme est complément libre de tout javascript. C’est une sacrée amélioration pour les performances d’affichage !

L’utilisation des librairies dans le code passe par “l’attachement” de celles-ci à l’élément où le code est nécessaire. Pour cela, nous pourrons nous appuyer sur plusieurs mécanismes en fonction de la situation. La philosophie de Drupal 8 est d’essayer de cibler spécifiquement l’élément (la page, le contenu, le bloc, la table, etc.) pour que le code ne soit chargé que lorsque cela est vraiment nécessaire.

L’attachement de la librairie utilise l’attribut #attached déjà présent en Drupal 7.

Attachement à un type d’élément

Nous avons vu précédemment l’API de rendu et la notion de type. Il est possible de cibler un type particulier pour que la librairie lui soit toujours attachée. Pour cela, il faut utiliser le hook_element_info_alter() permettant me modifier le render array par défaut de ces types.

<span class="comment shell-comment token"># module_name.module</span>
<span class="keyword token">function</span> <span class="function token">module_name_element_info_alter</span><span class="punctuation token">(</span><span class="keyword token">array</span> <span class="operator token">&amp;</span><span class="token variable">$types</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="function token">isset</span><span class="punctuation token">(</span><span class="token variable">$types</span><span class="punctuation token">[</span><span class="string token">'table'</span><span class="punctuation token">]</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
    <span class="token variable">$types</span><span class="punctuation token">[</span><span class="string token">'table'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="string token">'#attached'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="string token">'library'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="punctuation token">]</span> <span class="operator token">=</span> <span class="string token">'module_name/jslib'</span><span class="punctuation token">;</span>
  <span class="punctuation token">}</span>
<span class="punctuation token">}</span>

Attachement à une entité

Pour cibler une entité en particulier on peut passer par le hook_entity_view_alter().

<span class="comment shell-comment token"># module_name.module</span>
<span class="keyword token">function</span> <span class="function token">module_name_entity_view_alter</span><span class="punctuation token">(</span><span class="operator token">&amp;</span><span class="token variable">$build</span><span class="punctuation token">,</span> <span class="token variable">$type</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="token variable">$build</span><span class="punctuation token">[</span><span class="string token">'#entity_type'</span><span class="punctuation token">]</span> <span class="operator token">==</span> <span class="string token">'node'</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
    <span class="token variable">$build</span><span class="punctuation token">[</span><span class="string token">'#attached'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="string token">'library'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="punctuation token">]</span> <span class="operator token">=</span> <span class="string token">'module_name/jslib'</span><span class="punctuation token">;</span>
  <span class="punctuation token">}</span>
<span class="punctuation token">}</span>

Attachement à la page complète

Cette fois on vise la page complète et donc potentiellement toutes les pages via le hook_page_attachments_alter().

<span class="comment shell-comment token"># module_name.module</span>
<span class="keyword token">function</span> <span class="function token">module_name_page_attachments_alter</span><span class="punctuation token">(</span><span class="keyword token">array</span> <span class="operator token">&amp;</span><span class="token variable">$attachments</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="token variable">$attachments</span><span class="punctuation token">[</span><span class="string token">'#attached'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="string token">'library'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="punctuation token">]</span> <span class="operator token">=</span> <span class="string token">'module_name/jslib'</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>

Attachement dans un template Twig

Twig propose une méthode pour attacher directement une librairie dans un template.

<span class="other token"># stuff.html.twig:</span>
<span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">attach_library</span><span class="punctuation token">('</span><span class="property token">module_name</span><span class="operator token">/</span><span class="property token">jslib</span><span class="string token"><span class="punctuation token">'</span></span><span class="punctuation token">)</span> <span class="rd token"><span class="punctuation token">}}</span></span></span>

Écrire du javascript

L’écriture de code Javascript ne change fondamentalement pas. Drupal 8 utilise toujours jQuery (version 2.1.4), le code est toujours à inclure dans une closure et le Drupal Behaviors est toujours à utiliser pour permettre à notre code de réagir à chaque requête. En nouveauté, on voit apparaître l’utilisation fortement conseillée du use strict; facilitant l’écriture de code Javascript correct et sécurisé. Il devient par exemple impossible d’utiliser une variable non déclarée préalablement.

# file<span class="punctuation token">.</span>js
<span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>$<span class="punctuation token">,</span> Drupal<span class="punctuation token">,</span> drupalSettings<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="comment token" spellcheck="true">// closure</span>
  <span class="string token">'use strict'</span><span class="punctuation token">;</span>
  Drupal<span class="punctuation token">.</span>behaviors<span class="punctuation token">.</span>awesome <span class="operator token">=</span> <span class="punctuation token">{</span> <span class="comment token" spellcheck="true">// behaviors</span>
    attach<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>context<span class="punctuation token">)</span> <span class="punctuation token">{</span>
    <span class="punctuation token">}</span><span class="punctuation token">,</span>
    detach<span class="punctuation token">:</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>context<span class="punctuation token">,</span> trigger<span class="punctuation token">)</span> <span class="punctuation token">{</span>
    <span class="punctuation token">}</span>
  <span class="punctuation token">}</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span><span class="punctuation token">(</span>jQuery<span class="punctuation token">,</span> Drupal<span class="punctuation token">,</span> drupalSettings<span class="punctuation token">)</span><span class="punctuation token">)</span><span class="punctuation token">;</span>

Concernant la liaison entre une action et un objet du DOM, il est conseillé d’utiliser des attributs data- HTML 5 quand cela est possible ou bien de préfixer les classes utilisées par js- quand cela est possible. L’idée est de créer une frontière entre le monde CSS et JS pour limiter les régressions en cas de modification d’une classe par exemple.

Ce travail est encore en cours dans Drupal 8 : https://www.drupal.org/node/2431671

Si vous souhaitez passer des paramètres à votre script, il faut ajouter une dépendance sur core/drupalSettings à votre librairie et l’injecter dans votre closure (voir exemple ci-dessus), enfin si nécessaire on peut passer les paramètres de la façon suivante lors de l’attachement :

<span class="comment shell-comment token"># module_name.module</span>
<span class="keyword token">function</span> <span class="function token">module_name_page_attachments_alter</span><span class="punctuation token">(</span><span class="keyword token">array</span> <span class="operator token">&amp;</span><span class="token variable">$attachments</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="token variable">$attachments</span><span class="punctuation token">[</span><span class="string token">'#attached'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="string token">'library'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="punctuation token">]</span> <span class="operator token">=</span> <span class="string token">'module_name/jslib'</span><span class="punctuation token">;</span>
  <span class="token variable">$attachments</span><span class="punctuation token">[</span><span class="string token">'#attached'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="string token">'drupalSettings'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="string token">'some'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="string token">'var'</span><span class="punctuation token">]</span><span class="punctuation token">[</span><span class="string token">'name'</span><span class="punctuation token">]</span> <span class="operator token">=</span> <span class="string token">'bar'</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>

On accédera alors dans le code javascript à la variable drupalSettings.some.var.name

Drupal permet toujours de déclarer des fonctions de thème en Javascript :

# file<span class="punctuation token">.</span>js
Drupal<span class="punctuation token">.</span>theme<span class="punctuation token">.</span>hello <span class="operator token">=</span> <span class="keyword token">function</span> <span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="keyword token">var</span> markup <span class="operator token">=</span> <span class="string token">'Hello world!'</span><span class="punctuation token">;</span>
  <span class="keyword token">return</span> markup<span class="punctuation token">;</span>
<span class="punctuation token">}</span><span class="punctuation token">;</span>

Drupal<span class="punctuation token">.</span>theme<span class="punctuation token">.</span>helloYou <span class="operator token">=</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>name<span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="keyword token">var</span> markup <span class="operator token">=</span> <span class="string token">'Hello '</span> <span class="operator token">+</span> name <span class="string token">'!'</span><span class="punctuation token">;</span>
  <span class="keyword token">return</span> markup<span class="punctuation token">;</span>
<span class="punctuation token">}</span><span class="punctuation token">;</span>

<span class="keyword token">var</span> example <span class="operator token">=</span> Drupal<span class="punctuation token">.</span><span class="function token">theme</span><span class="punctuation token">(</span><span class="string token">'hello'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="comment token" spellcheck="true">// Hello world!</span>
<span class="keyword token">var</span> another_example <span class="operator token">=</span> Drupal<span class="punctuation token">.</span><span class="function token">theme</span><span class="punctuation token">(</span><span class="string token">'helloYou'</span><span class="punctuation token">,</span> <span class="string token">'Dries'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="comment token" spellcheck="true">// Hello Dries!</span>

Pensez à utiliser $.extend() pour déclarer plusieurs fonctions d’un coup :

# file<span class="punctuation token">.</span>js
$<span class="punctuation token">.</span><span class="function token">extend</span><span class="punctuation token">(</span>Drupal<span class="punctuation token">.</span>theme<span class="punctuation token">,</span> <span class="punctuation token">{</span>
  hello<span class="punctuation token">:</span> <span class="keyword token">function</span> <span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="string token">'Hello world!'</span><span class="punctuation token">;</span> <span class="punctuation token">}</span><span class="punctuation token">,</span>
  helloYou<span class="punctuation token">:</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>name<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="string token">'Hello '</span> <span class="operator token">+</span> name <span class="string token">'!'</span><span class="punctuation token">;</span> <span class="punctuation token">}</span>
<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span>

Backbone.js

Une autre nouveauté concernant le monde Javascript dans Drupal 8 est l’intégration de Backbone.js (et sa dépendance underscore.js) dans le core.

C’est une librairie permettant la création structurée d’applications javascript facilitant la mise en place d’interface riche (utilisée par Foursquare, Disqus, Trello). Elle apporte un cadre composé (entre autres) de :

  • modèles (Model) qui sont une représentation des données via un système interne de clés/valeurs qui lance des évènements spécifiques lors des changements de valeurs.
  • vues (View) qui est la partie gérant l’affichage d’un modèle, a l’écoute des évènements et qui envoi des données au modèle.
  • collections (Collection) qui proposent une API pour gérer des groupes de modèles, s’occupant de leur chargement et de leur sauvegarde.

Backbone est utilisé dans Drupal 8 pour gérer la toolbar ou la fonctionnalité d’édition en ligne rapide. Pour l’utiliser dans vos scripts il suffit de le mettre en dépendance de votre librairie.

<span class="comment token" spellcheck="true"># foo.libraries.yml</span>
<span class="atrule key token">foo-lib</span><span class="punctuation token">:</span>
  <span class="atrule key token">version</span><span class="punctuation token">:</span> VERSION
  <span class="atrule key token">js</span><span class="punctuation token">:</span>
    <span class="atrule key token">js/foo-lib.js</span><span class="punctuation token">:</span> <span class="punctuation token">{</span><span class="punctuation token">}</span>
  <span class="atrule key token">dependencies</span><span class="punctuation token">:</span>
    <span class="punctuation token">-</span> core/backbone

Ressources associées

Par GoZ
Fabien CLEMENT

Module Commerce discount gift choice

Module Commerce discount gift choice

Le module Commerce discount gift choice ajoute une offre au module discount.
Cette offre permet au commerçant de définir les produits cadeaux qu'il souhaite offrir avec cette remise. Le client pourra alors choisir un de ces produits sur son panier.

Ce type d'offre n'est disponible que sur les remises de commande.

La configuration d'un point de vue commerçant est la suivante:

GoZ
lun 21/03/2016 - 18:08

Par Artusamak
Julien Dubois

Replay du webinar « Choisir Drupal 8 » du 17 mars 2016.

Replay du webinar « Choisir Drupal 8 » du 17 mars 2016.
lun, 21/03/2016 - 09:50
Bès

La présentation de Drupal 8 et des problématiques de migration du 17 mars est en replay :

https://www.webikeo.fr/webinar/drupal-8-pourquoi-quand-et-comment/replay (il faut vous connecter pour la voir)

Le replay vous permet de revoir la présentation et de lire les questions posées ainsi que les réponses données.

Si vous avez besoin d'aide ou d'accompagnement sur Drupal, n'hésitez pas à consulter notre formation de Drupal 7 à Drupal 8 pour les développeurs dont vous retrouvez régulièrement des extraits sur ce blog.

 

Crédit photo : commons.wikimedia.org

Catégorie
Par flocondetoile
Adhérent

Gérer des librairies externes dynamiquement avec Drupal 8

Drupal 8 a revu en profondeur la gestion des librairies et nous permet de gérer très finement les ressources à charger sur les différentes pages d'un site (cf. La gestion des libraries avec Drupal 8). Néanmoins, si nous souhaitons pouvoir gérer une librairie tierce dont nous ne connaissons pas à posteriori l'emplacement, il nous est difficile de pouvoir la déclarer statiquement. Pour gérer ces différents cas de figure, il va falloir gérer dynamiquement cette librairie. Découvrons comment simplement y parvenir avec le module Libraries API.

Par Artusamak
Julien Dubois

Drupal 8 : Responsive design

Drupal 8 : Responsive design
jeu, 17/03/2016 - 17:04
Artusamak

Cet article est extrait de notre formation drupal 8 "de Drupal 7 à Drupal 8" à destination des développeurs. N'hésitez pas à nous contacter pour en savoir plus !

La pratique du design responsive est petit à petit entrain de s’imposer sur tous les projets. Le responsive vient avec la notion de Breakpoint. Drupal permet une fois le module du cœur Breakpoint activé, de créer ces derniers. Il n’existe pas d’interface pour cela, c’est à chaque module de déclarer ses Breakpoints dans un fichier <module>.breakpoints.yml.

Les Breakpoints sont composés de :

  • un label,
  • une media query,
  • un poids,
  • un coefficient multiplicateur (utile pour gérer la densité des écrans).

Vous aurez probablement besoin de les grouper dans un fichier <module>.breakpoint_group.yml (voir documentation).

Oui mais voilà… créer les Breakpoints ne vous avance pas beaucoup. Ils ne sont pas intégrés à vos CSS automagiquement, vous aurez toujours à écrire vos media queries avec les dimensions qui vous intéressent sans pouvoir les récupérer depuis votre interface Drupal.

Pour que ce module prenne de l’intérêt il faut l’activer avec le module du cœur Responsive Image qui expose un formateur de champ ainsi qu’un plugin de champ pour Views. Chacun d’eux vous laissera choisir quel style d’image utiliser en fonction du Breakpoint d’un groupe pris en compte lors de l’affichage de votre page. Idéal pour gérer des formats d’image différents (ou pas d’image du tout) selon les cas.

Cela se base sur l’utilisation du tag HTML 5 picture et de picturefill.js pour gérer la compatibilité descendante.

Si vous voulez découvrir les options avancées du module, vous pouvez (tenter) de visionner cette vidéo d’attiks. Courage, sa voix n’est pas des plus entraînantes.

Liens intéressants

Par flocondetoile
Adhérent

Une feuille de route pour Drupal 8. Et après ?

Depuis la sortie de la première version stable de Drupal 8, Drupal utilise désormais un système de gestion sémantique des versions. La mise en place d'un tel système de gestion des versions s'est accompagnée d'une définition d'une nouvelle politique de mise à jour, nous permettant de disposer d'une vision à long terme des différentes versions à venir du coeur de Drupal. Découvrons en détail le fonctionnement de ce système, et la feuille de route sous-jacente de Drupal pour les années à venir.

Par vincent59
Vincent Liefooghe

Cartographie Drupal : utilisation de AddressField + Geocoder

Dans la série Cartographie, nous avons vu auparavant quels modules utiliser pour stocker les données géographiques, et les présenter de manière simple.

Cependant, à ce stade la saisie d'une adresse n'était possible que via ses coordonnées géographiques, ou sur une carte.

Si on veut coupler la saisie d'une adresse et l'encodage, on peut utiliser 2 modules complémentaires :

  • Address Field : très utilisé dans Drupal Commerce, il permet de fournir un champ de saisie des adresses, de manière normalisée, avec un formulaire propre au pays
  • Geocoder : couplé avec Address Field, il pemettra d'encoder l'adresse pour la transformer en coordonnées géographiques.

A priori, il semble qu'il faille également installer le module Entity, sinon on a des messages d'erreur de type :

PHP Fatal error:  Call to undefined function entity_get_all_property_info() in /var/www/drupal/sites/all/modules/contrib/geocoder/geocoder.widget.inc on line 89

L'installation des modules s'effectue normalement. Par exemple avec drush :

drush dl geocoder addressfield entity
drush en geocoder addressfield entity

Address Field

Imaginons que nous voulions gérer des magasins dans plusieurs pays : France, Belgique, Suisse. Nous allons créer un type de contenu Magasin, avec un Nom, une description et y ajouter un champ Adresse :

Au niveau du Widget, on pourra alors sélectionner les pays : Belgium, France, Switzerland (pour la démo, j'utilise un site en anglais). Et définir la France comme pays par défaut.

En saisie, on a alors le formulaire qui s'affiche, avec la France comme pays par défaut :

Mais à ce stade, l'adresse est uniquement affichée de manière textuelle, car nous n'avons pas encore introduit l'encodage :

Geofield + Encodage

Pour permettre la cartographie, il faut donc un champ qui stocke les coordonnées géographiques (apporté par Geofield). Le fait d'avoir ajouté le module Geocoder permet d'ajouter une option dans les Widgets :

Lorsqu'on ajoute un champ de type Geofield, on peut alors sélectionner le widget de type "Geocode from another field". Au niveau des paramètres du champ, on laisse le défaut.

On va ensuite choisir quel champ sera utilisé comme base de géocoding, et le "moteur" à utiliser. Plusieurs choix sont possibles :

Dès lors, lorsqu'on saisit une adresse, celle-ci est envoyée au service de géocodage. Les coordonnées sont bien récupérées. Dans l'exemple ci-après, elles sont juste affichées au format WKT.

En effet, le paramétrage de l'affichage du champ est le suivant :

Conclusion

Nous avons vu dans cet article comment utiliser les modules AddressField, Geocoder, Geofield et Entity pour ajouter un champ de type adresse et un champ de type Geofield sur un type de contenu, et récupérer les coordonnées géographiques de l'adresse via les services de Geocodage.

Les données stockées dans le champ Geofield peuvent ensuite servir à l'affichage.

Si on modifie le type de format du champ en Dynamic Google Map par exemple :

L'affichage se présente sous la forme d'une carte :

Dans de prochains articles, nous verrons comment intégrer Views et d'autres affichages (Leaflet par exemple)

 

Catégorie: 


Tag: 

Par vincent59
Vincent Liefooghe

Cartographie avec Drupal

Dans un précédent article (https://www.vincentliefooghe.net/content/cartographie-rapide-avec-drupal), j'ai montré comment on pouvait ajouter rapidement et en quelques modules des fonctions de cartographie.

Dans cet article, nous allons aller un peu plus loin dans ce domaine, avec la présentation des principes et de différents modules.

Principes et composants de la cartographie

Lorsque l'on parle de cartographie, on pense souvent à la restitution des données. Google Maps a permis de démocratiser cet aspect.
Cependant, la chaîne complète repose sur un ensemble de composants :

Pour chacun de ces composants, Drupal propose souvent plusieurs solutions, parfois incompatibles. Il convient donc de faire le bon choix afin de construire une solution globale pérenne et fonctionnelle.

Nous allons voir, dans les différentes parties de cet article, quels sont les composants / modules Drupal que nous pouvons utiliser.

Et pour ceux qui sont pressés et qui veulent rapidement mettre en oeuvre une solution, ils peuvent s'orienter vers le guide du mapping rapide, qui permet d'avoir une solution complète en installant 5 modules :

  • geophp : pré-requis pour les fonctions de géolocalisations
  • ctools : pré-requis pour geofield et views
  • geofield : stockage des données
  • geofield_gmap : widget de saisie Google Maps pour le champ geofield
  • views : pour l'affichage des différents contenus sur une seule page

Stockage des données

Pour le stockage des données géographiques, c'est geofield qui est le plus utilisé et le plus supporté.
Il nécessite en pré-requis les modules geophp et ctools.

L'installation et l'activation du module via drush consiste donc en :

drush dl ctools geophp geofield

Project ctools (7.x-1.9) downloaded to sites/all/modules/ctools.                                          [success]
Project ctools contains 10 modules: term_depth, ctools_access_ruleset, views_content, page_manager, bulk_export, stylizer, ctools_ajax_sample, ctools_custom_content, ctools_plugin_example, ctools.
Project geophp (7.x-1.7) downloaded to sites/all/modules/geophp.                                          [success]
Project geofield (7.x-2.3) downloaded to sites/all/modules/geofield.                                      [success]
Project geofield contains 2 modules: geofield_map, geofield.

drush en -y ctools geophp geofield
Do you really want to continue? (y/n): y
ctools was enabled successfully.                                                                                         [ok]
ctools defines the following permissions: use ctools import
geofield was enabled successfully.                                                                                       [ok]
geophp was enabled successfully.                                                                                         [ok]

A ce stade, on a uniquement un nouveau type de champ, qui peut être ajouté à des contenus.

Saisie des coordonnées

La saisie des coordonnées peut utiliser plusieurs options :

  • saisie directe des valeurs de latitude, longitude
  • définition d'une zone à partir d'une carte
  • encodage à partir d'une adresse
  • positionnement direct du marqueur sur une carte
  • combinaison de l'adresse et du positionnement

Le plus intuitif, à mon sens, reste la saisie d'une adresse et/ou le positionnement du marqueur sur la carte, qui est intéressant lorsque la position à saisir n'est pas une adresse (par exemple un point perdu dans la campagne ou la montagne...).

Défaut avec Geofield

Par défaut, une fois que geofield est installé, on peut voir sur la capture d'écran, que seuls 4 types de widgets sont disponibles par défaut :

  • GeoJSON : données au format json
  • Well Known Text (WKT) : format WKT, un format qui permet de définir le type de d'objet et ses coordonnées
  • Latitude / Longitude : 2 champs de saisie, un pour chaque donnée
  • Bounds : saisie de plusieurs points pour définir une frontière

Dans notre exemple, nous avons choisi Latitude / Longitude. L'ajout d'un contenu de type POI demande donc la saisie de la latitude et de la longitude du point.
C'est précis, mais pas très intuitif.


Si on a activé la géolocalisation HTML 5, on peut récupérer la localisation actuelle du navigateur en cliquant sur "Find my location" .

Dans ce deuxième exemple, on a changé le widget de saisie en GeoJSON. La saisie demande donc ce format, par exemple :

{"type":"Point",
"coordinates":[3.1,50.0] }

On constate également que l'affichage (pour l'instant) n'utilise que des champs textes : WKT, GeoJSON, KML, etc. :

Avec des widgets Graphiques

Plutôt que de faire une saisie manuelle des coordonnées, on peut vouloir placer le marqueur directement sur une carte. Pour ce faire, on peut utiliser plusieurs modules, tels que :

Geofield Gmap

L'installation est simple avec drush :

drush dl geofield_gmap
drush en -y geofield_gmap

Une fois le module téléchargé et activé, on dispose d'un nouveau Widget pour notre champ Localisation : Google Map.


On peut alors entrer une adresse, ou une ville dans le champ d'adresse, mais aussi travailler directement sur la carte.

A ce stade, on a donc une saisie et un rendu graphiques de la localisation sur notre type de contenu, en ayant installé 4 modules : ctools, geophp, geofield et geofield_gmap.

Ceci fonctionne bien pour un affichage individuel, mais si on veut afficher sur une seule carte les différents points, il va falloir aller plus loin et utiliser notamment les fonctionnalités de Views

Encodage

L'encodage consiste à transformer les données saisies en latitude / longitude, afin d'alimenter le champ geofield. Jusqu'ici, notre exemple n'a pas utilisé d'encodage, et il faut donc saisir ces données manuellement, ce qui n'est pas très user friendly.

Des modules d'encodage à partir d'adresse sont souvent utilisés. Dans ce domaine, on utilise souvent le couple de modules :

  • Address pour la saisie des adresses
  • Geocoder pour l'encodage à partir de l'adresse saisie

Cette partie sera traitée dans un autre article.

Restitution / affichage

Avec le module Geofield

Par défaut avec geofield, lorsqu'on visualise le contenu, le résultat est assez austère. L'affichage utilise le format WKT (Well Known Text). Par exemple :

POINT (3.1 50)

On peut modifier cet affichage, pour voir les différents autres formats texte.

Par exemple, si on choisit GeoJSON, on a alors les coordonnées en format JSON :

{"type":"Point","coordinates":[3.1,50]}

Pour améliorer la restitution, on peut activer le module Geofield map qui permet d'avoir un nouveau formatter pour la visualisation.

Une fois choisi Geofield Map, on dispose de beaucoup plus d'options pour l'affichage

Le résultat est déjà plus intéressant ; quand on affiche un contenu, la localisation est affichée sous la forme d'une Map Google :

Affichage avec d'autres modules

Il existe d'autres modules qui gèrent l'affichage du contenu, et qui peuvent être intégrés avec Views, afin de réaliser des cartes englobants plusieurs points.
Ces modules utilisent généralement des librairies Javascript, qui effectuent le rendu côté client.

On peut citer notamment :

  • Leaflet
  • OpenLayers
  • IP Geoloc

Nous verrons dans un autre article comment utiliser Leaflet, avec des clusters de point et des icônes spécifiques selon une taxonomie.

 

Catégorie: 


Tag: 

Par Artusamak
Julien Dubois

Drupal 8 : Twig et les thèmes

Drupal 8 : Twig et les thèmes
jeu, 10/03/2016 - 12:42
Artusamak

Cet article est extrait de notre formation drupal 8 "de Drupal 7 à Drupal 8" à destination des développeurs. N'hésitez pas à nous contacter pour en savoir plus !

Au revoir PHPTemplate, bonjour Twig. Mais ça n’est pas tout. Le responsive étant beaucoup plus présent dans cette nouvelle version de Drupal, les thèmes de base Bartik et Seven se basent sur un nouveau thème Classy qui a vocation à fournir un balisage et des classes de base pour la structure et la mise en page de votre site.
Si vous voulez démarrer un nouveau thème vierge, ignorez
Classy et vous ne serez pas encombré par les classes non désirées.

A l’instar des modules, les thèmes custom seront à créer dans un dossier themes à la racine de l’installation de Drupal (Ou dans sites/**/themes ou dans profiles/**/themes)

Ce qu’apporte l’utilisation de Twig

PHPTemplate a été retiré du cœur mais il sera sans doute rapidement disponible en module contribué bien qu’il soit recommandé de ne plus s’en servir pour des raisons de sécurité. Les bon vieux .tpl.php sont maintenant remplacés par des .html.twig.

Même s’il est toujours possible de créer une entrée de thème dont le rendu est généré par une fonction, la méthode par défaut, utilisée dans tout le cœur, est d’utiliser un template Twig.

Les fichiers de template sont de facto attendus dans un dossier templates/, plus besoin de spécifier le chemin vers le fichier de template depuis votre hook_theme().

Un fichier de template ressemble à cela :

<span class="other token"># maintenance-task-list.html.twig
<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;</span>h2</span> <span class="attr-name token">class</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>visually-hidden<span class="punctuation token">"</span></span><span class="punctuation token">&gt;</span></span></span><span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="string token"><span class="punctuation token">'</span>Installation tasks<span class="punctuation token">'</span></span><span class="operator token">|</span><span class="property token">t</span> <span class="rd token"><span class="punctuation token">}}</span></span></span><span class="other token"><span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>h2</span><span class="punctuation token">&gt;</span></span>
<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;</span>ol</span> <span class="attr-name token">class</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>task-list<span class="punctuation token">"</span></span><span class="punctuation token">&gt;</span></span></span>
<span class="tag token"><span class="ld token"><span class="punctuation token">{%</span> <span class="keyword token">for</span></span> <span class="property token">task</span> <span class="operator token">in</span> <span class="property token">tasks</span> <span class="rd token"><span class="punctuation token">%}</span></span></span>
<span class="other token">&lt;li</span><span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">task</span><span class="punctuation token">.</span><span class="property token">attributes</span> <span class="rd token"><span class="punctuation token">}}</span></span></span><span class="other token">&gt;</span>
  <span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">task</span><span class="punctuation token">.</span><span class="property token">item</span> <span class="rd token"><span class="punctuation token">}}</span></span></span>
  <span class="tag token"><span class="ld token"><span class="punctuation token">{%</span> <span class="keyword token">if</span></span> <span class="property token">task</span><span class="punctuation token">.</span><span class="property token">status</span> <span class="rd token"><span class="punctuation token">%}</span></span></span><span class="other token"><span class="tag token"><span class="tag token"><span class="punctuation token">&lt;</span>span</span> <span class="attr-name token">class</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>visually-hidden<span class="punctuation token">"</span></span><span class="punctuation token">&gt;</span></span> (</span><span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">task</span><span class="punctuation token">.</span><span class="property token">status</span> <span class="rd token"><span class="punctuation token">}}</span></span></span><span class="other token">)<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>span</span><span class="punctuation token">&gt;</span></span></span><span class="tag token"><span class="ld token"><span class="punctuation token">{%</span> <span class="keyword token">endif</span></span> <span class="rd token"><span class="punctuation token">%}</span></span></span>
<span class="other token"><span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>li</span><span class="punctuation token">&gt;</span></span></span>
<span class="tag token"><span class="ld token"><span class="punctuation token">{%</span> <span class="keyword token">endfor</span></span> <span class="rd token"><span class="punctuation token">%}</span></span></span>
<span class="other token"><span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>ol</span><span class="punctuation token">&gt;</span></span></span>

Les avantages de Twig

  • C’est un langage interprété, cela apporte plus de sécurité vis à vis de ce qui peut être fait par les thémeurs. Fini les requêtes SQL au milieu du template.
  • Auto escape par défaut : toute chaîne rendue est filtrée (de la même manière que check_plain() dans Drupal 7).
  • La séparation des responsabilité est plus claire, il n’est plus possible d’écrire de PHP dans les fichiers.
  • Le débug est plus simple, Twig fourni des fonctions qui permettent de trouver les variables et d’identifier les fichiers de template utilisés. Par exemple: la fonction dump().
  • C’est un langage “autonome”, c’est un projet qui mène sa propre vie, vous pouvez lire la documentation dédiée et le réutiliser sur des projets qui utilisent une autre technologie que Drupal.

Pour activer le débug de Twig, modifiez le fichier services.yml modifiez la valeur du paramètre debug de twig à true.

<span class="comment token" spellcheck="true"># services.yml</span>
<span class="atrule key token">parameters</span><span class="punctuation token">:</span>
  <span class="atrule key token">twig.config</span><span class="punctuation token">:</span>
    <span class="atrule key token">debug</span><span class="punctuation token">:</span> <span class="boolean important token">true</span>

Vous pourrez ainsi voir le nom du fichier de template utilisé lors du rendu du contenu ainsi que les suggestions de noms de fichier pour surcharger le template. Il est également possible de désactiver le cache de Twig afin de le forcer à recompiler les templates à chaque fois qu’ils sont modifiés ou à chaque fois qu’ils sont demandés en modifiant respectivement les valeurs auto_reload ou cache du fichier services.yml.

Fonctionnalités courantes

Le moteur de Twig est très performant et extensible. Même s’il ne permet par l’utilisation directe de PHP, il remplace de manière sécuritaire et parfois enrichie les principales structures de contrôle et la gestion de variables. Afin de permettre aux thémeurs d’aller plus loin, Twig implémente aussi un système de fonctions, appelées “filtres” qui permettent de modifier une valeur avant de l’afficher.

Afin de se distinguer pleinement de PHP et HTML, la syntaxe de Twig se base sur un balisage par des accolades. Un appel de type inline se fera entre des doubles accolades et un appel de type block se fera entre une paire de balises délimitées par une accolade et un symbole de pourcentage. À noter que, comme en HTML, un appel de type block peut contenir d’autres appels de type inline. Il est aussi possible d’écrire des commentaires en les encadrant d’une accolade et d’un dièse.

<span class="other token"># somewhere.html.twig</span>

<span class="comment token" spellcheck="true">{# Exemple of inline call #}</span>
<span class="other token">Bienvenue</span> <span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">user</span><span class="punctuation token">.</span><span class="property token">firstname</span> <span class="rd token"><span class="punctuation token">}}</span></span></span><span class="other token">!</span>

<span class="comment token" spellcheck="true">{# Exemple of block call containing inline calls #}</span>
<span class="tag token"><span class="ld token"><span class="punctuation token">{%</span> <span class="keyword token">if</span></span> <span class="property token">label</span> <span class="rd token"><span class="punctuation token">%}</span></span></span>
<span class="other token">&lt;span</span> <span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">label_attributes</span> <span class="rd token"><span class="punctuation token">}}</span></span></span><span class="other token">&gt;</span><span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">label</span> <span class="rd token"><span class="punctuation token">}}</span></span></span><span class="other token"><span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>span</span><span class="punctuation token">&gt;</span></span></span>
<span class="tag token"><span class="ld token"><span class="punctuation token">{%</span> <span class="keyword token">endif</span></span> <span class="rd token"><span class="punctuation token">%}</span></span></span>

<span class="comment token" spellcheck="true">{# We can have
multi-line comments too! #}</span>

Il est donc également possible d’utiliser un des nombreux filtres du système, l’un de ceux ajoutés par Drupal ou encore d’en créer vous même en étendant la classe TwigExtension. Appliquer un filtre se fait très simplement dans un appel inline en utilisant le symbole pipe “|”. Même si c’est assez peu utilisé dans Drupal, il est également possible de filtrer des blocs de contenu.

<span class="other token"># somewhere.html.twig</span>

<span class="tag token"><span class="ld token"><span class="punctuation token">{%</span>  <span class="keyword token">set</span></span> <span class="property token">label</span> <span class="operator token">=</span> <span class="string token"><span class="punctuation token">'</span>My &lt;strong&gt;field&lt;/strong&gt;<span class="punctuation token">'</span></span> <span class="rd token"><span class="punctuation token">%}</span></span></span>

<span class="other token"><span class="tag token"><span class="tag token"><span class="punctuation token">&lt;</span>h1</span><span class="punctuation token">&gt;</span></span></span><span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">label</span><span class="operator token">|</span><span class="property token">striptags</span> <span class="rd token"><span class="punctuation token">}}</span></span></span><span class="other token"><span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>h1</span><span class="punctuation token">&gt;</span></span></span>
<span class="comment token" spellcheck="true">{# Outputs &lt;h1&gt;My field&lt;/h1&gt; #}</span>

<span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="string token"><span class="punctuation token">'</span>Translate this<span class="punctuation token">'</span></span><span class="operator token">|</span><span class="property token">t</span> <span class="rd token"><span class="punctuation token">}}</span></span></span>
<span class="comment token" spellcheck="true">{# Outputs the translation of 'Translate this' in the current language #}</span>

<span class="tag token"><span class="ld token"><span class="punctuation token">{%</span> <span class="keyword token">filter</span></span> <span class="property token">upper</span> <span class="rd token"><span class="punctuation token">%}</span></span></span><span class="tag token"><span class="ld token"><span class="punctuation token">{%</span> <span class="keyword token">trans</span></span> <span class="rd token"><span class="punctuation token">%}</span></span></span><span class="other token">Enable</span> <span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">view</span><span class="punctuation token">.</span><span class="property token">name</span> <span class="rd token"><span class="punctuation token">}}</span></span></span><span class="tag token"><span class="ld token"><span class="punctuation token">{%</span> <span class="keyword token">endtrans</span></span> <span class="rd token"><span class="punctuation token">%}</span></span></span><span class="tag token"><span class="ld token"><span class="punctuation token">{%</span> <span class="keyword token">endfilter</span></span> <span class="rd token"><span class="punctuation token">%}</span></span></span>
<span class="comment token" spellcheck="true">{# Outputs the translation of 'Enable @view.name' in the current language and
replaces @view.name then change the result to uppercase #}</span>

Outre toutes les structures de contrôle de base permettant de gérer conditions et boucles, Twig offre également de très nombreuses autres possibilités comme l’inclusion ou l’héritage de templates.

À noter qu'un thème d'exemple existe pour mettre en avant les possilités de Twig dans un contexte "live", il s'agit du thème Pistachio. Il n'a pas vocation à être utilisé en production mais est là à titre d'exemple.

Créer un hook_theme

Peu de choses ont changé en ce qui concerne les Hooks de thème.

On les déclare toujours via le hook_theme() qui garde à peu près les mêmes clefs que dans Drupal 7.

<span class="comment shell-comment token"># happy_alexandrie.module</span>

<span class="comment token" spellcheck="true">/**
* Implements hook_theme().
*/</span>
<span class="keyword token">function</span> <span class="function token">happy_alexandrie_theme</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
  <span class="token variable">$theme</span> <span class="operator token">=</span> <span class="punctuation token">[</span><span class="punctuation token">]</span><span class="punctuation token">;</span>

  <span class="token variable">$theme</span><span class="punctuation token">[</span><span class="string token">'happy_cover'</span><span class="punctuation token">]</span> <span class="operator token">=</span> <span class="punctuation token">[</span>
    <span class="string token">'variables'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="punctuation token">[</span><span class="string token">'cover_src'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="string token">''</span><span class="punctuation token">,</span> <span class="string token">'cover_title'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="string token">''</span><span class="punctuation token">]</span><span class="punctuation token">,</span>
  <span class="punctuation token">]</span><span class="punctuation token">;</span>

  <span class="keyword token">return</span> <span class="token variable">$theme</span><span class="punctuation token">;</span>
<span class="punctuation token">}</span>

Par défaut, les fichiers templates se placent dans le répertoire templates à la racine du module. Il faut toujours remplacer les undescore (_) par des tiret (-) et l’extension du fichier est maintenant .html.twig. Sachez que les variables sont affichées en les mettant entre double accolade {{ variable }}.

<span class="other token"># templates/happy-cover.html.twig
<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;</span>p</span><span class="punctuation token">&gt;</span></span>
  &lt;img src="</span><span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">cover_src</span> <span class="rd token"><span class="punctuation token">}}</span></span></span><span class="other token">" alt="Cover for</span> <span class="tag token"><span class="ld token"><span class="punctuation token">{{</span></span> <span class="property token">cover_title</span> <span class="rd token"><span class="punctuation token">}}</span></span></span><span class="other token">" /&gt;
<span class="tag token"><span class="tag token"><span class="punctuation token">&lt;/</span>p</span><span class="punctuation token">&gt;</span></span></span>

Une fois déclaré, le Hook de thème peut être utilisé dans un render array. Il suffit d’en indiquer le nom dans la clef #theme de votre render array et de passer vos diverses variables en préfixant leur nom d’un dièse. Toutes variables qui ne seraient pas présente dans le render array héritera de la valeur par défaut définie dans le hook_theme().

<span class="comment shell-comment token"># RemoteCover.php</span>

  <span class="comment token" spellcheck="true">/**
   * This method is the wrapper for each field value.
   */</span>
  <span class="keyword token">public</span> <span class="keyword token">function</span> <span class="function token">viewElements</span><span class="punctuation token">(</span>FieldItemListInterface <span class="token variable">$items</span><span class="punctuation token">,</span> <span class="token variable">$langcode</span> <span class="operator token">=</span> <span class="keyword token">NULL</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
    <span class="token variable">$elements</span> <span class="operator token">=</span> <span class="keyword token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
    <span class="keyword token">foreach</span> <span class="punctuation token">(</span><span class="token variable">$items</span> <span class="keyword token">as</span> <span class="token variable">$delta</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="token variable">$item</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
      <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="token variable">$item</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">value</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
        <span class="comment token" spellcheck="true">// Part calling a theme function.</span>
        <span class="token variable">$elements</span><span class="punctuation token">[</span><span class="token variable">$delta</span><span class="punctuation token">]</span> <span class="operator token">=</span> <span class="keyword token">array</span><span class="punctuation token">(</span>
          <span class="string token">'#theme'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="string token">'happy_cover'</span><span class="punctuation token">,</span>
          <span class="string token">'#cover_url'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="token variable">$item</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">value</span><span class="punctuation token">,</span>
          <span class="string token">'#cover_title'</span> <span class="operator token">=</span><span class="operator token">&gt;</span> <span class="token variable">$items</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getEntity</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">label</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span>
        <span class="punctuation token">)</span><span class="punctuation token">;</span>
      <span class="punctuation token">}</span>
    <span class="punctuation token">}</span>
    <span class="keyword token">return</span> <span class="token variable">$elements</span><span class="punctuation token">;</span>
  <span class="punctuation token">}</span>

 

Pages