Dispatching

The dispatcher’s job is to find one or more classes that can handle the current page request. Once found, the dispatcher executes the methods handleRequest in the matching classes and receives their results. These results are then combined and passed out as website content.

Here’s the dispatcher path, step by step:

activate Bootstrap
Bootstrap -> Bootstrap: handleRequest()

Bootstrap -> RequestHandlerResolver: resolveRequestHandler()
activate RequestHandlerResolver
Bootstrap <-- RequestHandlerResolver: RequestHandler
deactivate RequestHandlerResolver
activate RequestHandler
Bootstrap -> RequestHandler: handleRequest()

activate RequestBuilder
RequestHandler -> RequestBuilder: build()
activate Request
RequestHandler <-- RequestBuilder: Request
deactivate RequestBuilder

RequestHandler -> Request: setIsCached()
activate Dispatcher
RequestHandler -> Dispatcher: dispatch(Request)
activate Controller
Dispatcher -> Dispatcher: resolveController(Request)
Dispatcher -> Controller: processRequest(Request)
Controller -> Response
activate Response

Bootstrap <-- RequestHandler: Response
deactivate RequestHandler
Bootstrap -> Response: shutdown()
deactivate Response
Bootstrap <-- Response: content
deactivate Bootstrap

RequestHandlerResolver

Extbase innately contains a couple of classes for various requests and makes them available via Configuration/Extbase/RequestHandlers.php:

return [
    \TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler::class,
    \TYPO3\CMS\Extbase\Mvc\Web\BackendRequestHandler::class,
];

This is how Extbase provides a RequestHandler for requests in frontend or backend context.

Thanks to this configuration, one can register a custom RequestHandler. For example, a handler for AJAX requests can be registered here.

The class-specific method canHandleRequest() decides whether the request can be handled by its RequestHandler. For a BackendRequestHandler, the check looks like this:

canHandleRequest()
{
    return $this->environmentService->isEnvironmentInBackendMode()
        && !Environment::isCli();
}

Because multiple RequestHandlers can be responsible for a request, a prioritization system is necessary. This is because only one RequestHandler can take responsibility. Default priority is set to 100. If another RequestHandler needs to take priority, the method getPriority() should return a higher value.

Once the definitive RequestHandler has been identified, its method handleRequest() is executed. This method creates an object containing all of the necessary data for the page request, using the RequestBuilder. It searches through the plugin configuration in ext_localconf.php in order to find out which controller and action should be used as standard. The RequestBuilder also checks the Uri, to check whether an alternative controller or an alternative action should be loaded instead of the entries from ext_localconf.php.

Note

Extbase does not yet use PSR-7 for requests, but custom implementations.

The dispatcher

The dispatcher fetches the controller name from the request object and creates the controller.

The object Request is passed to the controller, and the role of the dispatcher is complete. The Controller now returns the response, which is handed back all the way to the Bootstrap which calls shutdown(). It is now up to the response to handle further stuff, e.g., send headers and return rendered content.

That is the point when TYPO3 receives the content and integrates it into the rendering. The response itself will already send headers, and no TYPO3 API will be used at this place.

The controller

The controller generates a \Psr\Http\Message\ResponseInterface object. The controller can also set further response headers and access arguments from the Request.

Accessing the request

Within the controller, $this->request allows to access the incoming request. This way, it is possible to access all arguments provided to the request directly. Still, it is better to use argument mapping instead.

In case of a forward, the request also enables access to the original request.

Further information like the controller name and plugin name can be retrieved from the request.

Arguments can be accessed through:

$this->request->getArgument('argumentName');

In order to make arguments available within the request or for mapping, they need to conform to Extbase’s naming standard in order to be mapped to the extension. The default is to prefix arguments with the plugin signature. This can be adjusted via TypoScript option view.pluginNamespace, see View.

Creating a response

Extending the ActionController usually makes it unnecessary to manually create a response, as the callActionMethod() already takes care of it. However, to gain better control over the returned response, a PSR-7 response can be created and returned, for example, if headers should be set explicitly.

Responses need to implement \Psr\Http\Message\ResponseInterface. To create a response, it is recommended to use the PSR-17 response factory:

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;

// ...

public function __construct(ResponseFactoryInterface $responseFactory)
{
   $this->responseFactory = $responseFactory;
}

public function yourAction(): ResponseInterface
{
    $response = $this->responseFactory
        ->createResponse()
        ->withHeader('Content-Type', 'application/json; charset=utf-8');
    $response->getBody()->write(json_encode($data));
    return $response;
}

Returning content

Each action within the controller can optionally return content. If nothing is returned, the default implementation will render $this->view and return the rendering result.

Content can be returned as string or object with __toString() implementation.

Forwarding a request

Within an action the current request can be forwarded to another action by returning a ForwardResponse:

use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Http\ForwardResponse;

class FooController extends ActionController
{
   public function listAction(): ResponseInterface
   {
        // do something

        return (new ForwardResponse('show'))
            ->withControllerName('ForeignController')
            ->withExtensionName('ForeignExtension')
            ->withArguments(['foo' => 'bar'])
       ;
   }
}

Redirecting a request

Within an action the current request can be redirected to another action by returning the redirect method:

return $this->redirect(
   'newAction',
   'ForeignController',
   'ForeignExtension',
   ['param1' => $param1, 'param2' => $param2]
);

or to another uri:

return $this->redirectToUri('https://example.com');

Note

A redirection leads to a reload of the page. All the $_REQUEST variable is lost. Therefore all data needed on the destination must be passed as an array in parameter 4. If the redirection happens after a new / create form, then it must be taken care that the database record is stored into the database before the redirection:

// use \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager
$persistenceManager->persistAll();

Data Transfer Objects in Extbase are an alternative solution to store the form data between redirections.

In the first example, Extbase will build the URL and call redirectToUri().

Changed in version 11.3: Formerly the methods redirect and redirectToUri depended on throwing a StopActionException. This Exception has however been deprecated with 11.3 as it is not PSR-7 conform. Therefore returning the results of the redirect methods becomes mandatory with TYPO3 12.