PSR-7 for backend modules

psr-7 backend tutorial

3 minutes, 33 seconds

Ah, you have a non-extbase extension whose content is cleanly structured into Classes, Configuration, Resources, etc. But then there are these pesky modX/index.php files for your backend modules. That's not nice, but there is help: PSR-7. This tutorial shows how to setup your backend modules according to PSR-7.

The PSR-7 standard describes the communication with HTTP messages.

You probably set up your backend modules this way in your ext_tables.php:

$extPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY);

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule(
    'web', 'txmoduleM1', '', $extPath . 'mod1/', [
        'access' => 'group,user',
        'name' => 'web_txmoduleM1',
        'labels' => [
            'tabs_images' => [
                'tab' => 'moduleicon.gif',
            ],
            'll_ref' => 'LLL:EXT:huselpusel/mod1/locallang.xlf',
        ]
    ]
);

The hard-coded extension path looks not so nice and even triggers an entry in the deprecation log:

Registered "web_txmoduleM1" as a script-based module. Script-based modules are deprecated since TYPO3 CMS 7. Support will be removed with TYPO3 CMS 8, use the "routeTarget" option or dispatched modules instead.

To achieve this, the first step is creating a new controller, let's call it Classes/Controller/BackendModuleController.php:

<?php
namespace Foobar\Huselpusel\Controller;

use TYPO3\CMS\Backend\Module\BaseScriptClass;

class BackendModuleController extends BaseScriptClass
{
}

The next step is inserting a public method that is called for dispatching the module. Let's call it mainAction:

public function mainAction(
    \Psr\Http\Message\ServerRequestInterface $request,
    \Psr\Http\Message\ResponseInterface $response
) {
    // Logic goes here...
}

mainAction is the entry point to the backend module which takes the ServerRequestInterface and ResponseInterface of PSR-7 as parameters. This method handles the logic of your backend module. There is one important thing that behaves completely different: content is not echoed anymore (most likely by the method printContent()), you must use the ResponseInterface object.

You may get your GET and POST parameters with the request object:

$get = $request->getQueryParams();
$post = $request->getParsedBody();

To return the content, you may write into response's body and return the object:

$response->getBody()->write($this->doStuff());
return $response;

You may also define a HTTP status code or set the content type:

if ($FAIL) {
    $response = $response->withStatus(500);
} else {
    $response->getBody()->write($this->doStuff());
    $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
}

The content type for backend modules is by default text/html, changing this is more important for AJAX calls based on PSR-7, but this will be explained in another blog post (hint hint).

So far, so good. Let's get back ext_tables.php where the module is registered. The fourth parameter of ExtensionManagementUtility::addModule() requires the path to the module. Drop this, it must be an empty string now. As a replacement, adjust the configuration array by the new key routeTarget:

[
    'routeTarget' => \Foobar\Huselpusel\Controller\BackendModuleController::class . '::mainAction',
    'access' => 'group,user',
    'name' => 'web_txmoduleM1',
    //...
]

After clearing the caches of TYPO3, the backend module is now called by the PSR-7 way and there should be no new entry in the deprecation log. You may copy the whole logic of your former modX/index.php which one exception:

$SOBE = GeneralUtility::makeInstance('tx_huselpusel_module1');
$SOBE->main();
$SOBE->printContent();

Drop that code, you don't need it anymore as the module is already dispatched with your mainAction.

You may move the rest of mod1 into their appropriate location:

moduleicon.gif => Resources/Public/Images/Icons/BackendModuleController/ModuleIcon.gif
locallang.xlf => Resources/Private/Language/locallang_mod.xlf

The whole registration should now look like this:

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule(
    'web', 'txmoduleM1', '', '', [
        'routeTarget' => \Foobar\Huselpusel\Controller\BackendModuleController::class . '::mainAction',
        'access' => 'group,user',
        'name' => 'web_txmoduleM1',
        'labels' => [
            'tabs_images' => [
                'tab' => 'EXT:huselpusel/Resources/Public/Images/Icons/BackendModuleController/ModuleIcon.gif',
            ],
            'll_ref' => 'LLL:EXT:huselpusel/Resources/Private/Language/locallang_mod.xlf',
        ]
    ]
);

For TYPO3 v8, the registration slightly differs:

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule(
    'web', 'txmoduleM1', '', '', [
        'routeTarget' => \Foobar\Huselpusel\Controller\BackendModuleController::class . '::mainAction',
        'access' => 'group,user',
        'name' => 'web_txmoduleM1',
        'icon' => 'EXT:huselpusel/Resources/Public/Images/Icons/BackendModuleController/ModuleIcon.gif',
        'labels' => 'LLL:EXT:huselpusel/Resources/Private/Language/locallang_mod.xlf',
    ]
);

After that, you can drop the module folder as it's not required anymore.

Next Post Previous Post