Fournir un formulaire personnalisé aux entités de Drupal 8

un masque coloré

A l'instar des modes d'affichage qui permettent d'afficher une entité de multiples manières, Drupal 8 permet de créer de multiples modes de saisie, ou formulaires, utilisables sur les entités, que ce soient les utilisateurs, les termes de taxonomy, les contenus ou n'importe quelle entité personnalisée. Découvrons ici comment utiliser ces modes de saisie, depuis leur création jusqu'à leur exploitation pour personnaliser la saisie par exemple des informations d'un utilisateur.

La création de modes de saisie est assez simple et se fait en quelques clics, depuis l'interface d'administration (depuis le chemin /admin/structure/display-modes/form).

Drupal 8 form modes

 

Ajoutons un nouveau mode de saisie que nous allons appeler par exemple Profil.

Form mode Profil

 

Et l'entité Utilisateur dispose désormais d'un nouveau mode de saisie Profil, en plus de celui déjà existant Register (utilisé au passage pour le formulaire d'inscription sur un site Drupal 8).

Et nous retrouvons notre nouveau mode de saisie sur la page de configuration des formulaires de saisie (chemin /admin/config/people/accounts/form-display) des utilisateurs de Drupal.

Profil form

Que nous pouvons activer pour ensuite pouvoir configurer quels champs seront rendus sous ce mode de saisie. Par exemple nous pouvons configurer ce mode de saisie pour renseigner uniquement les champs First name, Last name, Organization et Picture qui ont été créés pour l'entité Utilisateur.

Form profil configuration

 

Jusque ici tout va bien. Mais comment utiliser notre nouveau mode de saisie ? Depuis quel chemin ?

Pour finaliser cela nous allons recourir à un petit module que nous allons appeler my_module.

Ce module va nous permettre d'une part de déclarer notre nouveau mode de saisie pour l'entité Utilisateur, et de créer une route, ainsi qu'un menu, qui nous permettra d'accéder et de renseigner notre formulaire de saisie.

Dans un premier temps, déclarons ce mode de saisie et nous lui associons une Class, depuis le fichier my_module.module

/**
 * Implements hook_entity_type_build().
 */
function my_module_entity_type_build(array &$entity_types) {
  $entity_types['user']->setFormClass('profil', 'Drupal\user\ProfileForm');
}

Ici nous associons à notre mode de saisie profil la classe du formulaire par défaut ProfilForm des utilisateurs. Nous aurions tout aussi bien pu utiliser une Class MyProfilCustomForm en étendant la Class AccountForm.

Il ne nous reste plus qu'à créer une route, depuis le fichier my_module.routing.yml, et nous pourrons alors accéder à notre formulaire de saisie.

my_module.user.profile:
  path: '/user/{user}/profil'
  defaults:
    _entity_form: 'user.profil'
    _title: 'Profil'
  requirements:
    _entity_access: 'user.update'
    _custom_access: '\Drupal\my_module\Access\MyModuleUserAccess::editProfil'
    user: \d+
  options:
    _admin_route: FALSE

Depuis la déclaration de la route, nous spécifions le chemin (/user/{user}/profil), le mode de saisie à utiliser pour l'entité Utilisateur, spécifions des droits d'accès à la route (le droit de modifier un utilisateur, ainsi que des permissions personnalisées si besoin), et pouvons aussi spécifier si la route correspond à une route d'administration, ou non, permettant de définir le thème sous lequel sera rendu le formulaire (backoffice ou frontoffice).

Pour finaliser notre nouveau mode de saisie, nous allons créer une entrée de menu dynamique dans le menu user account, afin de donner un lien d'accès aux utilisateurs ou aux administrateurs. Dans le fichier my_module.links.menu.yml, ajoutons une entrée pour créer le lien de menu correspondant.

my_module.user.profil:
  title: 'Profil'
  weight: 10
  route_name: my_module.user.profil
  base_route: entity.user.canonical
  menu_name: user-account
  class: Drupal\my_module\Plugin\Menu\ProfilUserBase

Ce qui est notable ici, dans cette entrée de menu, est la propriété class qui va nous permettre de définir le paramètre dynamique {user} de la route correspondant à cette entrée de menu. 

Cette Class ProfilUserBase va juste retourner l'identifiant de l'utilisateur consulté, si le lien de menu est affiché sur la page de l'utilisateur, ou de renvoyer l'identifiant de l'utilisateur connecté dans le cas contraire, à défaut.

<?php

namespace Drupal\my_module\Plugin\Menu;

use Drupal\Core\Menu\MenuLinkDefault;
use Drupal\Core\Url;
use Drupal\user\UserInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Menu\StaticMenuLinkOverridesInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;

/**
 * Profile Menu Link
 */
class ProfilUserBase extends MenuLinkDefault implements ContainerFactoryPluginInterface {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManager
   */
  protected $entityTypeManager;

  /**
   * The current route match service.
   *
   * @var \Drupal\Core\Routing\CurrentRouteMatch
   */
  protected $currentRouteMatch;

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * Constructs a new MenuLinkDefault.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $static_override
   *   The static override storage.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   * @param \Drupal\Core\Routing\RouteMatchInterface $current_route_match
   *   The current route match service.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, StaticMenuLinkOverridesInterface $static_override, EntityTypeManagerInterface $entity_type_manager, RouteMatchInterface $current_route_match, AccountInterface $current_user) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $static_override);
    $this->entityTypeManager = $entity_type_manager;
    $this->currentRouteMatch = $current_route_match;
    $this->currentUser = $current_user;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('menu_link.static.overrides'),
      $container->get('entity_type.manager'),
      $container->get('current_route_match'),
      $container->get('current_user')
    );
  }

  public function getRouteParameters() {
    return ['user' => $this->getUserIdFromRoute()];
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return ['user', 'url'];
  }


  /**
   * Get the Account user id from the request or fallback to current user.
   *
   * @return int
   */
  public function getUserIdFromRoute() {
    $user = $this->currentRouteMatch->getParameter('user');
    if ($user instanceof AccountInterface) {
     return $user->id();
    }
    elseif (!empty($user)) {
      $user = $this->entityTypeManager->getStorage('user')->load($user);
      if($user instanceof AccountInterface) {
        return $user->id();
      }
    }

    return $this->currentUser->id();
  }

}

Et désormais vous pouvez utiliser votre nouveau mode de saisie que vous pouvez personnaliser à volonté, que ce soit depuis l'interface graphique ou encore depuis une Class de formulaire personnalisée vous permettant d'introduire toute logique métier et/ou complexe aisément.

Enfin, je ne peux pas conclure ce billet sur les formulaires de saisie de Drupal 8 sans évoquer le module Form Mode Manager qui peut vous permettre de réaliser tout cela sans avoir besoin de recourir à un développeur Drupal. En fonction de vos besoins, du niveau de maitrise souhaité, vous pourrez privilégier l'une ou l'autre de ces solutions. Mais ceci pourra faire l'objet d'un autre billet.

 

Ajouter un commentaire