Drupal 8 : déclarer un champ extrafield calculé (computed field)

Lorsque vous devez insérer un champ sur une entité mais que cette donnée est calculée et n'est pas saisie par l'utilisateur, vous avez le réflexe de penser à un extra field. C'est un bon début mais pour appliquer un formateur de champ, vous serez limités car cela n'est pas possible ! Fort heureusement, une solution a été introduite dans Drupal 8, il s'agit des computed fields.

Lorsque vous devez insérer un champ sur une entité mais que cette donnée est calculée et n'est pas saisie par l'utilisateur, vous avez le réflexe de penser à un extra field. C'est un bon début mais pour appliquer un formateur de champ, vous serez limités car cela n'est pas possible ! Fort heureusement, une solution a été introduite dans Drupal 8, il s'agit des computed fields.

Comment cela fonctionne-t-il ?

Au lieu d'utiliser le hook_entity_extra_field_info(), vous allez cette fois déclarer un hook_entity_bundle_field_info() et allez déclarer un base field auquel vous direz qu'il est calculé et indiquerez la classe qui fournie ses données. Avec du code c'est plus simple :

/**
 * Implements hook_entity_bundle_field_info().
 */
function hc_core_entity_bundle_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  $fields = [];
  if ($entity_type->id() == 'node') {
    if ($bundle == 'blog') {
      $fields['blog_related_posts'] = \Drupal\Core\Field\BaseFieldDefinition::create('entity_reference')
        ->setLabel(t('Related posts'))
        ->setComputed(TRUE)
        ->setClass('\Drupal\hc_core\BlogRelatedPostsComputed')
        ->setDisplayConfigurable('view', TRUE);
    }
  }
  return $fields;
}

Avec ce code, si vous videz les caches, vous verrez apparaître dans la gestion de l'affichage de votre entité ce nouveau champ auquel vous pourrez appliquer les formateurs pertinents. Cela dépendra du type de données que vous aurez sélectionné sur lequel il se basera.

Jetons maintenant un œil à la classe qui fournit les données sources du champ.

Pour faire les choses simplement, je vous conseille d'étendre la classe de liste du type de données de votre champ (dans mon exemple je m'appuierai sur EntityReferenceFieldItemList) et nous utiliserons le trait dédié aux champs calculés (computed fields). La classe retourne les NIDs des nœuds qui partagent les mêmes catégories que le nœud actuellement consulté.

# Fichier modules/hc_core/src/BlogRelatedPostsComputed.php
<?php

namespace Drupal\hc_core;

use Drupal\Core\Field\EntityReferenceFieldItemList;
use Drupal\Core\TypedData\ComputedItemListTrait;

class BlogRelatedPostsComputed extends EntityReferenceFieldItemList {
  use ComputedItemListTrait;

  /**
   * Computed related blog posts.
   */
  protected function computeValue() {
    $delta = 0;

    // It's a bit tricky, authors are not UIDs but NIDs because we are looking
    // for humans and humans are nodes, not users.
    $blog_categories = $this->getParent()->getValue()->get('field_category')->getValue();
    $blog_nid = $this->getParent()->getValue()->id();

    if (count($blog_categories) > 0) {
      foreach ($blog_categories as $category) {
        $category_ids[] = $category['target_id'];
      }

      $q = \Drupal::entityQuery('node')
        ->condition('type', 'blog', '=')
        ->condition('field_category', $category_ids, 'IN')
        ->condition('status', NODE_PUBLISHED, '=')
        ->condition('nid', $blog_nid, '<>')
        ->range(0, 5)
        ->sort('created', 'DESC')
        ->execute();
      if ($q) {
        foreach ($q as $rev => $r) {
          $this->list[$delta] = $this->createItem($delta, $r);
          $delta++;
        }
      }
    }
  }
}

Grâce au trait, on se concentre uniquement sur la récupération des valeurs qui nous intéressent et on utilise $this->createItem() pour remplir la collection de valeurs de $this->list.

Vous pourrez ajouter à cela quelques tags pour optimiser le rendu de vos données et vous voilà prêts à exploiter la puissance des champs calculés et rendus grâce à des formateurs de champs ! Plutôt simple, non ?

Commentaires

Caron Christophe Mercredi 15 août 2018 - 22:27
Feature très propre de drupal 8 et facile à mettre en place, ça réponds à pas mal de problématique et participe en soit à l'ergonomie du back office en évitant des saisies à l'internaute
opi Lundi 3 décembre 2018 - 15:54

Super article, je me suis largement servi de ces "extra field deluxe" dans mon projet actuel.

Remarque : lors de l'implementation du hook `hook_entity_bundle_field_info` dans le fichier .module, il est nécessaire d'avoir la ligne `use Drupal\Core\Field\BaseFieldDefinition;` en haut de fichier.

Aussi, pour les gros noob comme moi, ça m'aurait aidé de savoir où mettre le code de la classe `BlogRelatedPostsComputed`, a savoir dans un sous-dossier `src` de son module custom, portant le nom BlogRelatedPostsComputed.php

Ce sont sûrement des erreurs que l'on ne fait plus avec l'experience (et/ou avec un IDE beau gosse), mais qui ne sont pas intuitive quand on débute avec Drupal8

En réponse à par opi
Artusamak Lundi 3 décembre 2018 - 17:24

Concernant le use. On peut effectivement appeler BaseFieldDefinition::create(...) et dans ce cas, il faut ajouter le use en début de fichier (il faut le placer après le mot clé namespace). On peut aussi s'en affranchir en utilisant le nom complet de la classe de la façon suivante : \Drupal\Core\Field\BaseFieldDefinition::create(...).
J'ai modifié l'exemple pour qu'il soit "copiable  / collable", merci pour la remarque.

Concernant la classe, j'ai ajouté un commentaire pour indiquer le nom du fichier, en effet, on prend vite le pli de savoir où placer les fichiers pour que le namespace soit valide. On finit par ne plus indiquer le chemin (un peu comme pour un hook, par habitude, on sait que cela se déclare dans le fichier .module). Ça va rentrer vite ! ;-)

Encore merci pour tes retours.

opi Mardi 12 mars 2019 - 22:47

Autre remarque, pour clarifier le code : La récupération de l'entité hote peut se faire avec $this->getEntity(); plutot que $this->getParent()->getValue(). Je trouve ca plus parlant.

Votre commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement.
Votre adresse servira à afficher un Gravatar et à vous notifier des réponses. Votre commentaire sera anonymisé si ce billet est dépublié pendant plus de 3 mois.

À propos de Julien

Gérant - Co-fondateur - Scrum master & Expert technique

Utilisateur de Drupal depuis 2008, j’ai fait mes armes comme développeur chez Commerce Guys puis me suis mis à encadrer les nouveaux arrivants avant de donner des formations, participer aux avant ventes et accompagner les équipes au passage à Scrum.

Je suis impliqué dans la communauté française de Drupal depuis 2009, j’ai été tour à tour président puis vice-président de l’association Drupal France et francophonie entre 2011 et 2013.

Nous rejoindre ?

Vous avez envie de rejoindre une équipe éthique ?
Vous avez envie de faire un partenariat ?