Response Context
Info
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.
Motivation
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.
Process overview
- Contao determines the responsible Page Controller based on the URL that was requested by the visitor.
- The Controller knows in which context it is used. It has access to the
Request
and produces theResponse
. It now defines theResponseContext
and its capabilities. It then renders the different fragments into its content. - The different fragments can access the current
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). - The Page Controller applies the changes of the
ResponseContext
in the way it thinks is correct, produces theResponse
and finishes the currentResponseContext
.
Creating a Response Context
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;
}
}
Tip
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 core capabilities/services
The Contao core-bundle
ships with currently two core services which you can reuse.
Tip
Have a look at the CoreResponseFactory
in case you want to reuse the default Contao ResponseContext
.
The 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()
.
Info
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 yourself
The JsonLdManager
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()
The 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.
Future
The Response Context will likely be the place where additional possibilities will be introduced such as
- Adding/managing
<script>
tags - Adding/managing
<link>
tags - Adding/managing
<meta>
tags - …