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()
- 10 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 463
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 (EnforcedResponseException $e) {
$response = $e->getResponse();
if (!$response instanceof RedirectResponse) {
throw $e;
}
$ajax_response = new AjaxResponse();
if ($response instanceof SecuredRedirectResponse) {
// Only redirect to safe locations.
$ajax_response->addCommand(new RedirectCommand($response->getTargetUrl()));
}
else {
try {
// SecuredRedirectResponse is an abstract class that requires a
// concrete implementation. Default to LocalRedirectResponse, which
// considers only redirects to within the same site as safe.
$safe_response = LocalRedirectResponse::createFromRedirectResponse($response);
$safe_response->setRequestContext($this->requestContext);
$ajax_response->addCommand(new RedirectCommand($safe_response->getTargetUrl()));
} catch (\InvalidArgumentException) {
// If the above failed, it's because the redirect target wasn't
// local. Do not follow that redirect. Log an error message
// instead, then return a 400 response to the client with the
// error message. We don't throw an exception, because this is a
// client error rather than a server error.
$message = 'Redirects to external URLs are not allowed by default, use \\Drupal\\Core\\Routing\\TrustedRedirectResponse for it.';
$this->logger
->error($message);
$ajax_response->addCommand(new MessageCommand($message));
}
}
$ajax_response = $this->filterEmbeddedResponse($fake_request, $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);
// Send the stop signal.
$this->sendChunk("\n" . static::STOP_SIGNAL . "\n");
break;
} 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.