This feature is available in Contao 4.12 and later.
This section describes the “Response Context” concept. A concept that is special to Contao and thus deserves its own section in the documentation.
Content Management Systems do not share all the same properties as static site generators, or your
good old Symfony based website.
In a traditional Symfony application, you as the developer control 100% of the output of
a particular HTML website. Speaking in Symfony terms, your controller
controls the full Response
instance, from its HTTP headers to its content, everything from
the opening <html>
to the closing </html>
, all the scripts, the stylesheets, the page title and
the description.
In Contao, however, most of the content is dynamically created. That’s why it’s a Content Management
System after all. Let’s take e.g. a news reader front end module as an example. It displays the
detail of a certain news entry and thus also would like to adjust the <title>
tag in the <head>
section of the HTML document. The controller, however, does not know if that news reader front end module
was placed on that page and the front end module on the other hand does not even know if there is a <title>
element it can update.
The news reader element is a fragment only so it can be used in a completely different context other than a
regular HTML web page. E.g. it could be rendered as an ESI fragment, a partial loaded via Ajax or rendered into
an e-mail. In all cases there is no <title>
element to update.
The “Response Context” is here to solve this problem.
Request
and produces the Response
.
It now defines the ResponseContext
and its capabilities. It then renders the different fragments into its content.ResponseContext
(could also be that there’s none!) and can
check for its capabilities. Depending on what they can do, they can decide what they want to do (e.g. update the
page title).ResponseContext
in the way it thinks is correct, produces the Response
and finishes the current ResponseContext
.The ResponseContext
class is a container for capabilities. Basically, a capability is just a “service” that can
be accessed by its class name or in case it implements interfaces, by their respective interfaces.
Setting and accessing the current ResponseContext
is simplified by the ResponseContextAccessor
:
// src/Controller/Page/ExamplePageController.php
namespace App\Controller\Page;
use Contao\CoreBundle\DependencyInjection\Attribute\AsPage;
use Contao\PageModel;
use Contao\CoreBundle\Routing\ResponseContext\HtmlHeadBag\HtmlHeadBag;
use Contao\CoreBundle\Routing\ResponseContext\ResponseContext;
use Contao\CoreBundle\Routing\ResponseContext\ResponseContextAccessor;
use Symfony\Component\HttpFoundation\Response;
#[AsPage]
class ExamplePageController
{
private ResponseContextAccessor $responseContextAccessor;
public function __construct(ResponseContextAccessor $responseContextAccessor)
{
$this->responseContextAccessor = $responseContextAccessor;
}
public function __invoke(Request $request, PageModel $pageModel): Response
{
$responseContext = new ResponseContext();
$responseContext->add(new HtmlHeadBag());
$this->responseContextAccessor->setResponseContext($responseContext);
// Render elements, front end modules, everything can access the current context
// by checking e.g. $responseContext->has(HtmlHeadBag::class) and do something with it.
// Setting e.g. the <title>
$myHtmlContent = '<html><head><title>%s</title></head><body>Content</body></html>';
$myHtmlContent = sprintf($myHtmlContent, $responseContext->get(HtmlHeadBag::class)->getTitle());
$response = new Response($myHtmlContent);
$this->responseContextAccessor->finalizeCurrentContext($response);
return $response;
}
}
The ResponseContext
also contains an addLazy()
method which you should always prefer over add()
if you can.
Just because your controller provides a capability in the context does not mean there’s even any module or element
going to use it. We can save some resources by making them lazy.
The Contao core-bundle
ships with currently two core services which you can reuse.
Have a look at the CoreResponseFactory
in case you want to reuse the default Contao ResponseContext
.
HtmlHeadBag
The class name of the HtmlHeadBag
service already implies, that this will have more capabilities in the future.
The goal should be to be able to manage everything within <head>
dynamically.
Right now, it’s a very basic service that allows you to override <title>
by calling setTitle()
. Same goes for
setMetaDescription()
and setMetaRobots()
.
This feature is available in Contao 4.13 and later.
As of Contao 4.13 and if enabled in the root page settings, Contao will also generate a rel="canoncial"
link pointing to
itself while removing all the query parameters from the current URL. Alternatively, the user can provide a custom other URL and
configure the query parameters which should be kept (e.g. if ?foobar=42
is relevant, they add foobar
in which case
it would not get removed). You can also dynamically adjust the behaviour to your needs using the HtmlHeadBag
and for
example automatically mark a certain query parameter to always be kept to have the users not even require to add it
in their page settings:
setKeepParamsForCanonical()
- overrides the query parameters to keepaddKeepParamsForCanonical()
- adds a query parameter name to the (possibly) already existing array of parameters to keepsetCanonicalUri()
- completely set the URL yourselfJsonLdManager
The JsonLdManager
is a central place to manage JSON-LD data collected within all the elements.
It is capable of managing multiple schemas, but the most likely use case will be schema.org
data
which is why this is used as an example here:
<?php
use Contao\CoreBundle\Routing\ResponseContext\JsonLd\JsonLdManager;
use Contao\CoreBundle\Routing\ResponseContext\ResponseContext;
use Spatie\SchemaOrg\ImageObject;
// This is how the JsonLdManager is created
$schemaManager = new JsonLdManager(new ResponseContext());
// This is how you would access it from the current context (in case it exists)
$schemaManager = $this->responseContextAccessor->getResponseContext()->get(JsonLdManager::class);
// Get the graph for schema.org
$graph = $schemaManager->getGraphForSchema(JsonLdManager::SCHEMA_ORG);
// Add a new ImageObject
$graph->add((new ImageObject())->name('Name')->caption('Caption'));
/**
* This will generate the following now:
*
* <script type="application/ld+json">
* [
* {
* "@context": "https:\/\/schema.org",
* "@graph": [
* {
* "@type": "ImageObject",
* "name": "Name",
* "caption": "Caption"
* }
* ]
* }
* ]
* </script>
*/
$schemaManager->collectFinalScriptFromGraphs()
CspHandler
The CspHandler
allows you to modify Content Security Policies for the current Contao request, when enabled in the
website root. A detailed description can be found in its dedicated article.
The Response Context will likely be the place where additional possibilities will be introduced such as
<script>
tags<link>
tags<meta>
tags