9

I need to programmatically generate the entity form for my custom entity - basically get the same output that happens when you call

\Drupal::formBuilder()->getForm('form namespace')

When I try to do that with my entity form I get the error:

Call to a member function getEntityTypeId() on null in Drupal\Core\Entity\EntityForm->getBaseFormId() (line 77 of core/lib/Drupal/Core/Entity/EntityForm.php).

Really stumped on how to proceed.


Here's an obfuscated version of code I'm using

Entity Definition:

/**
 * Defines the My Entity entity.
 *
 * @ingroup my_module
 *
 * @ContentEntityType(
 *   id = "my_entity",
 *   label = @Translation("My Entity"),
 *   handlers = {
 *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
 *     "list_builder" = "Drupal\my_module\MyEntityListBuilder",
 *     "views_data" = "Drupal\my_module\Entity\MyEntityViewsData",
 *     "translation" = "Drupal\my_module\MyEntityTranslationHandler",
 *
 *     "form" = {
 *       "default" = "Drupal\my_module\Form\MyEntityForm",
 *       "add" = "Drupal\my_module\Form\MyEntityForm",
 *       "edit" = "Drupal\my_module\Form\MyEntityForm",
 *       "delete" = "Drupal\my_module\Form\MyEntityDeleteForm",
 *     },
 *     "access" = "Drupal\my_module\MyEntityAccessControlHandler",
 *     "route_provider" = {
 *       "html" = "Drupal\my_module\MyEntityHtmlRouteProvider",
 *     },
 *   },
 *   base_table = "my_entity",
 *   data_table = "my_entity_field_data",
 *   translatable = TRUE,
 *   admin_permission = "administer as my_entity entities",
 *   entity_keys = {
 *     "id" = "id",
 *     "label" = "name",
 *     "uuid" = "uuid",
 *     "uid" = "user_id",
 *     "langcode" = "langcode",
 *     "status" = "status",
 *   },
 *   links = {
 *     "canonical" = "/admin/structure/my_entity/{my_entity}",
 *     "add-form" = "/admin/structure/my_entity/add",
 *     "edit-form" = "/admin/structure/my_entity/{my_entity}/edit",
 *     "delete-form" = "/admin/structure/my_entity/{my_entity}/delete",
 *     "collection" = "/admin/structure/my_entity",
 *   },
 *   field_ui_base_route = "my_entity.settings",
 *   constraints = {
 *     "CustomContraint" = {}
 *   }
 * )
 */
class MyEntity extends ContentEntityBase implements MyEntityInterface {

Form namespace

namespace Drupal\my_module\Form;

use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;

/**
 * Form controller for My Entity edit forms.
 *
 * @ingroup my_module
 */
class MyEntityForm extends ContentEntityForm {

Okay, so this is what I've found so far. User oknate's answer partially works; I also had to add a dummy function to my container form class to get around a callback error, which looked like this:

 public function processForm($element, FormStateInterface $form_state, $form)
  {
    return $element;
  }

This is probably not the right way to do this, but it does result in a form object that will render in another form.

8
  • 1
    According to the docs the argument must either be the form ID or the class implementing the form. That form must implement FormInterface. api.drupal.org/api/drupal/…
    – Kevin
    Commented Sep 30, 2016 at 15:45
  • 1
    Ex. \Drupal::formBuilder()->getForm(Drupal\mymodule\Form\FormName)
    – Kevin
    Commented Sep 30, 2016 at 15:48
  • 3
    See: drupal.stackexchange.com/questions/182405/…
    – Kevin
    Commented Sep 30, 2016 at 15:48
  • 1
    Yes, like I said I tried that. Commented Sep 30, 2016 at 15:55
  • 1
    My form does extend from the ContentEntityForm class, which implements the ContentEntityFormInterface, which extends the EntityFormInterface, which extends the BaseFromInterface, which extends the FormInterface, like you said. Commented Sep 30, 2016 at 15:55

3 Answers 3

22

You can use the entity form builder service. I was able to load the user form this way (with $entity being the current user):

$entity = User::load($uid);
$user_form = \Drupal::service('entity.form_builder')->getForm($entity, 'default');

If you want to get the user register form (which is basically an entity form without a saved entity), you can just use the create method on your entity class before passing it to the form:

$entity = User::create();
$user_form = \Drupal::service('entity.form_builder')->getForm($entity, 'default');
10
  • 1
    Okay, this answer has gotten me the closest. When I try doing it, the page loads, but all that shows up is a warning that the form class I'm trying to inject the entity form into doesn't have a 'processForm' method. Commented Sep 30, 2016 at 22:05
  • 1
    The 'processForm' function seems to live in the \Drupal\Core\Entity\EntityForm class. Commented Sep 30, 2016 at 22:08
  • 1
    Since the function just returns one of the arguments it receives ($element), I created a dummy version of that function in my container form class. After doing that, the entity form DOES render. I don't know what the consequences of bypassing that call are though. Commented Sep 30, 2016 at 22:11
  • 1
    @oknate, Thanks for your code, it works perfect, But, I want to attach #after_build method for ajax operations. The #after_build method is not firing/calling, Have you come across with this situation? or anyone has a solution to attach #after_build method Commented Jun 16, 2017 at 7:03
  • 1
    If you are using a build array to hold the form and you are using a template that renders the form properly, then it should work fine. If you are having trouble with ajax not loading, you may be pre-rendering the form and then it's getting cached in the template, where it didn't properly signal to add the javascript on subsequent page loads.
    – oknate
    Commented Jun 16, 2017 at 14:19
5

Entity forms don't work without an entity. If you want an empty form then use a newly created entity instance. You can use this example from the node entity:

/**
 * Provides the node submission form.
 *
 * @param \Drupal\node\NodeTypeInterface $node_type
 *   The node type entity for the node.
 *
 * @return array
 *   A node submission form.
 */
public function add(NodeTypeInterface $node_type) {
  $node = $this->entityManager()->getStorage('node')->create(array(
    'type' => $node_type->id(),
  ));

  $form = $this->entityFormBuilder()->getForm($node);

  return $form;
}

The entity form builder is injected in the controller base class. So usually you use a controller to build entity forms.

When creating a node you need to define a bundle (content type), for which the entity form will be built. If your entity has no bundles you can use an empty array.


Comments

Comments need more work, because they are attached to an entity field.

Example how to build a form for the comment field of a node:

$comment = $this->entityTypeManager()->getStorage('comment')->create([
  'entity_type' => 'node',
  'entity_id' => $node->id(),
  'field_name' => 'comment',
]);
$build = $this->entityFormBuilder()->getForm($comment);

Or the same outside of a controller without injected services:

$comment = \Drupal::entityTypeManager()->getStorage('comment')->create([
  'entity_type' => 'node',
  'entity_id' => $node->id(),
  'field_name' => 'comment',
]);
$build = \Drupal::service('entity.form_builder')->getForm($comment);
1
  • Thanks for sharing, if we altered a form and added #after_build. #after_build method is not working. Do you have any solution? Commented Jun 15, 2017 at 12:47
3

Just to clarify, the below code does work for me in Drupal 8.2.x:

$form = \Drupal::formBuilder()->getForm(Drupal\user\Form\UserLoginForm::class);
3
  • 1
    When I tried your code it initially didn't work, but adding a \ in front of 'Drupal\user\Form\UserLoginForm::class' made it work. When I tried doing it with my custom class, I got the same error though. I've tried updated my question with my entity definition - my guess is that I haven't implemented something that I'd need to implement for your code to work. Commented Sep 30, 2016 at 17:32
  • 1
    Okay, I finally noticed that the example form you used isn't an entity form, it just extends from FormBase. I need to render an entity form (Specifically a content entity, something that extends from ContentEntityForm) Commented Sep 30, 2016 at 21:41
  • 1
    Yes, for my use case this is wrong. Commented Sep 30, 2016 at 22:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.