If you have e.g a twig template or layout builder, or basically anything that allows a View to be part of a Display Mode, and you use that View mode for the Rendered Item Search API Field plugin, it will absolutely die on a normal "save" operation on anode when the index is set to index immediately.
The reason is really stupid. A view, any, on rendering will always invoke the exposed form, even if there is no exposed form, no exposed filter, nothing. (c'mon drupal) and bc of that it will trigger a chain of attempts to fill up exposed filters (even if there are NO exposed filters... really drupal) including an attempt of getting data from an active session. But hey, drupal, if an active session is requested, and the session is empty, it actually tries to create a session, sending headers in the middle of an already ongoing process because Search API indexing happens on "destruction", basically when http is closing.
You will see stuff like this
RuntimeException while trying to render item entity:node/SOMENUMBER:en with view mode SOME_VIEW_MODE for search index SOMEINDEX: Failed to start the session because headers have already been sent by "/var/www/html/studio.esmero.io/vendor/symfony/http-foundation/Response.php" at line 410. in Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->start() (line 132 of /var/www/html/studio.esmero.io/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php).
The solution is super hackish:
On active indexing Search API will emit an event. If so, we need to catch that and set a memory only (e.g a fake service container) flag saying "we are indexing"... so when the view is called, just before it happens we need to set the "input" (which would be normally be filled by an URL) to an empty array, bypassing so these lines in the ViewExecutable class at ::getExposedInput() : (line 722 or so)
// If we have no input at all, check for remembered input via session.
if (empty($this->exposed_input) && $this->request->hasSession()) {
$session = \Drupal::request()->getSession();
// If filters are not overridden, store the 'remember' settings on the
// default display. If they are, store them on this display. This way,
// multiple displays in the same view can share the same filters and
// remember settings.
$display_id = ($this->display_handler->isDefaulted('filters')) ? 'default' : $this->current_display;
if (!empty($session->get('views')[$this->storage->id()][$display_id])) {
$this->exposed_input = $session->get('views')[$this->storage->id()][$display_id];
}
}
What I totally hate is that a form should not be even rendered at all ... and that this might even change in the future ...
What?
A big mess Drupal 10
The reason is really stupid. A view, any, on rendering will always invoke the exposed form, even if there is no exposed form, no exposed filter, nothing. (c'mon drupal) and bc of that it will trigger a chain of attempts to fill up exposed filters (even if there are NO exposed filters... really drupal) including an attempt of getting data from an active session. But hey, drupal, if an active session is requested, and the session is empty, it actually tries to create a session, sending headers in the middle of an already ongoing process because Search API indexing happens on "destruction", basically when http is closing.
You will see stuff like this
RuntimeException while trying to render item entity:node/SOMENUMBER:en with view mode SOME_VIEW_MODE for search index SOMEINDEX: Failed to start the session because headers have already been sent by "/var/www/html/studio.esmero.io/vendor/symfony/http-foundation/Response.php" at line 410. in Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->start() (line 132 of /var/www/html/studio.esmero.io/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php).
The solution is super hackish:
::getExposedInput()
: (line 722 or so)What I totally hate is that a form should not be even rendered at all ... and that this might even change in the future ...
@alliomeria @patdunlavey for your eyes ...