Routen¶
Seit Version 5.2.0 nutzt JTL-Shop die PHP-Bibliothek league/route für das Routing, also die Auflösung von URLs. Auch in Plugins können seitdem eigene Routen definiert werden.
Hint
Das allgemeine Demo-Plugin zeigt auch, wie eigene Routen verwendet werden können. Es befindet sich im öffentlichen Gitlab-Repository. Ein komplexeres Beispiel stellt das ebenfalls frei verfügbare Plugin 10 minute rest api dar.
Registrierung¶
Der Einstiegspunkt zur Registrierung von Routen findet über den Hook HOOK_ROUTER_PRE_DISPATCH
statt.
Dieser übergibt in seinen Parametern eine Instanz der Klasse \JTL\Router\Router
, welche die Methode
addRoute(string $slug, callable $cb, ?string $name = null, array $methods = [GET], ?\Psr\Http\Server\MiddlewareInterface $middleware = null
bereitstellt.
Es wird dabei stets erwartet, dass der Callback eine Instanz von \Psr\Http\Message\ResponseInterface
zurückgibt.
Hierfür kann die im Shop mit ausgelieferte Bibliothek laminas-diactoros
verwendet werden, die beispielsweise Implementationen normaler Text- oder JSON-Responses bereitstellt.
Ein einfaches Beispiel könnte so aussehen:
<?php declare(strict_types=1);
namespace Plugin\example_routing;
use JTL\Events\Dispatcher;
use JTL\Plugin\Bootstrapper;
use JTL\Router\Router;
use Laminas\Diactoros\Response\JsonResponse;
class Bootstrap extends Bootstrapper
{
public function boot(Dispatcher $dispatcher): void
{
parent::boot($dispatcher);
$dispatcher->hookInto(\HOOK_ROUTER_PRE_DISPATCH, function (array $args) {
/** @var Router $router */
$router = $args['router'];
$router->addRoute('/jsonexample', function () {
return new JsonResponse(['foobar' => 42]);
});
});
}
}
Hier wurde die Route <shop-url>/jsonexample
erzeugt, die bei Aufruf direkt ein als JSON formatiertes Array zurückliefert.
Smarty-Templates¶
Wie oben erwähnt, müssen die registrierten Callbacks Instanzen des Interfaces ResponseInterface
zurückgeben
statt - wie sonst in JTL-Shop üblich - reinen Text wie z.B. gerenderten HTML-Code.
Daher sollten an dieser Stelle auf keinen Fall die Smarty-Methoden display()
bzw. fetch()
genutzt werden.
Falls gewünscht ist, den Inhalt eines Smarty-Templates zurückzugeben, wird die Nutzung der Methode
\JTL\Smarty\JTLSmarty::getResponse(string $template): ResponseInterface
empfohlen.
Diese arbeitet analog zu display(), verpackt den gerenderten Text aber in eine Textresponse mit dem HTTP-Statuscode 200.
Hint
Als Parameter erhalten die Callback-Methoden stets 3 Parameter:
- Psr\Http\Message\ServerRequestInterface $request (das gesendete Request)
- array $args (die Routen-Parameter)
- \JTL\Smarty\JTLSmarty $smarty (eine Instanz des Frontend-Renderers)
Der Hook von oben könnte nun so geändert werden:
<?php declare(strict_types=1);
namespace Plugin\example_routing;
use JTL\Events\Dispatcher;
use JTL\Plugin\Bootstrapper;
use JTL\Router\Router;
use JTL\Smarty\JTLSmarty;
use Psr\Http\Message\ServerRequestInterface;
class Bootstrap extends Bootstrapper
{
public function boot(Dispatcher $dispatcher): void
{
parent::boot($dispatcher);
$dispatcher->hookInto(\HOOK_ROUTER_PRE_DISPATCH, function (array $args) {
/** @var Router $router */
$router = $args['router'];
$router->addRoute('/smartyexample', function (ServerRequestInterface $request, array $args, JTLSmarty $smarty) {
return $smarty->assign('myVariable', 42)
->getResponse(__DIR__ . '/mytest.tpl');
});
});
}
}
Ruft man nun <shop-url>/smartytest
auf, so wird die Templatedatei mytest.tpl
aus dem Plugin-Hauptverzeichnis
gerendert und dargestellt.
Parameter¶
Analog zur Dokumentation lassen sich zu den Routen nun noch dynamische URL-Segmente hinzufügen. Ein optionaler Parameter - erkennbar an den eckigen Klammern - könnte so aussehen:
$router->addRoute('/withoptionalparam[/{id}]', function () {});
Diese Route würde sowohl bei Aufruf von <shop-url>/withoptionalparam
als auch bei <shop-url>/withoptionalparam/test
matchen.
Außerdem können einfache Typen oder komplexe reguläre Ausdrücke vorgegeben werden:
$router->addRoute('/withnumberparam/{id:number}', function () {});
Ein Aufruf der URL <shop-url>/withnumberparam
oder <shop-url>/withnumberparam/test
würde nun einen 404-Fehler
erzeugen, da der Pflichtparameter nicht übergeben wurde bzw. nicht numerisch ist. Die URL <shop-url>/withnumberparam/42
würde hingegen matchen.
Der Wert des dynamischen URL-Segements wird immer im zweiten Parameter (hier $args
) an die Callbackmethode übergeben.
<?php declare(strict_types=1);
namespace Plugin\example_routing;
use JTL\Events\Dispatcher;
use JTL\Plugin\Bootstrapper;
use JTL\Router\Router;
use JTL\Smarty\JTLSmarty;
use Psr\Http\Message\ServerRequestInterface;
class Bootstrap extends Bootstrapper
{
public function boot(Dispatcher $dispatcher): void
{
parent::boot($dispatcher);
$dispatcher->hookInto(\HOOK_ROUTER_PRE_DISPATCH, function (array $args) {
/** @var Router $router */
$router = $args['router'];
$router->addRoute('/smarty/{examplenumber:number}', function (ServerRequestInterface $request, array $args, JTLSmarty $smarty) {
return $smarty->assign('myVariable', $args['examplenumber'])
->getResponse('string:<p>Number:</p>{$myVariable}');
});
});
}
}
Im obigen Beispiel wurde die Pflichtangabe examplenumber
als number
definiert und im Callback aus dem Parameter
$args
extrahiert und der Smarty-Variablen $myVariable
zugewiesen. Anschließend erfolgt die Ausgabe des gerenderten
Templates. Ruft man nun die Seite <shop-url>/smarty/42
auf, so sollte nun die Ausgabe Number: 42
erscheinen.
Hint
Smarty unterstützt auch das Rendering via string:
- dies erspart in den Beispielen die Darstellung einer weiteren
Datei. Die Ausgabe von $smarty->getRespone('string:Hallo {$test}');
ist analog zu $smarty->getResponse('test.tpl');
wobei die Datei test.tpl
den Inhalt Hallo {$test}
hat.
Middleware¶
Via JTL\Router\Router::adRoute()kann optional auch eine
Middleware definiert werden.
Hierfür wird im Plugin-Hauptverzeichnis eine neue Klasse angelegt, die das Interface Psr\Http\Server\MiddlewareInterface
implementiert:
<?php declare(strict_types=1);
namespace Plugin\example_routing;
use Laminas\Diactoros\Response\RedirectResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ExampleMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (random_int(0, 2) === 1) {
return new RedirectResponse('https://google.de/');
}
return $handler->handle($request);
}
}
Anschließend wird sie registriert:
<?php declare(strict_types=1);
namespace Plugin\example_routing;
use JTL\Events\Dispatcher;
use JTL\Plugin\Bootstrapper;
use JTL\Router\Router;
use JTL\Smarty\JTLSmarty;
use Psr\Http\Message\ServerRequestInterface;
class Bootstrap extends Bootstrapper
{
public function boot(Dispatcher $dispatcher): void
{
parent::boot($dispatcher);
$dispatcher->hookInto(\HOOK_ROUTER_PRE_DISPATCH, function (array $args) {
/** @var Router $router */
$router = $args['router'];
$router->addRoute(
'/smarty/{examplenumber:number}',
function (ServerRequestInterface $request, array $args, JTLSmarty $smarty) {
return $smarty->assign('myVariable', $args['examplenumber'])
->getResponse('string:<h1>test</h1>My variable:{$myVariable}');
},
'myCustomRouteName',
['GET', 'POST'],
new ExampleMiddleware()
);
});
}
}
Die URL <shop-url>/smarty/42
kann nun via GET oder POST aufgerufen werden und würde statistisch
bei einem von 3 Aufrufen zu google.de weiterleiten statt das Template darzustellen.
Exceptions¶
Die Methode addRoute() wirft eine Exception vom Typ FastRoute\BadRouteException
, falls die Route nicht registrierbar sein sollte.
Dies ist insbesondere dann der Fall, wenn dieselbe URL mehrfach verwendet wird. Es bietet sich daher an, den Methodenaufruf
in einen Try-Catch-Block zu verpacken:
$router->addRoute('/exampleroute', function () {});
try {
$router->addRoute('/exampleroute', function () {});
} catch (Exception $e) {
// FastRoute\BadRouteException: Cannot register two routes matching "/exampleroute" for method "GET"
}
URLs generieren¶
Auch umgekehrt lassen sich mit eigenen Routen aus Parametern heraus die URL-Pfade generieren.
<?php declare(strict_types=1);
namespace Plugin\example_routing;
use JTL\Events\Dispatcher;
use JTL\Plugin\Bootstrapper;
use JTL\Router\Router;
use JTL\Shop;
class Bootstrap extends Bootstrapper
{
public function boot(Dispatcher $dispatcher): void
{
parent::boot($dispatcher);
$dispatcher->hookInto(\HOOK_ROUTER_PRE_DISPATCH, function (array $args) {
/** @var Router $router */
$router = $args['router'];
$router->addRoute(
'/year/{year:number}/month/{month:number}/day/{day:word}',
function() { /** ... */},
'myDateRoute',
['GET', 'POST']
);
});
}
public function getMyPath(): void
{
$router = Shop::getRouter();
$path = $router->getNamedPath('myDateRouteGET', ['year' => 1984, 'month' => 9, 'day' => 'Tuesday']);
dump($path); // /year/1984/month/9/day/Tuesday
}
}
Im obigen Beispiel wurde eine Route mit 3 dynamischen Teilen generiert - year
, month
und day
.
Möchte man nun an einer beliebigen Stelle eine URL erzeugen, lässt sich dies anhand des Routennamens (myDateRoute
) und
der gewünschten HTTP-Methode (hier GET
) über die Methode
\JTL\Router\Router::getNamedPath(string $name, ?array $replacements = null): string
tun.
Als zweiten Parameter erwartet die Methode ein Array bei dem die Key-Namen jeweils den einzelnen dynamischen URL-Teilen entsprechen.