Attention
TYPO3 v11 has reached end-of-life as of October 31th 2024 and is no longer being maintained. Use the version switcher on the top left of this page to select documentation for a supported version of TYPO3.
Need more time before upgrading? You can purchase Extended Long Term Support (ELTS) for TYPO3 v11 here: TYPO3 ELTS.
Middlewares (Request handling)
TYPO3 has implemented PSR-15 for handling incoming HTTP requests. The
implementation within TYPO3 is often called "Middlewares", as PSR-15 consists of
two interfaces where one is called Middleware
.
Basic concept
The most important information is available at https://www.php-fig.org/psr/psr-15/ and https://www.php-fig.org/psr/psr-15/meta/ where the standard itself is explained.
The idea is to use PSR-7 Request
and Response
as a base, and
wrap the execution with middlewares which implement PSR-15. PSR-15 will receive
the incoming request and return the created response. Within PSR-15 multiple
request handlers and middlewares can be executed. Each of them can adjust the
request and response.
TYPO3 implementation
TYPO3 has implemented the PSR-15 approach in the following way:
- TYPO3 will create a TYPO3 request object.
- TYPO3 will collect and sort all configured PSR-15 middlewares.
- TYPO3 will convert all middlewares to PSR-15 request handlers.
- TYPO3 will call the first middleware with request and the next middleware.
- Each middleware is processed, see Middlewares.
- In the end each middleware has to return a PSR-7 response.
- This response is passed back to the execution flow.
Middlewares
Each middleware has to implement the PSR-15 Middleware
:
namespace Psr\Http\Server;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* Participant in processing a server request and response
*
* An HTTP middleware component participates in processing an HTTP message:
* by acting on the request, generating the response, or forwarding the
* request to a subsequent middleware and possibly acting on its response.
*/
interface MiddlewareInterface
{
/**
* Process an incoming server request
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface;
}
By doing so, the middleware can do one or multiple of the following:
- Adjust the incoming request, e.g. add further information.
- Create and return a PSR-7 response.
- Call next request handler (which again can be a middleware).
- Adjust response received from the next request handler.
Using Extbase
One note about using Extbase in middlewares: do not! Extbase relies on frontend TypoScript being present; otherwise the configuration is not applied. This is usually no problem - Extbase plugins are typically either included as USER content object (its content is cached and returned together with other content elements in fully-cached page context), or the Extbase plugin is registered as USER_INT. In this case, the TSFE takes care of calculating TypoScript before the plugin is rendered, while other USER content objects are fetched from page cache.
With TYPO3 v11, the "calling Extbase in a context where TypoScript has not been calculated" scenario did not fail, but simply returned an empty array for TypoScript, crippling the configuration of the plugin in question. This mitigation hack will be removed in TYPO3 v13, though. Extension developers that already use Extbase in a middleware have the following options:
- Consider not using Extbase for the use case: Extbase is quite expensive. Executing it from within middlewares can increase the parse time in fully-cached page context significantly and should be avoided especially for "simple" things. In many cases, directly manipulating the response object and skipping the Extbase overhead in a middleware should be enough.
- Move away from the middleware and register the Extbase instance as a casual USER_INT object via TypoScript: Extbase is designed to be executed like this, the TSFE bootstrap will take care of properly calculating TypoScript, and Extbase will run as expected.
Returning a custom response
This middleware will check whether TYPO3 is in maintenance mode and will return an unavailable response in that case. Otherwise the next middleware will be called, and its response is returned instead.
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
if (/* if logic */) {
return GeneralUtility::makeInstance(ErrorController::class)
->unavailableAction(
$request,
'This page is temporarily unavailable.'
);
}
return $handler->handle($request);
}
Enriching the request
The current request can be extended with further information, e.g. the current resolved site and language could be attached to the request.
In order to do so, a new request is built with additional attributes, before calling the next request handler with the enhanced request.
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$routeResult = $this->matcher->matchRequest($request);
$request = $request->withAttribute('site', $routeResult->getSite());
$request = $request->withAttribute('language', $routeResult->getLanguage());
return $handler->handle($request);
}
Enriching the response
This middleware will check the length of generated output, and add a header with this information to the response.
In order to do so, the next request handler is called. It will return the generated response, which can be enriched before it gets returned.
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$response = $handler->handle($request);
if (/* if logic */) {
$response = $response->withHeader(
'Content-Length',
(string)$response->getBody()->getSize()
);
}
return $response;
}
Configuring middlewares
In order to implement a custom middleware, this middleware has to be configured. TYPO3 already provides some middlewares out of the box. Beside adding your own middlewares, it's also possible to remove existing middlewares from the configuration.
The configuration is provided within
Configuration/
of an extension:
return [
'frontend' => [
'middleware-identifier' => [
'target' => \Vendor\SomeExtension\Middleware\ConcreteClass::class,
'before' => [
'another-middleware-identifier',
],
'after' => [
'yet-another-middleware-identifier',
],
],
],
'backend' => [
'middleware-identifier' => [
'target' => \Vendor\SomeExtension\Middleware\AnotherConcreteClass::class,
'before' => [
'another-middleware-identifier',
],
'after' => [
'yet-another-middleware-identifier',
],
],
],
];
TYPO3 has multiple stacks where one middleware might only be necessary in one of them. Therefore the configuration defines the context on its first level to define the context. Within each context the middleware is registered as new subsection with an unique identifier as key.
The default stacks are: frontend
and backend
.
Each middleware consists of the following options:
- target
-
PHP string
FQCN (=Fully Qualified Class Name) to use as middleware.
- before
-
PHP Array
List of middleware identifiers. The middleware itself is executed before any other middleware within this array.
- after
-
PHP Array
List of middleware identifiers. The middleware itself is executed after any other middleware within this array.
- disabled
-
PHP boolean
Allows to disable specific middlewares.
Override ordering of middlewares
To change the ordering of middlewares shipped by the Core an extension can override the registration in
Configuration/
:
return [
'frontend' => [
'middleware-identifier' => [
'after' => [
'another-middleware-identifier',
],
'before' => [
'3rd-middleware-identifier',
]
],
]
];
However, this could lead to circular ordering depending on the ordering constraints of other middlewares. Alternatively an existing middleware can be disabled and reregistered again with a new identifier. This will circumvent the risk of circularity:
return [
'frontend' => [
'middleware-identifier' => [
'disabled' => true
],
'overwrite-middleware-identifier' => [
'target' => \MyVendor\SomeExtension\Middleware\MyMiddleware::class,
'after' => [
'another-middleware-identifier',
],
'before' => [
'3rd-middleware-identifier',
]
]
]
];
Important
Always check the integrity of the middleware stack after changing the default ordering. This can be done in the configuration module that comes with EXT:lowlevel.
Creating new request / response objects
PSR-17 HTTP Factory interfaces are provided by psr/
and should be used as
dependencies for PSR-15 request handlers or services that need to create PSR-7 message objects.
It is discouraged to explicitly create PSR-7 instances of classes from the \TYPO3\
namespace (they are not public APIs). Instead, use type declarations against PSR-17 HTTP Message Factory
interfaces and dependency injection.
Example
A middleware that needs to send a JSON response when a certain condition is met, uses the PSR-17 response factory interface (the concrete TYPO3 implementation is injected as a constructor dependency) to create a new PSR-7 response object:
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class StatusCheckMiddleware implements MiddlewareInterface
{
/** @var ResponseFactoryInterface */
private $responseFactory;
public function __construct(ResponseFactoryInterface $responseFactory)
{
$this->responseFactory = $responseFactory;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($request->getRequestTarget() === '/check') {
$data = ['status' => 'ok'];
$response = $this->responseFactory->createResponse()
->withHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write(json_encode($data));
return $response;
}
return $handler->handle($request);
}
}
Executing HTTP requests in middlewares
The PSR-18 HTTP Client is intended to be used by PSR-15 request handlers in order to perform HTTP requests based on PSR-7 message objects without relying on a specific HTTP client implementation.
PSR-18 consists of a client interface and three exception interfaces:
\Psr\
Http\ Client\ Client Interface \Psr\
Http\ Client\ Client Exception Interface \Psr\
Http\ Client\ Network Exception Interface \Psr\
Http\ Client\ Request Exception Interface
Request handlers use dependency injection to retrieve the concrete implementation
of the PSR-18 HTTP client interface \Psr\
.
The PSR-18 HTTP Client interface is provided by psr/
and may be used as
dependency for services in order to perform HTTP requests using PSR-7 request objects.
PSR-7 request objects can be created with the PSR-17 Request Factory interface.
Note
This does not replace the currently available Guzzle wrapper
\TYPO3\
, but is available as a more generic,
framework-agnostic alternative. The PSR-18 interface does not allow you to pass
request-specific guzzle options. But global options defined in $GLOBALS
are taken into account because GuzzleHTTP is used as the backend for this PSR-18 implementation.
The concrete implementation is internal and will be replaced by a native guzzle PSR-18
implementation once it is available.
Example usage
A middleware might need to request an external service in order to transform the response into a new response. The PSR-18 HTTP client interface is used to perform the external HTTP request. The PSR-17 Request Factory Interface is used to create the HTTP request that the PSR-18 HTTP Client expects. The PSR-7 Response Factory is then used to create a new response to be returned to the user. All of these interface implementations are injected as constructor dependencies:
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ExampleMiddleware implements MiddlewareInterface
{
/** @var ResponseFactory */
private $responseFactory;
/** @var RequestFactory */
private $requestFactory;
/** @var ClientInterface */
private $client;
public function __construct(
ResponseFactoryInterface $responseFactory,
RequestFactoryInterface $requestFactory,
ClientInterface $client
) {
$this->responseFactory = $responseFactory;
$this->requestFactory = $requestFactory;
$this->client = $client;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($request->getRequestTarget() === '/example') {
$req = $this->requestFactory->createRequest('GET', 'https://api.external.app/endpoint.json')
// Perform HTTP request
$res = $this->client->sendRequest($req);
// Process data
$data = [
'content' => json_decode((string)$res->getBody())
];
$response = $this->responseFactory->createResponse()
->withHeader('Content-Type', 'application/json; charset=utf-8');
$response->getBody()->write(json_encode($data));
return $response;
}
return $handler->handle($request);
}
}
Debugging
In order to see which middlewares are configured and to see the order of execution, TYPO3 offers a the menu entry HTTP Middlewares (PSR-15) within the "Configuration" module: