May 27, 2014

Implementing forms

In Drupal 8 implementing forms is very different from D7. In its overall structure D7 primarily uses hook_menu(). In Drupal 8 there are core classes provided from which you derive child classes. Those classes are named in the routing file to connect them with specific paths.

For the Optimizely module, the set of code at this point implements three different tabs, each of which contains a form. Because the code would be a bit lengthy to show in its entirety in this posting, I only provide extracts to illustrate the main points.

First, an extract from the optimizely.routing.yml file.
optimizely.acct_info:
  path: /admin/config/system/optimizely/settings
  defaults:
    _form: \Drupal\optimizely\AccountInfoForm
    _title: Optimizely
  requirements:
    _permission: administer optimizely
 
The change from the previous version of the routing file is that instead of _content the _form property is used. The value of the _form property is the namespaced path to a class that implements FormInterface. In this case the name of the class is AccountInfoForm. More about the class hierarchy below.

Second, class  AccountInfoForm is defined in its own file.

  modules/custom/optimizely/src/AccountInfoForm.php

Here's an extract from that class definition. To keep it short and to the point, I've omitted parts of the bodies of the methods as well as boilerplate comments.
namespace Drupal\optimizely;
use Drupal\Core\Form\FormBase;
 

class AccountInfoForm extends FormBase {

  public function getFormID() {
    return 'optimizely_account_info';
  }
 

  public function buildForm(array $form, 
                            array &$form_state) {    
    $form['optimizely_id'] = array(      
      '#type' => 'textfield',
      '#title' => t('Optimizely ID Number'),
      '#size' => 60,
      '#maxlength' => 256,
      '#required' => TRUE,
    );
    $form['actions'] = array('#type' => 'actions', );
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => 'Submit',
    );

    return $form;
  }
 

  public function validateForm(array &$form,
                               array &$form_state) {
    if (!preg_match('/^\d+$/', 

          $form_state['values']['optimizely_id'])) {
      \Drupal::formBuilder()->setErrorByName(

        'optimizely_id', $form_state,
        t('Your Optimizely ID should be numeric.'));
    }
  }
 

  public function submitForm(array &$form, 
                             array &$form_state) {
    // Code to update the database ...

    //    . . . . .

    drupal_set_message(t('The default project ...'),
                       'status');    
    // Redirect back to projects listing.
    $form_state['redirect_route']['route_name']
      = 'optimizely.listing';

    return;
  }
}
Notes.
  • Class AccountInfoForm is derived from abstract class FormBase, which implements FormInterface. Because of this hierarchy, class AccountInfoForm must implement methods  getFormIDbuildForm,  and  submitForm. Implementing method validateForm is optional since FormBase contains an implementation (but it is empty, so by default no validating is done). 
  • Whereas in D7 the logic to build a form, validate it, and submit it are provided in three different functions that are related to each other through a naming convention, in D8 the logic is coded into three methods that are part of the same class. The class that you create implements  FormInterface which declares the names and signatures of those methods. By designating the _form  property in the routing file, at runtime D8 core provides for instantiating an instance of your class and for calling the methods at the appropriate times.
  • In buildForm() the render arrays are largely the same as in D7 (from my limited understanding).
  • To set an error message during validation, instead of calling  form_set_error() call \Drupal::formBuilder()->setErrorByName() using the same parameters. Function form_set_error() is deprecated and expected to be removed.

Update for Drupal 8, beta 1: As of beta 1, there have been several API changes that affect the code here and render it partly obsolete. I have written a fresh blog post with code that is correct as of this writing. However, almost all of the notes and non-code content of this post are still accurate and useful.  See http://optimizely-to-drupal-8.blogspot.com/2014/11/implementing-form-d8-beta-1.html

Sources:

Drupal 8: Forms, OOP style, August 12, 2013
http://effulgentsia.drupalgardens.com/content/drupal-8-forms-oop-style
Tutorial on writing forms in Drupal 8 modules. Author has kept code up-to-date with ongoing changes to D8. Very helpful example.

Form API in Drupal 8
https://drupal.org/node/2117411
This is a great reference with examples and explanations.

Form API Reference
https://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7
This is from Drupal 7 but seems almost entirely applicable to D8.

interface FormInterface
https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Form!FormInterface.php/interface/FormInterface/8

function  form_set_error
https://api.drupal.org/api/drupal/core!includes!form.inc/function/form_set_error/8
This function is deprecated and should not be use for new code.

No comments:

Post a Comment