Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

The form state build info supports a new 'callback' key that can be used to explicitly set a form builder callback. Moreover now any callable can be used as a form handler/callback. This allows the new Entity form controllers to work, and can also be used to collect all the required functions for a form into a class more easily. The old syntax is still valid.

Before:

class MyForm {
  function build($form, &$form_state) {
    // ...
    $form['#validate'] = 'my_oo_form_validate';
    $form['#submit'] = 'my_oo_form_submit';
    return $form;
  }
}

function my_oo_form_page(MyForm $object)
  return drupal_get_form('my_oo_form', $object);
}

function my_oo_form($form, &$form_state, $object)
  $form_state['object'] = $object;
  return $object->build($form, $form_state);
}

function my_oo_form_validate($form, &$form_state) {
  $form_state['object']->validate($form, $form_state);
}

function my_oo_form_submit($form, &$form_state) {
  $form_state['object']->submit($form, $form_state);
}

After:

class MyForm {
  function build($form, &$form_state) {
    // The ::methodName syntax is automatically expanded to array($this, 'validate').
    // This only works for the form object, not partial forms in widgets, plugins or form alters.
    $form['#validate'][] = '::validate';
    $form['#submit'][] = '::submit';
  }
}

function my_oo_form_page(MyForm $object)
  $form_state = array();
  $form_state['build_info']['callback'] = array($object, 'build');
  return drupal_build_form('my_oo_form', $form_state);
}

Additionally, '#element_validate' and '#ajax' callbacks can now defined as methods, as well.

Before:

class MyField {
  function build($form, &$form_state) {
    $form['foo'] = array(
      // ...
      '#element_validate' = array('my_element_validator'),
    );

    $form['bar'] = array(
      // ...
      '#ajax' => array(
        'callback' => 'my_ajax_callback',
        'wrapper' => 'foo',
      ),
    );

    return $form;
  }
}

function my_ajax_callback() {
  // Do something fancy.
}

function my_element_validator() {
   // Validate something.
}

After:

use Drupal\Core\Form\FormStateInterface;

class MyField {
  function build($form, &$form_state) {
    $form['foo'] = array(
      // ...
      '#element_validate' = array(array($this, 'myElementValidator')),
    );

    $form['bar'] = array(
      // ...
      '#ajax' => array(
        'callback' => array($this, 'myAjaxCallback'),
        'wrapper' => 'foo',
      ),
    );

    return $form;
  }

  function myAjaxCallback() {
    // Do something fancy.
  }

  /**
   * Validates my element.
   *
   * @param array $element
   *   The form element to process.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param array $complete_form
   *   The complete form structure.
   */
  public static function myElementValidator(&$element, FormStateInterface $form_state, &$complete_form) {
     // Validate something.
  }
}

See core's Url::validateUrl() for an example.

If operating outside of a class context (say in a form alter hook), a fully-qualified class can be specified instead of $this. In this case, it would be 'Drupal\my_module\Path\MyField'.

Impacts: 
Module developers

Comments

xano’s picture

Note that #value_callback does not yet support methods. See #2040559: Replace function_exists() with is_callable() in FormBuilder.