
Comment créer une entité personnalisée dans Drupal 8
L'API d'entité dans Drupal 8 est maintenant intégrée au noyau et est désormais si bien organisée qu'il n'y a presque aucune excuse pour créer des tables de base de données qui ne sont pas des entités en même temps. Si vous êtes sérieux au sujet du développement Drupal, consultez cet article ci-dessous. Les entités dans Drupal déchirent vraiment!
Si vous créez une entité, vous bénéficiez de l'intégration de Views gratuitement, vous pouvez autoriser l'entité à être fieldable, et cela permettra dès le départ d'ajouter divers champs. Vous pouvez également rechercher l'entité avec Entity Drupal::EntityQuery et bien plus encore.
J'ai récemment dû créer une entité simple pour un dictionnaire en ligne et je pense que c'est une excellente occasion de partager avec vous ce que j'ai appris.
Entité de terme de dictionnaire
L'entité stockera les traductions de mots de l'anglais au polonais. C'est une entité vraiment simple avec seulement 2 champs de données :
- pl - champ pour stocker le mot polonais
- en - champ pour stocker le mot anglais
Je vais en fait ajouter quelques champs supplémentaires qui valent la peine d'être ajoutés à presque toute entité :
- id - identifiant unique
- uuid - Drupal 8 a maintenant un support natif pour créer des identifiants universellement uniques
- user_id - identifiant du créateur de l'entité (une référence à l'utilisateur Drupal)
- created - un horodatage de la création de l'entité
- changed - un horodatage de la dernière mise à jour de l'entité
Créons un module de 'dictionnaire'
Dans /sites/modules/custom, j'ai créé un dossier 'dictionary', avec les fichiers initiaux suivants :
dictionary.info.yml
name: dictionnaire
type: module
description: Dictionnaire
core: 8.x
package: Application
<?php
/**
* @file
* Contient \Drupal\content_entity_example\Entity\ContentEntityExample.
*/
namespace Drupal\dictionary\Entity;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\user\UserInterface;
use Drupal\Core\Entity\EntityChangedTrait;
/**
* Définit l'entité ContentEntityExample.
*
* @ingroup dictionary
*
*
* @ContentEntityType(
* id = "dictionary_term",
* label = @Translation("Entité Terme de dictionnaire"),
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\dictionary\Entity\Controller\TermListBuilder",
* "form" = {
* "add" = "Drupal\dictionary\Form\TermForm",
* "edit" = "Drupal\dictionary\Form\TermForm",
* "delete" = "Drupal\dictionary\Form\TermDeleteForm",
* },
* "access" = "Drupal\dictionary\TermAccessControlHandler",
* },
* list_cache_contexts = { "user" },
* base_table = "dictionary_term",
* admin_permission = "administer dictionary_term entity",
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "user_id" = "user_id",
* "created" = "created",
* "changed" = "changed",
* "pl" = "pl",
* "en" = "en",
* },
* links = {
* "canonical" = "/dictionary_term/{dictionary_term}",
* "edit-form" = "/dictionary_term/{dictionary_term}/edit",
* "delete-form" = "/dictionary_term/{dictionary_term}/delete",
* "collection" = "/dictionary_term/list"
* },
* field_ui_base_route = "entity.dictionary.term_settings",
* )
*/
class Term extends ContentEntityBase {
use EntityChangedTrait;
/**
* {@inheritdoc}
*
* Lorsqu'une nouvelle instance d'entité est ajoutée, définissez la référence de l'entité user_id sur
* l'utilisateur actuel en tant que créateur de l'instance.
*/
public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
parent::preCreate($storage_controller, $values);
// Auteur par défaut pour l'utilisateur actuel.
$values += array(
'user_id' => \Drupal::currentUser()->id(),
);
}
/**
* {@inheritdoc}
*
* Définissez ici les propriétés du champ.
*
* Le nom, le type et la taille du champ déterminent la structure de la table.
*
* De plus, nous pouvons définir comment le champ et son contenu peuvent être manipulés
* dans l'interface graphique. Le comportement des widgets utilisés peut être défini ici.
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
// Champ standard, utilisé comme unique si index principal.
$fields['id'] = BaseFieldDefinition::create('integer')
->setLabel(t('ID'))
->setDescription(t('L'ID de l'entité Term.'))
->setReadOnly(TRUE);
// Champ standard, unique en dehors du cadre du projet actuel.
$fields['uuid'] = BaseFieldDefinition::create('uuid')
->setLabel(t('UUID'))
->setDescription(t('L'UUID de l'entité Contact.'))
->setReadOnly(TRUE);
// Champ de nom pour le contact.
// Nous définissons les options d'affichage pour la vue ainsi que le formulaire.
// Les utilisateurs ayant les privilèges corrects peuvent modifier la vue et la configuration d'édition.
$fields['pl'] = BaseFieldDefinition::create('string')
->setLabel(t('Polonais'))
->setDescription(t('Version polonaise.'))
->setSettings(array(
'default_value' => '',
'max_length' => 255,
'text_processing' => 0,
))
->setDisplayOptions('view', array(
'label' => 'above',
'type' => 'string',
'weight' => -6,
))
->setDisplayOptions('form', array(
'type' => 'string_textfield',
'weight' => -6,
))
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
$fields['en'] = BaseFieldDefinition::create('string')
->setLabel(t('Anglais'))
->setDescription(t('Version anglaise.'))
->setSettings(array(
'default_value' => '',
'max_length' => 255,
'text_processing' => 0,
))
->setDisplayOptions('view', array(
'label' => 'above',
'type' => 'string',
'weight' => -4,
))
->setDisplayOptions('form', array(
'type' => 'string_textfield',
'weight' => -4,
))
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
// Champ propriétaire du contact.
// Champ de référence d'entité, contient la référence à l'objet utilisateur.
// La vue montre le champ de nom d'utilisateur de l'utilisateur.
// Le formulaire présente un champ d'auto-complétion pour le nom d'utilisateur.
$fields['user_id'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Nom d'utilisateur'))
->setDescription(t('Le nom de l'utilisateur associé.'))
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
->setDisplayOptions('view', array(
'label' => 'above',
'type' => 'author',
'weight' => -3,
))
->setDisplayOptions('form', array(
'type' => 'entity_reference_autocomplete',
'settings' => array(
'match_operator' => 'CONTAINS',
'size' => 60,
'placeholder' => '',
),
'weight' => -3,
))
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Créé'))
->setDescription(t('Le moment où l'entité a été créée.'));
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Modifié'))
->setDescription(t('Le moment où l'entité a été dernièrement modifiée.'));
return $fields;
}
}
Vous pouvez voir que la classe Term s'étend ContentEntityBase qui est la classe de base utilisée pour créer des entités dans Drupal. Un point important à noter est les annotations dans le commentaire juste au-dessus de la classe. De nombreuses définitions importantes sont définies ici. Notez en particulier :
- id - identifiant unique de l'entité dans le système
- handlers - liens vers tous les contrôleurs
- base_table - le nom de la table qui sera utilisée pour cette entité. Vous n'avez pas besoin de créer un schéma séparément, il est lu à partir des définitions de champ
C'est tout. Votre entité est prête. Tout le travail futur est consacré à obtenir toutes les vues, les éditions et les écrans de liste et les formulaires pour fonctionner.
Mettons en place le routage et les autorisations pour ceux-ci d'abord.
dictionary.routing.yml
# Ce fichier rassemble tout. Très pratique !
# Le nom de la route peut être utilisé à plusieurs endroits (liens, redirections, actions locales, etc.)
entity.dictionary_term.canonical:
path: '/dictionary_term/{dictionary_term}'
defaults:
# Appelle le contrôleur de vue, défini dans l'annotation de l'entité dictionary_term
_entity_view: 'dictionary_term'
_title: 'Contenu de dictionary_term'
requirements:
# Appelle le contrôleur d'accès de l'entité, $operation 'view'
_entity_access: 'dictionary_term.view'
entity.dictionary_term.collection:
path: '/dictionary_term/list'
defaults:
# Appelle le contrôleur de liste, défini dans l'annotation de l'entité dictionary_term.
_entity_list: 'dictionary_term'
_title: 'Liste de dictionary_term'
requirements:
# Vérifie l'autorisation directement.
_permission: 'view dictionary_term entity'
entity.dictionary.term_add:
path: '/dictionary_term/add'
defaults:
# Appelle le contrôleur form.add, défini dans l'entité dictionary_term.
_entity_form: dictionary_term.add
_title: 'Ajouter dictionary_term'
requirements:
_entity_create_access: 'dictionary_term'
entity.dictionary_term.edit_form:
path: '/dictionary_term/{dictionary_term}/edit'
defaults:
# Appelle le contrôleur form.edit, défini dans l'entité dictionary_term.
_entity_form: dictionary_term.edit
_title: 'Modifier dictionary_term'
requirements:
_entity_access: 'dictionary_term.edit'
entity.dictionary_term.delete_form:
path: '/dictionary_term/{dictionary_term}/delete'
defaults:
# Appelle le contrôleur form.delete, défini dans l'entité dictionary_term.
_entity_form: dictionary_term.delete
_title: 'Supprimer dictionary_term'
requirements:
_entity_access: 'dictionary_term.delete'
entity.dictionary.term_settings:
path: 'admin/structure/dictionary_term_settings'
defaults:
_form: '\Drupal\dictionary\Form\TermSettingsForm'
_title: 'Paramètres de dictionary_term'
requirements:
_permission: 'administrer l'entité dictionary_term'
dictionary.persmissions.yml
'supprimer l'entité dictionary_term':
title: Supprimer le contenu de l'entité terme.
'ajouter l'entité dictionary_term':
title: Ajouter le contenu de l'entité terme
'voir l'entité dictionary_term':
title: Voir le contenu de l'entité terme
'modifier l'entité dictionary_term':
title: Modifier le contenu de l'entité terme
'administrer l'entité dictionary_term':
title: Administrer les paramètres de l'entité terme
Habituellement, les entités ont également des liens locaux et des tâches utiles, comme la tâche locale /edit.
dictionary.links.tasks.yml
Ajouter des tâches locales
# Définir les liens 'locaux' pour le module
entity.dictionary_term.settings_tab:
route_name: dictionary.term_settings
title: Paramètres
base_route: dictionary.term_settings
entity.dictionary_term.view:
route_name: entity.dictionary_term.canonical
base_route: entity.dictionary_term.canonical
title: Voir
entity.dictionary_term.page_edit:
route_name: entity.dictionary_term.edit_form
base_route: entity.dictionary_term.canonical
title: Modifier
entity.dictionary_term.delete_confirm:
route_name: entity.dictionary_term.delete_form
base_route: entity.dictionary_term.canonical
title: Supprimer
weight: 10
dictionary.links.action.yml
Ajouter un terme à la liste des termes
dictionary._term_add:
# Quelle route sera appelée par le lien
route_name: entity.dictionary.term_add
title: 'Ajouter un terme'
# Où le lien apparaîtra, défini par le nom de la route.
appears_on:
- entity.dictionary_term.collection
- entity.dictionary_term.canonical
Maintenant que des menus et tâches sont créés, créons la page qui liste nos entités ainsi que les formulaires d'ajout, de modification et de suppression
/src/Entity/Controller/TermListBuilder.php
À partir de tous les contrôleurs, nous allons mettre en œuvre le contrôleur pour la liste des entités. Après tout, nous aimerions voir la liste de façon significative avec le mot anglais et polonais côte à côte dans un tableau
<?php
/**
* @file
* Contient \Drupal\dictionaryEntity\Controller\TermListBuilder.
*/
namespace Drupal\dictionary\Entity\Controller;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Fournit un contrôleur de liste pour l'entité dictionary_term.
*
* @ingroup dictionary
*/
class TermListBuilder extends EntityListBuilder {
/**
* Le générateur d'URL.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$container->get('url_generator')
);
}
/**
* Construit un nouvel objet DictionaryTermListBuilder.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* Le type d'entité terme.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* La classe de stockage des entités.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* Le générateur d'URL.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, UrlGeneratorInterface $url_generator) {
parent::__construct($entity_type, $storage);
$this->urlGenerator = $url_generator;
}
/**
* {@inheritdoc}
*
* Nous surchageons ::render() afin que nous puissions ajouter notre propre contenu au-dessus du tableau.
* parent::render() est l'endroit où EntityListBuilder crée le tableau en utilisant nos
* implémentations de buildHeader() et buildRow().
*/
public function render() {
$build['description'] = array(
'#markup' => $this->t('Content Entity Example implémente un modèle DictionaryTerms. Ce sont des entités fieldables. Vous pouvez gérer les champs sur la <a href="@adminlink">page d'administration des termes</a>.', array(
'@adminlink' => $this->urlGenerator->generateFromRoute('entity.dictionary.term_settings'),
)),
);
$build['table'] = parent::render();
return $build;
}
/**
* {@inheritdoc}
*
* Construction de l'en-tête et des lignes de contenu pour la liste dictionary_term.
*
* Appeler parent::buildHeader() ajoute une colonne pour les actions possibles
* et insère les liens 'modifier' et 'supprimer' tels que définis pour le type d'entité.
*/
public function buildHeader() {
$header['id'] = $this->t('ID Terme');
$header['pl'] = $this->t('Polonais');
$header['en'] = $this->t('Anglais');
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
/* @var $entity \Drupal\dictionary\Entity\Term */
$row['id'] = $entity->id();
$row['pl'] = $entity->pl->value;
$row['en'] = $entity->en->value;
return $row + parent::buildRow($entity);
}
}
Vous pouvez voir les fonctions buildHeader et buildRow qui définissent les colonnes de notre tableau.
ajouter/modifier le formulaire - src/Form/TermForm.php
<?php
/**
* @file
* Contient Drupal\dictionary\Form\TermForm.
*/
namespace Drupal\dictionary\Form;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
/**
* Contrôleur de formulaire pour les formulaires d'édition d'entité content_entity_example.
*
* @ingroup content_entity_example
*/
class TermForm extends ContentEntityForm {
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
/* @var $entity \Drupal\dictionary\Entity\Term */
$form = parent::buildForm($form, $form_state);
return $form;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
// Rediriger vers la liste des termes après l'enregistrement.
$form_state->setRedirect('entity.dictionary_term.collection');
$entity = $this->getEntity();
$entity->save();
}
}
supprimer le formulaire - src/Form/TermForm.php
<?php
/**
* @file
* Contient \Drupal\dictionary\Form\TermDeleteForm.
*/
namespace Drupal\dictionary\Form;
use Drupal\Core\Entity\ContentEntityConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Fournit un formulaire pour supprimer une entité content_entity_example.
*
* @ingroup dictionary
*/
class TermDeleteForm extends ContentEntityConfirmFormBase {
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->t('Êtes-vous sûr de vouloir supprimer l'entité %name?', array('%name' => $this->entity->label()));
}
/**
* {@inheritdoc}
*
* Si la commande de suppression est annulée, retourner à la liste de contacts.
*/
public function getCancelUrl() {
return new Url('entity.dictionary_term.collection');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Supprimer');
}
/**
* {@inheritdoc}
*
* Supprime l'entité et enregistre l'événement. logger() remplace watchdog.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$entity = $this->getEntity();
$entity->delete();
$this->logger('dictionary')->notice('supprimé %title.',
array(
'%title' => $this->entity->label(),
));
// Rediriger vers la liste des termes après la suppression.
$form_state->setRedirect('entity.dictionary_term.collection');
}
}
src/Form/TermSettingsForm.php
Enfin et surtout, le formulaire de paramètres qui nous permet de gérer les paramètres de l'entité. Notre formulaire ici restera vide. Je n'ai pas besoin de paramètres ici, mais je pourrais gérer des paramètres de formulaire supplémentaires ici.
<?php
/**
* @file
* Contient \Drupal\dictionary\Form\TermSettingsForm.
*/
namespace Drupal\dictionary\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Classe ContentEntityExampleSettingsForm.
*
* @package Drupal\dictionary\Form
*
* @ingroup dictionary
*/
class TermSettingsForm extends FormBase {
/**
* Retourne une chaîne unique identifiant le formulaire.
*
* @return string
* La chaîne unique identifiant le formulaire.
*/
public function getFormId() {
return 'dictionary_term_settings';
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Implémentation vide de la classe de soumission abstraite.
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['dictionary_term_settings']['#markup'] = 'Formulaire de paramètres pour le terme de dictionnaire. Gérez les paramètres de champ ici.';
return $form;
}
}