Drupal → Создание back/reverse reference computed field

09.05.2021

Дано — два типа нод pаrent и child, Parent ссылается на Child с помощью поля field_child.

Задача — создать у нод типа Child вычисляемое поле, по которому можно получить родительскую ноду.

1. Создаём класс вычисляемого поля:

// src/ComputedParentNodeFieldItemList.php

namespace Drupal\MODULENAME;

use Drupal\Core\Field\FieldItemList;
use Drupal\Core\TypedData\ComputedItemListTrait;
use Drupal\node\NodeInterface;

class ComputedParentNodeFieldItemList extends FieldItemList {

  use ComputedItemListTrait;

  /**
   * {@inheritdoc}
   */
  protected function computeValue() {
    $child_node = $this->getEntity(); /** @var NodeInterface $child_node */
    if ($parent_node_id = $this->getParentNodeId($child_node->id())) {
      $this->list[0] = $this->createItem(0, $parent_node_id);
    }
  }

  /**
   * Return parent node id by child node.
   */
  protected function getParentNodeId(int $child_node_id): ?int {
    $result = \Drupal::entityQuery('node')
      ->condition('type', 'parent')
      ->condition('field_child', $child_node_id)
      ->execute();
    return $result ? (int)current($result) : NULL;
  }

}

2. Добавляем нодам Child новое поле:

// MODULENAME.module

use Drupal\MODULENAME\ComputedParentNodeFieldItemList;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;

/**
 * Implements hook_entity_bundle_field_info().
 */
function MODULENAME_entity_bundle_field_info(EntityTypeInterface $entity_type, string $bundle, array $base_field_definitions): ?array {
  if ($entity_type->id() == 'node' && $bundle == 'child') {
    $fields['parent'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Parent node'))
      ->setComputed(TRUE)
      ->setClass(ComputedParentNodeFieldItemList::class)
      ->setReadOnly(TRUE)
      ->setSetting('target_type', 'node');

    return $fields;
  }

  return NULL;
}

В отличии от предыдущего примера вместо hook_entity_base_field_info() здесь используется хук hook_entity_bundle_field_info(), который позволяет добавлять поля для конкретного бандла.

3. По желанию интегрируем новое поле и модуль Token:

// MODULENAME.tokens.inc

use Drupal\Core\Render\BubbleableMetadata;
use Drupal\node\NodeInterface;

/**
 * Implements hook_token_info().
 */
function MODULENAME_token_info(): array {
  $token_info['tokens']['node']['parent'] = [
    'name' => t('Parent node'),
    'type' => 'node',
  ];

  return $token_info;
}

/**
 * Implements hook_tokens().
 */
function MODULENAME_tokens(string $type, array $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata): array {
  $replacements = [];

  if ($type == 'node' && !empty($data['node'])) {
    $node = $data['node']; /** @var NodeInterface $node */
    $token_service = \Drupal::token();

    if ($parent_tokens = $token_service->findWithPrefix($tokens, 'parent')) {
      /** @var NodeInterface $parent_node */
      if ($parent_node = $node->get('parent')->entity) {
        $replacements += $token_service->generate('node', $parent_tokens, ['node' => $parent_node], $options, $bubbleable_metadata);
      }
    }
  }

  return $replacements;
}

После этого можно пользоваться кодом:

$child_node = Node::load(123);
$parent_node = $child_node->get('parent')->entity;
$text = \Drupal::token()->replace('Parent node title = [node:parent:title]', ['node' => $child_node]);

Подробнее про вычисляемые поля на drupal.org
Код в виде модуля на github

Написанное актуально для
Drupal 8+
Похожие записи

Добавить комментарий