This is the successor of #2367555: Deprecate EnforcedResponseException support, which is the issue that several @todos in Drupal core are referring to.

Follow-up to #2263569: Bypass form caching by default for forms using #ajax.

Problem/Motivation

In #2263569: Bypass form caching by default for forms using #ajax. forms have been setup to POST ajax requests to their original paths instead of system/ajax.

The form is then 'searched for' on the page during the rendering pipeline and once found an Exception is thrown, the rendering aborted and the ajax callback called.

The reason _why_ a form needs to be searched is that the form is displayed in context of a block, a controller or whatever.

e.g

// deep down somewhere in the code

$some_form = new SomeForm();

$build['my_form'] = $this->formBuilder->getForm($some_form);

As long as $some_form defines FormInterface and has appropriate getFormId(), buildForm(), validateForm() and submitForm() options that works.

However what now happens is that someone does:

// deep down somewhere in the code

$some_form = new SomeForm($some_entity, $some_service_to_use);

$build['my_form'] = $this->formBuilder->getForm($some_form);

that means that SomeForm can hold _state_, which is similar to the default parameters provided in Drupal 7. (drupal_get_form('form_id', $some_value, $some_entity, ...))

But what is worse is that SomeForm also might depend on FormState containing some of that external state and then it gets difficult.

Also consider this form living in a block plugin somewhere in the code base.

Proposed resolution

"Interfaces are the answer to the life, the universe and everything." (or was that 42?)

Add a new IndependentFormInterface / ReconstructableFormInterface / LazyBuilderInterface ...

and provide the means for the form to re-create itself.

e.g.

class ReconstructibleSomeForm extends SomeForm implements LazyBuilderInterface {

  public static function lazyBuild(ContainerInterface $container, $entity_type, $entity_id) {
     $entity = Entity::load($entity_type, $entity_id);
     return new static(
        $entity,
        $container->get('some_service'),
     );
  }

  public function getLazyBuilderArguments() {
     return [ $this->entity->entityType(), $this->entity->id() ]; // Must only be scalar values or null.
  }
}

When the form builder encounters a form implementing that interface AND this is a GET request, then it calls the getReconstructContext() method, ensures it only contains scalar values (very similar to #lazy_builder's) and creates the following render array:


$key = NULL;

if ($form instanceof LazyBuilderInterface) {
  $args = $form->getLazyBuilderArguments();

  // Put real callback to the start of the arguments.
  array_unshift($args, get_class($form) . '::lazyBuild');

  $fast_path_renderable = [
    '#lazy_builder' => [ 'lazy_form_builder:buildForm', $args ],
  ];

  $key = 'lazy_form_builder_' . hash('sha1', serialize($fast_path_renderable));

  // Same as we fixed the autocomplete issue ...
  if (!$this->keyValue->get($key)) {
    $this->keyValue->set($key, $fast_path_renderable);
  }
}

if ($key) {
  // Set the _form_ajax GET argument to $key.
}

Then in onController (pseudo-code):

if (isset($_GET['_form_ajax']) && $_GET['_form_ajax'] != 1) {
  $fast_path_renderable = $this->keyValue->get($_GET['_form_ajax']);
  $controller = 'lazy_builder_controller:execute';
  $arguments = $fast_path_renderable;
}

Then in LazyBuilderController:


class LazyBuilderController extends Controller {

  public function __construct(Renderer $renderer) {
    $this->renderer = $renderer;
  }

  public function execute($fast_path_renderable) {
     return $this->renderer->render($fast_path_renderable);
  }
}

class LazyFormBuilder {

  public function buildForm() {
     $args = func_get_args();
     $executable = array_shift($args);
     $form = call_user_func_array($executable, $args);
     return $this->formBuilder->getForm($form);
  }
}

Note: This is idealized from what I plan for ESI (as its the exact same mechanism), so for this issue _could_ e.g. combine LazyFormBuilder and LazyBuilderController.

The idea however remains the same.

Remaining tasks

- Discuss
- Do it

User interface changes

- None

API changes

- New Interface

Comments

fabianx’s picture

Status: Reviewed & tested by the community » Active
tim.plunkett’s picture

Assigned: effulgentsia » Unassigned

Guessing this was cloned from the other issue, it doesn't need to be assigned to @effulgentsia just yet.

catch’s picture

Marking #2367555: Deprecate EnforcedResponseException support as duplicate. That's the older issue, but this is more relevant to current status.

catch’s picture

Title: Make it possible to add a fast-path for ajax and other form submissions » Allow both AJAX and non-AJAX forms to POST to dedicated URLs
Related issues: +#2463567: Push CSRF tokens for forms to placeholders + #lazy_builder

This was discussed in the performance meeting at DrupalCon Barcelona. Doing this would be one of the most significant visitor-facing performance changes we could make - for example the comment form on this page, node forms doing a quicker POST could save potentially seconds with quite small changes.

wim leers’s picture

[…] could save potentially seconds with quite small changes.

almaudoh’s picture

Version: 8.0.x-dev » 8.1.x-dev

Any hope of this in 8.1.x ??

dawehner’s picture

@almaudoh
Well, the best thing is for your to try to work on it.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

wim leers’s picture

wim leers’s picture

Issue summary: View changes

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

Could we introduce a /form route which allows to take an actual path, so like /form?path=user, which we use inside the form rendering process?

This would then do a subrequest by default. When we do that we can be more sure that we can actually serve form from different URLs. Once this is sure, people can create specific routes and use them somehow in the form.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

andypost’s picture

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Category: Bug report » Task

Still very valid, but moving this to a task since it's an API improvement as opposed to a functional bug.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Status: Active » Postponed

Postponing this on #3395776: Make POST requests render cacheable which is much simpler (if cleverer) and will achieve similar results without having to come up with a new access layer.

wim leers’s picture

Status: Postponed » Active

#3395776: Make POST requests render cacheable is in.

What's left here? #30 makes it sound like there's nothing left to do?

catch’s picture

Title: Allow both AJAX and non-AJAX forms to POST to dedicated URLs » [PP-*] Allow both AJAX and non-AJAX forms to POST to dedicated URLs
Status: Active » Postponed

#3395776: Make POST requests render cacheable fixes most of the performance issue here (there's follow-up/parallel work left to do like making forms cacheable and putting them in lazy builders etc.).

However this issue is still valid in the sense that EnforcedResponseException is weird and we're still rendering entire pages to find forms in deeply nested arrays even if we're now doing so with some caching of some of those arrays enabled. If we submitted especially AJAX forms to a dedicated form action URL, we'd know the form ID in advance, and wouldn't need to 'detect' the form at all. However doing this for non-AJAX forms (or AJAX forms with JavaScript disabled) is going to be a lot trickier because we need to render the whole page to show validation errors, not to mention access issues.

#2504115: AJAX forms should submit to $form['#action'] instead of <current> is probably the first step towards this, so marking postponed on that instead.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.