5

I'm trying to extend registration form programatically to add an additional field. I was following an example from Extend _form in Drupal 8

  • I created a new service that will redirect to the new form.
  • I created a route subscriber to that will return the new form.
  • I created a form class that will extend the form.

It works for the user login form but wont work on user registration form because it is an _entity_form and I guess it should be treated differently. I changed the router subscriber to match registration form but i get this error.

Call to a member function getEntityTypeId() on null.

if ($route = $collection->get('user.register')) {$route->setDefault('_entity_form', '\Drupal\form_overwrite\Form\NewUserLoginForm');

Can anyone suggest please what's the right way to extend this form? I tried with the all known hook_form_alter but even like that I'm not sure how to save the new field content in the configuration files.

1
  • To add a new field to an entity form you simply add this field to the entity. For the entity User you find this here /admin/config/people/accounts/fields. If you want to design a separate register form enable the form mode Register on Manage Form Display at the bottom of the page in Custom Display Settings.
    – 4uk4
    May 23, 2017 at 12:15

3 Answers 3

4

I had this same question and it doesn't seem to be answered anywhere so I'll give the answer that I figure out, hopefully this helps someone.

First you need to add the form to the entity do this in the module file in hook_entity_type_build something like the following sample:

/**
 * Implements hook_entity_build
 */
function MYMODULE_entity_type_build(array &$entity_types) {

  $entity_types['user']->setFormClass(
    'MYMODULE_register',
    'Drupal\MYMODULE\Form\myModuleRegisterForm'
  );
}

Create a new class src/Form/myModuleRegisterForm.php to extend RegisterForm:

namespace Drupal\MYMODULE\Form;

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

class myModuleRegisterForm extends RegisterForm {

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

    // Change the form here

    return $form;
  }

}

Then load it in your controller

public function myModuleRegister {

  $build = [];

  $entity = \Drupal::entityTypeManager()
    ->getStorage('user')
    ->create([]);

  $formObject = \Drupal::entityTypeManager()
    ->getFormObject('user', 'MYMODULE_register')
    ->setEntity($entity);

   $build['form'] = \Drupal::formBuilder()->getForm($formObject);

   return $build;
}

That pretty much covers it, obviously you need the route to point to the appropriate function in your controller but otherwise this should do it. Hope it helps.

3

The other answers focus on overwriting the route itself. If you don't care about changing the route, but just want to change the form markup, here's how to do it:

In your .module file:

function your_module_entity_type_alter(array &$entity_types) {
  $entity_types['user']->setFormClass('register', '\Drupal\your_module\Form\NewRegisterForm');
}

This overrides the Annotations set in User.php

*   handlers = {
 *     "storage" = "Drupal\user\UserStorage",
 *     "storage_schema" = "Drupal\user\UserStorageSchema",
 *     "access" = "Drupal\user\UserAccessControlHandler",
 *     "list_builder" = "Drupal\user\UserListBuilder",
 *     "views_data" = "Drupal\user\UserViewsData",
 *     "route_provider" = {
 *       "html" = "Drupal\user\Entity\UserRouteProvider",
 *     },
 *     "form" = {
 *       "default" = "Drupal\user\ProfileForm",
 *       "cancel" = "Drupal\user\Form\UserCancelForm",
 *       "register" = "Drupal\user\RegisterForm"
 *     },
 *     "translation" = "Drupal\user\ProfileTranslationHandler"
 *   },

The one we're interesting in is the "register" key under "form".

Then in your NewRegisterForm.php you don't need to implement __construct or create, as these are inherited from RegisterForm. In my case, I just wanted to change the submit button text, so I have:

class NewRegisterForm extends RegisterForm {

  protected function actions(array $form, FormStateInterface $form_state) {
    $element = parent::actions($form, $form_state);
    $element['submit']['#value'] = 'Submit';
    return $element;
  }

}

Hope that helps, took me a while to find why overwriting the route didn't do what I wanted.

1
  • Thank you for the perfect answer, how can I modify fields on the form? Feb 18, 2021 at 12:28
1

I had problem with my class throwing an error :

Error: Call to a member function getEntityTypeId() on null in Drupal\Core\Entity\EntityForm->getBaseFormId()

Then I found this code and I think that for Entity form you have to add __construct and create functions:

<?php
namespace Drupal\my_module\Form;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityFormBuilderInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\user\Entity\User;
use Drupal\user\RegisterForm;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a user register form.
 */
class NewUserRegisterForm extends RegisterForm {

    public function __construct(EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL, ModuleHandlerInterface $moduleHandler) {
        $this->setEntity(new User([], 'user'));
        $this->setModuleHandler($moduleHandler);

        parent::__construct($entity_manager, $language_manager, $entity_type_bundle_info, $time);
    }

    /**
     * @inheritdoc
     */
    public static function create(ContainerInterface $container) {
        return new static(
            $container->get('entity.manager'),
            $container->get('language_manager'),
            $container->get('entity_type.bundle.info'),
            $container->get('datetime.time'),
            $container->get('module_handler')
        );
    }

    public function form(array $form, FormStateInterface $form_state) {
        $form = parent::form($form, $form_state);
        $form['test'] = [
            '#markup' => '<p>Test extended form</p>',
        ];
        return $form;
    }
} 

Code from David Duymelinck Thanks to him.

2
  • 1
    This code is a hack to use an entity form in _form of a route. Normally you use it in _entity_form and then the entity is provided by EntityForm:getEntityFromRouteMatch().
    – 4uk4
    Oct 30, 2017 at 10:03
  • I do not know if it's related but now if I try to create a new account I have an error: Drupal\Core\Entity\EntityStorageException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'uuid' cannot be null: INSERT INTO {users} (uid, uuid, langcode) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2); Array ( [:db_insert_placeholder_0] => 175 [:db_insert_placeholder_1] => [:db_insert_placeholder_2] => fr ) in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 805)
    – anou
    Oct 30, 2017 at 12:10

Your Answer

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

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