Lerne Coding
Routing mit PHP realisieren - Bramus Router
10.03.2021

URL-Routing in PHP und wofür es nützlich ist

Inhaltsverzeichnis
[[TABLE OF CONTENTS]]
access_timeGeschätzte Lesezeit ca. Minuten

Routing ist das eigenständige Abwickeln der Webanfragen an den Server durch PHP. Dazu werden eigene Routen (Pfadangaben) erstellt und anschließend an eine zuständige Funktion oder Klasse (auch Controller genannt) gemäß Web-MVC (Model View Controller) übergeben. Das ist nützlich, um nicht für jede Seite eine eigene Datei erstellen zu müssen, es können automatisch Footer und Header eingebunden werden und Parameter können schöner übergeben werden z.b. hellocoding.de/blog/coding-language/php/bramus-router (siehe obige URL). Mit Views kannst du Komponenten erstellen, damit du gewisse Teile deiner Webseite überall verwenden kannst.

Solltest du den Router in Kombination mit einer Datenbank verwenden, kannst du so bspw. die Angabe von Parametern sofort prüfen. Bei HelloCoding wird geprüft, ob die aufgerufene Artikel-ID existiert oder nicht. Ist das nicht der Fall, kann im gleichen Request eine Fehlermeldung ausgegeben werden.

Github Logobramus/router
1.058 49 246

A lightweight and simple object oriented PHP Router

Anfragearten

Dein Browser liefert je nach Anfrage auch Daten mit. Diese werden meistens über GET oder POST überliefert. Diese Wörter hast du sicherlich schon ein mal gehört, falls du ein Formular in HTML abschicken wolltest. GET sind normale Anfragen über die URL, POST sind verschlüsselte Werte, welche in der Anfrage mit verpackt und einmalig mitgeliefert werden, wie eine Art Cookie.

Zudem gibt es noch PUT, DELETE, OPTIONS, PATCH und HEAD welche eher für APIs verwendet werden.

Kurzer Exkurs: APIs sind Schnittstellen für externe Programme, Webseiten o.ä. welche mit den Daten deiner Webseite oder deines Systems arbeiten. Somit kannst du bspw. eine Datenverarbeitung implementieren, welche dann über eine Webseite und eine App verwendet werden kann. Beide stellen nur noch Web-Requests an dein System, das dann mit der Datenbank interagiert.

Bibliotheken

Den Router musst du in PHP nicht jedes Mal neu erfinden. Es gibt viele Frameworks, welche dir das Routing professionell abnehmen. So z.B. der Bramus Router, Nikic's Fastrouter, aber auch große PHP-Frameworks wie Laravel und Symfony haben Routing integriert. Ich persönlich mag den Bramus Router am liebsten, da er sehr einfach zu bedienen ist.

Bramus Router installieren

Für die Installation von Bramus verwende ich Composer. Falls du Composer noch nicht installiert hast, kannst du ihn als ZIP herunterladen, jedoch würde ich dir empfehlen erst den Composer Artikel dazu durchzulesen, da Composer die Installation einfacher und wartbarer macht. Ist er installiert, kannst du den Router über folgenden Befehl installieren:

composer require bramus/router

Nun wird der Router in dein vendor Verzeichnis installiert und du kannst ihn verwenden. Solltest du eine Versionierungssoftware wie Git verwenden, kannst du den vendor Ordner in deine .gitignore schreiben, dann wird er nicht gepusht. Das spart Speicherplatz auf dem Git-Server. Erstelle nun eine index.php Datei in deinem Webverzeichnis und inkludiere den Composer Autoloader.

require_once "vendor/autoload.php";

Anfragen an den Router weiterleiten

Da der Webserver standardmäßig nicht weiß, ob du einen Router hast oder nicht, müssen wir diesem zuvor mitteilen, wohin er die Anfragen weiterleiten soll. Dazu sollen alle Anfragen erst mal normal vom Server geroutet werden, da Bilder oder anderweitige CSS- oder Javascript Dateien nicht von PHP verarbeitet werden müssen und dadurch keine weitere Verarbeitung angetriggert werden soll, um die Homepage auszubremsen.

Apache Webserver

Bei XAMPP auf dem lokalen Windows Recher, oder auf Linux im Produktivsystem kann eine .htaccess Datei im Hauptverzeichnis erstellt werden. Diese wird bei jeder Anfrage ausgelesen und verarbeitet, weswegen diese prägnant geschrieben werden sollte. Hier wird eine sogenannte RewriteEngine verwendet, welche, sofern der Dateipfad eine Datei nicht finden sollte, die Anfrage an eine Datei weiterleitet. Ich habe diese Datei hier index.php genannt, du kannst sie aber auch in router.php oder main.php umbenennen. Somit müssen keine weiteren Änderungen in der Serverkonfiguration vorgenommen werden. Möchtest du aber ein bisschen Performance sparen und 5-10 Anfragen Sekunde mehr verarbeiten könne, kannst du das auch in der Serverkonfiguration einstellen und anschließend den Server neu starten.

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

Mit dem integrierten Webserver debuggen

Solltest du deine Webserver nicht mit einem Apache Virtual-Host, sondern über den integrierten PHP Webserver testen, kannst du ihn wie folgt starten:


php -S 0.0.0.0:8008 index.php

/* 
Erklärung des Befehls:

-S -> starte einen Server
0.0.0.0:8008 -> verarbeite alle Anfragen mit Port 8008
index.php -> Name des Routersscripts
*/

Doch der integrierte Webserver hat ein Problem: statische Inhalte wie Bilder werden nicht geroutet. Du musst deshalb in deinem Script weit oben, am besten in der ersten Zeile, erkennen, ob er die Datei routen soll, oder nicht.

if ($env['debug'] and preg_match('/\.(?:png|jpg|jpeg|css|js|svg)$/', $_SERVER["REQUEST_URI"])) {
    return false;
}

In diesem Code Frage ich zunächst ab, ob es sich um die lokale Entwicklungsumgebung handelt oder nicht. Sollte das nicht der Fall sein, wird die if-Bedingung abgebrochen und nicht weiter ausgeführt. Sollte sie jedoch true sein, prüfe ich im nächsten Schritt mit einer Regex, ob am Ende der Dateiendung "png", "jpg", "jpeg", "css", "js" oder "svg" steht. Wenn ja, beende ich das Script mit false und PHP routet die Anfrage normal im Dateisystem.

Routen erstellen

Da du nun auf die Klasse Bramus\Router\Router zugreifen kannst und alle wichtigen Anfragen an die index.php weitergeleitet werden, erstelle eine Instanz der Klasse.

require_once "vendor/autoload.php";

// create composer instances
$router = new \Bramus\Router\Router();

// führt das routing nach registrierung der seiten aus
$router->run();

Ich habe dir oben bereits erklärt, dass es verschiedene Möglichkeiten gibt, eine Webseite anzufragen. Solange du jedoch nur eine Webseite und keine API schreibst, genügen GET und POST, du kannst aber auch mehrere oder alle HTTP-Methoden routen.

Was sind Routing-Pattern

Routing Pattern sind Strings, welche die Route angeben. Sie definieren, was hinter der Domain oder der URL stehen muss, damit eine gewisse Aktion ausgeführt wird. Für die Homepage genügt ein /, für das Kontaktformular könntest du /kontakt angeben. D.h. example.com wird an die Funktion mit dem Parameter / übergeben und example.com/kontakt an /kontakt.

Solltest du Werte über die URL übergeben wollen (dazu gleich mehr), können auch Regex verwendet werden. Darf eine ID nur numerisch sein, könntest du /artikel/(\d+) verwenden, oder wenn sie nur eine sechsstelliges Hexadezimal sein darf, dann kannst du /farbe/(#?[a-fA-F0-9]{6}) angeben.

Beispiel GET

Um deine Hauptseite zu routen würde ich GET nehmen. Get kann für alle Seiten verwendet werden, welche im Browser lediglich etwas anzeigen sollen. Lass uns zunächst einmal "Hello World" auf deiner Homepage ausgeben

// include composer autoload
require_once "vendor/autoload.php";

// create composer instances
$router = new \Bramus\Router\Router();

// define routes
$router->get('/', function() {
    echo "Hello World";
});

// router run!
$router->run();

Informationen über GET übergeben

Du kannst auch Informationen über die URL übergeben, ohne wie zuvor ein Fragezeichen verwenden zu müssen (example.com/artikel?id=123). Wie oben erklärt, kannst du nun Regex im Pattern verwenden, um einen validen Wert zu übergeben.

// include composer autoload
require_once "vendor/autoload.php";

// create composer instances
$router = new \Bramus\Router\Router();

// define routes
$router->get('/', function() { echo "Hello World"; });

$router->get('/artikel/([\d]+)', function($artikel_id) {
    echo "Du liest nun den Artikel mit der ID: ".$artikel_id;
});

// router run!
$router->run();

Optionale Parameter über GET übergeben

Um nicht drei verschiedene Routen für optionale Parameter angeben zu müssen, kann man beim Bramus Router auch Subpatterns und Subrouten verwenden. Subpattern sind reguläre Ausdrücke, nur ineinander verschachtelt. Solltest du kein Regex beherrschen, wirst du dich damit schwertun und ich empfehle Subrouting. Aber ich möchte, dass du es dennoch zumindest einmal gesehen hast.

$router->get(
    '/blog(/\d+(/\d+(/\d+(/[a-z0-9_-]+)?)?)?)?',
    function($year = null, $month = null, $day = null, $slug = null) {
        if (!$year) { echo 'Blog overview'; return; }
        if (!$month) { echo 'Blog year overview'; return; }
        if (!$day) { echo 'Blog month overview'; return; }
        if (!$slug) { echo 'Blog day overview'; return; }
        echo 'Blogpost ' . htmlentities($slug) . ' detail';
    }
);

Das hier definierte Pattern bedeutet:/blog/year/month/day/slug, jedoch ist alles ab /blog optional. Die Seite kann also mit/blog,/blog/2020/blog/2020/12 /blog/2020/12/12 und /blog/2020/12/12/php aufgerufen werden. Das Return am Ende beendet lediglich die anonyme Funktion und sagt PHP, die Arbeit für diese Route ist getan.

Ich komme noch mal auf das Submounting zurück. Es wird zunächst die Route /movies definiert und durch das use die Instanz dem Router zugänglich gemacht. Nun können in der Route weitere Subrouten verwendet werden. Hier wurde also nun die Route /movies/ und /movies/123 erlaubt.

$router->mount('/movies', function() use ($router) {

    // gibt die movies aus
    $router->get('/', function() {
        echo 'movies overview';
    });

    // will result in '/movies/id'
    $router->get('/(\d+)', function($id) {
        echo 'movie id ' . htmlentities($id);
    });

});

Beispiel POST

Nachdem du nun auf deiner Seite auch ein Kontaktformular eingebaut hast, möchtest du diese Werte gerne via Post an dein Backend übergeben. Die Route "/contact" wird jedoch nur für GET-Requests verwendet. Wenn du deine Daten via Post verschickst, kannst du dein Formular an die gleiche Seite weiterleiten, jedoch kann nun eine andere Verarbeitung gestartet werden:

// index.php

// include composer autoload
require_once "vendor/autoload.php";

// create composer instances
$router = new \Bramus\Router\Router();

// define routes
$router->get('/', function() { echo ""; });
$router->get('/contact', function() { echo "print formular"; });
$router->post('/contact', function() { 

    // liest die Variablen ein
    $name = $_POST['name'] ?? "";
    $email = $_POST['email'] ?? "";
    $message = $_POST['message'] ?? "";

    // do stuff
});

// router run!
$router->run();

Eine 404 Seite anlegen

Um 404 Seiten anlegen zu können, gibt es im Bramus Router eine eigene Funktion. Bedenke: Setze immer einen Error Header, damit du dem Client signalisierst, diese Anfrage ist gescheitert. Nun werden alle nicht route-baren Anfragen an diese Route weitergeleitet.

// include composer autoload
require_once "vendor/autoload.php";

// create composer instances
$router = new \Bramus\Router\Router();

// error 404
$router->set404(function() { 
    header('HTTP/1.1 404 Not Found'); 
    echo "Error 404!"; 
});

// router run!
$router->run();

Fehler 404 triggern

Wenn eine Seite nicht existiert, kannst du diese Anfrage auch direkt verarbeiten. Bei nicht definierten Anfragen wird die Funktion set404standardmäßig ausgeführt. Die 404 Seite kann aber auch in anderen Seiten getriggert werden. Dazu muss die Router Instanz mit use zugänglich gemacht werden. Im Beispiel hier möchte ich die Webseite ausschließlich unter der Woche zugänglich machen. Am Wochenende soll diese nicht existieren.

// include composer autoload
require_once "vendor/autoload.php";

// create composer instances
$router = new \Bramus\Router\Router();

// define routes
$router->get('/', function() use ($router) { 
    // on weekend throw 404 because I don't work on weekends
    if(date("N") > 5) {
        $router->trigger404();
    }

    echo "Hello World";
});

// error 404
$router->set404( function() { 
    header('HTTP/1.1 404 Not Found'); 
    echo "Error 404!"; 
});

// router run!
$router->run();

PHP Code vor der Route ausführen

Sollte eine Admin-Route existieren, sollte diese zunächst prüfen, ob der Nutzer überhaupt eingeloggt ist. Da aber nicht in jeder Funktion die Session individuell geprüft werden sollte, kann der Bramus Router Code vor der Route ausführen. Somit muss alles nur einmal programmiert werden und wird ständig wiederverwendet.

In diesem Beispiel werden für alle GET und POST Anfragen, welche mit "admin" beginnen, zunächst geprüft, ob die Session-Variable user gesetzt ist. Ist das nicht der Fall, wird der Nutzer zur Log-In-Seite weitergeleitet und die Anfrage beendet. Somit wird verhindert, dass nicht eingeloggte Nutzer eine Route erreichen, die sie nicht erreichen dürfen.

$router->before('GET|POST', '/admin/.*', function() {
    // check ob user eingeloggt ist
});

Fazit

Routen sind ein schönes Werkzeug um gut aussehende URLs bauen zu können. Gerade in Kombination mit Controllern und Views kann Code auch mehrfach wiederverwendet werden, ohne redundanten, inkonsistenten Code schreiben zu müssen. Durch das Einhalten der einheitlichen Datenstruktur größerer Frameworks (z.B. Laravel, Symfony, Shopware) kann zudem bessere Wartbarkeit gewährleistet werden. Seitdem ich mich mit Routen auseinandergesetzt habe, möchte ich sie für größere Anwendungen nicht mehr missen.

Bildquelle - Vielen Dank an die Ersteller:innen für dieses Bild
Kommentare zum Artikel

Es sind noch keine Kommentare vorhanden? Sei der/die Erste und verfasse einen Kommentar zum Artikel "Routing mit PHP realisieren - Bramus Router"!

Kommentar schreiben

Verwante Beiträge
close