Front Controller Pattern: Von index.php bis zum Controller
· Lesezeit: ca. 15 Minuten · Kategorie: Magento 2 · Architektur
Front Controller Pattern:
Von index.php bis zum Controller
Wie verarbeitet Magento 2 jeden HTTP-Request? Bootstrap, FrontController, Router-Kette, URL-Rewriting und Action-Klassen – der komplette Weg vollständig erklärt.
Das Gateway zu jeder Magento-Seite
Jeder HTTP-Request an einen Magento-Shop – ob Produktseite, Checkout oder REST-API-Aufruf – beginnt an exakt einer Stelle: pub/index.php. Von dort aus entscheidet Magento in Millisekunden, welcher Code für diesen Request ausgeführt wird. Das Herzstück dieses Mechanismus ist das Front Controller Pattern.
Das Front Controller Pattern ist kein Magento-spezifisches Konzept. Es stammt aus den GoF-Entwurfsmustern (Gang of Four, 2002) und beschreibt einen einzigen Einstiegspunkt, der alle eingehenden Requests zentralisiert, bevor er sie an die zuständigen Handler weiterleitet. Viele Frameworks implementieren es – aber Magento macht es besonders komplex, weil zwischen dem FrontController und der eigentlichen Controller-Action ein mehrstufiges Router-System liegt.
Dieser Deep Dive erklärt den vollständigen Weg: vom ersten Byte des HTTP-Requests bis zur Instanziierung der Action-Klasse – und wie du dieses System mit eigenen Routern erweiterst.
- 1. Das Front Controller Pattern: Theorie und Motivation
- 2. Der Bootstrap-Prozess: Was vor dem FrontController passiert
- 3. FrontController: Implementierung und Dispatch-Loop
- 4. Die fünf Router-Typen in Magento 2
- 5. Standard-Router: frontName, Controller und Action
- 6. URL-Rewrite-Router: SEO-URLs auflösen
- 7. Eigene Router erstellen: RouterInterface implementieren
- 8. Action-Klassen: HttpGetActionInterface und Co.
- 9. Das RequestInterface: URL, Parameter und mehr
- 10. Zusammenfassung
- 11. FAQ
1. Das Front Controller Pattern: Theorie und Motivation
Das Front Controller Pattern adressiert ein zentrales Problem in Web-Applikationen: Ohne zentralen Einstiegspunkt müsste jede PHP-Datei im Webroot selbst Bootstrap-Logik, Security-Checks und Routing-Code enthalten. Das führt zu massiver Code-Duplikation.
Die Lösung: Ein einziger Einstiegspunkt für alle Requests. Dieser Einstiegspunkt übernimmt alle Cross-Cutting-Concerns und delegiert dann an spezialisierte Handler.
OHNE Front Controller Pattern (Anti-Pattern):
/catalog/product.php ← eigener Bootstrap, eigene Auth-Prüfung
/checkout/cart.php ← eigener Bootstrap, eigene Auth-Prüfung
/customer/account.php ← eigener Bootstrap, eigene Auth-Prüfung
/api/products.php ← eigener Bootstrap, eigene Auth-Prüfung
MIT Front Controller Pattern (Magento-Ansatz):
pub/index.php ← EINZIGER Einstiegspunkt
↓
FrontController ← Cross-Cutting-Concerns
│ ├── Sicherheits-Checks
│ ├── Session-Management
│ ├── Area-Bestimmung (frontend / adminhtml / api)
│ └── CSRF-Schutz
↓
Router-Chain ← Routing
↓
Konkreter Handler ← Business Logic (nur ein Handler, keine Duplikation)
In Magento kommt ein weiterer Vorteil hinzu: Der FrontController ist im DI-Container registriert und kann über Plugins erweitert werden. Du kannst Request-Preprocessing oder Response-Postprocessing hinzufügen, ohne pub/index.php anzufassen.
2. Der Bootstrap-Prozess: Was vor dem FrontController passiert
Bevor der FrontController auch nur eine Zeile Code ausführt, durchläuft Magento einen umfangreichen Bootstrap-Prozess. pub/index.php ist dabei nur der Auslöser:
<?php
// pub/index.php (vereinfacht, Original hat ca. 30 Zeilen)
declare(strict_types=1);
use Magento\Framework\App\Bootstrap;
// 1. Autoloader initialisieren (Composer)
require __DIR__ . '/../app/bootstrap.php';
// 2. Bootstrap-Objekt erstellen
// Liest: BP (Base Path), MAGE_MODE, MAGE_PROFILER
$bootstrap = Bootstrap::create(BP, $_SERVER);
// 3. Applikations-Objekt erstellen und ausführen
// Für HTTP-Requests: Magento\Framework\App\Http
$app = $bootstrap->createApplication(\Magento\Framework\App\Http::class);
// 4. run() startet den gesamten Request-Lifecycle
$bootstrap->run($app);
Bootstrap-Sequenz (was Bootstrap::run() auslöst):
pub/index.php
↓
Bootstrap::create()
├── Composer-Autoloader laden
├── Environment-Variablen lesen (MAGE_MODE, etc.)
└── ObjectManager initialisieren (DI-Container)
↓
DI-Konfiguration laden (di.xml aller Module mergen)
Preferences und Plugins registrieren
Bootstrap::run(Http::class)
↓
Http::launch()
├── Area-Konfiguration laden (frontend/adminhtml/...)
├── Response-Objekt bereitstellen
└── FrontController::dispatch($request) aufrufen
Performance-Hinweis: Das Mergen aller di.xml-Dateien im Bootstrap ist die teuerste Operation beim Magento-Start. Im Production-Mode wird das Ergebnis in var/di/ gecacht. Im Developer-Mode passiert es bei jedem Request – deshalb ist Developer-Mode merklich langsamer.
3. FrontController: Implementierung und Dispatch-Loop
Das Interface FrontControllerInterface ist minimal: eine einzige Methode dispatch(RequestInterface $request): ResponseInterface. Die Implementierung in Magento\Framework\App\FrontController enthält die Router-Loop:
<?php
declare(strict_types=1);
// vendor/magento/framework/App/FrontController.php (vereinfacht)
namespace Magento\Framework\App;
use Magento\Framework\App\Response\Http as HttpResponse;
use Magento\Framework\Controller\ResultInterface;
class FrontController implements FrontControllerInterface
{
public function __construct(
private readonly RouterListInterface $routerList,
private readonly HttpResponse $response
) {}
/**
* Dispatch the request through the router chain.
* Returns HTTP response — HTML, JSON, redirect, etc.
*/
public function dispatch(RequestInterface $request): ResponseInterface
{
$validCounter = 0;
$allowedLoop = 100; // safeguard against infinite redirect loops
do {
// Reset routing state for each loop iteration
$request->setDispatched(false);
// Walk through ALL registered routers in sortOrder sequence
foreach ($this->routerList as $router) {
// Each router tries to match the current request path
/** @var ActionInterface|null $action */
$action = $router->match($request);
if ($action === null) {
// This router did not match — try next router
continue;
}
// Match found — execute the action
$result = $action->execute();
// Mark request as dispatched so the loop exits
$request->setDispatched(true);
if ($result instanceof ResultInterface) {
// Result renders itself into the response object
$result->renderResult($this->response);
}
break; // stop iterating routers
}
++$validCounter;
} while (!$request->isDispatched() && $validCounter < $allowedLoop);
return $this->response;
}
}
Wichtig: Die do-while-Schleife erlaubt interne Weiterleitungen. Ein Router kann den Request modifizieren (z.B. URL-Rewriting zu einem anderen Pfad) und setDispatched(false) setzen, um die Router-Chain erneut zu starten. Das ist der Mechanismus hinter URL-Rewrites.
4. Die fünf Router-Typen in Magento 2
Magento registriert standardmäßig fünf Router, die in der Reihenfolge ihrer sortOrder durchsucht werden. Der erste Router, der einen Match liefert, gewinnt:
<!-- vendor/magento/module-store/etc/di.xml (vereinfacht) -->
<type name="Magento\Framework\App\RouterList">
<arguments>
<argument name="routerList" xsi:type="array">
<!-- sortOrder bestimmt die Reihenfolge -->
<item name="admin" xsi:type="array">
<item name="class" xsi:type="string">Magento\Backend\App\Router\DefaultRouter</item>
<item name="disable" xsi:type="boolean">false</item>
<item name="sortOrder" xsi:type="string">1</item>
</item>
<item name="robots" xsi:type="array">
<item name="class" xsi:type="string">Magento\Robots\Controller\Router</item>
<item name="sortOrder" xsi:type="string">10</item>
</item>
<item name="urlrewrite" xsi:type="array">
<item name="class" xsi:type="string">Magento\UrlRewrite\Controller\Router</item>
<item name="sortOrder" xsi:type="string">20</item>
</item>
<item name="standard" xsi:type="array">
<item name="class" xsi:type="string">Magento\Framework\App\Router\Base</item>
<item name="sortOrder" xsi:type="string">30</item>
</item>
<item name="cms" xsi:type="array">
<item name="class" xsi:type="string">Magento\Cms\Controller\Router</item>
<item name="sortOrder" xsi:type="string">60</item>
</item>
<item name="default" xsi:type="array">
<item name="class" xsi:type="string">Magento\Framework\App\Router\DefaultRouter</item>
<item name="sortOrder" xsi:type="string">100</item>
</item>
</argument>
</arguments>
</type>
| Router | sortOrder | Matcht |
|---|---|---|
| Admin Router | 1 | Admin-URLs (/admin/...), prüft den konfigurierten Admin-Frontname |
| URL-Rewrite Router | 20 | SEO-URLs aus url_rewrite-Tabelle (Produkt-, Kategorie-, CMS-URLs) |
| Standard Router | 30 | Technische URLs im Format /frontName/controller/action |
| CMS Router | 60 | CMS-Seiten nach URL-Key aus der Datenbank |
| Default Router | 100 | Fallback — rendert immer die 404-Seite |
5. Standard-Router: frontName, Controller und Action
Der Standard-Router ist der wichtigste Router für eigene Module. Er matcht URLs im Format /frontName/controller/action und mappt sie auf Klassen unter Controller/ im Modul-Verzeichnis.
<!-- app/code/Mironsoft/Blog/etc/frontend/routes.xml -->
<!-- Registriert den frontName für dieses Modul beim Standard-Router -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="mironsoft_blog" frontName="blog">
<module name="Mironsoft_Blog"/>
</route>
</router>
</config>
Mit dieser Konfiguration matcht der Standard-Router alle URLs, die mit /blog/ beginnen. Das Mapping von URL zu Klasse folgt einer strikten Konvention:
URL-zu-Klassen-Mapping (Standard-Router):
URL: /blog/post/view
│ │ │
│ │ └── Action: Controller/Post/View.php
│ └──────── Controller: Controller/Post/
└───────────── frontName: routes.xml (frontName="blog")
Vollständiger Klassenname: Mironsoft\Blog\Controller\Post\View
URL: /blog/post/index → Mironsoft\Blog\Controller\Post\Index
URL: /blog/index/index → Mironsoft\Blog\Controller\Index\Index
URL: /blog/ → Mironsoft\Blog\Controller\Index\Index (Defaults)
Defaults:
- Kein Controller in URL → "Index"
- Keine Action in URL → "Index"
<?php
// Der Standard-Router tut intern (vereinfacht) Folgendes:
// vendor/magento/framework/App/Router/Base.php
namespace Magento\Framework\App\Router;
class Base implements RouterInterface
{
/**
* Try to match the request to a module action class.
*/
public function match(RequestInterface $request): ?ActionInterface
{
// Parse URL path: /frontName/controllerPath/actionName
$pathParts = explode('/', trim($request->getPathInfo(), '/'));
$frontName = $pathParts[0] ?? 'index';
$controllerPath = $pathParts[1] ?? 'index';
$actionName = $pathParts[2] ?? 'index';
// Find module by frontName (from routes.xml registry)
$module = $this->routeConfig->getModulesByFrontName($frontName);
if (empty($module)) {
return null; // This router does not handle this URL
}
// Build class name: Vendor\Module\Controller\ControllerPath\ActionName
$actionClassName = $this->actionList->get($module, $controllerPath, $actionName);
if (!$actionClassName || !class_exists($actionClassName)) {
return null;
}
// Set routing parameters on the request for later use
$request->setModuleName($module);
$request->setControllerName($controllerPath);
$request->setActionName($actionName);
// Instantiate and return the action via DI
return $this->objectManager->create($actionClassName);
}
}
6. URL-Rewrite-Router: SEO-URLs auflösen
Produktseiten in Magento haben URLs wie /rotes-t-shirt-xl.html statt /catalog/product/view/id/42. Diese Übersetzung übernimmt der URL-Rewrite-Router, der vor dem Standard-Router ausgeführt wird (sortOrder 20 vs. 30).
URL-Rewrite Mechanismus:
Browser-Request: GET /rotes-t-shirt-xl.html
URL-Rewrite-Router (sortOrder 20):
↓
SELECT * FROM url_rewrite WHERE request_path = 'rotes-t-shirt-xl.html'
↓
Ergebnis: { target_path: 'catalog/product/view/id/42', redirect_code: 0 }
↓
Falls redirect_code = 301 oder 302:
→ HTTP Redirect zurückgeben (externe URL-Änderung)
Falls redirect_code = 0 (internes Rewrite):
→ Request-Path auf 'catalog/product/view/id/42' setzen
→ setDispatched(false) → FrontController startet Router-Loop neu
→ Standard-Router matcht jetzt: catalog/product/view → Catalog\Product\View
Ergebnis für Browser: URL bleibt /rotes-t-shirt-xl.html
Intern wird aber catalog/product/view/id/42 ausgeführt
<?php
// Vereinfacht: Magento\UrlRewrite\Controller\Router::match()
namespace Magento\UrlRewrite\Controller;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
class Router implements RouterInterface
{
/**
* Look up the request path in url_rewrite table.
* Modifies request for internal rewrites; returns redirect action for external ones.
*/
public function match(RequestInterface $request): ?ActionInterface
{
// Current URL path (e.g. "rotes-t-shirt-xl.html")
$requestPath = ltrim($request->getPathInfo(), '/');
// Database lookup
$rewrite = $this->urlFinder->findOneByData([
UrlRewrite::REQUEST_PATH => $requestPath,
UrlRewrite::STORE_ID => $this->storeManager->getStore()->getId(),
]);
if ($rewrite === null) {
return null; // No rewrite found — next router's turn
}
// External redirect (301/302)
if ($rewrite->getRedirectType() > 0) {
return $this->getRedirectAction($rewrite);
}
// Internal rewrite — silently change the path and re-dispatch
$request->setPathInfo('/' . $rewrite->getTargetPath());
$request->setAlias(Url::REWRITE_REQUEST_PATH_ALIAS, $requestPath);
// Trigger re-dispatch loop in FrontController
$request->setDispatched(false);
return null; // No action to return — router loop will handle the new path
}
}
Wichtig: Nach einem internen URL-Rewrite gibt der Router null zurück (kein Action-Objekt) und setzt setDispatched(false). Die do-while-Schleife im FrontController startet dann die Router-Chain erneut – jetzt mit dem neuen, internen Pfad. Der Browser sieht davon nichts.
7. Eigene Router erstellen: RouterInterface implementieren
Für spezielle URL-Strukturen – etwa Vanity-URLs, API-Proxies oder parameterreiche Permalinks – kannst du eigene Router registrieren. Das Interface ist einfach:
<?php
declare(strict_types=1);
namespace Mironsoft\Blog\Controller;
use Magento\Framework\App\ActionFactory;
use Magento\Framework\App\ActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\RouterInterface;
use Magento\Framework\App\Action\Forward;
/**
* Custom router: resolves /blog/<slug> URLs to the blog post view action.
* Handles slugs that don't follow the standard frontName/controller/action pattern.
*/
class Router implements RouterInterface
{
public function __construct(
private readonly ActionFactory $actionFactory,
private readonly PostRepositoryInterface $postRepository
) {}
/**
* Match /blog/<slug> pattern and forward to the post view action.
*/
public function match(RequestInterface $request): ?ActionInterface
{
$identifier = trim($request->getPathInfo(), '/');
// Only handle paths that start with "blog/" but have no further segments
// e.g. /blog/mein-erster-beitrag (not /blog/post/view)
if (!preg_match('#^blog/([a-z0-9-]+)$#', $identifier, $matches)) {
return null;
}
$slug = $matches[1];
// Verify the slug exists
$post = $this->postRepository->getBySlug($slug);
if ($post === null) {
return null; // Pass to next router (CMS, Default/404)
}
// Forward to existing action — no redirect, URL stays clean
$request->setModuleName('mironsoft_blog');
$request->setControllerName('post');
$request->setActionName('view');
$request->setParam('id', $post->getId());
$request->setDispatched(true);
// ActionFactory creates an internal forward (re-runs action without HTTP redirect)
return $this->actionFactory->create(Forward::class);
}
}
<!-- app/code/Mironsoft/Blog/etc/di.xml -->
<!-- Router im DI-Container registrieren -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Framework\App\RouterList">
<arguments>
<argument name="routerList" xsi:type="array">
<item name="mironsoft_blog" xsi:type="array">
<item name="class" xsi:type="string">Mironsoft\Blog\Controller\Router</item>
<item name="disable" xsi:type="boolean">false</item>
<!-- sortOrder 25: nach URL-Rewrite (20), vor Standard-Router (30) -->
<item name="sortOrder" xsi:type="string">25</item>
</item>
</argument>
</arguments>
</type>
</config>
Die Wahl der sortOrder ist entscheidend. Eigene Router sollten nach dem URL-Rewrite-Router (20) und vor dem Standard-Router (30) stehen, damit sie spezifische URLs abfangen, bevor der generische Fallback greift.
8. Action-Klassen: HttpGetActionInterface und Co.
Seit Magento 2.3 gibt es HTTP-Method-Interfaces, die besser als die ältere Action-Basisklasse sind. Sie ermöglichen es Magento, falsche HTTP-Methoden (z.B. POST auf eine GET-only-Route) automatisch mit 405 Method Not Allowed abzuweisen:
<?php
declare(strict_types=1);
namespace Mironsoft\Blog\Controller\Post;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\View\Result\PageFactory;
use Mironsoft\Blog\Api\PostRepositoryInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Controller\Result\RedirectFactory;
/**
* GET action: displays a single blog post.
* Only responds to HTTP GET — POST/PUT/DELETE get 405 automatically.
*/
class View implements HttpGetActionInterface
{
public function __construct(
private readonly PageFactory $pageFactory,
private readonly RequestInterface $request,
private readonly PostRepositoryInterface $postRepository,
private readonly RedirectFactory $redirectFactory
) {}
public function execute(): ResultInterface
{
$postId = (int) $this->request->getParam('id');
try {
$this->postRepository->getById($postId);
} catch (NoSuchEntityException) {
return $this->redirectFactory->create()->setPath('noroute');
}
$page = $this->pageFactory->create();
$page->getConfig()->getTitle()->set(__('Blog'));
return $page;
}
}
<?php
declare(strict_types=1);
namespace Mironsoft\Blog\Controller\Post;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\CsrfAwareActionInterface;
use Magento\Framework\App\Request\InvalidRequestException;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\App\Action\Context;
/**
* POST action: saves a blog comment.
* Implements CsrfAwareActionInterface for CSRF token validation.
*/
class Comment implements HttpPostActionInterface, CsrfAwareActionInterface
{
public function __construct(
private readonly JsonFactory $jsonFactory,
private readonly RequestInterface $request
) {}
/**
* Validate CSRF token — called automatically by Magento before execute().
*/
public function validateForCsrf(RequestInterface $request): ?bool
{
// Return null to use Magento's default CSRF validation
return null;
}
/**
* Create CSRF validation exception — called when CSRF validation fails.
*/
public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException
{
$response = $this->jsonFactory->create();
$response->setData(['error' => true, 'message' => 'Invalid form key.']);
return new InvalidRequestException($response);
}
public function execute(): ResultInterface
{
$result = $this->jsonFactory->create();
// ... process comment
return $result->setData(['success' => true]);
}
}
| Interface | HTTP-Methode | Typischer Einsatz |
|---|---|---|
| HttpGetActionInterface | GET | Seiten anzeigen, Daten laden |
| HttpPostActionInterface | POST | Formulare verarbeiten, Daten speichern |
| HttpPutActionInterface | PUT | REST-Updates (selten im Frontend) |
| HttpDeleteActionInterface | DELETE | REST-Löschoperationen |
9. Das RequestInterface: URL, Parameter und mehr
Das RequestInterface-Objekt ist der zentrale Datenspeicher für alle Request-Informationen. Es ist während des gesamten Request-Lifecycle verfügbar und wird vom Router mit Routing-Informationen befüllt:
<?php
declare(strict_types=1);
namespace Mironsoft\Blog\Controller\Post;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\View\Result\PageFactory;
/**
* Demonstrates RequestInterface methods available in actions.
*/
class Demo implements HttpGetActionInterface
{
public function __construct(
private readonly RequestInterface $request,
private readonly PageFactory $pageFactory
) {}
public function execute(): ResultInterface
{
// --- URL-Informationen ---
$pathInfo = $this->request->getPathInfo(); // "/blog/post/view"
$moduleName = $this->request->getModuleName(); // "mironsoft_blog"
$controller = $this->request->getControllerName(); // "post"
$action = $this->request->getActionName(); // "view"
// --- Query-Parameter (?id=42&tab=comments) ---
$id = (int) $this->request->getParam('id');
$tab = $this->request->getParam('tab', 'default'); // mit Default-Wert
// --- POST-Daten ---
$postData = $this->request->getPost(); // alle POST-Werte als Array
$email = $this->request->getPost('email'); // einzelner POST-Wert
// --- HTTP-Methode ---
$method = $this->request->getMethod(); // "GET", "POST", etc.
$isAjax = $this->request->isAjax(); // X-Requested-With: XMLHttpRequest
// --- Header ---
$accept = $this->request->getHeader('Accept');
$referer = $this->request->getHeader('Referer');
// --- Routing-Status ---
$dispatched = $this->request->isDispatched(); // true während Ausführung
return $this->pageFactory->create();
}
}
Sicherheitshinweis: getParam() liefert sowohl GET- als auch POST-Parameter. Für POST-Formulare immer getPost() verwenden und zusätzlich CsrfAwareActionInterface implementieren. Alle Werte aus getParam() sind unsanitisiert – im Template immer über $block->escapeHtml() ausgeben.
Mironsoft
Magento 2 Routing & Architektur
Routing-Probleme in Magento lösen?
Wir analysieren und erweitern Magento-Routing: Custom-Router für komplexe URL-Strukturen, URL-Rewrite-Optimierung und vollständige Router-Diagnose – nach aktuellen Best Practices.
10. Zusammenfassung
Das Front Controller Pattern in Magento 2 ist ein mehrstufiges System: pub/index.php triggert den Bootstrap, der Bootstrap erstellt das HTTP-Applikations-Objekt, das den FrontController::dispatch() aufruft. Dieser durchsucht in einer do-while-Schleife eine sortierte Liste von Routern. Der erste Router, der matcht, liefert eine Action-Klasse zurück, die execute() ausführt und ein Result-Objekt zurückgibt.
Front Controller Pattern – Überblick
Bootstrap
pub/index.php initialisiert Composer-Autoloader und DI-Container. Im Production-Mode gecacht in var/di/. Teuerste Phase — einmalig pro Request.
Router-Chain
Admin (1) → URL-Rewrite (20) → Standard (30) → CMS (60) → Default/404 (100). Eigene Router via di.xml mit gewähltem sortOrder einklinken.
URL-Rewrite
Übersetzt SEO-URLs (/rotes-shirt.html) in technische Pfade (catalog/product/view/id/42). Internes Rewrite: URL bleibt, Pfad ändert sich. Externes Rewrite: HTTP 301/302.
Action-Klassen
HttpGetActionInterface statt alter Action-Basisklasse. Magento validiert HTTP-Methode automatisch — falsche Methoden → 405. CsrfAwareActionInterface für POST-Formulare.
11. FAQ: Front Controller Pattern in Magento 2
1 FrontController vs. normaler Controller: Was ist der Unterschied?
HttpGetActionInterface, enthält die eigentliche Ausführungslogik.2 Warum beginnt jeder Request bei pub/index.php?
pub/ ist als Webroot konfiguriert. Nginx/Apache leitet alle Requests, die keine statische Datei treffen, per try_files an pub/index.php weiter. Das ist das Front Controller Pattern: ein einziger Einstiegspunkt für alle dynamischen Requests.3 In welcher Reihenfolge werden Magento-Router durchsucht?
di.xml mit gewähltem sortOrder einfügen. sortOrder 25 ist der typische Platz für Custom-Router (nach URL-Rewrite, vor Standard).4 Wie registriert man einen eigenen Router?
RouterInterface (match()-Methode) erstellen. 2. In etc/di.xml beim Typ Magento\Framework\App\RouterList registrieren – als Item mit class, disable=false und sortOrder. Danach: setup:di:compile + Cache-Flush.5 HttpGetActionInterface vs. alte Action-Basisklasse?
Action-Basisklasse antwortete auf alle HTTP-Methoden. HttpGetActionInterface beschränkt auf GET — ein POST-Request ergibt automatisch 405 Method Not Allowed ohne manuellen Code. Verbessert Sicherheit und REST-Compliance.6 Wie debuggt man den Routing-Status in Magento?
FrontController::dispatch() · Plugin auf RouterInterface::match() zum Loggen · bin/magento dev:profiler:enable mit HTML-Profiler · Query-Log für url_rewrite-Lookups · Developer-Mode für detaillierte Fehlermeldungen.7 Warum gibt es URL-Rewrite-Router UND Standard-Router?
8 Wie funktioniert das Routing in der Admin-Area?
/admin/, in etc/env.php änderbar). Admin-Actions in etc/adminhtml/routes.xml registrieren. Admin-Actions erben von Magento\Backend\App\Action – inkl. Auth + ACL-Checks.9 Was passiert wenn kein Router matcht?
do-while-Schleife im FrontController verlässt sich darauf: ohne Default-Router gäbe es eine Endlosschleife. Die 404-Seite ist über noroute_index_index.xml konfigurierbar.10 Wie teste ich einen Custom-Router mit PHPUnit?
RequestInterface mocken, getPathInfo() auf Testpfad setzen, Repository mocken. Dann router->match($request) aufrufen – prüfen ob null (kein Match) oder ActionInterface zurückkommt. Für Integrationstests: Magento\TestFramework\Request mit echten DB-Fixtures.