How to render programatically custom entity form mode in Drupal?
Sometime we want to render entity form on a custom page or even a block. It is possible to do in Drupal 8, because we can create custom entity form mode.
Let's imagine we have a node type "Post" and this content type has a lot of custom fields. We have technical field "E-mails" and we want to send e-mails, when someone comments this node.
For the sake of the example we don't want to "pollute" the main node form with "E-mails" field and we want to create a separate form mode "email_settings" for it.
If you try to display it programatically, you will face the exception:
public function emailSettings($node) {
return $this->entityFormBuilder()->getForm($node, 'email_settings');
}
The website encountered an unexpected error. Please try again later.
Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException: The "node" entity type did not specify a "email_settings" form class. in Drupal\Core\Entity\EntityTypeManager->getFormObject() (line 223 of core/lib/Drupal/Core/Entity/EntityTypeManager.php). Drupal\Core\Entity\EntityFormBuilder->getForm(Object, 'email_settings') (Line: 10)
Drupal\custom_entity_form_mode\Controller\CustomEntityFormModeController->emailSettings(Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 582)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 151)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 693)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
but it the same time if we use "default" form mode, it works perfectly
public function emailSettings($node) {
return $this->entityFormBuilder()->getForm($node, 'default');
}
Why it happens? Well, it is quite simple the entity, in our case node, just has no idea how to handle our custom form mode. To be more precise entity doesn't know, which class to use to handle form mode.
If you check node entity annotation, there is no information about our custom form mode. Unfortunately, It is not done automatically.
* "form" = {
* "default" = "Drupal\node\NodeForm",
* "delete" = "Drupal\node\Form\NodeDeleteForm",
* "edit" = "Drupal\node\NodeForm",
* "delete-multiple-confirm" = "Drupal\node\Form\DeleteMultiple"
* },
We have to alter the entity for this we will use hook_entity_type_alter. Full code could be found here: https://github.com/LOBsTerr/drupal-modules-examples/tree/master/custom_entity_form_mode
/**
* Implements hook_entity_type_alter().
*/
function custom_entity_form_mode_entity_type_alter(array &$entity_types) {
// You can provide more complex logic and checks here, we are sure that node
// module is active and it is enabled in our case.
$default_handler_class = $entity_types['node']->getHandlerClasses()['form']['default'];
$entity_types['node']->setFormClass('emails_settings', $default_handler_class);
}
Now you can create a node of content type, for which you have create form mode and open your custom page. In our example /email-settings/[nid]
Now you can display your form in any place you want