FieldTypes, FieldWidgets et FieldFormatters
Aperçu
Drupal 8 est livré avec une grande bibliothèque de classes de base qui vous permettent de travailler avec votre propre contenu. Lorsqu'il s'agit d'entités de contenu, vous souhaitez utiliser des champs. Il est important de comprendre les champs, car c'est là que vos entités stockent leurs données.
FieldTypes (Types de champ)
Les types de champ de base :
- boolean
- changed
- comment
- created
- datetime
- daterange
- decimal
- entity_reference
- file
- float
- image
- integer
- language
- link
- list_float
- list_integer
- list_string
- map
- password
- path
- string
- string_long
- telephone
- text
- text_long
- text_with_summary
- timestamp
- uri
- uuid
Types de champs personnalisés
Chaque fois que vous souhaitez représenter des données d’une manière que Drupal ne fournit pas, vous voudrez peut-être créer un nouveau type de champ pour vos données.
Supposons que vous ayez une entité de contenu qui contient des données sensibles. Le créateur de ce contenu peut autoriser certains utilisateurs à accéder à cette entité via un mot de passe différent pour chaque utilisateur. En termes de base de données, vous voulez quelque chose comme :
| entity_id | uid | password | ----------------------------------- | 1 | 1 | 'helloworld' | | 1 | 2 | 'goodbye' |
Comme vous pouvez le voir, notre entité avec l’ID 1 a deux mots de passe différents pour deux utilisateurs différents. Alors, comment pouvons-nous implémenter cela dans Drupal sans créer manuellement une table ? Nous créons un nouveau type de champ.
Puisque Drupal implémente la logique des champs comme un plugin, nous avons une classe de base dont nous pouvons hériter pour que cela fonctionne dans Drupal. Pour un nouveau type de champ, créez la structure de dossiers suivante dans votre module :
modules/custom/MODULENAME/src/Plugin/Field/FieldType
C’est un chemin assez long et un peu pénible, mais Drupal facilite la gestion des nombreuses fonctionnalités pouvant coexister dans vos modules.
Pour notre exemple, créons le fichier EntityUserAccessField.php
namespace Drupal\MODULENAME\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("Entity User Access"),
* description = @Translation("Ce champ stocke une référence à un utilisateur et un mot de passe pour cet utilisateur sur l'entité."),
* )
*/
class EntityUserAccessField extends FieldItemBase {
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
// À implémenter.
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
// À implémenter.
}
}
Comme vous le voyez, un type de champ ressemble beaucoup à une entité de contenu. En fait, il n’y a pas de grande différence entre les deux, mais c’est un sujet pour un autre article ;)
Tout d’abord, nous avons l’annotation pour notre type de champ :
- @FieldType : appelle la classe d’annotation FieldType de la bibliothèque Drupal
- id : le nom machine de notre type de champ pour pouvoir l’utiliser ailleurs. Assurez-vous de ne pas écraser des noms prédéfinis comme ceux de PHP.
- label : le nom lisible par l’utilisateur, traduit.
- description : si le label ne suffit pas, vous pouvez ajouter une description.
Ensuite, notre classe étend FieldItemBase, ce qui nous oblige à implémenter deux méthodes pour que le type de champ fonctionne correctement :
- propertyDefinitions() : semblable à baseFieldDefinition d’une entité (mais ce n’est pas la même chose !). Elle définit les données utilisées dans les formulaires des entités où ce type de champ est utilisé.
- schema() : sur les entités cette méthode est dépréciée, mais elle est encore requise pour les champs. Elle décrit la structure des données en base. Elle peut différer des propriétés.
Comme il n’est pas évident de savoir quoi mettre dans ces méthodes, voici un exemple pour vous aider :
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['uid'] = DataDefinition::create('integer')
->setLabel(t('User ID Reference'))
->setDescription(t('L’ID de l’utilisateur référencé.'))
->setSetting('unsigned', TRUE);
$properties['password'] = DataDefinition::create('string')
->setLabel(t('Password'))
->setDescription(t('Un mot de passe enregistré en clair. Ce n’est pas sûr !'));
$properties['created'] = DataDefinition::create('timestamp')
->setLabel(t('Created Time'))
->setDescription(t('Le moment de la création de l’entrée.'));
// À compléter avec d’autres propriétés.
return $properties;
}
Il est aussi possible d’utiliser DataReferenceDefinition pour stocker l’ID utilisateur, ce qui pourra être abordé plus tard.
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$columns = [
'uid' => [
'description' => 'L’ID de l’utilisateur référencé.',
'type' => 'int',
'unsigned' => TRUE,
],
'password' => [
'description' => 'Mot de passe en clair.',
'type' => 'varchar',
'length' => 255,
],
'created' => [
'description' => 'Timestamp de création.',
'type' => 'int',
],
// À compléter avec d’autres colonnes.
];
$schema = [
'columns' => $columns,
'indexes' => [],
'foreign keys' => [],
];
return $schema;
}
La méthode schema() est nécessaire pour que Drupal sache comment enregistrer les données. Les colonnes du schéma doivent être un sous-ensemble des propriétés définies dans propertyDefinitions().
Vous avez maintenant un tout nouveau type de champ. Il n’a pas encore de logique pour gérer la saisie, mais il peut être utilisé dans n’importe quelle entité comme champ. Vous pouvez aussi l’utiliser comme baseField d’une entité :
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
// Autres champs...
$fields['entity_user_access'] = BaseFieldDefinition::create('entity_user_access')
->setLabel(t('Entity User Access'))
->setDescription(t('Spécifie les mots de passe pour chaque utilisateur qui veut voir cette entité.'))
->setCardinality(-1); // Permet plusieurs valeurs.
// Autres champs...
return $fields;
}
- BaseFieldDefinition::create() : utiliser le nom machine du type de champ.
- setCardinality(-1) : définit le nombre d’éléments que le champ peut contenir. -1 signifie un nombre illimité.
FieldWidget (Widget de champ)
Si vous avez des données personnalisées, vous pouvez vouloir une représentation personnalisée dans les formulaires. Les widgets définissent comment l’utilisateur saisit ces données. Par exemple :
- Un entier affiché sous forme de case à cocher
- Un autocomplétion pour vos données
- Une saisie de mot de passe via une interface spécifique
Le widget de champ se trouve dans
modules/custom/MODULENAME/src/Plugin/Field/FieldWidget
Encore un chemin long, mais il devient clair pourquoi Drupal structure ainsi les fichiers.
Créons le widget dans EntityUserAccessWidget.php
namespace Drupal\MODULENAME\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
/**
* Plugin implementation of the 'entity_user_access_w' widget.
*
* @FieldWidget(
* id = "entity_user_access_w",
* label = @Translation("Entity User Access - Widget"),
* description = @Translation("Entity User Access - 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) {
// À implémenter.
}
}
Vous avez sans doute remarqué que Drupal utilise ce même style avec annotation et classe de base pour toutes les fonctionnalités.
- @FieldWidget : annotation de classe
- id : nom machine du widget
- field_types : tableau des types de champ compatibles avec ce widget
- multiple_values : par défaut FALSE, définit si plusieurs valeurs sont possibles dans le formulaire
Pour utiliser ce widget avec votre type de champ, modifiez l’annotation du type de champ ainsi :
// ...
/**
* @FieldType(
* id = "entity_user_access",
* label = @Translation("Entity User Access"),
* description = @Translation("Ce champ stocke une référence à un utilisateur et un mot de passe pour cet utilisateur sur l'entité."),
* default_widget = "entity_user_access_w",
* )
*/
// ...
C’est fait ! Mais attendez, rien ne se passe tant que vous n’avez pas implémenté formElement() dans votre widget :
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['userlist'] = [
'#type' => 'select',
'#title' => t('User'),
'#description' => t('Select group members from the list.'),
'#options' => [
0 => t('Anonymous'),
1 => t('Admin'),
2 => t('foobar'),
// À améliorer !
],
];
$element['passwordlist'] = [
'#type' => 'password',
'#title' => t('Password'),
'#description' => t('Select a password for the user'),
];
// Définir les valeurs par défaut pour chaque champ.
$childs = Element::children($element);
foreach ($childs as $child) {
$element[$child]['#default_value'] = isset($items[$delta]->{$child}) ? $items[$delta]->{$child} : NULL;
}
return $element;
}
Si vous ouvrez le formulaire avec ce widget, vous verrez au moins deux champs : un pour choisir l’utilisateur et un autre pour saisir le mot de passe. Pour gérer la sauvegarde, vous devez implémenter la validation dans ce widget ou dans le formulaire de l’entité. Voir l’API Formulaire Drupal 8 pour plus d’informations.
Vous avez maintenant fait une grande partie du travail pour un champ personnalisé. Si vous ne comprenez pas tout, essayez le code ou regardez les modules de base pour approfondir le sujet.
FieldFormatters (Formatteurs de champ)
Enfin, il reste la présentation des données dans ce qu’on appelle le mode vue d’une entité — le widget est en mode formulaire. Cela arrive par exemple lorsque vous consultez une entité via yourdrupalpage.com/myentity/1/view
Plutôt que trop parler, voici le code. Dans modules/custom/MODULENAME/src/Plugin/Field/FieldFormatter, créez EntityUserAccessFormatter.php
namespace Drupal\MODULENAME\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
/**
* Plugin implementation of the 'entity_user_access_f' formatter.
*
* @FieldFormatter(
* id = "entity_user_access_f",
* label = @Translation("Entity User Access - Formatter"),
* description = @Translation("Entity User Access - Formatter"),
* field_types = {
* "entity_user_access",
* }
* )
*/
class EntityUserAccessFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
$elements[$delta] = [
'uid' => [
'#markup' => \Drupal\user\Entity\User::load($item->uid)->getUsername(),
],
// Ajouter plus de contenu ici.
];
}
return $elements;
}
}
Cet exemple a une annotation similaire à celle du widget, donc pas besoin d’en dire beaucoup. La méthode viewElements() affiche simplement le nom d’utilisateur correspondant à l’ID stocké dans notre champ. La gestion des droits d’accès doit être réalisée dans l’entité. Cette implémentation affichera donc tous les noms des utilisateurs qui ont un mot de passe pour l’entité.