Définition et utilisation des définitions de champs d’entité de contenu
Les entités de contenu doivent définir explicitement tous leurs champs en fournissant des définitions dans la classe des entités. Les définitions de champs reposent sur la Typed Data API (voir aussi Comment les entités l’implémentent).
Définitions des champs
Les types d’entités définissent leurs champs de base via une méthode statique dans la classe de l’entité. Les champs de base sont des champs non configurables qui existent toujours pour ce type d’entité, comme le titre d’un nœud ou les dates de création et de modification. Le gestionnaire d’entités complète ces champs avec des champs configurables et non configurables fournis par d’autres modules en appelant hook_entity_field_info()
et hook_entity_field_info_alter()
. C’est aussi ainsi que les champs configurés via l’interface utilisateur sont ajoutés (ces hooks n’existent plus dans l’API actuelle).
Les définitions de champs sont des objets simples implémentant l’interface FieldDefinitionInterface, tandis que les champs de base sont généralement créés via la classe BaseFieldDefinition, et les champs configurables implémentent directement l’interface avec des objets de configuration (Field et FieldInstance).
Les définitions de champs sont aussi le lieu où définir les contraintes de validation pour les éléments ou propriétés du champ. Tous les plugins de type de champ peuvent être utilisés. (Cette interface et cette classe ne sont plus présentes).
Les champs sont toujours des listes d’éléments, ce qui signifie que la classe FieldItem définie comme type est contenue dans une classe FieldItemList représentant la liste des éléments du champ.
Tous les champs (y compris les champs de base) peuvent aussi avoir des widgets et des formatteurs pour leur affichage et édition.
Champs de base
Voici un exemple abrégé de définitions de champs pour le type d’entité nœud :
use Drupal\Core\Field\BaseFieldDefinition; class Node implements NodeInterface { /** * {@inheritdoc} */ public static function baseFieldDefinitions($entity_type) { // L'ID du nœud est un entier, utilisant le type d’élément IntegerItem. $fields['nid'] = BaseFieldDefinition::create('integer') ->setLabel(t('Node ID')) ->setDescription(t('The node ID.')) ->setReadOnly(TRUE); // Le champ UUID utilise le type uuid_field qui génère automatiquement un UUID lors de la création de l’entité. $fields['uuid'] = BaseFieldDefinition::create('uuid') ->setLabel(t('UUID')) ->setDescription(t('The node UUID.')) ->setReadOnly(TRUE); // Le code langue est défini comme un language_field, qui garantit un code langue valide par défaut. $fields['langcode'] = BaseFieldDefinition::create('language') ->setLabel(t('Language code')) ->setDescription(t('The node language code.')); // Le titre est un StringItem, valeur par défaut vide, avec une contrainte de longueur max 255. $fields['title'] = BaseFieldDefinition::create('string') ->setLabel(t('Title')) ->setDescription(t('The title of this node, always treated as non-markup plain text.')) ->setRequired(TRUE) ->setTranslatable(TRUE) ->setSettings([ 'default_value' => '', 'max_length' => 255, ]); // L’uid est une référence d’entité vers l’utilisateur, accessible via $node->uid->target_id et $node->uid->entity. $fields['uid'] = BaseFieldDefinition::create('entity_reference') ->setLabel(t('User ID')) ->setDescription(t('The user ID of the node author.')) ->setSettings([ 'target_type' => 'user', 'default_value' => 0, ]); // Le champ changed met à jour automatiquement le timestamp à chaque sauvegarde de l’entité. $fields['changed'] = BaseFieldDefinition::create('changed') ->setLabel(t('Changed')) ->setDescription(t('The time that the node was last edited.')); return $fields; } }
Champs multivalués
Pour définir le nombre maximal d’éléments permis dans un champ, utilisez la méthode setCardinality().
Par exemple, pour un champ pouvant contenir 3 éléments :
->setCardinality(3);
Pour un champ avec un nombre illimité d’éléments, utilisez :
->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
Exemple de champ multivalué référant des entités utilisateur :
$fields['my_field'] = BaseFieldDefinition::create('entity_reference') ->setLabel(t('The label of the field')) ->setDescription(t('The description of the field.')) ->setRevisionable(TRUE) ->setSetting('target_type', 'user') ->setSetting('handler', 'default') ->setTranslatable(TRUE) ->setDisplayOptions('view', [ 'label' => 'hidden', 'type' => 'author', 'weight' => 0, ]) ->setDisplayOptions('form', [ 'type' => 'entity_reference_autocomplete', 'weight' => 5, 'settings' => [ 'match_operator' => 'CONTAINS', 'size' => '60', 'autocomplete_type' => 'tags', 'placeholder' => '', ], ]) ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE) ->setRequired(TRUE) ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
Une entité peut aussi fournir des champs spécifiques à un bundle, ou les modifier pour un bundle. Par exemple, le titre d’un nœud peut avoir une étiquette différente par bundle. Pour cela, la définition du champ de base doit être clonée avant modification pour ne pas impacter tous les bundles.
use Drupal\node\Entity\NodeType; /** * {@inheritdoc} */ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) { $node_type = NodeType::load($bundle); $fields = []; if (isset($node_type->title_label)) { $fields['title'] = clone $base_field_definitions['title']; $fields['title']->setLabel($node_type->title_label); } return $fields; }
Types de champs
Le noyau Drupal fournit une liste de types de champs pouvant être utilisés pour les champs de base. Les modules peuvent aussi fournir des types supplémentaires utilisables :
- string : chaîne simple
- boolean : booléen stocké comme entier
- integer : entier avec validation min/max (aussi decimal et float)
- decimal : nombre décimal avec précision et échelle configurables
- float : nombre à virgule flottante
- language : code et langue avec propriété calculée
- timestamp : timestamp UNIX stocké en entier
- created : timestamp initialisé à l’heure courante
- changed : timestamp mis à jour automatiquement lors de la sauvegarde
- datetime : date stockée en chaîne ISO 8601
- uri : URI. Le module link fournit un champ lien avec titre et URI pouvant être interne ou externe.
- uuid : UUID généré par défaut
- email : email avec validation, widgets et formatteurs
- entity_reference : référence à une entité via target_id et propriété entity. Le module entity_reference fournit widgets et formatteurs associés.
- map : peut contenir n’importe quel nombre de propriétés sérialisées en chaîne
Champs configurables
Des champs supplémentaires peuvent être enregistrés via hook_entity_base_field_info()
et hook_entity_bundle_field_info()
. Voici des exemples d’ajout de champs base et bundle :
use Drupal\Core\Field\BaseFieldDefinition; /** * Implémente hook_entity_base_field_info(). */ function path_entity_base_field_info(EntityTypeInterface $entity_type) { if ($entity_type->id() === 'taxonomy_term' || $entity_type->id() === 'node') { $fields['path'] = BaseFieldDefinition::create('path') ->setLabel(t('The path alias')) ->setComputed(TRUE); return $fields; } } /** * Implémente hook_entity_bundle_field_info(). */ function field_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) { if ($entity_type->isFieldable()) { // Les champs configurables attachés à un bundle spécifique sont ajoutés « par bundle ». return Field::fieldInfo()->getBundleInstances($entity_type->id(), $bundle); } }
Des hooks alter correspondants existent pour chacun des précédents.
Stockage des champs
Si votre champ n’a pas d’exigences spécifiques, l’API Entity Field gère le stockage en base et met à jour les schémas automatiquement. C’est le comportement par défaut pour les champs non marqués comme calculés (setComputed(TRUE)
) ou n’ayant pas de stockage personnalisé (setCustomStorage(TRUE)
).
Supposons que vous vouliez ajouter un champ de base booléen à tous les nœuds pour indiquer si le contenu est mis en avant :
use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; /** * Implémente hook_entity_base_field_info(). */ function MYMODULE_entity_base_field_info(EntityTypeInterface $entity_type) { $fields = []; // Ajoute un champ de base 'Highlight' à tous les types de nœuds. if ($entity_type->id() === 'node') { $fields['highlight'] = BaseFieldDefinition::create('boolean') ->setLabel(t('Highlight')) ->setDescription(t('Whether or not the node is highlighted.')) ->setRevisionable(TRUE) ->setTranslatable(TRUE) ->setDisplayOptions('form', [ 'type' => 'boolean_checkbox', 'settings' => [ 'display_label' => TRUE, ], ]) ->setDisplayConfigurable('form', TRUE); } return $fields; }
J’ai souvent constaté que visiter update.php ne suffisait pas pour ajouter une colonne en base, mais exécuter :
\Drupal::entityTypeManager()->clearCachedDefinitions(); \Drupal::service('entity.definition_update_manager')->applyUpdates();
permet de créer la colonne en base. Notez que cela applique aussi toutes les mises à jour en attente pour d’autres définitions de champs.
Note : ce code ne fonctionne plus avec Drupal 8.7.
Voir l’exemple dans ce changelog.
Installation d’une nouvelle définition de stockage de champ :
function example_update_8701() { $field_storage_definition = BaseFieldDefinition::create('boolean') ->setLabel(t('Revision translation affected')) ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.')) ->setReadOnly(TRUE) ->setRevisionable(TRUE) ->setTranslatable(TRUE); \Drupal::entityDefinitionUpdateManager() ->installFieldStorageDefinition('revision_translation_affected', 'block_content', 'block_content', $field_storage_definition); }
Si votre module ajoute un nouveau champ, il sera ajouté automatiquement à l’activation et supprimé à la désactivation du module.
Si le module est déjà activé et qu’il faut écrire une mise à jour hook_update_N
pour modifier la définition des champs, vous pouvez faire :
/** * Ajoute le champ highlight à tous les nœuds. */ function MYMODULE_update_8001() { $entity_type = \Drupal::service('entity_type.manager')->getDefinition('node'); \Drupal::service('entity.definition_update_manager')->updateEntityType($entity_type); }
Ou :
/** * Ajoute le champ 'revision_translation_affected' aux entités 'node'. */ function node_update_8001() { $storage_definition = BaseFieldDefinition::create('boolean') ->setLabel(t('Revision translation affected')) ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.')) ->setReadOnly(TRUE) ->setRevisionable(TRUE) ->setTranslatable(TRUE); \Drupal::entityDefinitionUpdateManager() ->installFieldStorageDefinition('revision_translation_affected', 'node', 'node', $storage_definition); }
Voir https://www.drupal.org/node/2554097 pour plus d’informations et d’exemples.
Manipulation des définitions de champs
Note : Comme les objets sont des données complexes, ils doivent implémenter ComplexDataInterface
. Du point de vue des données typées, tous les éléments typés contenus dans un objet complexe sont des propriétés. Cette contrainte / attribution de nom peut être levée.
// Vérifie si une entité possède un champ donné. $entity->hasField('field_tags'); // Retourne un tableau associatif des définitions de tous les champs, par exemple le champ 'image'. $field_definitions = $entity->getFieldDefinitions(); // Retourne un tableau associatif des définitions des propriétés des items du champ image, par exemple 'file_id' et 'alt'. $property_definitions = $entity->image->getFieldDefinition()->getPropertyDefinitions(); // Retourne la définition pour la propriété 'alt' uniquement. $alt_definition = $entity->image->getFieldDefinition()->getPropertyDefinition('alt'); // Les définitions de champs d’entités peuvent aussi être récupérées via le gestionnaire d’entités : \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type); // Ce qui retourne les champs disponibles pour un bundle donné : \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle);
Widgets et formatteurs pour champs de base
Les champs de base peuvent spécifier les widgets et formatteurs à utiliser, comme les champs configurables. Le widget, le formatteur et leurs paramètres sont définis dans la classe FieldDefinition ainsi :
use Drupal\Core\Field\BaseFieldDefinition; // ... $fields['title'] = BaseFieldDefinition::create('string') ->setLabel(t('Title')) ->setDescription(t('The title of this node, always treated as non-markup plain text.')) ->setRequired(TRUE) ->setTranslatable(TRUE) ->setSettings([ 'default_value' => '', 'max_length' => 255, ]) ->setDisplayOptions('view', [ 'label' => 'hidden', 'type' => 'string', 'weight' => -5, ]) ->setDisplayOptions('form', [ 'type' => 'string', 'weight' => -5, ]) ->setDisplayConfigurable('form', TRUE);
Ici, le formatteur 'string' et le widget sont utilisés, et un poids est configuré pour le titre du nœud. setDisplayConfigurable()
permet de rendre le champ visible dans l’interface de gestion d’affichage du formulaire et de la vue, pour permettre la modification de l’ordre et de l’affichage des labels. Le noyau ne permet pas actuellement de modifier le widget ou ses réglages via l’interface utilisateur.
Pour définir un champ comme caché par défaut, vous pouvez aussi définir la clé region dans le tableau passé à setDisplayOptions()
et la mettre sur 'hidden'.