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
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done

Comments

Stalski’s picture

It needs one altering though to be a valid callback.

class MyForm {
  function build($form, &$form_state) {
    // ...
    $form['#validate'] = array(array($this, 'validate'));
    $form['#submit'] = array(array($this, 'submit'));
  }
}
plach’s picture

I updated the example, thanks.

Xano’s picture

sprocketman’s picture

Is there any similar method to this for form elements using AJAX? For example:

$form['my_element'] = array(
  #type => 'radios',
  #title => t('My AJAX Element'),
  ...
  #ajax => array(
    'callback' => array(array($this, 'my_method')),
    'wrapper' => 'my-wrapper',
  ),
);
sprocketman’s picture

Let me rephrase my question...I'm actually curious about using callbacks that are not located in the current class (so not using $this, but some other class object). Is that possible? If so, how might that look?

colan’s picture

I just updated the page to include information on this.

aaronbauman’s picture

1. Should the callback methods be public, protected, or private?
2. Must the callback methods be connected to a route in .routing.yml?

colan’s picture

1. Should the callback methods be public, protected, or private?

I just updated the page to include information on this.

2. Must the callback methods be connected to a route in .routing.yml?

No.