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:

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.
  }

  function myElementValidator() {
     // Validate something.
  }
}
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?

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?