Torna al blog

hook_entity_extra_field_info() in Drupal: Pseudo-Campi Custom nel Manage Display

In Drupal, i campi che vedi nel Manage Display di un content type sono quelli definiti nella configurazione dell'entità — i field storage. Ma a volte serve mostrare nel view display qualcosa che non è un campo: una lista di entità correlate, un dato calcolato, un blocco di markup costruito da codice custom. E serve poterlo posizionare e ordinare dall'interfaccia, esattamente come un campo reale.

Per questo esiste hook_entity_extra_field_info(). Dichiara pseudo-campi — extra field — che appaiono nel Manage Display (e volendo nel Manage Form) ma non hanno storage. Il rendering è interamente gestito da codice PHP.

L'hook non è limitato ai nodi: funziona su qualsiasi entity type — user, taxonomy term, media, paragraph, commerce product, entità custom.

Come funziona

L'implementazione si divide in due passaggi:

  1. Dichiarare lo pseudo-campo con hook_entity_extra_field_info() — questo lo fa comparire nell'interfaccia Manage Display
  2. Renderizzarlo in hook_ENTITY_TYPE_view() — qui si costruisce il render array che Drupal mostrerà

Dichiarare lo pseudo-campo

function mymodule_entity_extra_field_info() {
  $extra = [];

  $extra['node']['article']['display']['reading_time'] = [
    'label' => t('Tempo di lettura'),
    'description' => t('Stima del tempo di lettura in minuti.'),
    'weight' => 0,
    'visible' => TRUE,
  ];

  return $extra;
}

La struttura dell'array segue il pattern $extra[entity_type][bundle][display|form][machine_name]. La chiave display lo registra per il view display (Manage Display), form lo registrerebbe per il form display (Manage Form).

Dopo aver svuotato la cache, lo pseudo-campo appare nella pagina Manage Display del content type. Da lì si può trascinare nella posizione desiderata, nasconderlo o inserirlo in un field group — esattamente come un campo con storage.

Un vantaggio importante: la configurazione è per view mode. Lo stesso extra field può essere visibile nel view mode full e nascosto in teaser, oppure posizionato in punti diversi a seconda del contesto. Tutto gestito dall'interfaccia, senza logica condizionale nel codice.

Renderizzare lo pseudo-campo

La dichiarazione da sola non produce output. Il rendering va implementato in hook_ENTITY_TYPE_view():

use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;

function mymodule_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
  if ($entity->bundle() !== 'article') {
    return;
  }

  // Controlla se lo pseudo-campo è abilitato nel display corrente.
  if ($display->getComponent('reading_time')) {
    $text = $entity->get('body')->value ?? '';
    $word_count = str_word_count(strip_tags($text));
    $minutes = max(1, (int) ceil($word_count / 200));

    $build['reading_time'] = [
      '#markup' => '<div class="field field--name-reading-time">'
        . t('@min min di lettura', ['@min' => $minutes])
        . '</div>',
    ];
  }
}

Il punto chiave è $display->getComponent('reading_time'). Questo metodo restituisce la configurazione del componente nel display corrente — o NULL se lo pseudo-campo è stato nascosto (trascinato nella zona "Disabled" del Manage Display). Controllarlo è essenziale: senza questo check, lo pseudo-campo verrebbe renderizzato sempre, in tutti i view mode, ignorando la configurazione dell'interfaccia.

La chiave nel $build array deve corrispondere al machine name dichiarato in hook_entity_extra_field_info().

Differenza con i computed field

Extra field e computed field risolvono problemi diversi:

  • Un computed field è un campo a tutti gli effetti nella Field API. Ha un tipo (boolean, string, ecc.), viene esposto automaticamente in JSON:API, ed è accessibile con $entity->get('field_name')->value.
  • Un extra field è un elemento del display. Non ha tipo, non è accessibile dalla Field API, non appare in JSON:API. È un render array che Drupal piazza nella posizione configurata nel Manage Display.

In breve: il computed field è un dato, l'extra field è un pezzo di interfaccia.

Caso reale: contatti collegati a un'azienda

In un gestionale associativo, ogni azienda (content type fla_company) ha dei contatti collegati — utenti Drupal che hanno un paragrafo company_roles con un entity reference all'azienda. La relazione è inversa: non è l'azienda a puntare all'utente, ma l'utente che punta all'azienda tramite il paragrafo.

Mostrare questi contatti nella scheda azienda non si può fare con un campo: non esiste un campo sull'azienda che li contiene. Serve una query che parta dai paragrafi, risalga agli utenti proprietari e ne mostri i dettagli.

La dichiarazione:

function fla_company_entity_extra_field_info() {
  $extra = [];

  $extra['node']['fla_company']['display']['related_contacts'] = [
    'label' => t('Contatti collegati'),
    'description' => t('Lista dei contatti/utenti collegati all\'azienda.'),
    'weight' => 0,
    'visible' => TRUE,
  ];

  return $extra;
}

Il rendering in hook_node_view():

if ($display->getComponent('related_contacts')) {
  // Query: trova tutti i paragrafi company_roles che puntano a questa azienda,
  // risali all'utente proprietario di ciascun paragrafo.
  $contacts = get_company_contacts($entity->id());
  $items = [];

  foreach ($contacts as $uid => $info) {
    $user = $info['entity'];
    $items[] = [
      '#type' => 'fieldset',
      '#title' => $user->getDisplayName(),
      'dettagli' => [
        '#markup' => render_contact_info($user, $info['paragraph']),
      ],
    ];
  }

  $build['related_contacts'] = $items + [
    '#prefix' => '<div class="field field--name-related-contacts">',
    '#suffix' => '</div>',
  ];
}

Il risultato è una sezione "Contatti collegati" nella scheda azienda, posizionabile dal Manage Display e inseribile in un field group (ad esempio un tab "Contatti"). Se un editor la nasconde dal display, il codice di rendering non viene eseguito grazie al check su getComponent().

Altri utilizzi

Il pattern si adatta a molti scenari. Alcuni esempi:

Mappa da coordinate — un content type ha due campi field_latitude e field_longitude. L'extra field location_map renderizza una mappa Leaflet o Google Maps a partire da quei valori. La mappa non è un campo: è un render element costruito da dati esistenti, e l'editor può decidere se mostrarla e in quale posizione nel display.

Pulsanti social share — un extra field social_share che genera i link di condivisione per Facebook, X, LinkedIn a partire dall'URL e dal titolo del nodo. Non è un dato dell'entità, è un elemento di UI. Con un extra field lo si posiziona dal Manage Display senza toccare i template, e si può mostrarlo nel view mode full ma nasconderlo nel teaser.

Nodi correlati — invece di usare un blocco Views o un campo entity reference manuale, un extra field related_content può eseguire una query basata sui tag in comune e renderizzare una lista di nodi correlati direttamente nel display, posizionabile come qualsiasi campo.

Embed di una View — qualsiasi View può essere renderizzata dentro un extra field. Utile quando la View dipende dal contesto dell'entità corrente (es. commenti filtrati, ordini di un cliente, log di attività) e deve essere posizionata nel Manage Display insieme agli altri campi.

if ($display->getComponent('recent_orders')) {
  $view = \Drupal\views\Views::getView('customer_orders');
  if ($view) {
    $view->setArguments([$entity->id()]);
    $build['recent_orders'] = $view->buildRenderable('block_1');
  }
}

Dati da sistemi esterni — un extra field crm_status che chiama un'API esterna (CRM, ERP, sistema di ticketing) e mostra lo stato aggiornato del cliente direttamente nella scheda del nodo. In questo caso è importante cachare la risposta dell'API — ad esempio con \Drupal::cache() o con i cache tag del render array — per evitare una chiamata HTTP ad ogni page load.

Extra field nei form

Lo stesso meccanismo funziona per i form. Invece di display si usa form:

$extra['node']['article']['form']['editorial_notes'] = [
  'label' => t('Note redazionali'),
  'description' => t('Area note per il team editoriale.'),
  'weight' => 10,
  'visible' => TRUE,
];

Il rendering va implementato in hook_form_alter() o hook_form_FORM_ID_alter(). L'elemento aggiunto al form deve usare come chiave lo stesso machine name dichiarato nell'hook:

function mymodule_form_node_article_edit_form_alter(&$form, $form_state) {
  $form['editorial_notes'] = [
    '#type' => 'textarea',
    '#title' => t('Note redazionali'),
    '#default_value' => '',
    '#description' => t('Note interne, non pubblicate.'),
  ];
}

Lo pseudo-campo appare nel Manage Form e può essere posizionato e raggruppato dall'interfaccia. Il salvataggio dei dati, se necessario, va gestito con un submit handler custom — l'extra field di per sé non persiste nulla.

Quando usarli

L'extra field è la scelta giusta quando serve mostrare nel display qualcosa che non è un campo dell'entità — e serve il controllo dall'interfaccia di Manage Display: posizionamento, ordinamento, visibilità per view mode, inserimento in field group.

Senza hook_entity_extra_field_info(), l'alternativa sarebbe iniettare markup in un preprocess o in un hook_view senza possibilità di riordinarlo o nasconderlo dall'admin.

Se invece il dato deve essere accessibile via API o manipolabile come campo, serve un computed field.