Coming back to this nearly five years later, because we now have to output an entire HTML (in our case, Smarty) template incl. assets that can be consumed by another service. Might be useful to someone, or myself when I google this in another five years.
We are rendering selected regions into a custom theme, that we then pass into an HTML render element.
We then call some hooks on the render element to allow modules to alter it.
We use the HTMLResponse
class to process attachments and add them to the response.
We use getContent()
on the response to get the final template and return it as text.
Note: the template becomes invalid when the processed asset URLs change! Drupal's absolute path URLs /sites/default/...
work for use because we are using a reverse proxy, but they are easy to str_replace in the rendered template.
<?php
namespace Drupal\my_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Render\HtmlResponse;
use Drupal\user\Entity\User;
use Symfony\Component\HttpFoundation\Response;
/**
* Returns template.
*/
class MyController extends ControllerBase
{
/**
* Returns the template.
*/
public function template()
{
$content = $this->generateTemplate();
$response = new Response(
$content,
Response::HTTP_OK,
[
'Content-Type' => 'text/plain; charset=UTF-8',
'X-Robots-Tag' => 'noindex, nofollow',
]
);
return $response;
}
/**
* Generates and saves the Smarty template.
*
* @return string
* The rendered template.
*/
private function generateTemplate()
{
// Switch to anon.
/** @var \Drupal\Core\Session\AccountSwitcherInterface $account_switcher */
$account_switcher = \Drupal::service('account_switcher');
$account_switcher->switchTo(User::getAnonymousUser());
// Create a theme that renders the required regions.
$build = [
'#theme' => 'regions_template',
'#preheader' => $this->renderRegion('preheader'),
'#header' => $this->renderRegion('header'),
'#prefooter' => $this->renderRegion('prefooter'),
'#footer' => $this->renderRegion('footer'),
];
// Add the regions to a page.
$html = [
'#type' => 'html',
'page' => [
'#type' => 'page',
'content' => $build,
],
];
// Call some hooks.
system_page_attachments($html['page']);
\Drupal::service('module_handler')->alter('page_attachments', $html['page']);
// Render the render array (this modifies it, as it is passed by ref).
\Drupal::service('renderer')->renderRoot($html);
// Use a response to process the attachments in the render array.
$response = new HtmlResponse();
$response->setContent($html);
$response = \Drupal::service('html_response.attachments_processor')
->processAttachments($response);
// Get the content from the response.
$rendered = $response->getContent();
// Switch back.
$account_switcher->switchBack();
return $rendered;
}
/**
* @param string $region
*/
private function renderRegion($region)
{
$build = [];
foreach ($this->getBlocksInRegion($region) as $block) {
$build[] = \Drupal::entityTypeManager()
->getViewBuilder('block')
->view($block);
}
return $build;
}
/**
* @param string $region
*
* @return array
*/
private function getBlocksInRegion($region)
{
return \Drupal::entityTypeManager()
->getStorage('block')
->loadByProperties([
'status' => 1,
'theme' => 'mytheme',
'region' => $region,
]);
}
}