How to add custom mode form to entities in Drupal 8 & 9
in this article we will discover how to use form modes, from their creation to their use to customize the input of a user's information, for example.
The creation of form modes is quite simple and can be done in a few clicks, from the administration interface (from the path /admin/structure/display-modes/form).
Let's add a new form mode that we will call for example Profil.
And the User entity now has a new Profil form mode, in addition to the existing Register form mode (used for the registration form on a Drupal 8 site).
And we find our new form mode on the configuration page of the forms display (path /admin/config/people/accounts/form-display) of Drupal users.
After we activate Profil mode then we can configure which fields will be rendered in this form mode. For example, we can configure this form mode to fill only the First name, Last name, Picture and Job fields that have been created for the User entity.
To finalize this we will use a custom module that we will call mymodule.
Let's create mymodule.info.yml file:
type: module
name: My Module
description: My Module description
package: Custom
core_version_requirement: ^8.8.0 || ^9.0
This module will allow us to declare our new form mode for the User entity, and to create a route, as well as a menu, which will allow us to access and complete our form.
First, let's declare this form mode and associate a Class with it, from the file mymodule.module:
<?php
/**
* Implements hook_entity_type_build().
*/
function mymodule_entity_type_build(array &$entity_types) {
$entity_types['user']->setFormClass('profil', 'Drupal\user\ProfileForm');
}
Here we associate the default form class User ProfileForm with our profil form mode. We could just as easily have used a Class ProfilCustomForm by extending the Class AccountForm.
All we have to do now is create a route, from the file mymodule.routing.yml, and we can then access our form.
mymodule.user.profil:
path: '/user/{user}/profil'
defaults:
_entity_form: 'user.profil'
_title: 'Profil'
requirements:
_entity_access: 'user.update'
user: \d+
options:
_admin_route: FALSE
Now we will create a dynamic menu entry in the user account menu, in order to give an access link to users. In the file mymodule.links.menu.yml, let's add an entry to create the corresponding menu link.
mymodule.user.profil:
title: 'Profil'
weight: 1
route_name: mymodule.user.profil
base_route: entity.user.canonical
menu_name: account
class: Drupal\mymodule\Plugin\Menu\ProfilUserBase
What is notable here, in this menu entry, is the class property that will allow us to define the dynamic {user} parameter of the route corresponding to this menu entry.
This ProfileUserBase Class will only return the ID of the accessed user, if the menu link is displayed on the user's page, or return the ID of the connected user if it is not, otherwise.
<?php
namespace Drupal\mymodule\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();
}
}
Finally here is an example of our profil form: