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:

<?php
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:

<?php
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:

<?php
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:

<?php
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.

<?php
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?