TYPO3 9 PSR-15 Middleware am einfachen Beispiel

Auf dem TYPO3CAMP Mitteldeutschland 2020 habe ich eine Session über PSR-15 in TYPO3 9 gehalten. Hier ist eine einfache Zusammenfassung zum nachlesen.

Am Ende des Artikels findet ihr ein paar ein- und weiterführende Links zu PSR-15 in TYPO3. Das Anwendungsbeispiel zeigt den Namen des aktuell eingeloggten Users im Frontend an, ohne unzählige Versionen der Seite im Cache zu haben.

PSR-15 im TYPO3 Backend

Im TYPO3 Backend findet ihr unter Konfiguration eine Übersicht der bereits registrierten Middlewares. Wie ihr seht gibt es davon schon einige. Um eure eigene zu Registrieren müsst ihr in eurer Extension im Ordner Configuration eine RequestMiddlewares.php Datei anlegen.

<?php

return [
    'frontend' => [
        'tk/uncachedcontent/uncached-content' => [
            'target' => \Tk\UncachedContent\Middleware\UncachedContent::class,
            'after' => [
                'typo3/cms-frontend/authentication'
            ],
            'before' => [
                'typo3/cms-frontend/backend-user-authentication'
            ]
        ],
    ],
];

Die Middleware muss einem Context zugeordnet werden, in meinem Fall 'frontend'. Als nächstes benötigt sie einen eindeutigen Namen, hier 'tk/uncachedcontent/uncached-content'. Für diesen muss eine existierende Klasse als Ziel angegeben werden.
Dann sollte noch angegeben werden an welcher Stelle die Middleware eingebunden werden soll. In meinem Fall nach der frontend authentication um den eingeloggten User auch zur Verfügung zu haben. Das funktioniert in TYPO3 v9 noch nicht ganz. Laut Benni Mack sollte in v10 besser sein (Forge).

Die Middleware Klasse

<?php

namespace Tk\UncachedContent\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Http\Response;
use TYPO3\CMS\Core\Http\Stream;

class UncachedContent implements MiddlewareInterface
{
    /**
     * @param \Psr\Http\Message\ServerRequestInterface $request
     * @param \Psr\Http\Server\RequestHandlerInterface $handler
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $normalizedParams = $request->getAttribute('normalizedParams');
        $uri = $normalizedParams->getRequestUri();

        if (strpos($uri, '/uncached') === 0) {
            $pathComponents = explode('/', $uri);
            $path = parse_url($pathComponents[2]);
            $command = (string) $path['path'];
            $result = '';
            if ($command) {
                $result = $this->$command();
            }

            $body = new Stream('php://temp', 'rw');
            $body->write($result);
            return (new Response())
                ->withHeader('content-type', 'text/plain; charset=utf-8')
                ->withBody($body)
                ->withStatus(200);
        }
        return $handler->handle($request);
    }

    /**
     * @return mixed|string|null
     */
    protected function getUsername()
    {
        // $GLOBALS['TSFE']->initUserGroups();
        $user = $GLOBALS['TSFE']->fe_user->user;

        if ($user) {
            if ($user['first_name'] && $user['last_name']) {
                $content = $user['first_name'] . ' ' . $user['last_name'];
            } else {
                $content = $user['email'];
            }
            return $content;
        }
        return null;
    }
}

Das ist die Klasse welche in der Konfiguration angegeben ist. Sie muss das MiddlewareInterface implementieren. Als erstes benötige ich die aufgerufene Uri um zu entscheiden ob die Middleware ausgeführt werden soll. Wenn diese passt, wird die entsprechende Methode aufgerufen. In dieser wird abgefragt ob es Daten für den eingeloggten User gibt. Um nur abzufragen ob es einen eingeloggten User gibt muss in der TYPO3 Version 9 noch '$GLOBALS['TSFE']->initUserGroups();' aufgerufen werden. Wenn der FE User einen Namen angegeben hat wir dieser zurück gegeben, ansonsten die Email.
Für die Rückgabe erzeuge ich eine neue Response die ich auf den content-type text/plain setze.

Das war es erst mal mit der Middleware. Übrigens werden eId Anfragen auch über Middleware erledigt so dass man diese evtl. auch gleich mit einer eigenen Middleware ersetzen kann.

Benutzung der Middleware im TYPO3 Frontend

Der Benutzername wird mit JavaScript abgefragt und angezeigt. Da sich dieser nur beim Login/Logout ändert, speichere ich ihn aud Performancegründen im SessionStorage. Das JavaScript kann sicher noch optimiert werden, funktionieren tut es aber. Auch ohne jQuery wink

// load uncached content

function changeLogin() {
    var login = document.querySelectorAll('.login_link_container .login')
    var loginArea = document.querySelectorAll('.login_link_container .logout')
    var username = document.querySelectorAll('.login_link_container span.username')
    var session = sessionStorage.getItem('username')
    if (session) {
        login.forEach(function (el) {
            el.style.display = 'none'
        })

        username.forEach(function (el) {
            el.innerHTML = session
        })

        loginArea.forEach(function (el) {
            el.style.display = 'block'
        }
        )
    } else {
        loginArea.forEach(function (el) {
            el.style.display = 'none'
        })

        login.forEach(function (el) {
            el.style.display = 'block'
        })
    }

}

function processLoginData() {
    var current = sessionStorage.getItem('username')
    if (this.responseText.length > 0) {
        sessionStorage.setItem('username', this.responseText)
    } else {
        sessionStorage.removeItem('username')
    }

    if (this.responseText != current) {
        changeLogin()
    }
}

function loadLoginData() {
    var host = window.location.host
    var protocol = window.location.protocol
    var url = protocol + '//' + host + '/uncached/getUsername'
    var xhr = new XMLHttpRequest()
    xhr.addEventListener('load', processLoginData)
    // no cache
    url = url + ((/\?/).test(url) ? "&" : "?") + '_' + (new Date()).getTime()
    xhr.open('GET', url, true)
    xhr.send()
}

// Init
loadLoginData()
document.addEventListener('DOMContentLoaded', function (event) {
    changeLogin()
})
Artikel Suchen