Planète

Par Mantalo Conseil
Agence web, Agence de Communication et Marketing en Dordogne (Aquitaine)

e-commerce : un catalogue vendeur sans budget photo

Image appareil photo et caddie

Un site marchand sans photos attrayantes... juste inimaginable !

Imaginez une boutique sans vitrine ou un catalogue commercial sans illustrations... un remède contre la vente !

Plus encore, l'image a pris une telle importance dans nos comportements d'achat qu'elle ne doit pas seulement exister, elle se doit aussi d'être attractive pour satisfaire notre sensibilité croissante au marketing sensoriel.

Les studios spécialisés dans la photo publicitaire offrent sans aucun doute la meilleure solution sur le plan technique. Sans avoir participé un jour à un shooting produits de A à Z, on imagine difficilement le temps et les ressources nécessaires... ce qui nous laisse pantois lorsqu'on reçoit les devis.

Le coût d'un shooting produits en studio

Toujours trop cher lorsque le budget global est serré, les devis des photographes sont toutefois généralement justifiés. Comptez globalement entre 900 et 1000 euros HT pour une vingtaine de photos avec le retraitement numérique et les droits d'exploitation.

Évidemment, si on considère q'un produit doit en moyenne faire l'objet de 3 vues différentes, et qu'une boutique en ligne doit offrir une gamme de produits suffisante pour être attractive (au moins 50 références hors déclinaisons), le budget atteint assez facilement 7500 euros, soit environ autant que le budget minimum permettant le développement d'un site marchand générique de qualité satisfaisante.

Alternatives aux photos professionnelles

Rares sont donc les jeunes e-commerçants qui ont recours aux services d'un studio professionnel. Pour autant, disposer de belles photos produits reste essentiel et pour ne pas condamner le site marchand à l'échec dès le départ, il faut s'organiser au mieux avec les ressources disponibles.

Installez un studio de forture dans vos locaux

N'importe quelle pièce peut faire l'affaire pour peu que vous puissiez mobiliser environ 10 m2durant quelques semaines, avec :

  • un point de lumière naturelle,
  • un plateau de présentation pour les prises de vue,
  • une étagère (ou des suspensions) pour y entreposer les accessoires ou les produits en cours de travail,
  • une zone de circulation pour accéder facilement aux tables sans déplacer le pied de l'appareil photo et les dispositifs d'éclairage.

Réaliser toutes les prises de vues à partir de la même installation garantit l'homogénéité des photos et du résultat final.

Disposez du bon matériel

L'outillage indispensable se résume à 3 équipements :

  • Un trépied pour stabiliser l'appareil photo au cours des réglages et des prises de vues,
  • Un appareil photo numérique qui permet un réglage manuel de l'ouverture et de l'exposition,
  • Une boite à lumière ou des sources de lumière continue d'une puissance suffisante et adaptée aux produits à shooter.

Pour le reste, faites appel à votre imagination et à votre ingéniosité :

  • usez de papier ou de toile pour le fond,
  • mettez à profit le ruban adhésif et le fil de nylon pour suspendre et fixer le fond sur son support,
  • tracez au crayon ou au blanco vos repères de positionnement sur la table de présentation,
  • montez une étagère en kit pour tout ranger et tout avoir sous la main,
  • ...

Considérez l'éclairage comme essentiel

Rassurez-vous, les plus grands professionnels passent eux aussi beaucoup de temps à trouver la bonne organisation des sources lumineuses.

Le bon choix d'éclairage permet des prises de vues qui respectent les couleurs et leurs contrastes. N'hésitez donc pas à passer le temps nécessaire aux essais car une fois trouvée, la solution servira durant toute la durée du shooting photo. Pensez aussi à garder sous la main un quart de feuille blanche qui vous servira à étalonner vos photos sur la plan de la luminosité.

Les ombres peuvent être parasites, ou de précieux alliés pour souligner les formes et donner du réalisme à vos produits.

Soignez la présentation des produits

Il reste la mise en scène des produits et sur ce point, vous pouvez simplement vous inspirer de certains sites référents qui impulsent les tendances pour des produits similaires aux votres. Bien sûr, si votre créativité vous autorise à innover... surprenez-nous !

5 clés pour valoriser à coup sûr les prises de vues de vos produits :

  • Inspectez vos produits sous toutes les coutures pour ne laisser aucun détail dégrader leur aspect (micro tâches, peluches, traces, grains de poussière, ...),
  • Trouvez l'angle le plus favorable pour mettre en évidence leurs signes distinctifs (rotation, inclinaison, surplomb, ...),
  • Associez vos produits à des objets très courants (clés, montre, carte de crédit, ...) si vous souhaitez que vos clients aient une bonne appréciation de leurs dimensions sans avoir à les rechercher dans le descriptif,
  • Enrichissez la présentation de vos produits par la présence d'accessoires s'ils contribuent fortement à intensifier l'intérêt ou le plaisir sensoriel,
  • Assurez-vous avant chaque prise de vue que votre fond soit resté impécable (exempt de plis, tâches, accrocs, ...) afin d'éviter les retouches post-production toujours ruineuses en temps.

Toutes les astuces sont permises pour imprimer un mouvement, maintenir en suspend un élément, raidir un support, ... et là, c'est à vous de jouer.

Focus sur la prise de vues

De la même façon que les repères sur votre fond guideront vos dispositions sur le plateau de shooting, le ou les points de prise de vues seront exactement tracés au sol, une fois définies comme satisfaisantes.

Prévoyez de demander à votre prestataire web une fonctionnalité zoom sur vos photos. Vos clients pourront ainsi examiner vos produits dans leurs moindres détails, et ils adorent ça... pour peu que les angles de prise de vues anticipent cette observation.

Doublez enfin toujours vos photos d'une prise de vue avec un quart de feuille blanche contre votre produit, face à l'angle de shoot. Vous aurez ainsi un repère constant de luminosité. Il sera un précieux indice pour guider le retraitement numérique définitif.

Traiter les photos avant intégration dans la boutique

Le retraitement numérique de vos photos se justifie par :

  • la volonté de montrer à vos clients, des photos totalement fidèles à l'aspect réel de vos produits,
  • l'intérêt de suivre les préconisation d'un guide de style que vous aurez préalablement établi en précisant notamment :
    • les contraintes de taille des images,
    • la dimension des différentes marges,
    • les couleurs de background,
    • les alignements pour les différentes catégories de produits,
    • les ombrages et leurs caractéristiques,
  • le respect des principales règles visuelles pour la vente en ligne telles qu'exigées par certaines plateformes de vente en ligne comme eBay, Amazon ou Google Shopping, si vous étiez amenés à les utiliser.

Cette opération est la moins accessible pour les e-commerçants qui ne maîtrisent pas les outils de retouche numérique. C'est sur ce point qu'ils peuvent mettre à profit les services de notre agence.

À propos de la Web agency Mantalo

Lorsque le budget de nos clients le permet, nous collaborons volontiers avec les studios spécialisés en photographie publicitaire.

Dans les autres cas, à l'instar de cette publication ouverte et compte tenu de notre souhait de contribuer à la réussite de leur commerce en ligne, nous les aidons à mettre en oeuvre les moyens requis pour produire des photos de produits dans les meilleures conditions par un accompagnement :

  • à la mise en place d'un studio "maison" dans leurs locaux,
  • à la formalisation d'un guide de style,
  • à la prise de vues.

Nous offrons également à nos clients, des prestations de retraitement numérique à l'aide de logiciels professionnels qui nécessitent une licence d'utilisation et une formation spécialisée.

Category: 

Votre projet de e-commerce tient la route ?

Votre budget photo est trop réduit pour faire appel à un studio professionnel en photographies publicitaires ?

Contactez-nous et voyons ensemble comment optimiser vos ressources disponibles !

Par Artusamak
Julien Dubois

Drupal 8 : Le cache, nouveautés, mécanismes

Drupal 8 : Le cache, nouveautés, mécanismes
Artusamak
lun 18/04/2016 - 10:20

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 et sites web modernes soulèvent tous des problèmes liés aux performances. Plus les systèmes s’assouplissent et se complexifient et plus les ressources nécessaires à leur exécution augmentent. Cependant, les utilisateurs recherchent de plus en plus de contenus personnalisés tout en conservant un temps de réponse optimal. Ce dernier critère étant devenu récemment l’un des plus importants pour le référencement via Google, nous ne pouvons plus nous permettre de ne pas faire attention aux performances de nos réalisations.

Afin d’éviter au maximum les calculs coûteux et répétitif, nous disposons de plusieurs niveaux de cache qui couvrent la totalité des besoins d’un développeur.

Le cache applicatif

Déjà présent dans Drupal 7 et dans la plupart des Frameworks modernes, cette couche de cache est la plus profonde et correspond à ce que l’on peut faire de plus unitaire possible. On distingue deux variantes en fonction du besoin : le cache statique et le cache d’exécution. La différence majeure entre ces deux types de cache concerne la durée pendant laquelle sont conservées les données. Le cache d’exécution n’est valable que pendant la durée de vie d’une requête HTTP alors que le cache statique lui peut persister plus longtemps.

Cache d’exécution

Dans Drupal 7 comme dans Drupal 8, celui-ci n’a pas changé. Il s’agit donc de faire appel à la fonction drupal_static() et de récupérer son retour comme une référence.

<span class="token shell-comment comment"># anywhere.php</span>
<span class="token keyword">function</span> <span class="token function">my_overused_function</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token variable">$cache</span> <span class="token operator">=</span> <span class="token operator">&amp;</span><span class="token function">drupal_static</span><span class="token punctuation">(</span><span class="token constant">__FUNCTION__</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token variable">$cache</span><span class="token punctuation">[</span><span class="token variable">$id</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$cache</span><span class="token punctuation">[</span><span class="token variable">$id</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">do_something_long</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token variable">$cache</span><span class="token punctuation">[</span>id<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

De cette manière, même si la fonction est appelée de très nombreuses fois lors de la même requête HTTP, le calcul ne sera fait qu’une fois et le résultat sera conservé en mémoire. Attention à ne pas abuser de cette fonction sous peine de saturer la mémoire de votre serveur.

Dans Drupal 8, ce cache aura tendance à être moins utilisé que dans Drupal 7 car la plupart des objets PHP sont toujours passés comme référence et peuvent donc faire office de conteneurs de stockage pour le temps de l’exécution.

Cache statique

Parfois, un calcul est si coûteux qu’il est préférable de le faire une seule fois ou de temps en temps mais pas à chaque requête. Dans Drupal 7 il était possible de stocker des données en cache de manière temporaire ou permanente à l’aide de la fonction cache_set() et de les récupérer à l’aide de cache_get().

Le principe de base n’a pas changé dans Drupal 8. Seule la syntaxe a évolué, introduisant l’utilisation d’un service de gestion du cache.

<span class="token shell-comment comment"># anywhere.php</span>
<span class="token keyword">function</span> <span class="token function">do_something_long</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token variable">$cid</span> <span class="token operator">=</span> <span class="token string">'something_long:'</span> <span class="token punctuation">.</span> <span class="token variable">$id</span><span class="token punctuation">;</span>

  <span class="token variable">$data</span> <span class="token operator">=</span> <span class="token keyword">NULL</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$cache</span> <span class="token operator">=</span> \<span class="token package">Drupal</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">cache</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token variable">$cid</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$data</span> <span class="token operator">=</span> <span class="token variable">$cache</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">data</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token variable">$data</span> <span class="token operator">=</span> <span class="token function">do_the_really_long_thing</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    \<span class="token package">Drupal</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">cache</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token variable">$cid</span><span class="token punctuation">,</span> <span class="token variable">$data</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

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

Par défaut, les caches sont stockés en base de données, dans un ensemble de tables cache_* qui peuvent être déportées en mémoire grâce à un système de type Memcache ou Redis.

Si vous avez un peu suivi vous aurez constaté que l’on peut combiner ensemble les deux exemples précédents afin de ne solliciter le cache statique qu’une seule fois par requête. Cette combinaison n’est ni nécessaire ni obligatoire car il faut toujours garder à l’esprit que tout a un coût. Le cache d’exécution a un coût en mémoire et le stockage statique un coût d’accès. Votre travail est de faire un choix en adéquation avec la situation.

Les caches d’objets

Depuis Drupal 6 et l’avènement de CCK puis Drupal 7 et l’arrivée des entités, les objets de données et la structure de la base se sont grandement complexifiés. Reconstituer une entité à partir de ses données en tenant compte des révisions, de la langue, des gestions de droits et des nombreux hooks qui peuvent intervenir au milieu de tout ça est devenu une opération coûteuse. Sont donc apparus des modules tels que Entity Cache ou Views Content Cache par exemple.

Dans Drupal 8, toutes les entités de contenu et de configuration sont mises en cache par défaut.

Le cache de rendu

Voilà enfin la grosse nouveauté de Drupal 8 sur laquelle repose une grande partie des performances. Introduit en tant que concept dans Drupal 7 à l’aide du module Render Cache, le cache de rendu s’est imposé dans le cœur petit à petit jusqu’à la beta 16 et l’apparition du module Internal Dynamic Page Cache.

Le principe de base est assez simple. Chaque élément d’une page que l’on veut construire est un render array qui dispose de méta-données spécifiant les conditions dans lesquelles il doit être mis en cache et qui peut contenir des sous-éléments disposant de leurs propres méta-données. Lors de la première construction de la page, chacun de ces éléments va être rendu et ses méta-données ainsi que celles de ses enfants vont être additionnées. Au second chargement de la page, seuls les éléments qui doivent varier seront compilés, les autres seront récupérés depuis le cache. On dit que ces méta-données se “propagent” (bubble up).

Ce module fonctionne donc sur le même modèle qu’un Reverse Proxy qui irait consommer les ressources associées à des Edge Sides Includes uniquement lorsque nécessaire. Ce modèle est tellement efficace qu’il ne serait pas étonnant de le voir apparaître sur les systèmes concurrents de Drupal à court terme.

Les méta-données de cache

Au nombre de quatre, celles-ci permettent de s’assurer que le cache est justement dosé. Un cache trop important permettrait potentiellement à des utilisateurs de voir le contenu destiné à d’autres alors qu’un cache pas assez important solliciterait trop les ressources du serveur. Afin de trouver un juste milieu, les méta-données de cache vont permettre de définir de façon explicite les variations possibles et les dépendances du contenu mis en cache.

Les clefs de cache (cache keys) permettent d’identifier l’objet rendu. Elles correspondent à l’identifiant utilisé par le service de cache pour stocker ou récupérer des données. Elles servent de préfixe à toutes les variantes du même contenu afin de pouvoir invalider un ensemble de variantes plus facilement. Les clefs de cache ne sont pas obligatoires et devraient être utilisées uniquement lorsque l’on souhaite procéder à un cache intermédiaire. Par exemple, la plupart des champs d’une entité n’auront pas de clé de cache car ils sont faciles à reconstruire. En revanche, certains champs plus complexes comme par exemple une référence d’entité en auront une pour pouvoir limiter l’invalidation et éviter d’avoir à tout reconstruire si un seul champ venait à changer.

Les contextes de cache (cache contexts) permettent de définir les diverses variantes du contenu. Ainsi, chaque variante est mise en cache séparément et servie aux utilisateurs qui correspondent aux critères. Par exemple, il est possible de définir un contenu qui variera en fonction de la langue et des permissions de l’utilisateur. Cela permettra de garantir que tous les utilisateurs disposant d’une combinaison de permissions équivalente verront le même contenu pour peu qu’ils accèdent au site dans la même langue.

Les contextes de cache sont résolus au moment de la génération du contenu et ajoutés aux clefs de cache afin de générer l’identifiant de la variation.

Les tags de cache (cache tags) sont utilisés pour définir les dépendances entre le cache et des objets arbitraires. Toutes les entités de contenu et de configuration sont des dépendances valides mais il est également possible de créer ses propres dépendances en cas de besoin. Si jamais l’un de ces objets est modifié, tous les caches qui lui sont rattachés sont immédiatement invalidés.

Les tags de cache s’expriment, par convention, sous la forme objet:identifiant. Par exemple, node:2 est un tag valable. Afin de simplifier la création de ces tags pour déclarer des entités en tant que dépendance, il est possible d’utiliser la méthode EntityInterface::getCacheTags() qui retourne les tags correspondant à l’entité (rappelez vous que ces derniers se propagent).

L’âge d’expiration (cache max-age) sert quant à lui dans les rares cas où l’on souhaite forcer le cache à expirer au bout d’un certain temps même si ses composants n’ont pas changé. C’est souvent utilisé lorsque l’on souhaite faire une remontée d’éléments aléatoire rafraîchie régulièrement. Cette valeur est exprimée en secondes. La valeur 0 indique que l’élément ne doit jamais être mis en cache et la valeur Cache::PERMANENT qu’il ne doit jamais expirer sur la base de son âge.

Voici un exemple reprenant tous ces éléments :

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

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

<span class="token keyword">function</span> <span class="token function">link_my_nodes</span><span class="token punctuation">(</span><span class="token variable">$ids</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token variable">$nodes</span> <span class="token operator">=</span> \<span class="token package">Drupal</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">entityManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getStorage</span><span class="token punctuation">(</span><span class="token string">'node'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">loadMultiple</span><span class="token punctuation">(</span><span class="token variable">$ids</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token variable">$build</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
    <span class="token string">'#theme'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'links'</span><span class="token punctuation">,</span>
    <span class="token string">'#links'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token string">'#cache'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span>
      <span class="token string">'keys'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token string">'link_my_nodes'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token string">'contexts'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token string">'language'</span><span class="token punctuation">,</span> <span class="token string">'user.permissions'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token string">'tags'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token string">'max-age'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">PERMANENT</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>

  <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$nodes</span> <span class="token keyword">as</span> <span class="token variable">$node</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#links'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
      <span class="token string">'title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">label</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'url'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">urlInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'contexts'</span><span class="token punctuation">]</span> <span class="token operator">=</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">mergeContexts</span><span class="token punctuation">(</span><span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'contexts'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getCacheContexts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'tags'</span><span class="token punctuation">]</span> <span class="token operator">=</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">mergeTags</span><span class="token punctuation">(</span><span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'tags'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getCacheTags</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'max-age'</span><span class="token punctuation">]</span> <span class="token operator">=</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">mergeMaxAges</span><span class="token punctuation">(</span><span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'max-age'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getCacheMaxAge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

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

Le cache HTTP

Le dernier niveau de cache est le plus simple en apparence. Il concerne le cache du navigateur (ou du reverse proxy si vous en avez un). Ce dernier s’appuie sur les entêtes HTTP renvoyés par l’application comme Cache-Control, Expires, Etag, Vary, Pragma, etc. La bonne nouvelle c’est que Drupal 8 s’en sort beaucoup mieux que son prédécesseur dans la création de ces entêtes. En effet, celui-ci s’appuie sur les méta-données de cache de la page, et donc de tous les éléments qui la constituent, pour générer ces entêtes. La configuration pour faire fonctionner Drupal 8 derrière un reverse proxy ou un CDN est donc très simple voir même pas nécessaire du tout !

Par Artusamak
Julien Dubois

Drupal 8 : Le cache, nouveautés, mécanismes

Drupal 8 : Le cache, nouveautés, mécanismes
lun, 18/04/2016 - 10:20
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 et sites web modernes soulèvent tous des problèmes liés aux performances. Plus les systèmes s’assouplissent et se complexifient et plus les ressources nécessaires à leur exécution augmentent. Cependant, les utilisateurs recherchent de plus en plus de contenus personnalisés tout en conservant un temps de réponse optimal. Ce dernier critère étant devenu récemment l’un des plus importants pour le référencement via Google, nous ne pouvons plus nous permettre de ne pas faire attention aux performances de nos réalisations.

Afin d’éviter au maximum les calculs coûteux et répétitif, nous disposons de plusieurs niveaux de cache qui couvrent la totalité des besoins d’un développeur.

Le cache applicatif

Déjà présent dans Drupal 7 et dans la plupart des Frameworks modernes, cette couche de cache est la plus profonde et correspond à ce que l’on peut faire de plus unitaire possible. On distingue deux variantes en fonction du besoin : le cache statique et le cache d’exécution. La différence majeure entre ces deux types de cache concerne la durée pendant laquelle sont conservées les données. Le cache d’exécution n’est valable que pendant la durée de vie d’une requête HTTP alors que le cache statique lui peut persister plus longtemps.

Cache d’exécution

Dans Drupal 7 comme dans Drupal 8, celui-ci n’a pas changé. Il s’agit donc de faire appel à la fonction drupal_static() et de récupérer son retour comme une référence.

<span class="token shell-comment comment"># anywhere.php</span>
<span class="token keyword">function</span> <span class="token function">my_overused_function</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token variable">$cache</span> <span class="token operator">=</span> <span class="token operator">&amp;</span><span class="token function">drupal_static</span><span class="token punctuation">(</span><span class="token constant">__FUNCTION__</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token variable">$cache</span><span class="token punctuation">[</span><span class="token variable">$id</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$cache</span><span class="token punctuation">[</span><span class="token variable">$id</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">do_something_long</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token variable">$cache</span><span class="token punctuation">[</span>id<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

De cette manière, même si la fonction est appelée de très nombreuses fois lors de la même requête HTTP, le calcul ne sera fait qu’une fois et le résultat sera conservé en mémoire. Attention à ne pas abuser de cette fonction sous peine de saturer la mémoire de votre serveur.

Dans Drupal 8, ce cache aura tendance à être moins utilisé que dans Drupal 7 car la plupart des objets PHP sont toujours passés comme référence et peuvent donc faire office de conteneurs de stockage pour le temps de l’exécution.

Cache statique

Parfois, un calcul est si coûteux qu’il est préférable de le faire une seule fois ou de temps en temps mais pas à chaque requête. Dans Drupal 7 il était possible de stocker des données en cache de manière temporaire ou permanente à l’aide de la fonction cache_set() et de les récupérer à l’aide de cache_get().

Le principe de base n’a pas changé dans Drupal 8. Seule la syntaxe a évolué, introduisant l’utilisation d’un service de gestion du cache.

<span class="token shell-comment comment"># anywhere.php</span>
<span class="token keyword">function</span> <span class="token function">do_something_long</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token variable">$cid</span> <span class="token operator">=</span> <span class="token string">'something_long:'</span> <span class="token punctuation">.</span> <span class="token variable">$id</span><span class="token punctuation">;</span>

  <span class="token variable">$data</span> <span class="token operator">=</span> <span class="token keyword">NULL</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$cache</span> <span class="token operator">=</span> \<span class="token package">Drupal</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">cache</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token variable">$cid</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$data</span> <span class="token operator">=</span> <span class="token variable">$cache</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">data</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token variable">$data</span> <span class="token operator">=</span> <span class="token function">do_the_really_long_thing</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    \<span class="token package">Drupal</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">cache</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token variable">$cid</span><span class="token punctuation">,</span> <span class="token variable">$data</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

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

Par défaut, les caches sont stockés en base de données, dans un ensemble de tables cache_* qui peuvent être déportées en mémoire grâce à un système de type Memcache ou Redis.

Si vous avez un peu suivi vous aurez constaté que l’on peut combiner ensemble les deux exemples précédents afin de ne solliciter le cache statique qu’une seule fois par requête. Cette combinaison n’est ni nécessaire ni obligatoire car il faut toujours garder à l’esprit que tout a un coût. Le cache d’exécution a un coût en mémoire et le stockage statique un coût d’accès. Votre travail est de faire un choix en adéquation avec la situation.

Les caches d’objets

Depuis Drupal 6 et l’avènement de CCK puis Drupal 7 et l’arrivée des entités, les objets de données et la structure de la base se sont grandement complexifiés. Reconstituer une entité à partir de ses données en tenant compte des révisions, de la langue, des gestions de droits et des nombreux hooks qui peuvent intervenir au milieu de tout ça est devenu une opération coûteuse. Sont donc apparus des modules tels que Entity Cache ou Views Content Cache par exemple.

Dans Drupal 8, toutes les entités de contenu et de configuration sont mises en cache par défaut.

Le cache de rendu

Voilà enfin la grosse nouveauté de Drupal 8 sur laquelle repose une grande partie des performances. Introduit en tant que concept dans Drupal 7 à l’aide du module Render Cache, le cache de rendu s’est imposé dans le cœur petit à petit jusqu’à la beta 16 et l’apparition du module Internal Dynamic Page Cache.

Le principe de base est assez simple. Chaque élément d’une page que l’on veut construire est un render array qui dispose de méta-données spécifiant les conditions dans lesquelles il doit être mis en cache et qui peut contenir des sous-éléments disposant de leurs propres méta-données. Lors de la première construction de la page, chacun de ces éléments va être rendu et ses méta-données ainsi que celles de ses enfants vont être additionnées. Au second chargement de la page, seuls les éléments qui doivent varier seront compilés, les autres seront récupérés depuis le cache. On dit que ces méta-données se “propagent” (bubble up).

Ce module fonctionne donc sur le même modèle qu’un Reverse Proxy qui irait consommer les ressources associées à des Edge Sides Includes uniquement lorsque nécessaire. Ce modèle est tellement efficace qu’il ne serait pas étonnant de le voir apparaître sur les systèmes concurrents de Drupal à court terme.

Les méta-données de cache

Au nombre de quatre, celles-ci permettent de s’assurer que le cache est justement dosé. Un cache trop important permettrait potentiellement à des utilisateurs de voir le contenu destiné à d’autres alors qu’un cache pas assez important solliciterait trop les ressources du serveur. Afin de trouver un juste milieu, les méta-données de cache vont permettre de définir de façon explicite les variations possibles et les dépendances du contenu mis en cache.

Les clefs de cache (cache keys) permettent d’identifier l’objet rendu. Elles correspondent à l’identifiant utilisé par le service de cache pour stocker ou récupérer des données. Elles servent de préfixe à toutes les variantes du même contenu afin de pouvoir invalider un ensemble de variantes plus facilement. Les clefs de cache ne sont pas obligatoires et devraient être utilisées uniquement lorsque l’on souhaite procéder à un cache intermédiaire. Par exemple, la plupart des champs d’une entité n’auront pas de clé de cache car ils sont faciles à reconstruire. En revanche, certains champs plus complexes comme par exemple une référence d’entité en auront une pour pouvoir limiter l’invalidation et éviter d’avoir à tout reconstruire si un seul champ venait à changer.

Les contextes de cache (cache contexts) permettent de définir les diverses variantes du contenu. Ainsi, chaque variante est mise en cache séparément et servie aux utilisateurs qui correspondent aux critères. Par exemple, il est possible de définir un contenu qui variera en fonction de la langue et des permissions de l’utilisateur. Cela permettra de garantir que tous les utilisateurs disposant d’une combinaison de permissions équivalente verront le même contenu pour peu qu’ils accèdent au site dans la même langue.

Les contextes de cache sont résolus au moment de la génération du contenu et ajoutés aux clefs de cache afin de générer l’identifiant de la variation.

Les tags de cache (cache tags) sont utilisés pour définir les dépendances entre le cache et des objets arbitraires. Toutes les entités de contenu et de configuration sont des dépendances valides mais il est également possible de créer ses propres dépendances en cas de besoin. Si jamais l’un de ces objets est modifié, tous les caches qui lui sont rattachés sont immédiatement invalidés.

Les tags de cache s’expriment, par convention, sous la forme objet:identifiant. Par exemple, node:2 est un tag valable. Afin de simplifier la création de ces tags pour déclarer des entités en tant que dépendance, il est possible d’utiliser la méthode EntityInterface::getCacheTags() qui retourne les tags correspondant à l’entité (rappelez vous que ces derniers se propagent).

L’âge d’expiration (cache max-age) sert quant à lui dans les rares cas où l’on souhaite forcer le cache à expirer au bout d’un certain temps même si ses composants n’ont pas changé. C’est souvent utilisé lorsque l’on souhaite faire une remontée d’éléments aléatoire rafraîchie régulièrement. Cette valeur est exprimée en secondes. La valeur 0 indique que l’élément ne doit jamais être mis en cache et la valeur Cache::PERMANENT qu’il ne doit jamais expirer sur la base de son âge.

Voici un exemple reprenant tous ces éléments :

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

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

<span class="token keyword">function</span> <span class="token function">link_my_nodes</span><span class="token punctuation">(</span><span class="token variable">$ids</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token variable">$nodes</span> <span class="token operator">=</span> \<span class="token package">Drupal</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">entityManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getStorage</span><span class="token punctuation">(</span><span class="token string">'node'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">loadMultiple</span><span class="token punctuation">(</span><span class="token variable">$ids</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token variable">$build</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
    <span class="token string">'#theme'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'links'</span><span class="token punctuation">,</span>
    <span class="token string">'#links'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token string">'#cache'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span>
      <span class="token string">'keys'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token string">'link_my_nodes'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token string">'contexts'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token string">'language'</span><span class="token punctuation">,</span> <span class="token string">'user.permissions'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token string">'tags'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token string">'max-age'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">PERMANENT</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>

  <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$nodes</span> <span class="token keyword">as</span> <span class="token variable">$node</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#links'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
      <span class="token string">'title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">label</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'url'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">urlInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'contexts'</span><span class="token punctuation">]</span> <span class="token operator">=</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">mergeContexts</span><span class="token punctuation">(</span><span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'contexts'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getCacheContexts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'tags'</span><span class="token punctuation">]</span> <span class="token operator">=</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">mergeTags</span><span class="token punctuation">(</span><span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'tags'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getCacheTags</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'max-age'</span><span class="token punctuation">]</span> <span class="token operator">=</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">mergeMaxAges</span><span class="token punctuation">(</span><span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'max-age'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getCacheMaxAge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

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

Le cache HTTP

Le dernier niveau de cache est le plus simple en apparence. Il concerne le cache du navigateur (ou du reverse proxy si vous en avez un). Ce dernier s’appuie sur les entêtes HTTP renvoyés par l’application comme Cache-Control, Expires, Etag, Vary, Pragma, etc. La bonne nouvelle c’est que Drupal 8 s’en sort beaucoup mieux que son prédécesseur dans la création de ces entêtes. En effet, celui-ci s’appuie sur les méta-données de cache de la page, et donc de tous les éléments qui la constituent, pour générer ces entêtes. La configuration pour faire fonctionner Drupal 8 derrière un reverse proxy ou un CDN est donc très simple voir même pas nécessaire du tout !

Par Artusamak
Julien Dubois

Drupal 8 : Le cache, nouveautés, mécanismes

Drupal 8 : Le cache, nouveautés, mécanismes
Artusamak
lun 18/04/2016 - 10:20

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 et sites web modernes soulèvent tous des problèmes liés aux performances. Plus les systèmes s’assouplissent et se complexifient et plus les ressources nécessaires à leur exécution augmentent. Cependant, les utilisateurs recherchent de plus en plus de contenus personnalisés tout en conservant un temps de réponse optimal. Ce dernier critère étant devenu récemment l’un des plus importants pour le référencement via Google, nous ne pouvons plus nous permettre de ne pas faire attention aux performances de nos réalisations.

Afin d’éviter au maximum les calculs coûteux et répétitif, nous disposons de plusieurs niveaux de cache qui couvrent la totalité des besoins d’un développeur.

Le cache applicatif

Déjà présent dans Drupal 7 et dans la plupart des Frameworks modernes, cette couche de cache est la plus profonde et correspond à ce que l’on peut faire de plus unitaire possible. On distingue deux variantes en fonction du besoin : le cache statique et le cache d’exécution. La différence majeure entre ces deux types de cache concerne la durée pendant laquelle sont conservées les données. Le cache d’exécution n’est valable que pendant la durée de vie d’une requête HTTP alors que le cache statique lui peut persister plus longtemps.

Cache d’exécution

Dans Drupal 7 comme dans Drupal 8, celui-ci n’a pas changé. Il s’agit donc de faire appel à la fonction drupal_static() et de récupérer son retour comme une référence.

<span class="token shell-comment comment"># anywhere.php</span>
<span class="token keyword">function</span> <span class="token function">my_overused_function</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token variable">$cache</span> <span class="token operator">=</span> <span class="token operator">&amp;</span><span class="token function">drupal_static</span><span class="token punctuation">(</span><span class="token constant">__FUNCTION__</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token variable">$cache</span><span class="token punctuation">[</span><span class="token variable">$id</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$cache</span><span class="token punctuation">[</span><span class="token variable">$id</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">do_something_long</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token variable">$cache</span><span class="token punctuation">[</span>id<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

De cette manière, même si la fonction est appelée de très nombreuses fois lors de la même requête HTTP, le calcul ne sera fait qu’une fois et le résultat sera conservé en mémoire. Attention à ne pas abuser de cette fonction sous peine de saturer la mémoire de votre serveur.

Dans Drupal 8, ce cache aura tendance à être moins utilisé que dans Drupal 7 car la plupart des objets PHP sont toujours passés comme référence et peuvent donc faire office de conteneurs de stockage pour le temps de l’exécution.

Cache statique

Parfois, un calcul est si coûteux qu’il est préférable de le faire une seule fois ou de temps en temps mais pas à chaque requête. Dans Drupal 7 il était possible de stocker des données en cache de manière temporaire ou permanente à l’aide de la fonction cache_set() et de les récupérer à l’aide de cache_get().

Le principe de base n’a pas changé dans Drupal 8. Seule la syntaxe a évolué, introduisant l’utilisation d’un service de gestion du cache.

<span class="token shell-comment comment"># anywhere.php</span>
<span class="token keyword">function</span> <span class="token function">do_something_long</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token variable">$cid</span> <span class="token operator">=</span> <span class="token string">'something_long:'</span> <span class="token punctuation">.</span> <span class="token variable">$id</span><span class="token punctuation">;</span>

  <span class="token variable">$data</span> <span class="token operator">=</span> <span class="token keyword">NULL</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$cache</span> <span class="token operator">=</span> \<span class="token package">Drupal</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">cache</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token variable">$cid</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$data</span> <span class="token operator">=</span> <span class="token variable">$cache</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">data</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token variable">$data</span> <span class="token operator">=</span> <span class="token function">do_the_really_long_thing</span><span class="token punctuation">(</span><span class="token variable">$id</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    \<span class="token package">Drupal</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">cache</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token variable">$cid</span><span class="token punctuation">,</span> <span class="token variable">$data</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

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

Par défaut, les caches sont stockés en base de données, dans un ensemble de tables cache_* qui peuvent être déportées en mémoire grâce à un système de type Memcache ou Redis.

Si vous avez un peu suivi vous aurez constaté que l’on peut combiner ensemble les deux exemples précédents afin de ne solliciter le cache statique qu’une seule fois par requête. Cette combinaison n’est ni nécessaire ni obligatoire car il faut toujours garder à l’esprit que tout a un coût. Le cache d’exécution a un coût en mémoire et le stockage statique un coût d’accès. Votre travail est de faire un choix en adéquation avec la situation.

Les caches d’objets

Depuis Drupal 6 et l’avènement de CCK puis Drupal 7 et l’arrivée des entités, les objets de données et la structure de la base se sont grandement complexifiés. Reconstituer une entité à partir de ses données en tenant compte des révisions, de la langue, des gestions de droits et des nombreux hooks qui peuvent intervenir au milieu de tout ça est devenu une opération coûteuse. Sont donc apparus des modules tels que Entity Cache ou Views Content Cache par exemple.

Dans Drupal 8, toutes les entités de contenu et de configuration sont mises en cache par défaut.

Le cache de rendu

Voilà enfin la grosse nouveauté de Drupal 8 sur laquelle repose une grande partie des performances. Introduit en tant que concept dans Drupal 7 à l’aide du module Render Cache, le cache de rendu s’est imposé dans le cœur petit à petit jusqu’à la beta 16 et l’apparition du module Internal Dynamic Page Cache.

Le principe de base est assez simple. Chaque élément d’une page que l’on veut construire est un render array qui dispose de méta-données spécifiant les conditions dans lesquelles il doit être mis en cache et qui peut contenir des sous-éléments disposant de leurs propres méta-données. Lors de la première construction de la page, chacun de ces éléments va être rendu et ses méta-données ainsi que celles de ses enfants vont être additionnées. Au second chargement de la page, seuls les éléments qui doivent varier seront compilés, les autres seront récupérés depuis le cache. On dit que ces méta-données se “propagent” (bubble up).

Ce module fonctionne donc sur le même modèle qu’un Reverse Proxy qui irait consommer les ressources associées à des Edge Sides Includes uniquement lorsque nécessaire. Ce modèle est tellement efficace qu’il ne serait pas étonnant de le voir apparaître sur les systèmes concurrents de Drupal à court terme.

Les méta-données de cache

Au nombre de quatre, celles-ci permettent de s’assurer que le cache est justement dosé. Un cache trop important permettrait potentiellement à des utilisateurs de voir le contenu destiné à d’autres alors qu’un cache pas assez important solliciterait trop les ressources du serveur. Afin de trouver un juste milieu, les méta-données de cache vont permettre de définir de façon explicite les variations possibles et les dépendances du contenu mis en cache.

Les clefs de cache (cache keys) permettent d’identifier l’objet rendu. Elles correspondent à l’identifiant utilisé par le service de cache pour stocker ou récupérer des données. Elles servent de préfixe à toutes les variantes du même contenu afin de pouvoir invalider un ensemble de variantes plus facilement. Les clefs de cache ne sont pas obligatoires et devraient être utilisées uniquement lorsque l’on souhaite procéder à un cache intermédiaire. Par exemple, la plupart des champs d’une entité n’auront pas de clé de cache car ils sont faciles à reconstruire. En revanche, certains champs plus complexes comme par exemple une référence d’entité en auront une pour pouvoir limiter l’invalidation et éviter d’avoir à tout reconstruire si un seul champ venait à changer.

Les contextes de cache (cache contexts) permettent de définir les diverses variantes du contenu. Ainsi, chaque variante est mise en cache séparément et servie aux utilisateurs qui correspondent aux critères. Par exemple, il est possible de définir un contenu qui variera en fonction de la langue et des permissions de l’utilisateur. Cela permettra de garantir que tous les utilisateurs disposant d’une combinaison de permissions équivalente verront le même contenu pour peu qu’ils accèdent au site dans la même langue.

Les contextes de cache sont résolus au moment de la génération du contenu et ajoutés aux clefs de cache afin de générer l’identifiant de la variation.

Les tags de cache (cache tags) sont utilisés pour définir les dépendances entre le cache et des objets arbitraires. Toutes les entités de contenu et de configuration sont des dépendances valides mais il est également possible de créer ses propres dépendances en cas de besoin. Si jamais l’un de ces objets est modifié, tous les caches qui lui sont rattachés sont immédiatement invalidés.

Les tags de cache s’expriment, par convention, sous la forme objet:identifiant. Par exemple, node:2 est un tag valable. Afin de simplifier la création de ces tags pour déclarer des entités en tant que dépendance, il est possible d’utiliser la méthode EntityInterface::getCacheTags() qui retourne les tags correspondant à l’entité (rappelez vous que ces derniers se propagent).

L’âge d’expiration (cache max-age) sert quant à lui dans les rares cas où l’on souhaite forcer le cache à expirer au bout d’un certain temps même si ses composants n’ont pas changé. C’est souvent utilisé lorsque l’on souhaite faire une remontée d’éléments aléatoire rafraîchie régulièrement. Cette valeur est exprimée en secondes. La valeur 0 indique que l’élément ne doit jamais être mis en cache et la valeur Cache::PERMANENT qu’il ne doit jamais expirer sur la base de son âge.

Voici un exemple reprenant tous ces éléments :

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

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

<span class="token keyword">function</span> <span class="token function">link_my_nodes</span><span class="token punctuation">(</span><span class="token variable">$ids</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token variable">$nodes</span> <span class="token operator">=</span> \<span class="token package">Drupal</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">entityManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getStorage</span><span class="token punctuation">(</span><span class="token string">'node'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">loadMultiple</span><span class="token punctuation">(</span><span class="token variable">$ids</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token variable">$build</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
    <span class="token string">'#theme'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'links'</span><span class="token punctuation">,</span>
    <span class="token string">'#links'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token string">'#cache'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span>
      <span class="token string">'keys'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token string">'link_my_nodes'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token string">'contexts'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token string">'language'</span><span class="token punctuation">,</span> <span class="token string">'user.permissions'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token string">'tags'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token string">'max-age'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">PERMANENT</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>

  <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$nodes</span> <span class="token keyword">as</span> <span class="token variable">$node</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#links'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
      <span class="token string">'title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">label</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'url'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">urlInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'contexts'</span><span class="token punctuation">]</span> <span class="token operator">=</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">mergeContexts</span><span class="token punctuation">(</span><span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'contexts'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getCacheContexts</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'tags'</span><span class="token punctuation">]</span> <span class="token operator">=</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">mergeTags</span><span class="token punctuation">(</span><span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'tags'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getCacheTags</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'max-age'</span><span class="token punctuation">]</span> <span class="token operator">=</span> Cache<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">mergeMaxAges</span><span class="token punctuation">(</span><span class="token variable">$build</span><span class="token punctuation">[</span><span class="token string">'#cache'</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'max-age'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getCacheMaxAge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

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

Le cache HTTP

Le dernier niveau de cache est le plus simple en apparence. Il concerne le cache du navigateur (ou du reverse proxy si vous en avez un). Ce dernier s’appuie sur les entêtes HTTP renvoyés par l’application comme Cache-Control, Expires, Etag, Vary, Pragma, etc. La bonne nouvelle c’est que Drupal 8 s’en sort beaucoup mieux que son prédécesseur dans la création de ces entêtes. En effet, celui-ci s’appuie sur les méta-données de cache de la page, et donc de tous les éléments qui la constituent, pour générer ces entêtes. La configuration pour faire fonctionner Drupal 8 derrière un reverse proxy ou un CDN est donc très simple voir même pas nécessaire du tout !

Par Artusamak
Julien Dubois

Drupal 8 : Les types d’entités

Drupal 8 : Les types d’entités
Artusamak
jeu 14/04/2016 - 10:04

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 !

Contexte

L’uniformisation débutée avec Drupal 7 pour stocker des données se poursuit dans Drupal 8 et a pris beaucoup de maturité puisque tous les contenus stockés sont maintenant des entités. Celles-ci sont scindées en deux groupes.
Nous avons d’un côté les
entités de configuration qui permettent, comme leur nom l’indique, d’exporter de la configuration avec par exemple les paramètres d’affichages des nœuds, les paramètres des blocs, les vues, etc.
De l’autre côté nous avons les
entités de contenu qui permettent de stocker les données générées par les utilisateurs. Il s’agit des nœuds, de la taxonomie, des comptes utilisateur, etc.

Entité de configuration

Les entités de configuration n’ont pas de champs. Elles sont des exports de fichiers YAML avec une structure définie dans un fichier <module>/config/schema/<type>.schema.yml.
Les entités de configuration sont exportées via l’API de configuration. Vous aurez besoin de créer un type d’entité de configuration lorsque vous inventerez quelque chose qui peut avoir des sous-types, les données plus simple
s pouvant être gérées par l’API de configuration directement.
Voici quelques exemples de types d’entités de configuration : des vues, des vocabulaires, des styles d’image, etc.
Des données “simples”, directement gérées par l’API de configuration et exportables peuvent être : le nom du site, l’activation de l'agrégation des JS, la version d’une librairie, etc. (Voir le chapitre dédié à la configuration pour plus de détails).

Entité de contenu

En devenant plus matures, les entités de contenu ont appris à gérer par défaut les révisions, le multilinguisme et l’ajout de champs.

Dans Drupal 7, il était de la responsabilité du contrôleur d’entité de gérer le chargement, la sauvegarde, la suppression et l’affichage des entités. Dans Drupal 8, ces responsabilités ont été séparées dans plusieurs objets. Le chargement, la sauvegarde et la suppression sont gérés par le handler de stockage. Des handlers supplémentaires existent pour gérer le contrôle d’accès, l’affichage, le listing et les formulaires (création, édition).

Type d’entités

Les types d’entités de contenu héritent avec D8 de classes dédiées ; terminée l’utilisation de stdClass() à tout va. Les classes qui définissent les types d’objets sont situées dans src/Entity et implémentent une Annotation, concept qui remplace en partie les hook_info() de Drupal 7 et qui sera largement abordé dans un chapitre ultérieur.

Exemple :

<span class="comment shell-comment token"># User.php</span>
<span class="comment token" spellcheck="true">/**
* Defines the user entity class.
*
* The base table name here is plural, despite Drupal table naming standards,
* because "user" is a reserved word in many databases.
*
* @ContentEntityType(
*   id = "user",
*   label = @Translation("User"),
*   handlers = {
*     "storage" = "Drupal\user\UserStorage",
*     "storage_schema" = "Drupal\user\UserStorageSchema",
*     "access" = "Drupal\user\UserAccessControlHandler",
*     "list_builder" = "Drupal\user\UserListBuilder",
*     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
*     "views_data" = "Drupal\user\UserViewsData",
*     "route_provider" = {
*         "html" = "Drupal\user\Entity\UserRouteProvider",
*     },
*     "form" = {
*         "default" = "Drupal\user\ProfileForm",
*         "cancel" = "Drupal\user\Form\UserCancelForm",
*         "register" = "Drupal\user\RegisterForm"
*     },
*     "translation" = "Drupal\user\ProfileTranslationHandler"
*   },
*   admin_permission = "administer user",
*   base_table = "users",
*   data_table = "users_field_data",
*   label_callback = "user_format_name",
*   translatable = TRUE,
*   entity_keys = {
*     "id" = "uid",
*     "langcode" = "langcode",
*     "uuid" = "uuid"
*   },
*   links = {
*     "canonical" = "/user/{user}",
*     "edit-form" = "/user/{user}/edit",
*     "cancel-form" = "/user/{user}/cancel",
*     "collection" = "/admin/people",
*   },
*   field_ui_base_route = "entity.user.admin_form",
* )
*/</span>
<span class="keyword token">class</span> <span class="class-name token">User</span> <span class="keyword token">extends</span> <span class="class-name token">ContentEntityBase</span> <span class="keyword token">implements</span> <span class="class-name token">UserInterface</span> <span class="punctuation token">{</span>
<span class="punctuation token">}</span>

A l’instar de Drupal 7, lors de la conception de votre application, si vous imaginez une table dédiée pour stocker vos données, vous aurez probablement besoin de créer un nouveau type d’entité de contenu.

Champs

Vous rêviez de pouvoir utiliser un formateur sur le titre de vos contenus ?
Drupal l’a fait (enfin presque). Tout est devenu champ dans Drupal 8, les anciennes propriétés sont devenues des champs de base (
Base fields) et les champs “traditionnels” s’appellent maintenant des champs configurables (Configurable fields). Tous les champs bénéficient de l’usage des formateurs et des widgets. Les champs de base seront visibles par défaut dans l’interface, les champs configurables le seront après l’écriture d’une ligne de code qui changera le paramètre qui les cache par défaut.

Les champs sont tous typés via la Typed Data API pour palier au faible typage de PHP (exemples : booléen, entier, entity reference, date, changed, uri, uuid, etc). Cela permet lorsque vous manipulez vos données avec l’Entity API de ne pas avoir d'ambiguïté.
Les champs de base sont exposés via la méthode
baseFieldDefinitions() de la classe qui définit le type d’entité. Vous pouvez y indiquer également le libellé, la description, le fait que la propriété soit en lecture seule ou non, exposée ou non dans l’écran de configuration de l’affichage ou des formulaires, etc.

Internationalisation

Tous les contenus étant traduisibles par défaut, plus besoin de manipuler explicitement la langue au niveau des champs (les “und”, “fr” et autre), on indique la langue au niveau de l’entité lors de sa récupération.

<span class="comment token" spellcheck="true">// Récupération et utilisation d'une traduction pour la langue active.</span>
<span class="token variable">$translation</span> <span class="operator token">=</span> <span class="token variable">$entity</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getTranslation</span><span class="punctuation token">(</span><span class="token variable">$active_langcode</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="token variable">$value</span> <span class="operator token">=</span> <span class="token variable">$translation</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">field_foo</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="comment token" spellcheck="true">// Récupérer le contenu traduit d'une entité.</span>
<span class="token variable">$entity</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getTranslation</span><span class="punctuation token">(</span><span class="string token">'it'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="comment token" spellcheck="true">// Récupérer un original.</span>
<span class="token variable">$translation</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getUntranslated</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="comment token" spellcheck="true">// Récupérer la langue d'une entité.</span>
<span class="token variable">$translation</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">language</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">id</span><span class="punctuation token">;</span>
Par Artusamak
Julien Dubois

Drupal 8 : Les types d’entités

Drupal 8 : Les types d’entités
Artusamak
jeu 14/04/2016 - 10:04

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 !

Contexte

L’uniformisation débutée avec Drupal 7 pour stocker des données se poursuit dans Drupal 8 et a pris beaucoup de maturité puisque tous les contenus stockés sont maintenant des entités. Celles-ci sont scindées en deux groupes.
Nous avons d’un côté les
entités de configuration qui permettent, comme leur nom l’indique, d’exporter de la configuration avec par exemple les paramètres d’affichages des nœuds, les paramètres des blocs, les vues, etc.
De l’autre côté nous avons les
entités de contenu qui permettent de stocker les données générées par les utilisateurs. Il s’agit des nœuds, de la taxonomie, des comptes utilisateur, etc.

Entité de configuration

Les entités de configuration n’ont pas de champs. Elles sont des exports de fichiers YAML avec une structure définie dans un fichier <module>/config/schema/<type>.schema.yml.
Les entités de configuration sont exportées via l’API de configuration. Vous aurez besoin de créer un type d’entité de configuration lorsque vous inventerez quelque chose qui peut avoir des sous-types, les données plus simple
s pouvant être gérées par l’API de configuration directement.
Voici quelques exemples de types d’entités de configuration : des vues, des vocabulaires, des styles d’image, etc.
Des données “simples”, directement gérées par l’API de configuration et exportables peuvent être : le nom du site, l’activation de l'agrégation des JS, la version d’une librairie, etc. (Voir le chapitre dédié à la configuration pour plus de détails).

Entité de contenu

En devenant plus matures, les entités de contenu ont appris à gérer par défaut les révisions, le multilinguisme et l’ajout de champs.

Dans Drupal 7, il était de la responsabilité du contrôleur d’entité de gérer le chargement, la sauvegarde, la suppression et l’affichage des entités. Dans Drupal 8, ces responsabilités ont été séparées dans plusieurs objets. Le chargement, la sauvegarde et la suppression sont gérés par le handler de stockage. Des handlers supplémentaires existent pour gérer le contrôle d’accès, l’affichage, le listing et les formulaires (création, édition).

Type d’entités

Les types d’entités de contenu héritent avec D8 de classes dédiées ; terminée l’utilisation de stdClass() à tout va. Les classes qui définissent les types d’objets sont situées dans src/Entity et implémentent une Annotation, concept qui remplace en partie les hook_info() de Drupal 7 et qui sera largement abordé dans un chapitre ultérieur.

Exemple :

<span class="comment shell-comment token"># User.php</span>
<span class="comment token" spellcheck="true">/**
* Defines the user entity class.
*
* The base table name here is plural, despite Drupal table naming standards,
* because "user" is a reserved word in many databases.
*
* @ContentEntityType(
*   id = "user",
*   label = @Translation("User"),
*   handlers = {
*     "storage" = "Drupal\user\UserStorage",
*     "storage_schema" = "Drupal\user\UserStorageSchema",
*     "access" = "Drupal\user\UserAccessControlHandler",
*     "list_builder" = "Drupal\user\UserListBuilder",
*     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
*     "views_data" = "Drupal\user\UserViewsData",
*     "route_provider" = {
*         "html" = "Drupal\user\Entity\UserRouteProvider",
*     },
*     "form" = {
*         "default" = "Drupal\user\ProfileForm",
*         "cancel" = "Drupal\user\Form\UserCancelForm",
*         "register" = "Drupal\user\RegisterForm"
*     },
*     "translation" = "Drupal\user\ProfileTranslationHandler"
*   },
*   admin_permission = "administer user",
*   base_table = "users",
*   data_table = "users_field_data",
*   label_callback = "user_format_name",
*   translatable = TRUE,
*   entity_keys = {
*     "id" = "uid",
*     "langcode" = "langcode",
*     "uuid" = "uuid"
*   },
*   links = {
*     "canonical" = "/user/{user}",
*     "edit-form" = "/user/{user}/edit",
*     "cancel-form" = "/user/{user}/cancel",
*     "collection" = "/admin/people",
*   },
*   field_ui_base_route = "entity.user.admin_form",
* )
*/</span>
<span class="keyword token">class</span> <span class="class-name token">User</span> <span class="keyword token">extends</span> <span class="class-name token">ContentEntityBase</span> <span class="keyword token">implements</span> <span class="class-name token">UserInterface</span> <span class="punctuation token">{</span>
<span class="punctuation token">}</span>

A l’instar de Drupal 7, lors de la conception de votre application, si vous imaginez une table dédiée pour stocker vos données, vous aurez probablement besoin de créer un nouveau type d’entité de contenu.

Champs

Vous rêviez de pouvoir utiliser un formateur sur le titre de vos contenus ?
Drupal l’a fait (enfin presque). Tout est devenu champ dans Drupal 8, les anciennes propriétés sont devenues des champs de base (
Base fields) et les champs “traditionnels” s’appellent maintenant des champs configurables (Configurable fields). Tous les champs bénéficient de l’usage des formateurs et des widgets. Les champs de base seront visibles par défaut dans l’interface, les champs configurables le seront après l’écriture d’une ligne de code qui changera le paramètre qui les cache par défaut.

Les champs sont tous typés via la Typed Data API pour palier au faible typage de PHP (exemples : booléen, entier, entity reference, date, changed, uri, uuid, etc). Cela permet lorsque vous manipulez vos données avec l’Entity API de ne pas avoir d'ambiguïté.
Les champs de base sont exposés via la méthode
baseFieldDefinitions() de la classe qui définit le type d’entité. Vous pouvez y indiquer également le libellé, la description, le fait que la propriété soit en lecture seule ou non, exposée ou non dans l’écran de configuration de l’affichage ou des formulaires, etc.

Internationalisation

Tous les contenus étant traduisibles par défaut, plus besoin de manipuler explicitement la langue au niveau des champs (les “und”, “fr” et autre), on indique la langue au niveau de l’entité lors de sa récupération.

<span class="comment token" spellcheck="true">// Récupération et utilisation d'une traduction pour la langue active.</span>
<span class="token variable">$translation</span> <span class="operator token">=</span> <span class="token variable">$entity</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getTranslation</span><span class="punctuation token">(</span><span class="token variable">$active_langcode</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="token variable">$value</span> <span class="operator token">=</span> <span class="token variable">$translation</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">field_foo</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="comment token" spellcheck="true">// Récupérer le contenu traduit d'une entité.</span>
<span class="token variable">$entity</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getTranslation</span><span class="punctuation token">(</span><span class="string token">'it'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="comment token" spellcheck="true">// Récupérer un original.</span>
<span class="token variable">$translation</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getUntranslated</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="comment token" spellcheck="true">// Récupérer la langue d'une entité.</span>
<span class="token variable">$translation</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">language</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">id</span><span class="punctuation token">;</span>
Par Artusamak
Julien Dubois

Drupal 8 : Les types d’entités

Drupal 8 : Les types d’entités
jeu, 14/04/2016 - 10: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 !

Contexte

L’uniformisation débutée avec Drupal 7 pour stocker des données se poursuit dans Drupal 8 et a pris beaucoup de maturité puisque tous les contenus stockés sont maintenant des entités. Celles-ci sont scindées en deux groupes.
Nous avons d’un côté les
entités de configuration qui permettent, comme leur nom l’indique, d’exporter de la configuration avec par exemple les paramètres d’affichages des nœuds, les paramètres des blocs, les vues, etc.
De l’autre côté nous avons les
entités de contenu qui permettent de stocker les données générées par les utilisateurs. Il s’agit des nœuds, de la taxonomie, des comptes utilisateur, etc.

Entité de configuration

Les entités de configuration n’ont pas de champs. Elles sont des exports de fichiers YAML avec une structure définie dans un fichier <module>/config/schema/<type>.schema.yml.
Les entités de configuration sont exportées via l’API de configuration. Vous aurez besoin de créer un type d’entité de configuration lorsque vous inventerez quelque chose qui peut avoir des sous-types, les données plus simple
s pouvant être gérées par l’API de configuration directement.
Voici quelques exemples de types d’entités de configuration : des vues, des vocabulaires, des styles d’image, etc.
Des données “simples”, directement gérées par l’API de configuration et exportables peuvent être : le nom du site, l’activation de l'agrégation des JS, la version d’une librairie, etc. (Voir le chapitre dédié à la configuration pour plus de détails).

Entité de contenu

En devenant plus matures, les entités de contenu ont appris à gérer par défaut les révisions, le multilinguisme et l’ajout de champs.

Dans Drupal 7, il était de la responsabilité du contrôleur d’entité de gérer le chargement, la sauvegarde, la suppression et l’affichage des entités. Dans Drupal 8, ces responsabilités ont été séparées dans plusieurs objets. Le chargement, la sauvegarde et la suppression sont gérés par le handler de stockage. Des handlers supplémentaires existent pour gérer le contrôle d’accès, l’affichage, le listing et les formulaires (création, édition).

Type d’entités

Les types d’entités de contenu héritent avec D8 de classes dédiées ; terminée l’utilisation de stdClass() à tout va. Les classes qui définissent les types d’objets sont situées dans src/Entity et implémentent une Annotation, concept qui remplace en partie les hook_info() de Drupal 7 et qui sera largement abordé dans un chapitre ultérieur.

Exemple :

<span class="comment shell-comment token"># User.php</span>
<span class="comment token" spellcheck="true">/**
* Defines the user entity class.
*
* The base table name here is plural, despite Drupal table naming standards,
* because "user" is a reserved word in many databases.
*
* @ContentEntityType(
*   id = "user",
*   label = @Translation("User"),
*   handlers = {
*     "storage" = "Drupal\user\UserStorage",
*     "storage_schema" = "Drupal\user\UserStorageSchema",
*     "access" = "Drupal\user\UserAccessControlHandler",
*     "list_builder" = "Drupal\user\UserListBuilder",
*     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
*     "views_data" = "Drupal\user\UserViewsData",
*     "route_provider" = {
*         "html" = "Drupal\user\Entity\UserRouteProvider",
*     },
*     "form" = {
*         "default" = "Drupal\user\ProfileForm",
*         "cancel" = "Drupal\user\Form\UserCancelForm",
*         "register" = "Drupal\user\RegisterForm"
*     },
*     "translation" = "Drupal\user\ProfileTranslationHandler"
*   },
*   admin_permission = "administer user",
*   base_table = "users",
*   data_table = "users_field_data",
*   label_callback = "user_format_name",
*   translatable = TRUE,
*   entity_keys = {
*     "id" = "uid",
*     "langcode" = "langcode",
*     "uuid" = "uuid"
*   },
*   links = {
*     "canonical" = "/user/{user}",
*     "edit-form" = "/user/{user}/edit",
*     "cancel-form" = "/user/{user}/cancel",
*     "collection" = "/admin/people",
*   },
*   field_ui_base_route = "entity.user.admin_form",
* )
*/</span>
<span class="keyword token">class</span> <span class="class-name token">User</span> <span class="keyword token">extends</span> <span class="class-name token">ContentEntityBase</span> <span class="keyword token">implements</span> <span class="class-name token">UserInterface</span> <span class="punctuation token">{</span>
<span class="punctuation token">}</span>

A l’instar de Drupal 7, lors de la conception de votre application, si vous imaginez une table dédiée pour stocker vos données, vous aurez probablement besoin de créer un nouveau type d’entité de contenu.

Champs

Vous rêviez de pouvoir utiliser un formateur sur le titre de vos contenus ?
Drupal l’a fait (enfin presque). Tout est devenu champ dans Drupal 8, les anciennes propriétés sont devenues des champs de base (
Base fields) et les champs “traditionnels” s’appellent maintenant des champs configurables (Configurable fields). Tous les champs bénéficient de l’usage des formateurs et des widgets. Les champs de base seront visibles par défaut dans l’interface, les champs configurables le seront après l’écriture d’une ligne de code qui changera le paramètre qui les cache par défaut.

Les champs sont tous typés via la Typed Data API pour palier au faible typage de PHP (exemples : booléen, entier, entity reference, date, changed, uri, uuid, etc). Cela permet lorsque vous manipulez vos données avec l’Entity API de ne pas avoir d'ambiguïté.
Les champs de base sont exposés via la méthode
baseFieldDefinitions() de la classe qui définit le type d’entité. Vous pouvez y indiquer également le libellé, la description, le fait que la propriété soit en lecture seule ou non, exposée ou non dans l’écran de configuration de l’affichage ou des formulaires, etc.

Internationalisation

Tous les contenus étant traduisibles par défaut, plus besoin de manipuler explicitement la langue au niveau des champs (les “und”, “fr” et autre), on indique la langue au niveau de l’entité lors de sa récupération.

<span class="comment token" spellcheck="true">// Récupération et utilisation d'une traduction pour la langue active.</span>
<span class="token variable">$translation</span> <span class="operator token">=</span> <span class="token variable">$entity</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getTranslation</span><span class="punctuation token">(</span><span class="token variable">$active_langcode</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="token variable">$value</span> <span class="operator token">=</span> <span class="token variable">$translation</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">field_foo</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="comment token" spellcheck="true">// Récupérer le contenu traduit d'une entité.</span>
<span class="token variable">$entity</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getTranslation</span><span class="punctuation token">(</span><span class="string token">'it'</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="comment token" spellcheck="true">// Récupérer un original.</span>
<span class="token variable">$translation</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">getUntranslated</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
<span class="comment token" spellcheck="true">// Récupérer la langue d'une entité.</span>
<span class="token variable">$translation</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="function token">language</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="operator token">-</span><span class="operator token">&gt;</span><span class="property token">id</span><span class="punctuation token">;</span>
Par admin

Compte Rendu du Sprint média

Message de Woprrr, qui a coordonné ce sprint :

"Cela c'est bien passé de notre côté."

Du point de vue du sprint nous avons décidé d'élargir le périmètre d'action ("Au moins un module qui pourrait avoir un impact sur la stack média") du sprint afin d'inciter à participer un peu plus pour les entreprises.

Points extrêmement positif "vpeltot" et "woprrr" (moi même), avons porté deux modules qui je pense seront extrêmement pratiques dans l'utilisation de Drupal 8.

1er initiative d'utilisation des form_modes mis à disposition par Drupal 8, qui permet une prise en main rapide et simple de ceux-ci. L'avantage est qu'il utilise entièrement les concepts de Drupal 8 empruntés à SF2, c'est à dire qu'il n'est pas un alter de la structure de D8 afin de combler le manque des form_modes dans Drupal 8, mais il s'intègre à n'importe quelle entité qui est capable d'utiliser les form_modes et utilise les routes de cette dernière. Il a été décidé que ce seraitune priorité, son intégration avec EntityBrowser et offrira la possibilité de pouvoir gérer les modes de rendu des formulaires disponible via EB, avec aussi une intégration de prévue avec Inline_entity_form pour tirer au maximum parti du potentiel des form_modes d'entities.

Lien du module : https://www.drupal.org/project/form_mode_manager

Seconde initiative et pas des moindres Entity_clone, comme le nom l'indique il s'agit de proposer la possibilité de cloner n'importe quelle entité de manière assez intelligente. Il peut s'agir d'un pur clonage, mais aussi d'un clone qui clone en cascade les références de ce dernier. Ainsi permettant de cloner un contenu à l'identique, mais de proposer une vie propre à ce clone. Même ci il s'agit d'une initiative très large en terme d'utilisation, l'intérêt pour Média est clairement de pouvoir cloner les différents Média sans devoir re-uploader les fichiers, pour un media sans pour autant avoir les memes champs au niveau de l'entité. Il permet également de pouvoir cloner les différents Providers offerts par EntityBrowser.

Lien du module : https://www.drupal.org/project/entity_clone

Dernier point aborder pendant le sprint ImageWidgetCrop qui lui est un pur produit Média. Il y a eu stabilisation et release stable du module et nous avons commencé à reprendre les différentes features request proposées par différents contributeurs et utilisateurs. Mais nous manquons encore de monde pour l'essayer et améliorer sont utilisation par des "novices" ou encore par les Sites Builders qui n'ont encore pas de notions.
Et enfin quelques contributions sur le Gitbook média (https://drupal-media.gitbooks.io/drupal8-guide/content/modules/image_widget_crop/intro.html).

Lien du module : https://www.drupal.org/project/image_widget_crop

En conclusion :
Un sprint "calme" concernant l'implication d'acteurs extérieurs à média, mais de belles initiatives sont nées. Je pense également que nous devons également remercier Degetel pour son implication et le temps qui a été consacré à ce sprint. J'espère dans le futur pouvoir organiser d'autres sprints ou initiative au sein de Drupal et plus particulièrement au sein de la communauté française qui je pense à un énorme potentiel, car nous avons de belles choses à faire valoir !

En page d'accueil : 
Par admin

Compte Rendu du Sprint média

Message de Woprrr, qui a coordonné ce sprint :

"Cela c'est bien passé de notre côté."

Du point de vue du sprint nous avons décidé d'élargir le périmètre d'action ("Au moins un module qui pourrait avoir un impact sur la stack média") du sprint afin d'inciter à participer un peu plus pour les entreprises.

Points extrêmement positif "vpeltot" et "woprrr" (moi même), avons porté deux modules qui je pense seront extrêmement pratiques dans l'utilisation de Drupal 8.

1er initiative d'utilisation des form_modes mis à disposition par Drupal 8, qui permet une prise en main rapide et simple de ceux-ci. L'avantage est qu'il utilise entièrement les concepts de Drupal 8 empruntés à SF2, c'est à dire qu'il n'est pas un alter de la structure de D8 afin de combler le manque des form_modes dans Drupal 8, mais il s'intègre à n'importe quelle entité qui est capable d'utiliser les form_modes et utilise les routes de cette dernière. Il a été décidé que ce seraitune priorité, son intégration avec EntityBrowser et offrira la possibilité de pouvoir gérer les modes de rendu des formulaires disponible via EB, avec aussi une intégration de prévue avec Inline_entity_form pour tirer au maximum parti du potentiel des form_modes d'entities.

Lien du module : https://www.drupal.org/project/form_mode_manager

Seconde initiative et pas des moindres Entity_clone, comme le nom l'indique il s'agit de proposer la possibilité de cloner n'importe quelle entité de manière assez intelligente. Il peut s'agir d'un pur clonage, mais aussi d'un clone qui clone en cascade les références de ce dernier. Ainsi permettant de cloner un contenu à l'identique, mais de proposer une vie propre à ce clone. Même ci il s'agit d'une initiative très large en terme d'utilisation, l'intérêt pour Média est clairement de pouvoir cloner les différents Média sans devoir re-uploader les fichiers, pour un media sans pour autant avoir les memes champs au niveau de l'entité. Il permet également de pouvoir cloner les différents Providers offerts par EntityBrowser.

Lien du module : https://www.drupal.org/project/entity_clone

Dernier point aborder pendant le sprint ImageWidgetCrop qui lui est un pur produit Média. Il y a eu stabilisation et release stable du module et nous avons commencé à reprendre les différentes features request proposées par différents contributeurs et utilisateurs. Mais nous manquons encore de monde pour l'essayer et améliorer sont utilisation par des "novices" ou encore par les Sites Builders qui n'ont encore pas de notions.
Et enfin quelques contributions sur le Gitbook média (https://drupal-media.gitbooks.io/drupal8-guide/content/modules/image_widget_crop/intro.html).

Lien du module : https://www.drupal.org/project/image_widget_crop

En conclusion :
Un sprint "calme" concernant l'implication d'acteurs extérieurs à média, mais de belles initiatives sont nées. Je pense également que nous devons également remercier Degetel pour son implication et le temps qui a été consacré à ce sprint. J'espère dans le futur pouvoir organiser d'autres sprints ou initiative au sein de Drupal et plus particulièrement au sein de la communauté française qui je pense à un énorme potentiel, car nous avons de belles choses à faire valoir !

En page d'accueil : 
Par Artusamak
Julien Dubois

Drupal 8 : Field API / Créer un nouveau type de champ

Drupal 8 : Field API / Créer un nouveau type de champ
Artusamak
mar 12/04/2016 - 15:25

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 !

Principes

Après l’intégration de CCK dans le coeur de Drupal 7, la Field API a continué d’évoluer. Il était frustrant de ne pas pouvoir appliquer aux propriétés des entités des formateurs ou des widgets. C’est maintenant possible si la déclaration de ces propriétés le permet ou en l’altérant (Cela est nécessaire pour l’entité Node et les champs title, author, created, etc.).

Les propriétés s’appellent des champs de base (Base fields) et les champs “classiques” sont des champs configurables (Configurable fields). Les propriétés n’ont pas totalement disparu : les champs sont composés de propriétés. Exemple : une valeur pour un champ de texte simple (Textfield), une valeur et un format de texte pour un champ de texte long (Textarea).
Dans la plupart des cas, les propriétés seront associées à une colonne dans une table mais pas toujours. Il est possible d’avoir des propriétés qui stockent des données calculées. Dans le cas précédent, le texte rendu dans le format de texte sera une donnée calculée que nous stockerons dans une propriété. Ces propriétés calculées combinées au cache de l’API de rendu permettent d’optimiser les performances. Pour voir comment implémenter ces données calculées, référez-vous à la documentation.

Les types de champs, formateurs et widgets sont des Plugins. Si vous souhaitez implémenter l’un de ces trois type de Plugin il suffit d’implémenter l’interface associée ou, si vous ne souhaitez pas réinventer la roue, étendre la classe annotée de base de chaque type :

TYPE DE PLUGIN

ANNOTATION

INTERFACE

CLASSE

Type de champ

@FieldType

FieldItemInterface

FieldItemBase

Widget

@FieldWidget

WidgetInterface

WidgetBase

Formateur

@FieldFormatter

FormatterInterface

FormatterBase

Le chemin PSR-4 de votre classe prend la forme suivante :  src/Plugin/Field/Field&lt;Type|Widget|Formatter&gt;/&lt;nomPlugin&gt;.php

À noter également que les champs sont maintenant stockés par type d’entité. Il devient donc possible d’utiliser le même nom de champ à plusieurs endroits.

Pour les développeurs habitués à développer avec les champs dans Drupal 7, un changement sémantique intervient avec Drupal 8. La notion de “champ” (Field) définissant la structure des données est maintenant appelé FieldStorage alors que la notion “d’instance de champ” identifiant la configuration d’un champ associé à une entité s’appelle désormais Field.

Exemple

La création d’un type de champ peut être nécessaire dans Drupal, notamment pour réaliser un champ composé de plusieurs données (comme link qui propose un titre et une URL). Cela permet de s’affranchir des modules comme Field collection ou Paragraphs pour simplifier le modèle de données ou compléter un type de champ existant proche de nos besoins. Nous allons voir l'implémentation d'un type de champ collectant un ISBN de livre à 10 ou 13 chiffres (2 champs de collecte).

Dans Drupal un champ est composé de 3 parties. Une principale, le type de champ (FieldType) qui est la définition technique du champ, et deux parties d’interface ; à savoir : le widget (FieldWidget) utilisé pendant l’édition d’un contenu et le formateur (FieldFormatter) qui s’occupe du rendu du champ lors de son affichage.

Ces deux derniers éléments peuvent être créés indépendamment du FieldType, ce qui permet de proposer des FieldWidget ou des FieldFormatter pour n’importe quel FieldType.

Chacune de ces 3 parties est gérée à l’aide de Plugins. Voici pour chacune les informations nécessaires à leur implémentation ainsi qu’un aperçu des méthodes qui remplissent les fonctions d’anciens hooks sous Drupal 7.

Le stockage des données

FieldType 

Interface : Drupal\Core\Field\FieldItemInterface

Classe abstraite : Drupal\Core\Field\FieldItemBase

Répertoire d’implémentation : /src/Plugin/Field/FieldType/

Namespace à utiliser : Drupal\&lt;module&gt;\Plugin\Field\FieldType

Nom du hook Drupal 7

Équivalent Drupal 8

hook_field_info()

Annotation de type @FieldType

hook_field_schema()

FieldItemInterface::schema()

hook_field_is_empty()

ComplexDataInterface::isEmpty()

L’Annotation de ce Plugin est assez simple, l’identifiant machine, un label, une description et les valeurs par défaut du widget et du formateur utilisé pour ce champ.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token operator">/</span><span class="token operator">*</span><span class="token operator">*</span>
<span class="token operator">*</span> Plugin implementation of the <span class="token string">'isbn'</span> field type<span class="token punctuation">.</span>
<span class="token operator">*</span>
<span class="token operator">*</span> @<span class="token function">FieldType</span><span class="token punctuation">(</span>
<span class="token operator">*</span>   id <span class="token operator">=</span> <span class="token string">"isbn"</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   label <span class="token operator">=</span> @<span class="token function">Translation</span><span class="token punctuation">(</span><span class="token string">"Isbn"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   description <span class="token operator">=</span> @<span class="token function">Translation</span><span class="token punctuation">(</span><span class="token string">"Stores a ISBN string in various format."</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   default_widget <span class="token operator">=</span> <span class="token string">"isbn_default"</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   default_formatter <span class="token operator">=</span> <span class="token string">"isbn"</span><span class="token punctuation">,</span>
<span class="token operator">*</span> <span class="token punctuation">)</span>
<span class="token operator">*</span>

La création d’un type de champ passe par la définition du modèle de données de ce champ. Pour cela il faut implémenter les méthodes schema() et propertyDefinitions(). Comme pour Drupal 7, avec le hook_field_schema() il s’agit de décrire la table SQL qui va recevoir les données.

Dans notre cas nous aurons 2 valeurs de l’ISBN à stocker.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">schema</span><span class="token punctuation">(</span>FieldStorageDefinitionInterface <span class="token variable">$field_definition</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
   <span class="token string">'columns'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
     <span class="token string">'isbn_13'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
       <span class="token string">'description'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'The isbn number with 13 digits.'</span><span class="token punctuation">,</span>
       <span class="token string">'type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span>
       <span class="token string">'length'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">13</span><span class="token punctuation">,</span>
     <span class="token punctuation">)</span><span class="token punctuation">,</span>
     <span class="token string">'isbn_10'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
       <span class="token string">'description'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'The isbn number with 10 digits.'</span><span class="token punctuation">,</span>
       <span class="token string">'type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span>
       <span class="token string">'length'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">10</span><span class="token punctuation">,</span>
     <span class="token punctuation">)</span><span class="token punctuation">,</span>
   <span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

La méthode propertyDefinitions() quant à elle permet une description au niveau de Drupal et propose plus d’informations. La description des propriétés se fait grâce à la Typed Data API qui permet d’interagir avec les données et leurs meta-données. Exemple : donner un nom plus compréhensible par un humain avec setLabel(), rendre un champ obligatoire avec setRequired(), définir des contraintes de validation avec addConstraint()...

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">propertyDefinitions</span><span class="token punctuation">(</span>FieldStorageDefinitionInterface <span class="token variable">$field_definition</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$properties</span><span class="token punctuation">[</span><span class="token string">'isbn_13'</span><span class="token punctuation">]</span> <span class="token operator">=</span> DataDefinition<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token string">'string'</span><span class="token punctuation">)</span>
   <span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">setLabel</span><span class="token punctuation">(</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-13'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token variable">$properties</span><span class="token punctuation">[</span><span class="token string">'isbn_10'</span><span class="token punctuation">]</span> <span class="token operator">=</span> DataDefinition<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token string">'string'</span><span class="token punctuation">)</span>
   <span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">setLabel</span><span class="token punctuation">(</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-10'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

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

Si l’un veut créer un champ composé, deux autres méthodes sont particulièrement importantes. isEmpty() permet à Drupal de savoir si votre champ doit être considéré comme vide pour afficher ou non le champ par exemple. Dans notre cas, on va considérer que c’est la valeur de la propriété ‘isbn_13’ qui va déterminer cela.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$value</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'isbn_13'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token function">empty</span><span class="token punctuation">(</span><span class="token variable">$value</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

La deuxième est mainPropertyName() qui permet de définir le nom de la propriété principale. La plupart des champs de base utilisent ‘value’ mais cela devient vite gênant quand on construit des champs complexes. Il est donc essentiel de fournir cette information aux autres modules pour qu’ils puissent utiliser au mieux notre champ.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">mainPropertyName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token string">'isbn_13'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

Bien sûr il existe encore de multiples méthodes, notamment les fieldSettingsForm(), preSave(), delete() et autres pour agir à différents moments de la vie de nos données de champ mais je vous laisse découvrir cela en regardant l’interface Drupal\Core\Field\FieldItemInterface.

On notera la présence de generateSampleValue() permettant de fournir un jeu de données basiques jouant le rôle de données de substitution lors de la génération de contenus fictifs. (Avec Devel generate par exemple).

Le widget du champ

FieldWidget

Interface : Drupal\Core\Field\WidgetInterface

Classe abstraite : Drupal\Core\Field\WidgetBase

Répertoire d’implémentation : /src/Plugin/Field/FieldWidget/

Namespace à utiliser : Drupal\&lt;module&gt;\Plugin\Field\FieldWidget

Nom du hook Drupal 7

Equivalent Drupal 8

hook_field_widget_info()

Annotation de type @FieldWidget

hook_field_widget_form()

WidgetInterface::formElement()

hook_field_widget_error()

WidgetInterface::errorElement()

Encore une fois, on utilisera un Plugin pour créer le widget de notre champ. Nous allons donc le définir à l’aide d’une Annotation.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldWidget/IsbnWidget.php</span>

<span class="token comment" spellcheck="true">/**
* Plugin implementation of the 'isbn' widget.
*
* @FieldWidget(
*   id = "isbn_default",
*   label = @Translation("ISBN"),
*   field_types = {
*     "isbn"
*   }
* )
*/</span>

Ensuite, nous allons définir le formulaire qui sera utilisé dans l’interface pour réaliser la saisie des valeurs du champs dans la méthode formElement().

<span class="token shell-comment comment">#/src/Plugin/Field/FieldWidget/IsbnWidget.php</span>

<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">formElement</span><span class="token punctuation">(</span>FieldItemListInterface <span class="token variable">$items</span><span class="token punctuation">,</span> <span class="token variable">$delta</span><span class="token punctuation">,</span> <span class="token keyword">array</span> <span class="token variable">$element</span><span class="token punctuation">,</span> <span class="token keyword">array</span> <span class="token operator">&amp;</span><span class="token variable">$form</span><span class="token punctuation">,</span> FormStateInterface <span class="token variable">$form_state</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'isbn_13'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
      <span class="token string">'#type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'textfield'</span><span class="token punctuation">,</span>
      <span class="token string">'#title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#placeholder'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getSetting</span><span class="token punctuation">(</span><span class="token string">'placeholder_isbn_13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#default_value'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token function">isset</span><span class="token punctuation">(</span><span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_13</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_13</span> <span class="token punctuation">:</span> <span class="token keyword">NULL</span><span class="token punctuation">,</span>
      <span class="token string">'#required'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'#required'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'isbn_10'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
      <span class="token string">'#type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'textfield'</span><span class="token punctuation">,</span>
      <span class="token string">'#title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-10'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#placeholder'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getSetting</span><span class="token punctuation">(</span><span class="token string">'placeholder_isbn_10'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#default_value'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token function">isset</span><span class="token punctuation">(</span><span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_10</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_10</span> <span class="token punctuation">:</span> <span class="token keyword">NULL</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

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

On remarquera l’utilisation de getSettings() qui permet de récupérer de la configuration qui pourrait être définie via settingsForm() et configurable dans l’interface de gestion de l’affichage du formulaire.

Le formateur du champ

FieldFormatter

Interface : Drupal\Core\Field\FormatterInterface

Classe abstraite : Drupal\Core\Field\FormatterBase

Répertoire d’implémentation : /src/Plugin/Field/FieldFormatter/

Namespace à utiliser : Drupal\&lt;module&gt;\Plugin\Field\FieldFormatter

Nom du hook Drupal 7

Equivalent Drupal 8

hook_field_formatter_info()

Annotation de type @FieldFormatter

hook_field_formatter_view()

FormatterInterface::viewElements()

hook_field_formatter_settings_form()

FormatterInterface::settingsForm()

hook_field_formatter_settings_summary()

FormatterInterface::settingsSummary()

Pour le formateur d’un champ, le travail est le même, cela débute par l’implémentation d’un Plugin avec une Annotation @FieldFormatter. Il faut ensuite implémenter viewElements() pour définir le rendu des valeurs. Enfin settingsForm() et settingsSummary() permettent de définir le formulaire des paramètres du champ et le résumé de leur valeur utilisés dans l’interface de gestion des View modes..

Une version détaillée de cette partie est visible dans notre article sur la création d'un formateur de champs.

Validation des données

Drupal introduit un concept de validateurs de contraintes issu de Symfony permettant de contrôler les valeurs d’un fieldType à la sauvegarde. On pourrait rajouter par exemple la ligne

<span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">addConstraint</span><span class="token punctuation">(</span><span class="token string">'Length'</span><span class="token punctuation">,</span> <span class="token keyword">array</span><span class="token punctuation">(</span><span class="token string">'max'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">13</span><span class="token punctuation">,</span> min <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">13</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

sur les propriétés définies dans propertyDefinitions() pour que cette validation soit faite automatiquement. Il en existe de plusieurs type (unicité, plage, bundle, etc) et il est possible de les étendre car ce sont des plugins de type @Constraint, cela sera vu dans un autre chapitre.

De manière plus classique, il est toujours possible de faire des validations au niveau du formulaire du widget en utilisant la Form API. La validation d’un élément du formulaire utilise toujours #element_validate, par contre on passe maintenant un tableau avec la classe utilisée et la méthode de la classe, plutôt qu’un nom de fonction.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldWidget/IsbnWidget.php</span>

<span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'isbn_13'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
<span class="token string">'#type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'textfield'</span><span class="token punctuation">,</span>
<span class="token string">'#title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">'#placeholder'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getSetting</span><span class="token punctuation">(</span><span class="token string">'placeholder_isbn_13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">'#default_value'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$default_isbn_value</span><span class="token punctuation">,</span>
<span class="token string">'#required'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'#required'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token string">'#element_validate'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span><span class="token keyword">array</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token punctuation">,</span> <span class="token string">'validateIsbnElement'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

Schéma et Configuration du champ

La plupart des Plugins implémentés durant cet exercice peuvent s’enrichir de configurations. Dans ce cas, elles sont stockées à l’aide d’entités de configuration et il faut déclarer le schéma de ces entités pour qu’elles puissent être exportées par le gestionnaire de configuration, profiter de la traduction et permettre le typage automatique des données.

La déclaration de ces schémas se fait dans le fichier /config/schema/isbn.schema.yml comme cela a été vue dans le chapitre sur la configuration (Configuration : fondements)

<span class="token comment" spellcheck="true">#/config/schema/isbn.schema.yml</span>

<span class="token key atrule">field.widget.settings.isbn_default</span><span class="token punctuation">:</span>
<span class="token key atrule">type</span><span class="token punctuation">:</span> mapping
<span class="token key atrule">label</span><span class="token punctuation">:</span> <span class="token string">'Isbn format settings'</span>
<span class="token key atrule">mapping</span><span class="token punctuation">:</span>
   <span class="token key atrule">placeholder_isbn_10</span><span class="token punctuation">:</span>
     <span class="token key atrule">type</span><span class="token punctuation">:</span> string
     <span class="token key atrule">label</span><span class="token punctuation">:</span> <span class="token string">'Placeholder for ISBN 10'</span>
   <span class="token key atrule">placeholder_isbn_13</span><span class="token punctuation">:</span>
     <span class="token key atrule">type</span><span class="token punctuation">:</span> string
     <span class="token key atrule">label</span><span class="token punctuation">:</span> <span class="token string">'Placeholder for ISBN 13'</span>
Par Artusamak
Julien Dubois

Drupal 8 : Field API / Créer un nouveau type de champ

Drupal 8 : Field API / Créer un nouveau type de champ
mar, 12/04/2016 - 15:25
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 !

Principes

Après l’intégration de CCK dans le coeur de Drupal 7, la Field API a continué d’évoluer. Il était frustrant de ne pas pouvoir appliquer aux propriétés des entités des formateurs ou des widgets. C’est maintenant possible si la déclaration de ces propriétés le permet ou en l’altérant (Cela est nécessaire pour l’entité Node et les champs title, author, created, etc.).

Les propriétés s’appellent des champs de base (Base fields) et les champs “classiques” sont des champs configurables (Configurable fields). Les propriétés n’ont pas totalement disparu : les champs sont composés de propriétés. Exemple : une valeur pour un champ de texte simple (Textfield), une valeur et un format de texte pour un champ de texte long (Textarea).
Dans la plupart des cas, les propriétés seront associées à une colonne dans une table mais pas toujours. Il est possible d’avoir des propriétés qui stockent des données calculées. Dans le cas précédent, le texte rendu dans le format de texte sera une donnée calculée que nous stockerons dans une propriété. Ces propriétés calculées combinées au cache de l’API de rendu permettent d’optimiser les performances. Pour voir comment implémenter ces données calculées, référez-vous à la documentation.

Les types de champs, formateurs et widgets sont des Plugins. Si vous souhaitez implémenter l’un de ces trois type de Plugin il suffit d’implémenter l’interface associée ou, si vous ne souhaitez pas réinventer la roue, étendre la classe annotée de base de chaque type :

TYPE DE PLUGIN

ANNOTATION

INTERFACE

CLASSE

Type de champ

@FieldType

FieldItemInterface

FieldItemBase

Widget

@FieldWidget

WidgetInterface

WidgetBase

Formateur

@FieldFormatter

FormatterInterface

FormatterBase

Le chemin PSR-4 de votre classe prend la forme suivante :  src/Plugin/Field/Field&lt;Type|Widget|Formatter&gt;/&lt;nomPlugin&gt;.php

À noter également que les champs sont maintenant stockés par type d’entité. Il devient donc possible d’utiliser le même nom de champ à plusieurs endroits.

Pour les développeurs habitués à développer avec les champs dans Drupal 7, un changement sémantique intervient avec Drupal 8. La notion de “champ” (Field) définissant la structure des données est maintenant appelé FieldStorage alors que la notion “d’instance de champ” identifiant la configuration d’un champ associé à une entité s’appelle désormais Field.

Exemple

La création d’un type de champ peut être nécessaire dans Drupal, notamment pour réaliser un champ composé de plusieurs données (comme link qui propose un titre et une URL). Cela permet de s’affranchir des modules comme Field collection ou Paragraphs pour simplifier le modèle de données ou compléter un type de champ existant proche de nos besoins. Nous allons voir l'implémentation d'un type de champ collectant un ISBN de livre à 10 ou 13 chiffres (2 champs de collecte).

Dans Drupal un champ est composé de 3 parties. Une principale, le type de champ (FieldType) qui est la définition technique du champ, et deux parties d’interface ; à savoir : le widget (FieldWidget) utilisé pendant l’édition d’un contenu et le formateur (FieldFormatter) qui s’occupe du rendu du champ lors de son affichage.

Ces deux derniers éléments peuvent être créés indépendamment du FieldType, ce qui permet de proposer des FieldWidget ou des FieldFormatter pour n’importe quel FieldType.

Chacune de ces 3 parties est gérée à l’aide de Plugins. Voici pour chacune les informations nécessaires à leur implémentation ainsi qu’un aperçu des méthodes qui remplissent les fonctions d’anciens hooks sous Drupal 7.

Le stockage des données

FieldType 

Interface : Drupal\Core\Field\FieldItemInterface

Classe abstraite : Drupal\Core\Field\FieldItemBase

Répertoire d’implémentation : /src/Plugin/Field/FieldType/

Namespace à utiliser : Drupal\&lt;module&gt;\Plugin\Field\FieldType

Nom du hook Drupal 7

Équivalent Drupal 8

hook_field_info()

Annotation de type @FieldType

hook_field_schema()

FieldItemInterface::schema()

hook_field_is_empty()

ComplexDataInterface::isEmpty()

L’Annotation de ce Plugin est assez simple, l’identifiant machine, un label, une description et les valeurs par défaut du widget et du formateur utilisé pour ce champ.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token operator">/</span><span class="token operator">*</span><span class="token operator">*</span>
<span class="token operator">*</span> Plugin implementation of the <span class="token string">'isbn'</span> field type<span class="token punctuation">.</span>
<span class="token operator">*</span>
<span class="token operator">*</span> @<span class="token function">FieldType</span><span class="token punctuation">(</span>
<span class="token operator">*</span>   id <span class="token operator">=</span> <span class="token string">"isbn"</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   label <span class="token operator">=</span> @<span class="token function">Translation</span><span class="token punctuation">(</span><span class="token string">"Isbn"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   description <span class="token operator">=</span> @<span class="token function">Translation</span><span class="token punctuation">(</span><span class="token string">"Stores a ISBN string in various format."</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   default_widget <span class="token operator">=</span> <span class="token string">"isbn_default"</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   default_formatter <span class="token operator">=</span> <span class="token string">"isbn"</span><span class="token punctuation">,</span>
<span class="token operator">*</span> <span class="token punctuation">)</span>
<span class="token operator">*</span>

La création d’un type de champ passe par la définition du modèle de données de ce champ. Pour cela il faut implémenter les méthodes schema() et propertyDefinitions(). Comme pour Drupal 7, avec le hook_field_schema() il s’agit de décrire la table SQL qui va recevoir les données.

Dans notre cas nous aurons 2 valeurs de l’ISBN à stocker.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">schema</span><span class="token punctuation">(</span>FieldStorageDefinitionInterface <span class="token variable">$field_definition</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
   <span class="token string">'columns'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
     <span class="token string">'isbn_13'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
       <span class="token string">'description'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'The isbn number with 13 digits.'</span><span class="token punctuation">,</span>
       <span class="token string">'type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span>
       <span class="token string">'length'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">13</span><span class="token punctuation">,</span>
     <span class="token punctuation">)</span><span class="token punctuation">,</span>
     <span class="token string">'isbn_10'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
       <span class="token string">'description'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'The isbn number with 10 digits.'</span><span class="token punctuation">,</span>
       <span class="token string">'type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span>
       <span class="token string">'length'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">10</span><span class="token punctuation">,</span>
     <span class="token punctuation">)</span><span class="token punctuation">,</span>
   <span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

La méthode propertyDefinitions() quant à elle permet une description au niveau de Drupal et propose plus d’informations. La description des propriétés se fait grâce à la Typed Data API qui permet d’interagir avec les données et leurs meta-données. Exemple : donner un nom plus compréhensible par un humain avec setLabel(), rendre un champ obligatoire avec setRequired(), définir des contraintes de validation avec addConstraint()...

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">propertyDefinitions</span><span class="token punctuation">(</span>FieldStorageDefinitionInterface <span class="token variable">$field_definition</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$properties</span><span class="token punctuation">[</span><span class="token string">'isbn_13'</span><span class="token punctuation">]</span> <span class="token operator">=</span> DataDefinition<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token string">'string'</span><span class="token punctuation">)</span>
   <span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">setLabel</span><span class="token punctuation">(</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-13'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token variable">$properties</span><span class="token punctuation">[</span><span class="token string">'isbn_10'</span><span class="token punctuation">]</span> <span class="token operator">=</span> DataDefinition<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token string">'string'</span><span class="token punctuation">)</span>
   <span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">setLabel</span><span class="token punctuation">(</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-10'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

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

Si l’un veut créer un champ composé, deux autres méthodes sont particulièrement importantes. isEmpty() permet à Drupal de savoir si votre champ doit être considéré comme vide pour afficher ou non le champ par exemple. Dans notre cas, on va considérer que c’est la valeur de la propriété ‘isbn_13’ qui va déterminer cela.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$value</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'isbn_13'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token function">empty</span><span class="token punctuation">(</span><span class="token variable">$value</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

La deuxième est mainPropertyName() qui permet de définir le nom de la propriété principale. La plupart des champs de base utilisent ‘value’ mais cela devient vite gênant quand on construit des champs complexes. Il est donc essentiel de fournir cette information aux autres modules pour qu’ils puissent utiliser au mieux notre champ.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">mainPropertyName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token string">'isbn_13'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

Bien sûr il existe encore de multiples méthodes, notamment les fieldSettingsForm(), preSave(), delete() et autres pour agir à différents moments de la vie de nos données de champ mais je vous laisse découvrir cela en regardant l’interface Drupal\Core\Field\FieldItemInterface.

On notera la présence de generateSampleValue() permettant de fournir un jeu de données basiques jouant le rôle de données de substitution lors de la génération de contenus fictifs. (Avec Devel generate par exemple).

Le widget du champ

FieldWidget

Interface : Drupal\Core\Field\WidgetInterface

Classe abstraite : Drupal\Core\Field\WidgetBase

Répertoire d’implémentation : /src/Plugin/Field/FieldWidget/

Namespace à utiliser : Drupal\&lt;module&gt;\Plugin\Field\FieldWidget

Nom du hook Drupal 7

Equivalent Drupal 8

hook_field_widget_info()

Annotation de type @FieldWidget

hook_field_widget_form()

WidgetInterface::formElement()

hook_field_widget_error()

WidgetInterface::errorElement()

Encore une fois, on utilisera un Plugin pour créer le widget de notre champ. Nous allons donc le définir à l’aide d’une Annotation.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldWidget/IsbnWidget.php</span>

<span class="token comment" spellcheck="true">/**
* Plugin implementation of the 'isbn' widget.
*
* @FieldWidget(
*   id = "isbn_default",
*   label = @Translation("ISBN"),
*   field_types = {
*     "isbn"
*   }
* )
*/</span>

Ensuite, nous allons définir le formulaire qui sera utilisé dans l’interface pour réaliser la saisie des valeurs du champs dans la méthode formElement().

<span class="token shell-comment comment">#/src/Plugin/Field/FieldWidget/IsbnWidget.php</span>

<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">formElement</span><span class="token punctuation">(</span>FieldItemListInterface <span class="token variable">$items</span><span class="token punctuation">,</span> <span class="token variable">$delta</span><span class="token punctuation">,</span> <span class="token keyword">array</span> <span class="token variable">$element</span><span class="token punctuation">,</span> <span class="token keyword">array</span> <span class="token operator">&amp;</span><span class="token variable">$form</span><span class="token punctuation">,</span> FormStateInterface <span class="token variable">$form_state</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'isbn_13'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
      <span class="token string">'#type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'textfield'</span><span class="token punctuation">,</span>
      <span class="token string">'#title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#placeholder'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getSetting</span><span class="token punctuation">(</span><span class="token string">'placeholder_isbn_13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#default_value'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token function">isset</span><span class="token punctuation">(</span><span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_13</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_13</span> <span class="token punctuation">:</span> <span class="token keyword">NULL</span><span class="token punctuation">,</span>
      <span class="token string">'#required'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'#required'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'isbn_10'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
      <span class="token string">'#type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'textfield'</span><span class="token punctuation">,</span>
      <span class="token string">'#title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-10'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#placeholder'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getSetting</span><span class="token punctuation">(</span><span class="token string">'placeholder_isbn_10'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#default_value'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token function">isset</span><span class="token punctuation">(</span><span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_10</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_10</span> <span class="token punctuation">:</span> <span class="token keyword">NULL</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

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

On remarquera l’utilisation de getSettings() qui permet de récupérer de la configuration qui pourrait être définie via settingsForm() et configurable dans l’interface de gestion de l’affichage du formulaire.

Le formateur du champ

FieldFormatter

Interface : Drupal\Core\Field\FormatterInterface

Classe abstraite : Drupal\Core\Field\FormatterBase

Répertoire d’implémentation : /src/Plugin/Field/FieldFormatter/

Namespace à utiliser : Drupal\&lt;module&gt;\Plugin\Field\FieldFormatter

Nom du hook Drupal 7

Equivalent Drupal 8

hook_field_formatter_info()

Annotation de type @FieldFormatter

hook_field_formatter_view()

FormatterInterface::viewElements()

hook_field_formatter_settings_form()

FormatterInterface::settingsForm()

hook_field_formatter_settings_summary()

FormatterInterface::settingsSummary()

Pour le formateur d’un champ, le travail est le même, cela débute par l’implémentation d’un Plugin avec une Annotation @FieldFormatter. Il faut ensuite implémenter viewElements() pour définir le rendu des valeurs. Enfin settingsForm() et settingsSummary() permettent de définir le formulaire des paramètres du champ et le résumé de leur valeur utilisés dans l’interface de gestion des View modes..

Une version détaillée de cette partie est visible dans notre article sur la création d'un formateur de champs.

Validation des données

Drupal introduit un concept de validateurs de contraintes issu de Symfony permettant de contrôler les valeurs d’un fieldType à la sauvegarde. On pourrait rajouter par exemple la ligne

<span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">addConstraint</span><span class="token punctuation">(</span><span class="token string">'Length'</span><span class="token punctuation">,</span> <span class="token keyword">array</span><span class="token punctuation">(</span><span class="token string">'max'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">13</span><span class="token punctuation">,</span> min <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">13</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

sur les propriétés définies dans propertyDefinitions() pour que cette validation soit faite automatiquement. Il en existe de plusieurs type (unicité, plage, bundle, etc) et il est possible de les étendre car ce sont des plugins de type @Constraint, cela sera vu dans un autre chapitre.

De manière plus classique, il est toujours possible de faire des validations au niveau du formulaire du widget en utilisant la Form API. La validation d’un élément du formulaire utilise toujours #element_validate, par contre on passe maintenant un tableau avec la classe utilisée et la méthode de la classe, plutôt qu’un nom de fonction.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldWidget/IsbnWidget.php</span>

<span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'isbn_13'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
<span class="token string">'#type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'textfield'</span><span class="token punctuation">,</span>
<span class="token string">'#title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">'#placeholder'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getSetting</span><span class="token punctuation">(</span><span class="token string">'placeholder_isbn_13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">'#default_value'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$default_isbn_value</span><span class="token punctuation">,</span>
<span class="token string">'#required'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'#required'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token string">'#element_validate'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span><span class="token keyword">array</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token punctuation">,</span> <span class="token string">'validateIsbnElement'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

Schéma et Configuration du champ

La plupart des Plugins implémentés durant cet exercice peuvent s’enrichir de configurations. Dans ce cas, elles sont stockées à l’aide d’entités de configuration et il faut déclarer le schéma de ces entités pour qu’elles puissent être exportées par le gestionnaire de configuration, profiter de la traduction et permettre le typage automatique des données.

La déclaration de ces schémas se fait dans le fichier /config/schema/isbn.schema.yml comme cela a été vue dans le chapitre sur la configuration (Configuration : fondements)

<span class="token comment" spellcheck="true">#/config/schema/isbn.schema.yml</span>

<span class="token key atrule">field.widget.settings.isbn_default</span><span class="token punctuation">:</span>
<span class="token key atrule">type</span><span class="token punctuation">:</span> mapping
<span class="token key atrule">label</span><span class="token punctuation">:</span> <span class="token string">'Isbn format settings'</span>
<span class="token key atrule">mapping</span><span class="token punctuation">:</span>
   <span class="token key atrule">placeholder_isbn_10</span><span class="token punctuation">:</span>
     <span class="token key atrule">type</span><span class="token punctuation">:</span> string
     <span class="token key atrule">label</span><span class="token punctuation">:</span> <span class="token string">'Placeholder for ISBN 10'</span>
   <span class="token key atrule">placeholder_isbn_13</span><span class="token punctuation">:</span>
     <span class="token key atrule">type</span><span class="token punctuation">:</span> string
     <span class="token key atrule">label</span><span class="token punctuation">:</span> <span class="token string">'Placeholder for ISBN 13'</span>
Par Artusamak
Julien Dubois

Drupal 8 : Field API / Créer un nouveau type de champ

Drupal 8 : Field API / Créer un nouveau type de champ
Artusamak
mar 12/04/2016 - 15:25

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 !

Principes

Après l’intégration de CCK dans le coeur de Drupal 7, la Field API a continué d’évoluer. Il était frustrant de ne pas pouvoir appliquer aux propriétés des entités des formateurs ou des widgets. C’est maintenant possible si la déclaration de ces propriétés le permet ou en l’altérant (Cela est nécessaire pour l’entité Node et les champs title, author, created, etc.).

Les propriétés s’appellent des champs de base (Base fields) et les champs “classiques” sont des champs configurables (Configurable fields). Les propriétés n’ont pas totalement disparu : les champs sont composés de propriétés. Exemple : une valeur pour un champ de texte simple (Textfield), une valeur et un format de texte pour un champ de texte long (Textarea).
Dans la plupart des cas, les propriétés seront associées à une colonne dans une table mais pas toujours. Il est possible d’avoir des propriétés qui stockent des données calculées. Dans le cas précédent, le texte rendu dans le format de texte sera une donnée calculée que nous stockerons dans une propriété. Ces propriétés calculées combinées au cache de l’API de rendu permettent d’optimiser les performances. Pour voir comment implémenter ces données calculées, référez-vous à la documentation.

Les types de champs, formateurs et widgets sont des Plugins. Si vous souhaitez implémenter l’un de ces trois type de Plugin il suffit d’implémenter l’interface associée ou, si vous ne souhaitez pas réinventer la roue, étendre la classe annotée de base de chaque type :

TYPE DE PLUGIN

ANNOTATION

INTERFACE

CLASSE

Type de champ

@FieldType

FieldItemInterface

FieldItemBase

Widget

@FieldWidget

WidgetInterface

WidgetBase

Formateur

@FieldFormatter

FormatterInterface

FormatterBase

Le chemin PSR-4 de votre classe prend la forme suivante :  src/Plugin/Field/Field&lt;Type|Widget|Formatter&gt;/&lt;nomPlugin&gt;.php

À noter également que les champs sont maintenant stockés par type d’entité. Il devient donc possible d’utiliser le même nom de champ à plusieurs endroits.

Pour les développeurs habitués à développer avec les champs dans Drupal 7, un changement sémantique intervient avec Drupal 8. La notion de “champ” (Field) définissant la structure des données est maintenant appelé FieldStorage alors que la notion “d’instance de champ” identifiant la configuration d’un champ associé à une entité s’appelle désormais Field.

Exemple

La création d’un type de champ peut être nécessaire dans Drupal, notamment pour réaliser un champ composé de plusieurs données (comme link qui propose un titre et une URL). Cela permet de s’affranchir des modules comme Field collection ou Paragraphs pour simplifier le modèle de données ou compléter un type de champ existant proche de nos besoins. Nous allons voir l'implémentation d'un type de champ collectant un ISBN de livre à 10 ou 13 chiffres (2 champs de collecte).

Dans Drupal un champ est composé de 3 parties. Une principale, le type de champ (FieldType) qui est la définition technique du champ, et deux parties d’interface ; à savoir : le widget (FieldWidget) utilisé pendant l’édition d’un contenu et le formateur (FieldFormatter) qui s’occupe du rendu du champ lors de son affichage.

Ces deux derniers éléments peuvent être créés indépendamment du FieldType, ce qui permet de proposer des FieldWidget ou des FieldFormatter pour n’importe quel FieldType.

Chacune de ces 3 parties est gérée à l’aide de Plugins. Voici pour chacune les informations nécessaires à leur implémentation ainsi qu’un aperçu des méthodes qui remplissent les fonctions d’anciens hooks sous Drupal 7.

Le stockage des données

FieldType 

Interface : Drupal\Core\Field\FieldItemInterface

Classe abstraite : Drupal\Core\Field\FieldItemBase

Répertoire d’implémentation : /src/Plugin/Field/FieldType/

Namespace à utiliser : Drupal\&lt;module&gt;\Plugin\Field\FieldType

Nom du hook Drupal 7

Équivalent Drupal 8

hook_field_info()

Annotation de type @FieldType

hook_field_schema()

FieldItemInterface::schema()

hook_field_is_empty()

ComplexDataInterface::isEmpty()

L’Annotation de ce Plugin est assez simple, l’identifiant machine, un label, une description et les valeurs par défaut du widget et du formateur utilisé pour ce champ.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token operator">/</span><span class="token operator">*</span><span class="token operator">*</span>
<span class="token operator">*</span> Plugin implementation of the <span class="token string">'isbn'</span> field type<span class="token punctuation">.</span>
<span class="token operator">*</span>
<span class="token operator">*</span> @<span class="token function">FieldType</span><span class="token punctuation">(</span>
<span class="token operator">*</span>   id <span class="token operator">=</span> <span class="token string">"isbn"</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   label <span class="token operator">=</span> @<span class="token function">Translation</span><span class="token punctuation">(</span><span class="token string">"Isbn"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   description <span class="token operator">=</span> @<span class="token function">Translation</span><span class="token punctuation">(</span><span class="token string">"Stores a ISBN string in various format."</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   default_widget <span class="token operator">=</span> <span class="token string">"isbn_default"</span><span class="token punctuation">,</span>
<span class="token operator">*</span>   default_formatter <span class="token operator">=</span> <span class="token string">"isbn"</span><span class="token punctuation">,</span>
<span class="token operator">*</span> <span class="token punctuation">)</span>
<span class="token operator">*</span>

La création d’un type de champ passe par la définition du modèle de données de ce champ. Pour cela il faut implémenter les méthodes schema() et propertyDefinitions(). Comme pour Drupal 7, avec le hook_field_schema() il s’agit de décrire la table SQL qui va recevoir les données.

Dans notre cas nous aurons 2 valeurs de l’ISBN à stocker.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">schema</span><span class="token punctuation">(</span>FieldStorageDefinitionInterface <span class="token variable">$field_definition</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
   <span class="token string">'columns'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
     <span class="token string">'isbn_13'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
       <span class="token string">'description'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'The isbn number with 13 digits.'</span><span class="token punctuation">,</span>
       <span class="token string">'type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span>
       <span class="token string">'length'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">13</span><span class="token punctuation">,</span>
     <span class="token punctuation">)</span><span class="token punctuation">,</span>
     <span class="token string">'isbn_10'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
       <span class="token string">'description'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'The isbn number with 10 digits.'</span><span class="token punctuation">,</span>
       <span class="token string">'type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span>
       <span class="token string">'length'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">10</span><span class="token punctuation">,</span>
     <span class="token punctuation">)</span><span class="token punctuation">,</span>
   <span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

La méthode propertyDefinitions() quant à elle permet une description au niveau de Drupal et propose plus d’informations. La description des propriétés se fait grâce à la Typed Data API qui permet d’interagir avec les données et leurs meta-données. Exemple : donner un nom plus compréhensible par un humain avec setLabel(), rendre un champ obligatoire avec setRequired(), définir des contraintes de validation avec addConstraint()...

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">propertyDefinitions</span><span class="token punctuation">(</span>FieldStorageDefinitionInterface <span class="token variable">$field_definition</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$properties</span><span class="token punctuation">[</span><span class="token string">'isbn_13'</span><span class="token punctuation">]</span> <span class="token operator">=</span> DataDefinition<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token string">'string'</span><span class="token punctuation">)</span>
   <span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">setLabel</span><span class="token punctuation">(</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-13'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token variable">$properties</span><span class="token punctuation">[</span><span class="token string">'isbn_10'</span><span class="token punctuation">]</span> <span class="token operator">=</span> DataDefinition<span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token string">'string'</span><span class="token punctuation">)</span>
   <span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">setLabel</span><span class="token punctuation">(</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-10'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

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

Si l’un veut créer un champ composé, deux autres méthodes sont particulièrement importantes. isEmpty() permet à Drupal de savoir si votre champ doit être considéré comme vide pour afficher ou non le champ par exemple. Dans notre cas, on va considérer que c’est la valeur de la propriété ‘isbn_13’ qui va déterminer cela.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$value</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'isbn_13'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token function">empty</span><span class="token punctuation">(</span><span class="token variable">$value</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

La deuxième est mainPropertyName() qui permet de définir le nom de la propriété principale. La plupart des champs de base utilisent ‘value’ mais cela devient vite gênant quand on construit des champs complexes. Il est donc essentiel de fournir cette information aux autres modules pour qu’ils puissent utiliser au mieux notre champ.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldType/IsbnItem.php</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token function">mainPropertyName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token string">'isbn_13'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

Bien sûr il existe encore de multiples méthodes, notamment les fieldSettingsForm(), preSave(), delete() et autres pour agir à différents moments de la vie de nos données de champ mais je vous laisse découvrir cela en regardant l’interface Drupal\Core\Field\FieldItemInterface.

On notera la présence de generateSampleValue() permettant de fournir un jeu de données basiques jouant le rôle de données de substitution lors de la génération de contenus fictifs. (Avec Devel generate par exemple).

Le widget du champ

FieldWidget

Interface : Drupal\Core\Field\WidgetInterface

Classe abstraite : Drupal\Core\Field\WidgetBase

Répertoire d’implémentation : /src/Plugin/Field/FieldWidget/

Namespace à utiliser : Drupal\&lt;module&gt;\Plugin\Field\FieldWidget

Nom du hook Drupal 7

Equivalent Drupal 8

hook_field_widget_info()

Annotation de type @FieldWidget

hook_field_widget_form()

WidgetInterface::formElement()

hook_field_widget_error()

WidgetInterface::errorElement()

Encore une fois, on utilisera un Plugin pour créer le widget de notre champ. Nous allons donc le définir à l’aide d’une Annotation.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldWidget/IsbnWidget.php</span>

<span class="token comment" spellcheck="true">/**
* Plugin implementation of the 'isbn' widget.
*
* @FieldWidget(
*   id = "isbn_default",
*   label = @Translation("ISBN"),
*   field_types = {
*     "isbn"
*   }
* )
*/</span>

Ensuite, nous allons définir le formulaire qui sera utilisé dans l’interface pour réaliser la saisie des valeurs du champs dans la méthode formElement().

<span class="token shell-comment comment">#/src/Plugin/Field/FieldWidget/IsbnWidget.php</span>

<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">formElement</span><span class="token punctuation">(</span>FieldItemListInterface <span class="token variable">$items</span><span class="token punctuation">,</span> <span class="token variable">$delta</span><span class="token punctuation">,</span> <span class="token keyword">array</span> <span class="token variable">$element</span><span class="token punctuation">,</span> <span class="token keyword">array</span> <span class="token operator">&amp;</span><span class="token variable">$form</span><span class="token punctuation">,</span> FormStateInterface <span class="token variable">$form_state</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'isbn_13'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
      <span class="token string">'#type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'textfield'</span><span class="token punctuation">,</span>
      <span class="token string">'#title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#placeholder'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getSetting</span><span class="token punctuation">(</span><span class="token string">'placeholder_isbn_13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#default_value'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token function">isset</span><span class="token punctuation">(</span><span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_13</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_13</span> <span class="token punctuation">:</span> <span class="token keyword">NULL</span><span class="token punctuation">,</span>
      <span class="token string">'#required'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'#required'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'isbn_10'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
      <span class="token string">'#type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'textfield'</span><span class="token punctuation">,</span>
      <span class="token string">'#title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-10'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#placeholder'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getSetting</span><span class="token punctuation">(</span><span class="token string">'placeholder_isbn_10'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token string">'#default_value'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token function">isset</span><span class="token punctuation">(</span><span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_10</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$delta</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">isbn_10</span> <span class="token punctuation">:</span> <span class="token keyword">NULL</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

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

On remarquera l’utilisation de getSettings() qui permet de récupérer de la configuration qui pourrait être définie via settingsForm() et configurable dans l’interface de gestion de l’affichage du formulaire.

Le formateur du champ

FieldFormatter

Interface : Drupal\Core\Field\FormatterInterface

Classe abstraite : Drupal\Core\Field\FormatterBase

Répertoire d’implémentation : /src/Plugin/Field/FieldFormatter/

Namespace à utiliser : Drupal\&lt;module&gt;\Plugin\Field\FieldFormatter

Nom du hook Drupal 7

Equivalent Drupal 8

hook_field_formatter_info()

Annotation de type @FieldFormatter

hook_field_formatter_view()

FormatterInterface::viewElements()

hook_field_formatter_settings_form()

FormatterInterface::settingsForm()

hook_field_formatter_settings_summary()

FormatterInterface::settingsSummary()

Pour le formateur d’un champ, le travail est le même, cela débute par l’implémentation d’un Plugin avec une Annotation @FieldFormatter. Il faut ensuite implémenter viewElements() pour définir le rendu des valeurs. Enfin settingsForm() et settingsSummary() permettent de définir le formulaire des paramètres du champ et le résumé de leur valeur utilisés dans l’interface de gestion des View modes..

Une version détaillée de cette partie est visible dans notre article sur la création d'un formateur de champs.

Validation des données

Drupal introduit un concept de validateurs de contraintes issu de Symfony permettant de contrôler les valeurs d’un fieldType à la sauvegarde. On pourrait rajouter par exemple la ligne

<span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">addConstraint</span><span class="token punctuation">(</span><span class="token string">'Length'</span><span class="token punctuation">,</span> <span class="token keyword">array</span><span class="token punctuation">(</span><span class="token string">'max'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">13</span><span class="token punctuation">,</span> min <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token number">13</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

sur les propriétés définies dans propertyDefinitions() pour que cette validation soit faite automatiquement. Il en existe de plusieurs type (unicité, plage, bundle, etc) et il est possible de les étendre car ce sont des plugins de type @Constraint, cela sera vu dans un autre chapitre.

De manière plus classique, il est toujours possible de faire des validations au niveau du formulaire du widget en utilisant la Form API. La validation d’un élément du formulaire utilise toujours #element_validate, par contre on passe maintenant un tableau avec la classe utilisée et la méthode de la classe, plutôt qu’un nom de fonction.

<span class="token shell-comment comment">#/src/Plugin/Field/FieldWidget/IsbnWidget.php</span>

<span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'isbn_13'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">array</span><span class="token punctuation">(</span>
<span class="token string">'#type'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'textfield'</span><span class="token punctuation">,</span>
<span class="token string">'#title'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">t</span><span class="token punctuation">(</span><span class="token string">'ISBN-13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">'#placeholder'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">getSetting</span><span class="token punctuation">(</span><span class="token string">'placeholder_isbn_13'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">'#default_value'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$default_isbn_value</span><span class="token punctuation">,</span>
<span class="token string">'#required'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$element</span><span class="token punctuation">[</span><span class="token string">'#required'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token string">'#element_validate'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">array</span><span class="token punctuation">(</span><span class="token keyword">array</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token punctuation">,</span> <span class="token string">'validateIsbnElement'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

Schéma et Configuration du champ

La plupart des Plugins implémentés durant cet exercice peuvent s’enrichir de configurations. Dans ce cas, elles sont stockées à l’aide d’entités de configuration et il faut déclarer le schéma de ces entités pour qu’elles puissent être exportées par le gestionnaire de configuration, profiter de la traduction et permettre le typage automatique des données.

La déclaration de ces schémas se fait dans le fichier /config/schema/isbn.schema.yml comme cela a été vue dans le chapitre sur la configuration (Configuration : fondements)

<span class="token comment" spellcheck="true">#/config/schema/isbn.schema.yml</span>

<span class="token key atrule">field.widget.settings.isbn_default</span><span class="token punctuation">:</span>
<span class="token key atrule">type</span><span class="token punctuation">:</span> mapping
<span class="token key atrule">label</span><span class="token punctuation">:</span> <span class="token string">'Isbn format settings'</span>
<span class="token key atrule">mapping</span><span class="token punctuation">:</span>
   <span class="token key atrule">placeholder_isbn_10</span><span class="token punctuation">:</span>
     <span class="token key atrule">type</span><span class="token punctuation">:</span> string
     <span class="token key atrule">label</span><span class="token punctuation">:</span> <span class="token string">'Placeholder for ISBN 10'</span>
   <span class="token key atrule">placeholder_isbn_13</span><span class="token punctuation">:</span>
     <span class="token key atrule">type</span><span class="token punctuation">:</span> string
     <span class="token key atrule">label</span><span class="token punctuation">:</span> <span class="token string">'Placeholder for ISBN 13'</span>
Par flocondetoile
Adhérent

Une application métier avec Drupal ?

Ce billet est une restranscription synthétique de la conférence Retour d'éxpérience : une application métier avec Drupal donnée au meetup Drupal Lyon le 7 avril 2016.

Drupal est particulièrement reconnu en tant que Gestionnaire de contenus (CMS) et plateforme de développement (CMF). Sa vocation principale est de propulser des sites Internet. Mais Drupal peut-il être utilisé comme support pour développer une application métier ?

Par Artusamak
Julien Dubois

Drupal 8 : Plugins et types de plugins

Drupal 8 : Plugins et types de plugins
ven, 08/04/2016 - 09:24
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 principe de plugins dans Drupal est de permettre au système de fournir une fonctionnalité extensible et remplaçable de manière simple.

D’une certaine manière les plugins remplacent bon nombre de hooks de Drupal 7 (les hook_*_info() et associés).

Deux concepts clés sont liés aux Plugins :

  • Les Plugins
  • Les types de Plugins (Plugins Type)

Si plusieurs plugins remplissent la même fonctionnalité, ils sont du même type (Plugin Type).

Les blocs sont par exemple des Plugins, chaque bloc en est un. Ils sont du Plugin Type Block.

Un autre exemple de Plugin Type est les ImageEffects, ils définissent les actions applicables sur une image. Chaque action ou effet est un Plugin.

Les Plugins sont utiles quand il est nécessaire de pouvoir facilement étendre une fonctionnalité générique mais que les implémentations possibles ne partagent que peu de code commun.
Pour les effets d'image utilisés par les styles d'image, chaque effet rempli le même but « Transformer une image » mais chaque implémentation peut être complètement différente (altérer les couleurs, redimensionner l’image).

Par Artusamak
Julien Dubois

Drupal 8 : Plugins et types de plugins

Drupal 8 : Plugins et types de plugins
Artusamak
ven 08/04/2016 - 09:24

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 principe de plugins dans Drupal est de permettre au système de fournir une fonctionnalité extensible et remplaçable de manière simple.

D’une certaine manière les plugins remplacent bon nombre de hooks de Drupal 7 (les hook_*_info() et associés).

Deux concepts clés sont liés aux Plugins :

  • Les Plugins
  • Les types de Plugins (Plugins Type)

Si plusieurs plugins remplissent la même fonctionnalité, ils sont du même type (Plugin Type).

Les blocs sont par exemple des Plugins, chaque bloc en est un. Ils sont du Plugin Type Block.

Un autre exemple de Plugin Type est les ImageEffects, ils définissent les actions applicables sur une image. Chaque action ou effet est un Plugin.

Les Plugins sont utiles quand il est nécessaire de pouvoir facilement étendre une fonctionnalité générique mais que les implémentations possibles ne partagent que peu de code commun.
Pour les effets d'image utilisés par les styles d'image, chaque effet rempli le même but « Transformer une image » mais chaque implémentation peut être complètement différente (altérer les couleurs, redimensionner l’image).

Par Artusamak
Julien Dubois

Drupal 8 : Plugins et types de plugins

Drupal 8 : Plugins et types de plugins
Artusamak
ven 08/04/2016 - 09:24

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 principe de plugins dans Drupal est de permettre au système de fournir une fonctionnalité extensible et remplaçable de manière simple.

D’une certaine manière les plugins remplacent bon nombre de hooks de Drupal 7 (les hook_*_info() et associés).

Deux concepts clés sont liés aux Plugins :

  • Les Plugins
  • Les types de Plugins (Plugins Type)

Si plusieurs plugins remplissent la même fonctionnalité, ils sont du même type (Plugin Type).

Les blocs sont par exemple des Plugins, chaque bloc en est un. Ils sont du Plugin Type Block.

Un autre exemple de Plugin Type est les ImageEffects, ils définissent les actions applicables sur une image. Chaque action ou effet est un Plugin.

Les Plugins sont utiles quand il est nécessaire de pouvoir facilement étendre une fonctionnalité générique mais que les implémentations possibles ne partagent que peu de code commun.
Pour les effets d'image utilisés par les styles d'image, chaque effet rempli le même but « Transformer une image » mais chaque implémentation peut être complètement différente (altérer les couleurs, redimensionner l’image).

Par vincent59
Vincent Liefooghe

Cartographie Drupal : Views + Geofield Map

Dans les articles précédents (stockage des données notamment) , nous avons vu comment ajouter un champ de type Geofield et l'afficher sous forme de carte.

Nous allons voir ici comment afficher plusieurs points sur une seule carte. Pour cela, nous devons juste installer Views, et activer les modules Views, Views UI et Geofield Map.

drush dl views
drush en views views_ui geofield_map

Création de la vue

Il faut ensuite créer une vue. Pour cela, on va dans Structure /  Vues, puis on va ajouter une vue (admin/structure/views/add). Nous allons nous baser sur le type de contenu créé auparavant,  qui contient un champ Geofield. On donne un nom à la vue, puis on sélectionne le type de contenu (Magasin). On peut créer un block, sur la base d'une liste de champs (Unformatted list of fields).

Si on veut on peut également créer une page, tout dépend de ce que l'on veut faire.

On clique ensuite sur Continue & Edit pour continuer la création de la vue.

Choix des champs

Par défaut, seul le titre est affiché :

Il faut alors cliquer sur "Add", puis choisir le champ Coordonnées qui est de type Geofield.

 

On supprime l'affichage du libellé, et on laisse les valeurs par défaut (y compris le formatter en Well Known Text). En effet c'est dans le type de formatage global que l'on choisira Google Map.

On valide tout cela. Dans les chjamps, on doit donc avoir Title et Coordonnées.

Choix du format d'affichage

A ce stade, on a uniquement le titre et les coordonnées. Rien de très sympathique. Il faut alors changer le format de la vue :

A ce niveau, on peut choisir "Geofield Map" :

On valide en cliquant sur "Apply". On peut alors choisir quel champ sert de source. On va choisir le champ field_coordonnees que l'on vient d'ajouter à notre vue :

On peut laisser les valeurs par défaut dans un premier temps. Si on se rend à l'url de la vue, on a un premier résultat :

Si on clique sur l'un des marqueurs, le titre apparaît.

Amélioration de l'affichage

En modifiant la vue on peut facilement :

  • Ajouter des informations dans la Pop-Up
  • Permettre le scroll dans la carte (ScrollWheel)
  • Mettre un niveau de zoom par défaut (Zoom / Zoom minimum et maximum)

Dans l'exemple, la hauteur de la carte a été modifiée à 450 pixels, et on a mis la description plutôt que le titre dans la pop-up.

C'est globalement la limite du couple Views + Geofield Map, qui offre un premier niveau de formatage, sans beaucoup de souplesse.

On peut aller plus loin, avec d'autres modules. Ceci fera l'objet d'un autre article.

 

 

 

Catégorie: 


Tag: 

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.

Pages