Usar clases específicas por tipo de contenido en Drupal

drupal2020_0.jpg
Solucionex
27
Ene 22

A partir de la versión 9.3, se han incluido las llamadas "bundle classes". Esto permite que, por ejemplo, se pueda usar una clase específica para los nodos de un determinado tipo (por ejemplo, usar una clase Article extendiendo de Node en lugar de la clase base directamente).

Estas clases pueden ser generadas con drush a partir de la versión 11. Ej (para un hipotético módulo vacío "web"):

user@web:/var/www/html$ drush gen entity:bundle-class 

Welcome to entity-bundle-class generator!
––––––––––––––––––––––––––––––––––––––––––– 

Module machine name [web]: 
➤  

Entity type: 
 [ 1] Bloque personalizado 
 [ 2] Recortar 
 [ 3] Archivo 
 [ 4] Multimedia 
 [ 5] Enlace de menú personalizado 
 [ 6] Contenido 
 [ 7] Alias de URL 
 [ 8] Redirección 
 [ 9] Término de la taxonomía 
 [10] Usuario 
 [11] Párrafo 
➤ 6

Bundles, comma separated: 
 [1] Evento 
➤ 1

Class for Evento bundle [EventBundle]: 
➤  

Use a base class? [No]: 
➤ Yes

Base class [NodeBundle]: 
➤  

The following directories and files have been created or updated:
––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 
• /var/www/html/web/modules/custom/web/web.module
• /var/www/html/web/modules/custom/web/src/Entity/Bundle/NodeBundle.php
• /var/www/html/web/modules/custom/web/src/Entity/Bundle/EventBundle.php

Eso genera los siguientes archivos:

/var/www/html/web/modules/custom/web/web.module

<?php

/**
 * @file
 * Primary module hooks for Web module.
 */

/**
 * Implements hook_entity_bundle_info_alter().
 */
function web_entity_bundle_info_alter(array &$bundles): void {
  if (isset($bundles['node']['event'])) {
    $bundles['node']['event']['class'] = \Drupal\web\Entity\Bundle\EventBundle::class;
  }
}

/var/www/html/web/modules/custom/web/src/Entity/Bundle/NodeBundle.php

<?php

namespace Drupal\web\Entity\Bundle;

use Drupal\node\Entity\Node;

/**
 * A base bundle class for node entities.
 */
abstract class NodeBundle extends Node {

}

/var/www/html/web/modules/custom/web/src/Entity/Bundle/EventBundle.php

<?php

namespace Drupal\web\Entity\Bundle;

/**
 * A bundle class for node entities.
 */
class EventBundle extends NodeBundle {

}

A partir de ahora, cada vez que se cargue un nodo de tipo evento (event), lo hará automáticamente con la clase "EventBundle" en lugar de la típica "Node". Ya solo con esto se puede empezar a crear código específico para dicho tipo de contenido.

Por poner un ejemplo, si el tipo de contenido Evento tiene un campo de rango de fecha (fecha inicio y fin en el mismo campo), pueden obtenerse de manera sencilla sus fechas por separado para su uso en plantillas (por defecto se permiten los métodos get*()).

/var/www/html/web/modules/custom/web/src/Entity/Bundle/EventBundle.php

<?php

namespace Drupal\web\Entity\Bundle;

/**
 * A bundle class for node entities.
 */
class EventBundle extends NodeBundle {

  /**
   * {@inheritdoc}
   */
  public function getStartDate(): string {
    return $this->field_event_date->start_date->format('Y-m-d');
  }

  /**
   * {@inheritdoc}
   */
  public function getEndDate(): string {
    return $this->field_event_date->end_date->format('Y-m-d');
  }

}

/var/www/html/web/themes/custom/web_theme/templates/content/node--event--full.html.twig

<article{{ attributes }}>
  {{ title_prefix }}
  <h2>{{ label }}</h2>
  {{ title_suffix }}

  {{ node.getStartDate() }}
  {{ node.getEndDate() }}
</article>

Si se tienen varios tipos de contenido y se quiere usar una clase para algunos específicos y otra base (pero que no sea Node directamente), en el hook_entity_bundle_info_alter() puede añadirse lo siguiente:

foreach ($bundles['node'] as $bundle => $definition) {
  $bundles['node'][$bundle]['class'] = match ($bundle) {
    'blog_post' => BlogPost::class,
    'event' => Event::class,
    'service' => Service::class,
    default => NodeBundle::class,
  };
}

Nota: En este caso habría que eliminar el "abstract" de la clase "NodeBundle".

Por último, es recomendable usar interfaces, por ejemplo para typehinting de funciones:

/var/www/html/web/modules/custom/web/src/Entity/Bundle/EventBundle.php

class EventBundle extends NodeBundle implements EventBundleInterface {

  ...

}

/var/www/html/web/modules/custom/web/src/Entity/Bundle/EventBundleInterface.php

interface EventBundleInterface extends NodeInterface {

  ...

}