blog

Drupal Forms API02/13/2017

TLDR
Set the route
Create form class
Initialize the form state
Do something with the submitted data

I recently wrote a Drupal module that uses the Form API. This post captures some learnings that may help you if you're working with that same service.

Set the route

We need a location for the form. In your module, create modulename.routing.yml. For the form location, you should write:

slide.add_slide:
  path: '/admin/structure/slide/add'
  requirements:
    _permission: 'add slide entities'

The permission field should come from modulename.permissions.yml. There should be a block that look's like this:

add slide entities:
  title: 'Create new Slide entities'

Create form class

This can be handled from the console. You want a class located in src/Form/[name]Form.php. The class should be Drupal\modulename\Form. It should inherit a form class like the following:

BlockForm
EntityForm
EntityDeleteForm
MenuLinkDefaultForm
UserPermissionsForm
AccountForm
EntityDisplayModeAddForm
ModulesListConfirmForm
ConfirmFormTestForm
ContentEntityForm
TermForm
ForumForm
BulkForm
NodeRevisionRevertForm
ContentEntityDeleteForm

The form class needs a public function buildForm. The buildForm method takes two arguments array $form and FormStateInterface $form_state. When the console is used to generate a form class, it generates the buildForm method, and calls the parent buildForm method. That body needs to be cleared and attributes need to be added to the $form array. The attributes need to be set to the input field information.

$form['slide_name'] = [
  '#type' => 'textfield',
  '#title' => $this->t('Slide Title')
];

$form['slide_img'] = [
  '#title' => t('Slide Image'),
  '#type' => 'managed_file',
  '#upload_location' => 'public://slider_images/',
];

$form['submit'] = [
  '#type' => 'submit',
  '#value' => $this->t('Submit')
];

At the end of the method, return the form:

return $form;

Initialize the form state

At the beginning of the buildForm method, add this line:

if (!$form_state->has('entity_form_initialized')) {
  $this->init($form_state);
}

This is something that I didn't do and it took me a very long time to figure out why I was having trouble. I would submit my form and I would just get an error. The error is in the block below. I got it from the nginx error log.

2017/02/16 22:25:55 [error] 1659#0: *27 FastCGI sent in stderr: "PHP message: PHP Fatal error:  Call to a member function extractFormValues() on null in /drupal/core/lib/Drupal/Core/Entity/ContentEntityForm.php on line 223
PHP message: PHP Stack trace:
PHP message: PHP   1. {main}() /drupal/index.php:0
PHP message: PHP   2. Drupal\Core\DrupalKernel->handle() /drupal/index.php:19
PHP message: PHP   3. Stack\StackedHttpKernel->handle() /drupal/core/lib/Drupal/Core/DrupalKernel.php:652
PHP message: PHP   4. Drupal\Core\StackMiddleware\NegotiationMiddleware->handle() /drupal/vendor/stack/builder/src/Stack/StackedHttpKernel.php:23
PHP message: PHP   5. Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle() /drupal/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php:50
PHP message: PHP   6. Drupal\page_cache\StackMiddleware\PageCache->handle() /drupal/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php:47
PHP message: PHP   7. Drupal\page_cache\StackMiddleware\PageCache->pass() /drupal/core/modules/page_cache/src/StackMiddleware/PageCache.php:78
PHP message: PHP   8. Drupal\Core\StackMiddleware\KernelPreHandle->handle() /drupal/core/modules/page_cache/src/StackMiddleware/PageCache.php:99
PHP message: PHP   9. Drupal\Core\StackMiddleware\Session->handle() /drupal/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php:47
PHP message: PHP  10. Symfony\Component\HttpKernel\HttpKernel->handle() /drupal/core/lib/Drupal/Core/StackMiddleware/Session.php:57
PHP message: PHP  11. Symfony\Component\HttpKernel\HttpKernel->handleRaw() /drupal/vendor/symfony/http-kernel/HttpKernel.php:64
PHP message: PHP  12. call_user_func_array:{/drupal/vendor/symfony/http-kernel/HttpKernel.php:144}() /drupal/vendor/symfony/http-kernel/HttpKernel.php:144
PHP message: PHP  13. Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() /drupal/vendor/symfony/http-kernel/HttpKernel.php:144
PHP message: PHP  14. Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapp
2017/02/16 22:25:55 [error] 1659#0: *27 FastCGI sent in stderr: "ssage: PHP  28. Drupal\Core\Entity\ContentEntityForm->copyFormValuesToEntity() /drupal/core/lib/Drupal/Core/Entity/EntityForm.php:291" while reading response header from upstream, client: 33.33.33.1, server: drupal.local, request: "POST /admin/structure/slide/add HTTP/1.1", upstream: "fastcgi://unix:/var/run/php5-fpm.sock:", host: "33.33.33.12", referrer: "http://33.33.33.12/admin/structure/slide/add"

Do Something with the submitted data

After all that is done, something has to happen with the data. The form class needs a submitForm method as follows:

public function submitForm(array &$form, FormStateInterface $form_state) {
  $this->processData($form['slide_name']['#value'], $form['slide_img']['#files']);
}