9.11.3. エンティティフック
過去の記事で、すでにフックに触れました。この記事では、エンティティを扱うのに役立つフックをより詳しく見ていきます。
フックとは何か、なぜ必要なのかは、この記事で全般的に読むことができます:
http://drupalbook.org/drupal/92-what-hook-drupal-8
エンティティに関連する特定のイベント――追加、削除、更新――で発火する独自のカスタムコードを追加するためにフックを使います。
すべての Drupal フックはこのページで見られます:
https://api.drupal.org/api/drupal/core!core.api.php/group/hooks/8.2.x
そのうち、コンテンツを扱うカスタムモジュールで最もよく見られる一部だけを扱います。
すべてのコードを github の drupalbook_examples モジュールに追加しました。モジュールをダウンロードして、私のウェブサイトに追加できます:
https://github.com/levmyshkin/drupalbook8
hook_entity_presave()
/**
* Implements hook_entity_presave().
*/
function drupalbook_examples_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
if ($entity->getEntityTypeId() == 'node' && $entity->getType() == 'article') {
$entity->title->value = $entity->title->value . 'by ' . date('d-m-Y');
}
}
hook_entity_presave() は、エンティティが保存されるたびに発火します。
フック内で $entity->save() を呼び出す必要はありません。エンティティオブジェクトは保存前に変更されるからです。この例では、記事のタイトルに、ノードが保存された現在の日付を追加します。翌日に記事を更新すると、新しい日付が再び追加されます。保存前にタイトルから日付を削除しないと、記事を再保存するたびにタイトルがどんどん新しくなっていきます。多くの場合、保存時にエンティティのフィールドに特定の値があるかを確認したり、記事の変更に関する通知メールを送ったりします。
まず目的のエンティティタイプを確認していることに注意してください。というのも、hook_entity_presave() のコードはすべてのエンティティ――コンテンツ、ブロック、コメント、タクソノミー用語――に対して機能するからです。そして、必要なノードのバンドルも確認します。
hook_entity_insert()
/**
* Implements hook_entity_insert().
*/
function drupalbook_examples_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
if ($entity->getEntityTypeId() == 'node' && $entity->getType() == 'page') {
$node = Node::create([
'type' => 'article',
'title' => 'New page created: ' . $entity->title->value,
]);
$node->save();
}
}
hook_entity_insert() は、新しいエンティティが追加されたときに呼び出されます。ですから例えば、サイトで新しいページを作成すると、記事が作成されます。そして、前のフックがあれば、作成された記事のタイトルに日付が追加されます。
hook_entity_insert() と hook_entity_presave() フックには違いがあることに注意してください。hook_entity_insert() はエンティティが追加されるときに一度だけ発火し、エンティティのフィールドの値を変更しません。つまり、最初のフックのコードを 2 番目のフックに貼り付けても、このコードは機能しません:
/**
* Implements hook_entity_insert().
*/
function drupalbook_examples_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
if ($entity->getEntityTypeId() == 'node' && $entity->getType() == 'article') {
$entity->title->value = $entity->title->value . 'by ' . date('d-m-Y');
}
}
もちろん、ノードの値の保存を達成することはできます:
function your_module_entity_insert(Drupal\Core\Entity\EntityInterface $entity){
if ($entity->getType() == 'article') {
drupal_register_shutdown_function('_your_module_post_insert', $entity);
}
}
function _your_module_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
if ($entity) {
$entity->save();
}
}
しかし、これはすべきではありません。保存時にエンティティ自体を変更するには hook_entity_presave() を使い、他のエンティティやアクションを変更するには hook_entity_insert() を使うのが最善です。
hook_entity_update()
/**
* Implements hook_entity_update().
*/
function drupalbook_examples_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
if ($entity->getEntityTypeId() == 'node' && $entity->getType() == 'page') {
\Drupal::messenger()->addMessage('Page has been changed: ' . $entity->title->value);
}
}
hook_entity_update() は、エンティティが更新されるたびに発火します。ここですぐに述べておくべきなのは、$entity オブジェクトのフィールドを更新する価値もないということです。このフックは、hook_entity_insert() と同様に、更新されたエンティティのフィールドのデータに関連しない特定のアクションを呼び出すために役立ちます。このフックも、ロギング、メッセージの送信、その他のアクションに使うべきです。
また、hook_entity_update() を再び呼び出さないように $entity->save() を実行しないでください。しかし実行した場合は、$entity->save() を呼び出さず、ループが発生しないようにする必要があります。
hook_entity_delete()
エンティティの削除後にロギングとアクションの実行を行うためのもう 1 つのフックです。
hook_entity_access()
<?php
use Drupal\Core\Access\AccessResult;
...
/**
* Implements hook_entity_access().
*/
function drupalbook_examples_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account) {
if ($entity->getEntityTypeId() == 'node' && $entity->getType() == 'article' && $operation == 'view' && in_array('administrator', $account->getRoles())) {
AccessResult::forbidden();
}
}
?>
この例では、administrator ロールを持たないすべてのユーザーに対して記事へのアクセスをブロックします。もちろん、コンテンツへのアクセスを区別するのにロールの標準的なロール権限で十分なら、権限設定を使うほうが良いです。hook_entity_access() は、コンテンツへのアクセスを柔軟に構成するために必要です。例えば、スケジュールに応じて、ユーザーポイント、カルマ、ユーザーレベルなどで特定の条件を満たすことによって、などです。さらに、このフックを通じて、標準のアクセス権を書き換えて特定のコンテンツへの通常のアクセス制限を行うと、プロジェクトを引き続き担当する別のプログラマーを混乱させる可能性があります。ここから、フックの主な欠点にたどり着きます。それはサードパーティコードの実行の非明白性です。もし仮に、フックが実装されたモジュールの存在を知らなければ、なぜ保存時に記事のタイトルが変わるのかは謎になります。もちろん、私たちはフックが存在することを知っているので、例えばノードの更新時に同様の動作に遭遇するたびに、プロジェクト全体を entity_presave や entity_update で検索しなければなりません。多くの場合、プログラマーはコードガイドに従い、フックについて「Implements hook_entity_presave()」というコメントを残すので、フックの名前で検索できます。しかし、すべてのプログラマーがコードガイドに従うわけではないことに留意してください。特に、プロジェクトを失敗させた前のチームからプロジェクトを引き継いだ場合はそうです。
いくつかのフックだけを調べましたが、フックをどこで使うかについて一定の理解が得られたと思います。Drupal のタスクをこなすほど、カスタムコードを書く必要性に遭遇することが多くなります。いずれかのフックがコードを追加するのに良い場所に見えるなら、それをカスタムモジュールに安心して実装できます。