function BigPipe::sendPlaceholders
Same name in other branches
- 9 core/modules/big_pipe/src/Render/BigPipe.php \Drupal\big_pipe\Render\BigPipe::sendPlaceholders()
- 8.9.x core/modules/big_pipe/src/Render/BigPipe.php \Drupal\big_pipe\Render\BigPipe::sendPlaceholders()
- 11.x core/modules/big_pipe/src/Render/BigPipe.php \Drupal\big_pipe\Render\BigPipe::sendPlaceholders()
Sends BigPipe placeholders' replacements as embedded AJAX responses.
Parameters
array $placeholders: Associative array; the BigPipe placeholders. Keys are the BigPipe placeholder IDs.
array $placeholder_order: Indexed array; the order in which the BigPipe placeholders must be sent. Values are the BigPipe placeholder IDs. (These values correspond to keys in $placeholders.)
\Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets: The cumulative assets sent so far; to be updated while rendering BigPipe placeholders.
Throws
\Exception If an exception is thrown during the rendering of a placeholder, it is caught to allow the other placeholders to still be replaced. But when error logging is configured to be verbose, the exception is rethrown to simplify debugging.
1 call to BigPipe::sendPlaceholders()
- BigPipe::sendContent in core/
modules/ big_pipe/ src/ Render/ BigPipe.php - Sends an HTML response in chunks using the BigPipe technique.
File
-
core/
modules/ big_pipe/ src/ Render/ BigPipe.php, line 498
Class
- BigPipe
- Service for sending an HTML response in chunks (to get faster page loads).
Namespace
Drupal\big_pipe\RenderCode
protected function sendPlaceholders(array $placeholders, array $placeholder_order, AttachedAssetsInterface $cumulative_assets) {
// Return early if there are no BigPipe placeholders to send.
if (empty($placeholders)) {
return;
}
// Send the start signal.
$this->sendChunk("\n" . static::START_SIGNAL . "\n");
// A BigPipe response consists of an HTML response plus multiple embedded
// AJAX responses. To process the attachments of those AJAX responses, we
// need a fake request that is identical to the main request, but with
// one change: it must have the right Accept header, otherwise the work-
// around for a bug in IE9 will cause not JSON, but <textarea>-wrapped JSON
// to be returned.
// @see \Drupal\Core\EventSubscriber\AjaxResponseSubscriber::onResponse()
$fake_request = $this->requestStack
->getMainRequest()
->duplicate();
$fake_request->headers
->set('Accept', 'application/vnd.drupal-ajax');
// Create a Fiber for each placeholder.
$fibers = [];
foreach ($placeholder_order as $placeholder_id) {
if (!isset($placeholders[$placeholder_id])) {
continue;
}
$placeholder_render_array = $placeholders[$placeholder_id];
$fibers[$placeholder_id] = new \Fiber(fn() => $this->renderPlaceholder($placeholder_id, $placeholder_render_array));
}
$iterations = 0;
while (count($fibers) > 0) {
foreach ($fibers as $placeholder_id => $fiber) {
try {
if (!$fiber->isStarted()) {
$fiber->start();
}
elseif ($fiber->isSuspended()) {
$fiber->resume();
}
// If the Fiber hasn't terminated by this point, move onto the next
// placeholder, we'll resume this Fiber again when we get back here.
if (!$fiber->isTerminated()) {
// If we've gone through the placeholders once already, and they're
// still not finished, then start to allow code higher up the stack
// to get on with something else.
if ($iterations) {
$fiber = \Fiber::getCurrent();
if ($fiber !== NULL) {
$fiber->suspend();
}
}
continue;
}
$elements = $fiber->getReturn();
unset($fibers[$placeholder_id]);
// Create a new AjaxResponse.
$ajax_response = new AjaxResponse();
// JavaScript's querySelector automatically decodes HTML entities in
// attributes, so we must decode the entities of the current BigPipe
// placeholder ID (which has HTML entities encoded since we use it to
// find the placeholders).
$big_pipe_js_placeholder_id = Html::decodeEntities($placeholder_id);
$ajax_response->addCommand(new ReplaceCommand(sprintf('[data-big-pipe-placeholder-id="%s"]', $big_pipe_js_placeholder_id), $elements['#markup']));
$ajax_response->setAttachments($elements['#attached']);
// Delete all messages that were generated during the rendering of this
// placeholder, to render them in a BigPipe-optimized way.
$messages = $this->messenger
->deleteAll();
foreach ($messages as $type => $type_messages) {
foreach ($type_messages as $message) {
$ajax_response->addCommand(new MessageCommand($message, NULL, [
'type' => $type,
], FALSE));
}
}
// Push a fake request with the asset libraries loaded so far and
// dispatch KernelEvents::RESPONSE event. This results in the
// attachments for the AJAX response being processed by
// AjaxResponseAttachmentsProcessor and hence:
// - the necessary AJAX commands to load the necessary missing asset
// libraries and updated AJAX page state are added to the AJAX
// response
// - the attachments associated with the response are finalized,
// which allows us to track the total set of asset libraries sent in
// the initial HTML response plus all embedded AJAX responses sent so
// far.
$fake_request->query
->set('ajax_page_state', [
'libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries()),
] + $cumulative_assets->getSettings()['ajaxPageState']);
$ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response);
// Send this embedded AJAX response.
$json = $ajax_response->getContent();
$output = <<<EOF
<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="{<span class="php-variable">$placeholder_id</span>}">
{<span class="php-variable">$json</span>}
</script>
EOF;
$this->sendChunk($output);
// Another placeholder was rendered and sent, track the set of asset
// libraries sent so far. Any new settings are already sent; we
// don't need to track those.
if (isset($ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])) {
$cumulative_assets->setAlreadyLoadedLibraries(explode(',', $ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries']));
}
} catch (\Exception $e) {
unset($fibers[$placeholder_id]);
if ($this->configFactory
->get('system.logging')
->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
throw $e;
}
else {
trigger_error($e, E_USER_ERROR);
}
}
}
$iterations++;
}
// Send the stop signal.
$this->sendChunk("\n" . static::STOP_SIGNAL . "\n");
}
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.