Using a closure in #after_build may result in a "Serialization of 'Closure' is not allowed" exception

Created on 27 February 2024, 4 months ago
Updated 8 March 2024, 4 months ago

Problem/Motivation

The documentation for #after_build in FormBuilderInterface.php describes the following:

$element['#after_build']: An array of callables called after self::doBuildForm() is done with its processing of the element. These are called in postorder traversal, meaning they are called for the child elements first, then for the parent element.

Using a closure as an #after_build callable will result in a "Serialization of 'Closure' is not allowed" exception when the element is serialized.

One example is when this occurs is when submitting a Webform with the "Inline" or "Modal" confirmation type, and with a (custom) Webform element that uses #after_build as described above. The form array is serialized to be cached, which triggers the above exception.

Steps to reproduce

Any form that gets serialized can be used to test this, but I'll write out the Webform example here:

Create a custom Webform element and use the #after_build property:

$form['#after_build'][] = function (
    array $form,
    FormStateInterface $form_state
) {
    return $form;
};

- Add the custom Webform element to a Webform and set its confirmation type to "Inline" or "Modal".

- Fill in the Webform, triggering the error.

Proposed resolution

There are already a few issues to standardise how callables are handled:

- https://www.drupal.org/project/drupal/issues/2966711 📌 Limit what can be called by a callback in form arrays Needs work suggests limiting the kinds of callables, but still includes closures. This could be updated to not include closures.

- https://www.drupal.org/project/drupal/issues/2982949 📌 Introduce CallableResolver to help standardise the DX and error handling for callbacks across various subsystems Fixed introduces a resolver for callables.

In both cases, the documentation for #after_build should be updated to reflect the recommended solution. At the time of writing, the documentation has not yet changed.

🐛 Bug report
Status

Active

Version

10.1

Component
Form 

Last updated about 11 hours ago

Created by

🇧🇪Belgium beerendlauwers

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @beerendlauwers
  • 🇮🇳India himanshu_jhaloya Mandsaur

    you should avoid using closures as #after_build callables. Instead, you can use a method of a class.

     public function buildForm(array $form, FormStateInterface $form_state) {
            $form['#after_build'][] = [$this, 'afterBuild'];
            return $form;
        }
    
        public function afterBuild(array $form, FormStateInterface $form_state) {
            return $form;
        }
  • 🇧🇪Belgium beerendlauwers

    @himanshu_jhaloya Indeed, this is an issue to change the documentation to reflect this knowledge.

Production build 0.69.0 2024