Drupal 9.3の新機能としてバンドルクラスというものが導入されました。今回はその使い方やメリットについて解説したいと思います。

バンドルクラスとは

Drupalにおけるバンドルとはノードのコンテンツタイプ(記事、基本ページ等)やタクソノミーのボキャブラリー(タグ等)など、特定のエンティティタイプのサブタイプを指します。

バンドルクラスとは、そのバンドル独自のビジネスロジックを埋め込んだクラスです。クラス構造的には従来のエンティティクラスを継承したクラスになります。

詳しくは後述しますが、バンドル自体にクラスを持たせることで、今までhookなどに散らばりやすかったバンドルごとのビジネスロジックを一つのクラスで管理できるようになりました。

バンドルクラスの導入方法

バンドルクラスの導入方法を紹介します。まずは元となっているエンティティクラスを継承したバンドルクラスを書きます。

namespace Drupal\my_module\Entity;

use Drupal\node\Entity\Node;
use Drupal\Core\Datetime\DrupalDateTime;

class Article extends Node {

  /**
  * Article独自のメソッド.
  */
  public function isExpired(): string {
    // 現在の日付が期限フィールドの値より後(期限切れ)であればTRUE.
    $timestamp = \Drupal::time()->getRequestTime();
    $current_date = DrupalDatetime::createFromTimestamp($timestamp)->format('Y-m-d');
    $expiration_date = $this->get('field_expiration_date')->getString();
    return $expiration_date < $current_date;
  }

}

次にhook_entity_bundle_info_alterを以下のように編集します。


use \Drupal\my_module\Entity\Article; function mymodule_entity_bundle_info_alter(array &$bundles): void { if (isset($bundles['node']['article'])) { $bundles['node']['article']['class'] = Article::class; } }

これで完了です。

バンドルクラスのメリット

バンドルクラスには以下のようなメリットがあります。

エンティティのロード時に自動的にロードされる

以下のすべてのコードで、ロード対象のノードのバンドルクラスが登録されていればそのクラスが自動的にロードされます。(バンドルクラスが無い場合はもとのエンティティクラスがロードされます。)

use \Drupal\node\Entity\Node;
$node = Node::load(1);

$node = \Drupal::service('entity_type.manager')->getStorage('node')->load(1)

// ルートパラメーターにエンティティが入っているパスで
\Drupal::routeMatch()->getParameter('node');

// hook_form_alter()で
$form_state->getFormObject()->getEntity()

以前はバンドルクラスのようなものを自作した場合、以下のように自分で呼び出す必要があったのですが、格段にすっきりしました。

// 現在のノードを取得し、そのバンドルのビジネスロジックを使用する.
$node = \Drupal::routeMatch()->getParameter('node');
$article = new MyArticleClass($node);
$article->isExpired();

Twigでメソッドを呼び出せる

Twigでも通常エンティティが入っている変数は自動的にバンドルクラスのものになります。

# バンドルクラスのオブジェクトが入っている
{{ node }}

DrupalのTwigSandBoxPolicyではgetで始まるpublicメソッドはすべて呼び出すことが可能です。なので、バンドルクラスにそのようなメソッドを実装しておけば、Twigでそのまま呼び出すことができます。

{{ node.getExpirationDate() }}

この方法を使えば、今までpreprocessでやっていたような処理もバンドルクラスに入れることができ、かなりコードがすっきりしますね。

preSave()などを上書きできる

バンドルクラスはエンティティクラスを継承しているので当然といえば当然ですが、もともとのエンティティクラスのメソッドを上書きすることができます。例えばpreSave()を上書きすれば今までhook_presaveなどで書いていたコードをバンドルクラスに収めることができます。

namespace Drupal\mymodule\Entity;

use Drupal\node\Entity\Node;
use Drupal\Core\Datetime\DrupalDateTime;

class Article extends Node {

  public function preSave(): string {
    // 値を加工する.
    if ($this->isExpired()) {
       $value = $this->getMyFieldValue();
       $this->set('field_my_field', $value);
    }

    parent::preSave();
  }

}

トレイト、インターフェース、抽象クラスを使うことができる

PHPのクラスなので当然ですが、トレイト、インターフェース、抽象クラスといったOOPの仕組みを利用できます。例えば似たような処理はトレイトで再利用したり、特定の性質を持ったバンドルにインターフェースを埋め込んで外部の条件分岐で利用したり、積極的にビジネスロジックを構造化していくことができるようになったんですね。

というわけで、バンドルクラスの紹介でした。個人的には、hookやpreprocessはいつか無くなるだろうと思いつつイベントで置き換える感じなのかと思ってたので、まさかのエンティティAPI&これ以上ないくらい合理的な方法で解決されてびっくりです。導入が簡単で便利なのでみなさんも使ってみてくださいね。