Moderne Routing-Verfahren in PHP-Apps
Zwar bin ich schon seit einiger Zeit auf Python umgestiegen und versuche PHP mittlerweile weitgehenst zu vermeiden, aber dennoch wurde ich vor einigen Tagen von einem Freund gefragt, wie man heutzutage seinen URLs den richtigen Controller zuordnet. Aus lauter Langeweile, habe ich so ein relativ umfangreiches Beispiel erstellt, welches die meiner Meinung nach schönste Möglichkeit aufzeigt.
Undzwar besteht diese aus einer Tabelle, welche mit Hilfe von Regulären Ausdrücken, URLs einen bestimmten Controller (im Beispiel eine einfache Datei) zuordnet. Die Regex-Tabelle hat den Vorteil, eine zentral gelegene, leicht zu ändernde und flexible Möglichkeit zum Registrieren von URLs zu bieten. Weiterhin werden GET-Parameter in Forum von URI-Komponenten (z.B. “username” in /profile/username/view) direkt mit geparst und in ein einfach zu bedienendes Array (hier: $matches) gepackt.
Kurze Erklärung, wie man das Script benutzen kann. Man erstellt sich einen Ordner mit beliebigem Namen auf dem eigenen Webserver. Dort erstellt man eine Datei, welche zwingend index.php heißen muss mit dem gleich folgenden Inhalt. Außerdem benötigt man eine Datei namens .htaccess, welche den in der index.php als Kommentar vorhanden Code enthält. Nachdem das getan ist, muss die Variable $url_prefix noch angepasst werden. Diese muss die URI enthalten, unter dem die index.php (bzw. deren übergeordneter Ordner) erreichbar ist. In meinem Beispiel ist dies /test/. wichtig ist der abschließende Slash! anschließend probiert man einfach mal die in der $urls-Variable angegebenen URLs aus.
-
<?php
-
-
// —————————————————————————-
-
// Inhalt der .htaccess.
-
// —————————————————————————-
-
-
/*
-
-
RewriteEngine On
-
-
RewriteCond %{REQUEST_FILENAME} -s [OR]
-
RewriteCond %{REQUEST_FILENAME} -l [OR]
-
RewriteCond %{REQUEST_FILENAME} -d
-
RewriteRule ^.*$ – [NC,L]
-
RewriteRule ^.*$ index.php [NC,L]
-
-
*/
-
-
// —————————————————————————-
-
// Das hier gehört eher in eine Konfigurations-Datei.
-
// —————————————————————————-
-
-
$url_prefix = '/test/'; // Oder: "/" für root-Verzeichnis
-
$urls = array(
-
'^$' => 'content/index.php',
-
'^forum$' => 'content/forum.php',
-
'^profile/(?P<user>.+)' => 'content/profile.php'
-
);
-
-
// —————————————————————————-
-
// Und das gehört in den URL-Resolver (extra Datei oder einfach index.php).
-
// —————————————————————————-
-
-
// Schneidet aus der URL den führenden Slash und den Prefix weg.
-
$uri = substr($_SERVER['REQUEST_URI'], strlen($url_prefix));
-
$page_found = False; // Dateiname falls gefunden, sonst False.
-
foreach ($urls as $regex => $file) {
-
// Eine Regex muss von Delimitern eingegrenzt werden. Wir nehmen den
-
// Slash, indem wir diesem drumherum setzen und alle vorkommenden Slashs
-
// escapen.
-
$regex = '/' . str_replace('/', '\/', $regex) . '/';
-
if (preg_match($regex, $uri, $matches)) {
-
$page_found = $file;
-
// Ganz wichtig wenn ein passender Eintrag gefunden wurde!
-
break;
-
}
-
}
-
-
// Nur zu Debugging-Zwecken:
-
header('Content-Type: text/plain');
-
-
if (! is_string($page_found)) {
-
header('HTTP/1.1 404 Not Found');
-
die("Error 404 – File not found:\n" . $uri);
-
} else {
-
print_r($matches);
-
// Zecks Debug auskommentiert…
-
//require $file;
-
}
Stichwort ist das “explizite” Routing im Gegensatz zu dem “impliziten”, wie z.B. standardmäßig von Ruby on Rails verwendet. Dort regelt halt die Konvention, zu welchem Controller gemappt wird.
Ergo: Ich bin mir nicht sicher ob das “moderner” ist, es ist auf jeden Fall hochkonfigurierbar und eigentlich sollte dieses Feature immer eine Option sein. (Bei Ruby on Rails ist es optional.)
Die Frage ist ja, als was es moderner ist. Moderner als das implizite Routing wie bei Ruby muss es nicht sein. Aber moderner, als das Verfahren, welches mein Kumpel verwendet hat auf jeden Fall. Ich erinner mich auch noch an meine Versuche mit: include($_GET['page']);
ich kenn mich leider nicht so ganz mit den preg-funktionen aus, aber kann man die expressions von python frameworks wie django/web.py/tornado etc. direkt in php verwenden? mit dem ?P. kam mir so grad die Frage, weil php ja nicht explizit bestimmte Parameter übergeben kann wie in python (fn(x=123,b=456,c=’abc’) bei fn(b,c,a) beispielsweise)
Für meinen Teil muss ich sagen, dass die Funktion mit dem expliziten Mappen mir deutlich mehr liegt bzw. ich sie schöner finde, da sie an einem Punkt definiert, was logisch und was technisch eine Komponente ist und nicht dies durch eine rein technische Angelegenheit definiert ist. Auch hat man damit nicht das Problem, dass unter /klasse1/funktion1/ automatisch eine Klasse klasse1 zur Verfügung steht, die vielleicht gar nicht öffentlich sein soll
Das implizierte Mappen hat natürlich für’s schnell aufbauen einer Seite meist einen Vorteil, sofern man nach dem Unix-Grundsatz handelt, dass ein Programm (und damit eine Klasse/Funktion/Datei) nur eine Funktion hat und daher seperat zu betrachten ist, dass man bei der Methode nicht extra die Konfiguration immer schreiben/ändern muss
Wenn man natürlich im Vorraus bereits festlegt, was wohin zeigen soll, ist das ganze auch wieder eine praktische Hilfe, da man weiß, was noch eingebaut werden muss und wie es gehen soll
Aber mal von dem Mappen weg: PHP ist schon eine schöne Sprache, wird nur leider von vielen vergewaltigt und in Richtung Performance und Umfang auf Rücksicht dieser ja leider nicht so stark fortentwickelt. Dennoch hat sie Funktionen wie private, vererbbare und statische Eigenschaften im OOP-Bereich, die beispielsweise wiederum python nicht kennt