logo

Extra Block Types (EBT) - Nueva experiencia con Layout Builder❗

Extra Block Types (EBT): tipos de bloques con estilo y personalizables: Presentaciones de diapositivas, Pestañas, Tarjetas, Acordeones y muchos más. Configuraciones integradas para fondo, DOM Box y plugins de JavaScript. Experimenta hoy el futuro de la construcción de diseños.

Módulos de demostración EBT Descargar módulos EBT

❗Extra Paragraph Types (EPT) - Nueva experiencia con Paragraphs

Extra Paragraph Types (EPT): conjunto de módulos basado en párrafos de forma análoga.

Módulos de demostración EPT Descargar módulos EPT

Scroll

TiposDeCampo, WidgetsDeCampo y FormateadoresDeCampo

18/06/2025, by Ivan

Resumen

Drupal 8 viene con una gran biblioteca de clases base que te permiten trabajar con tu propio contenido. Cuando se trata de entidades de contenido, quieres usar campos. Es importante entender los Campos, ya que es ahí donde tus entidades almacenan sus datos.

TiposDeCampo (FieldTypes)

Tipos básicos de campos:

 

Tipos de campos personalizados
Cada vez que quieres presentar datos de una forma que Drupal no provee, podrías querer crear un nuevo tipo de campo para tus datos.

Supongamos que tienes una entidad de contenido que contiene datos confidenciales. El creador de ese contenido puede permitir que ciertos usuarios accedan a la entidad usando una contraseña diferente para cada usuario. Si hablamos en términos de tablas de base de datos, querrías algo así:

| entity_id | uid | password      |
-----------------------------------
| 1         | 1   | 'holamundo'   |
| 1         | 2   | 'adiós'       |

Como ves, nuestra entidad con ID 1 tiene dos contraseñas diferentes para dos usuarios distintos. Entonces, ¿cómo podemos implementar esto en Drupal sin crear manualmente una tabla? Creamos un nuevo tipo de campo.

Dado que Drupal implementa la lógica del campo como Plugin, siempre tenemos una clase base de la cual heredar para que funcione en Drupal. Para un nuevo tipo de campo quieres crear la siguiente estructura de carpetas en tu módulo:
modules/custom/NOMBREMODULO/src/Plugin/Field/FieldType
Es un camino largo y un poco molesto, pero Drupal facilita el trabajo con todas las funciones diferentes que pueden coexistir en tus módulos.

Para nuestro ejemplo creamos el archivo EntityUserAccessField.php

namespace Drupal\NOMBREMODULO\Plugin\Field\FieldType;
     
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
     
/**
 * @FieldType(
 *   id = "entity_user_access",
 *   label = @Translation("Acceso de Usuario a Entidad"),
 *   description = @Translation("Este campo almacena una referencia a un usuario y una contraseña para ese usuario en la entidad."),
 * )
*/
     
class EntityUserAccessField extends FieldItemBase {
  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    //Por hacer: Implementar esto.
  }
     
  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    //Por hacer: Implementar esto.
  }
}

Como puedes ver, el tipo de campo es muy similar a una entidad de contenido. En realidad, no hay diferencia entre ambos, pero ese es tema para otro nodo ;)

Primero que nada, tenemos la anotación para nuestro tipo de campo:

  • @FieldType: llama a la clase de anotación FieldType de la biblioteca de Drupal
  • id: es el nombre máquina de nuestro tipo de campo para que podamos reutilizarlo. Asegúrate de no sobrescribir nombres predefinidos de PHP u otros.
  • label: puede ser una traducción legible para el usuario del nombre máquina.
  • description: si la etiqueta no es suficiente, también puedes agregar una descripción para el tipo de campo.

 

Segundo, nuestra clase extiende FieldItemBase, lo que nos obliga a implementar dos métodos para usar correctamente este tipo de campo:

  • propertyDefinitions(): Este método es similar a baseFieldDefinition del objeto contenido (¡no es lo mismo!). Podemos definir los datos que aparecerán en los formularios de entidades donde se usa este tipo de campo.
  • schema(): En objetos este método está obsoleto, pero aún lo tenemos en campos. Este método debe implementar la representación de los datos del campo en la base de datos. Puede diferir de las propiedades.

Como no está muy claro qué poner en estos métodos, añadamos algo de código para empezar.

public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
  $properties['uid'] = DataDefinition::create('integer')
      ->setLabel(t('Referencia de ID de usuario'))
      ->setDescription(t('El ID del usuario referenciado.'))
      ->setSetting('unsigned', TRUE);

  $properties['password'] = DataDefinition::create('string')
      ->setLabel(t('Contraseña'))
      ->setDescription(t('Una contraseña guardada en texto plano. ¡Eso no es seguro, amigo!'));

  $properties['created'] = DataDefinition::create('timestamp')
    ->setLabel(t('Hora de creación'))
    ->setDescription(t('La hora en que se creó la entrada'));

    // Por hacer: Agregar más propiedades.
 
    return $properties;
}

También es posible almacenar la referencia al usuario usando DataReferenceDefinition, esto podría considerarse en el futuro.

public static function schema(FieldStorageDefinitionInterface $field_definition) {
  $columns = array(
    'uid' => array(
      'description' => 'El ID del usuario referenciado.',
      'type' => 'int',
      'unsigned' => TRUE,
    ),
    'password' => array(
      'description' => 'Una contraseña en texto plano.',
      'type' => 'varchar',
      'length' => 255,
    ),
    'created' => array(
      'description' => 'Una marca de tiempo de cuando se creó esta entrada.',
      'type' => 'int',
    ),

    // Por hacer: Agregar más columnas.
  );
 
  $schema = array(
    'columns' => $columns,
    'indexes' => array(),
    'foreign keys' => array(),
  );

  return $schema;
}

Schema() es necesario para que Drupal sepa cómo guardar nuestros datos. Las columnas del esquema deben ser un subconjunto de las propiedades definidas en propertyDefinitions().

Ahora tenemos un tipo de campo completamente nuevo. No tiene ninguna lógica para procesar ninguna entrada de datos, pero puede usarse en cualquier entidad de contenido como campo. Si quieres, puedes usarlo como baseField para una entidad:

public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
  // Algunos campos arriba.
 
  $fields['entity_user_access'] = BaseFieldDefinition::create('entity_user_access')
    ->setLabel(t('Acceso de Usuario a Entidad'))
    ->setDescription(t('Especifica contraseñas para cualquier usuario que quiera ver esta entidad.'))
    ->setCardinality(-1); // Asegura que puedas tener más de un miembro
 
  // Más campos abajo.
 
  return $fields;
}
  • BaseFieldDefinition::create(): Debes usar el nombre máquina del tipo de campo en create()
  • setCardinality(-1): La cardinalidad representa la cantidad de datos del campo que puede tener una entidad. Por ejemplo, si pones 2, solo dos usuarios podrán acceder a la entidad; 3 serían tres usuarios, etc. -1 representa usuarios ilimitados.

 

Widget de campo (FieldWidget)

Si tienes datos personalizados, puede que necesites una presentación personalizada para esos datos. Los widgets se usan para mostrar cómo el usuario puede ingresar esos datos personalizados en los formularios. Por ejemplo:

  • si el formulario necesita un número entero pero el usuario solo puede marcar una casilla
  • si quieres autocompletar para tus datos
  • si la entrada de la contraseña se hace mediante una interfaz gráfica especial

etc.

El widget de campo en Drupal se encuentra en
modules/custom/NOMBREMODULO/src/Plugin/Field/FieldWidget
también es un camino muy largo. A estas alturas debería estar claro por qué Drupal usa este estilo para separar los archivos PHP. Es muy fácil perder qué archivos pertenecen y dónde.

Crearemos el widget de campo en EntityUserAccessWidget.php

namespace Drupal\NOMBREMODULO\Plugin\Field\FieldWidget;
 
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
 
/**
 * Implementación del plugin para el widget 'entity_user_access_w'.
 *
 * @FieldWidget(
 *   id = "entity_user_access_w",
 *   label = @Translation("Acceso de Usuario a Entidad - Widget"),
 *   description = @Translation("Acceso de Usuario a Entidad - Widget"),
 *   field_types = {
 *     "entity_user_access",
 *   },
 *   multiple_values = TRUE,
 * )
 */
 
class EntityUserAccessWidget extends WidgetBase {
  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    // Por hacer: Implementar esto.
  }
}

¿Ya notaste? Drupal 8 usa este estilo de código una y otra vez si quieres implementar funcionalidades. Hay una anotación y una clase base de la cual heredar. ¡Sí, Drupal puede usar esto!

  • @FieldWidget: define la clase de anotación
  • id: nombre máquina del widget
  • field_types: array de nombres máquina de tipos de campo que pueden usar este widget
  • multiple_values: por defecto FALSE. Si es TRUE permite enviar más de un valor en el formulario de entidad

Si ahora quieres usar este widget con tu tipo de campo, debes editar la anotación del tipo de campo así:

// ...

/**
 * @FieldType(
 *   id = "entity_user_access",
 *   label = @Translation("Acceso de Usuario a Entidad"),
 *   description = @Translation("Este campo almacena una referencia a un usuario y una contraseña para este usuario en la entidad."),
 *   default_widget = "entity_user_access_w",
 * )
 */
 
// ...

¡Listo! No, espera, no pasa nada aún porque debemos implementar formElement() en nuestro widget.

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element['userlist'] = array(
      '#type' => 'select',
      '#title' => t('Usuario'),
      '#description' => t('Selecciona miembros del grupo de la lista.'),
      '#options' => array(
         0 => t('Anónimo'),
         1 => t('Administrador'),
         2 => t('foobar'),
         // ¡Esto debería implementarse de mejor forma!
       ),
  
    );
  
    $element['passwordlist'] = array(
      '#type' => 'password',
      '#title' => t('Contraseña'),
      '#description' => t('Selecciona una contraseña para el usuario'),
    );

    // Asignar valores por defecto a todos los campos arriba
    $childs = Element::children($element);
    foreach ($childs as $child) {
        $element[$child]['#default_value'] = isset($items[$delta]->{$child}) ? $items[$delta]->{$child} : NULL;
    }
   
    return $element;
}

Si ahora abres el formulario con este widget, verás al menos dos campos de entrada. Uno es la selección del usuario, y el otro el campo de contraseña. Si quieres implementar la forma en que se guardan los datos, necesitas implementar métodos de validación en este widget o en el formulario de la entidad. Consulta la API de Formularios de Drupal 8 para más información.

Para este punto, ya habrás hecho la mayor parte del trabajo con un campo personalizado. Si no entiendes qué sucede, simplemente prueba el código o revisa módulos base para profundizar en el tema.

Formateadores de campo (FieldFormatters)

Lo último que falta es la representación de los datos en el llamado modo de vista de la entidad — por cierto, el widget es el modo formulario. Esto ocurre generalmente cuando llamas a la entidad a través de yourdrupalpage.com/myentity/1/view

Como aquí no hay mucho qué explicar, iremos directo al código. En modules/custom/NOMBREMODULO/src/Plugin/Field/FieldFormatter crea EntityUserAccessFormatter.php

namespace Drupal\NOMBREMODULO\Plugin\Field\FieldFormatter;
     
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
     
/**
 * Implementación del plugin para el formateador 'entity_user_access_f'.
 *
 * @FieldFormatter(
 *   id = "entity_user_access_f",
 *   label = @Translation("Acceso de Usuario a Entidad - Formateador"),
 *   description = @Translation("Acceso de Usuario a Entidad - Formateador"),
 *   field_types = {
 *     "entity_user_access",
 *   }
 * )
 */
     
class EntityUserAccessFormatter extends FormatterBase {
  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = array();
     
    foreach ($items as $delta => $item) {
      $elements[$delta] = array(
        'uid' => array(
          '#markup' => \Drupal\user\Entity\User::load($item->uid)->getUsername(),
          ),
        // Añadir más contenido
      );
    }
     
    return $elements;
  }
}

Este ejemplo tiene una anotación muy parecida a la del widget, por lo que no hay mucho que explicar. viewElements() simplemente muestra el nombre del usuario del ID de usuario guardado en nuestro tipo de campo. La implementación del acceso debería realizarse en la entidad. Así, esta implementación mostrará todos los nombres de usuario que tienen contraseña en la entidad.

Drupal’s online documentation is © 2000-2020 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.