Ajouter un formulaire à une entité existante

Problématique

Dans notre Drupal, nous avons une entité composée de plusieurs champs mais ne souhaitons pas qu'ils soient tous renseignés via le même formulaire ? 2 solutions s'offrent à nous :

  • La première est de créer notre controller, créer notre formulaire et gérer le chargement et l'enregistrement des données.
  • La seconde est d'altérer la définition de l'entité pour rajouter notre formulaire.

Nous conviendrons rapidement que la première méthode n'est pas la plus appropriée eu égard aux bonnes pratiques Drupal. De même, l'effort à fournir par le second cas sera moindre par rapport au premier. Pourquoi tout reconstruire alors que l'on peut utiliser les mécanismes existants ?

Voyons comment réaliser cela via un cas pratique : nous avons une entité 'user' pour laquelle nous souhaitons avoir un formulaire d'édition générale (e-mail / mot de passe) et un formulaire pour qu'il renseigne ses informations personnelles (nom / prénom / adresse).

Création du nouvel affichage de formulaire

Dans un premier temps, allons créer un nouvel affichage de formulaire.

  • Aller sur la page d'administration de l'affichage de notre type d'utilisateur /admin/config/people/accounts/form-display.
  • Ajouter un affichage de formulaire 'personal'.
  • N'y placer que les champs (que nous aurons préalablement créés) nom, prénom, adresse.
  • Dans l'affichage du formulaire par défaut, retirer ces 3 champs de l'affichage.

Nous avons maintenant un nouvel affichage de formulaire, sauf que celui-ci n'est utilisé nulle part.

Nous allons donc créer une nouvelle définition de route pour y accéder.

Accéder au nouvel affichage de formulaire

Dans le fichier de routing de notre module custom (MONMODULE) : MONMODULE.routing.yml, ajouter une nouvelle route :

MONMODULE.user.personal_form:
  path: '/user/{user}/personal_form'
  defaults:
    _entity_form: user.personal
    _title: Personal infromations
  requirements:
    _entity_access: user.update
    user: \d+
  options:
    _admin_route: FALSE

 

Voyons en détail à quoi servent ces lignes :

# Nom de la route.
MONMODULE.user.personal_form:
  # Chemin vers la route. On reprend le chemin d'un user, {user} permet de définir que l'on souhaite utiliser un jeton correspondant à notre entité user.
  path: '/user/{user}/personal'
  defaults:
    # Cette route pointe vers un formulaire d'entité de l'entité user avec l'affichage de formulaire que nous venons de créer : personal.
    _entity_form: user.personal
    _title: Personal infromations
  requirements:
    # Par mesure de sécurité, l'utilisateur doit avoir les droits d'accès de l'entité user.update (identique à l'édition normale d'une entité user).
    _entity_access: user.update
    # Définition des règles de validation de notre token user.
    user: \d+
  options:
    # Cette route n'est pas une route admin (FALSE par défaut).
    _admin_route: FALSE

 

On vide les caches, et nous pouvons maintenant accéder à notre formulaire qui ne contiendra que les champs voulus.

Modifier le formulaire

Si l'on souhaite modifier le formulaire existant pour par exemple y ajouter le rendu de notre utilisateur avant les champs d'édition et ajouter un message informatif, nous allons modifier la définition de l'entité user pour y ajouter une nouvelle classe de gestion de notre formulaire.

Dans le fichier MONMODULE.module, créer la méthode MONMODULE_entity_type_alter() ou y ajouter la ligne suivante :

/**
 * Implements hook_entity_type_alter().
 */
function MONMODULE_entity_type_alter(array &$entity_types) {
  $entity_types['user']->setFormClass('personal', 'Drupal\MONMODULE\Form\UserPersonalForm');
}

 

et créons dans src/Form/UserPersonalForm notre nouvelle classe qui hérite de AccountForm.

<?php

namespace Drupal\MONMODULE\Form;

use Drupal\Core\Form\FormStateInterface;
use Drupal\user\AccountForm;

/**
 * Class UserPersonalForm.
 *
 * @package Drupal\MONMODULE\Form
 */
class UserPersonalForm extends AccountForm {

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {
    // Chargement de l'entité en cours pour pouvoir générer son affichage.
    $user = $this->getEntity();

    $user_view = $this->entityTypeManager->getViewBuilder('user')->view($user, 'default');
    $user_view['#attributes'] = [
      'class' => ['user-default-infos'],
    ];
    $form['user_view'] = $user_view;

    $form = parent::form($form, $form_state);

    // Nous pouvons ici modifier le formulaire comme bon nous semble.

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);
    // Nous pouvons faire ici nos propres validations.
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    parent::submitForm($form, $form_state);
    // Nous pouvons faire ici des actions à la suite de la soumission.
  }

  /**
   * {@inheritdoc}
   */
  protected function actions(array $form, FormStateInterface $form_state) {
    $element = parent::actions($form, $form_state);

    // Nous pouvons ici modifier les éléments d'action (boutons).

    return $element;
  }

}

 

Ajouter un template de lien

Il y a une définition d'entité très pratique et simple à utiliser pour gérer des liens d'entités, il s'agit des templates de lien. On les retrouve par exemple dans l'annotation de notre entité user :

 *   links = {
 *     "canonical" = "/user/{user}",
 *     "edit-form" = "/user/{user}/edit",
 *     "cancel-form" = "/user/{user}/cancel",
 *     "collection" = "/admin/people",
 *   },

 

L'avantage de ces templates de liens est de pouvoir générer un lien à partir de l'entité plutôt que de devoir instancier le générateur de route, et de devoir lui passer tous les paramètres.

// Via le générateur de route.
$url = \Drupal::service('url_generator')->generateFromRoute('entity.user.edit_form', ['user' => $user]);
// Via le template de lien.
/** @var \Drupal\user\Entity\User $user */
$url = $user->url('edit-form');

 

Vu que nous avons ajouté notre formulaire personal, nous pouvons également définir un template de lien pour celui-ci.

Dans notre fichier MONMODULE.module, ajouter la ligne suivante au hook MONMODULE_entity_type_alter().

/**
 * Implements hook_entity_type_build().
 */
function MONMODULE_entity_type_alter(array &$entity_types) {
  $entity_types['user']->setFormClass('personal', 'Drupal\MONMODULE\Form\UserPersonalForm');
  $entity_types['user']->setLinkTemplate('personal-form', '/user/{user}/personal');
}

 

Ce qui nous permettra également de générer nos liens de la sorte :

/** @var \Drupal\user\Entity\User $user */
$url = $user->url('personal-form');