Découvrez nos offres pour faire du digital le levier de votre croissance !

Modèles

Téléchargez le Guide Ultime de gestion de projet digitale pour vous aider à piloter vos transformations et faire les bons choix !

Image mise en avant pour l'article

Les bundle classes, une nouvelle fonctionnalité dans Drupal 9.3

27 mai 2022
Drupal 9.3 introduit le concept de Bundle class. Il permet un meilleur contrôle sur les entités de contenus et offre un certain nombre d’avantages par rapport à l’ancienne méthode utilisant les hooks.


Les entités de contenu tels que les nodes ou les termes de taxonomies sont en principes modifiables. Il est également possible de créer des variantes de ces entités appelées Bundles, comme : un article est un bundle d’une entité de contenu node.

Pour chacun de ces bundles, il est désormais possible de créer une classe PHP qui implémentes des méthodes spécifiques à un bundle. Cette classe peut implémenter des interfaces, étendre une classe parente ou bien encore implémenter un ou plusieurs traits.

La création d’une bundle class

Deux étapes sont nécessaires pour définir une bundle class.

  1. Créer une class qui étend la class de base de l’entité de contenu.
    Par exemple, pour un bundle article qui est un node, il faut créer une class \Drupal\mon_module\Entity\Article qui étend \Drupal\node\Entity\Node.
    À savoir, une class n’étendant pas d’entité de base lancera une exception BundleClassInheritanceException.
  2. Cette class doit être défini par le bundle auquel elle doit s’appliquer. Pour les entités nous appartenant (entités custom), il faut implémenter le hook : hook_entity_bundle_info().
    
    use Drupal\mon_module\Entity\Brand;
    
    function hook_entity_bundle_info() {
      $bundles['my_entity']['my_bundle']['class'] = Brand::class;
      return $bundles;
    }
    

    Pour les entités venant d’autres modules tel que les nodes, il faut utiliser alter hook : hook_bundle_info_alter(array &$bundles).
    
    use Drupal\mon_module\Entity\Article;
    
     function hook_entity_bundle_info_alter(&$bundles) {
      if (!empty($bundles['node']['article'])) {
        $bundles['node']['article']['class'] = Article::class;
      }
    }
    

    À savoir, si plusieurs bundles sont définis avec la même class, une exception AmbiguousBundleClassException sera lancée.

 

Pourquoi utiliser une bundle class ?

Nous sommes dans une class PHP, ce qui nous permet d’utiliser les mécanismes de la programmation orientée objet rendu possible par PHP.

 

Utilisations d’interfaces

Si mon bundle dispose, par exemple, d’un champ field_color, je peux créer une interface de champ.


namespace Drupal\mon_module\Entity;

interface ColorFieldInterface {
  public function getColor(): string;
}

Une bonne pratique est également de créer une interface pour chaque bundle class.

Une classe Article peut donc implémenter une interface ArticleInterface :


namespace Drupal\mon_module\Entity;

use Drupal\node\NodeInterface;

interface ArticleInterface extends NodeInterface,
  ColorFieldInterface,
  DateFieldInterface,
  BodyFieldInterface {
}

Cette interface étend 4 autres interfaces dont 3 interfaces de champs : Color, Date et Body. Ceci nous permet de connaître rapidement les champs qui compose un article.

 

Utilisations de traits

Lorsqu’un même champ est utilisé dans plusieurs bundle class, il peut être utile de déporter la logique dans un trait.

Prenons par exemple le champ body, présent par défaut lors de la création d’un bundle :


namespace Drupal\mon_module\Entity;

trait BodyFieldTrait {
  public function getBody(): string {
    return $this->get('body')->value;
  }
}

Ce trait peut ensuite être utilisé dans toutes les classes qui ont besoin d’implémenter ce champ.

 

Utilisations de services

Un des inconvénients d’une bundle class, c’est qu’il n’est pas possible de faire de l’injection de dépendance via le constructeur ou depuis une méthode statique createInstance().

Pour le moment, il faut obligatoirement utiliser l’objet global \Drupal pour récupérer un service depuis le container.

 

Les bundles classes remplacent des hooks

L’intérêt principal d’un bundle, c’est évidemment de ne pas utiliser de hooks et de déporter au maximum ce code dans une bundle class. Ce qui permet d’éviter d’avoir du code, pour un même type de contenu, réparti dans différents modules ou encore dans les fichiers du thème.

Je vous donne quelques exemples :

Hook

Bundle class

hook_entity_insert()

postSave()

hook_node_load()

postLoad()

hook_node_delete()

postDelete()

Hook_entity_access()

Access()

 

Maintenant appliquons avec postLoad(). Je peux ajouter un comportement après le chargement d’une Node. Par exemple, ici j’ajoute un message de statut qui sera affiché à l’utilisateur après le chargement de la page.


  public static function postLoad(EntityStorageInterface $storage, array &$entities): void {
    foreach ($entities as $entity) {
      \Drupal::messenger()->addStatus(t('Article @title loaded', ['@title' => $entity->getTitle()]));
    }

    parent::postLoad($storage, $entities);
  }

Notez ici l’appel à la méthode parente. C’est une nécessité car Drupal réalise d’autres actions tel que l’appel aux hooks et l’invalidation du cache (voir notre article sur le cache Drupal).

 

Utilisation des bundle classes dans une gateway

 

Un autre avantage d’utiliser des bundle classes, c’est qu’elles permettent de travailler directement avec des entités de Article au lieu d’une simple Node.

Comme la classe Article étend la classe Node, je peux remplacer toutes les occurrences de Node par Article.


namespace Drupal\mon_module\Gateway;

class ArticleGateway implements ArticleGatewayInterface {

  public function __construct(
    protected EntityTypeManagerInterface $entityTypeManager
  ) {}

  /**
   * @return Article[]
   */
  public function getAll(): array {
    /** @var \Drupal\node\NodeStorageInterface $storage */
    $storage = $this->entityTypeManager->getStorage('node');
    $query = $storage->getQuery();

    $query
      ->condition('type', $storage->getBundleFromClass(Article::class))
      ->condition('status', Article::PUBLISHED, '=')
      ->sort('created', 'DESC');

    $results = $query->execute();

    $articles = Article::loadMultiple($results);
    return $articles;
  }
}

Ici, j’ai donc un tableau d’articles au lieu d’un tableau de nodes. Un IDE comme PHPStrom peut ensuite proposer directement toutes les méthodes spécifiques à ce type de bundle et aider à l’autocomplétion.

Vous noterez qu’on récupère, dans cette exemple, le nom du bundle grâce à la méthode getBundleFromClass() d’un objet NodeStorage.

 

Vous l’aurez compris cette évolution dans Drupal 9 simplifiera le code de nos pages Web.

Source :
01 - Drupal

Crédit photo :
Yurich84

Image mise en avant pour l'article
Nicolas Fabing
Développeur Drupal @Adimeo
De l'urgence de migrer sur Drupal 9 : conseils et bonnes pratiques
Voir le webinar
En pleine réflexion pour votre projet de refonte de site web ?
Nos experts Projets, UX, Graphiques, Techniques, vous accompagnent tout au long de votre projet.
Contactez-nous
Pourquoi s'abonner à
notre newsletter ?
Pour recevoir tous les mois, un condensé de contenus utiles et pertinents pour votre transformation digitale !