Heute möchte ich ein kurzes Tutorial zum Thema „SEO URLs“ veröffentlichen. SEO steht, für die die es noch nicht wussten, für „Search Engine Optimization“ – Also Suchmaschinenfreundliche URLs. Doch diese Web-Adressen sind gleichermaßen Benutzerfreundlich da sie keine kryptischen GET Parameter enthalten sondern klare Texte (sofern ihr eure Applikation/Website entsprechend aufbaut).
Dieses Tutorial sollte übrigens bei allen gängigen XAMPP installationen und Webservern funktionieren.

Die folgende nicht-SEO URL liefert dem PHP Skript im Hintergrund zwar eine klare ID, doch als Benutzer können wir damit nicht viel anfangen. Wir wissen weder wie der Eintrag heißt noch von wann er ist – Sprich: Sowohl Suchmaschinen- als auch Benutzer-unfreundlich.

http://web-developer-blog.com/?post_id=403

In dieser SEO URL dagegen wird ein kompletter String übergeben statt einer Zahl. Ich benutze hier im Blog das folgende Format: "<Jahr>/<Monat>/<Name des Eintrags>.html". Der heutige Eintrag ist also unter der folgenden URL zu finden…

http://web-developer-blog.com/2012/02/tutorial-einfache-seo-urls.html

Somit wissen wir auf einen Blick wie der Eintrag heißt und von wann ist.

Also wie erreichen wir diese schönen Adressen in unserer Applikation oder auf unserer Website?

Es gibt zwei Wege – Die erste hat zwar nichts mit PHP (oder sonstigen Entwicklung) zutun, doch ich möchte sie zur Vollständigkeit aufführen:
Ihr könnt auf eurem Webserver eine ganz normale Ordnerstruktur anlegen und innerhalb jedes Ordners eine „index.html“ (oder „index.php“) ablegen. Ihr könnt anschließend durch die („real“-) existierenden Ordner navigieren und könnt damit eine ähnliche Struktur abbilden wie in meinem Beispiel oben.
Das würde allerdings bedeuten das ihr in jeder index-Datei eines jeden Ordners das komplette Layout usw. einbringen müsst. Das ist natürlich alles andere als Pflegeleicht…

Nun aber zu der Methode die ich eigentlich ansprechen möchte!
Zu aller erst müssen wir uns eine „htaccess“ Datei anlegen. Das ist eine Datei ohne Namen mit der Endung „htaccess“. Eine solche Datei lässt sich nicht von Windows erstellen (Windows erlaubt keine Dateien ohne Namen) – Deshalb müssen wir diese entweder mit einem Editor erstellen (und als „.htaccess“ speichern) oder wir nutzen Linux 😉

Der Inhalt dieser Datei kann etwa so aussehen:

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L,QSA]

Diese Zeilen machen folgendes:
Als erstes wird das URL Rewriting aktiviert – Das ist nötig um die URLs selbst definieren zu können. Der Befehl RewriteBase / wird den HTTP Host als Ausgangsort festlegen (also z.B. „http://leonardfischer.de“) – Sollte eure Applikation/Website Beispielsweise in einem Unterordner liegen muss der Befehl entsprechend angepasst werden. Für „http://leonardfischer.de/applikation“ z.B. RewriteBase /applikation/.

Die nächsten beiden Zeilen RewriteCond %{REQUEST_FILENAME} !-f und RewriteCond %{REQUEST_FILENAME} !-d geben an, das tatsächlich existierende Dateien (-f) und Ordner (-d) angesteuert werden sollen.
Das ist unter anderem für ausgelagerte Dateien (wie Bilder, Javascript oder CSS) wichtig – Denn diese sollen ja schließlich „gefunden“ und angezeigt werden.

Die letzte Zeile RewriteRule .* index.php [L,QSA] leitet nun alle ankommenden Requests auf die „index.php“ um – Natürlich nur sofern keine der definierten Regeln zutrifft. Die Buchstaben in der eckigen Klammer sorgen dafür das nach dieser Zeile keine weiteren Regeln definiert werden können und die aktuellen Request-Daten aus unserer URL an die sogenannten Request-URI an gehangen werden.

Wir haben nun also quasi Narrenfreiheit was die URL angeht – Selbst Adressen wie z.b. „http://leonardfischer.de/test-eins-zwei/ordner/datei.endung.zweite-endung“ werden ganz normal auf unsere index.php weitergeleitet. Das ist genau das Verhalten welches wir erreichen wollten!

Legen wir nun eine „index.php“ Datei mit folgendem Inhalt in unseren Ausgangsordner:

Hallo Welt, wir befinden uns auf:<br />
<?php echo $_SERVER['REQUEST_URI']; ?>

So werden wir (mit der Beispiel-Adresse von vorhin) folgenden Text zu sehen bekommen:

Hallo Welt, wir befinden uns auf
/test-eins-zwei/ordner/datei.endung.zweite-endung

Wie wir diesen String nun verarbeiten ist uns vollkommen selbst überlassen! Aber für einen Schnellstart habe ich hier ein kleines PHP Skript abgetippt welches ein ganz rudimentäres Routing bereitstellt…

// Zuerst entfernen wir führende und abschließende Slashes.
$url = trim(strtolower($_SERVER['REQUEST_URI']), '/');

// Wir nehmen uns die ersten drei Ebenen der URL und bauen uns somit eine kleine Struktur auf.
list($controller, $action, $id) = explode('/', $url);

// Wir definieren einen standard Controller, falls unsere URL leer ist.
if (empty($controller))
{
    $controller = 'startseite';
}

// Wir definieren eine standard Action, falls unsere URL keine beinhaltet.
if (empty($action))
{
    $action = 'index';
}

if (class_exists($controller))
{
    $controller = new $controller;

    if (method_exists($controller, $action))
    {
        $controller->$action($id);
    }
    else
    {
        trigger_404('Entschuldigung, die angeforderte Aktion wurde nicht gefunden');
    }
}
else
{
    trigger_404('Entschuldigung, das angeforderte Modul wurde nicht gefunden');
}

function trigger_404($msg = '')
{
    header("HTTP/1.0 404 Not Found");
    header("Status: 404 Not Found");
    echo $msg;
}

Achtung – Mit diesem „routing“ wird es euch nicht möglich sein Bindestriche in der URL zu nutzen – Aber das könnt ihr ganz einfach „nachrüsten“:

// Damit auch Bindestriche in der URL funktionieren benutzt ihr statt:
$url = trim(strtolower($_SERVER['REQUEST_URI']), '/');

// Einfach diese Zeile:
$url = trim(strtolower(str_replace('-', '_', $_SERVER['REQUEST_URI'])), '/');

// Dadurch wir bei "http://leonardfischer.de/hallo-welt/" der Controller "hallo_welt" aufgerufen.

Anschließend braucht ihr nur noch für jedes Modul eine Klasse erstellen und entsprechende Actions definieren. Und schon habt ihr ein funktionierendes Routing, mit dem ihr arbeiten könnt. Ihr solltet allerdings euer Error-Reporting etwas umstellen, sonst kann es schnell passieren das ihr „NOTICE“ Meldungen bekommt weil das Array, das durch den explode Befehl erstellt wird ggf. keine Einträge beinhaltet, doch die list Funktion drei Stück erwartet:

// Wir möchten keine NOTICE-Meldungen erhalten.
error_reporting(E_ALL^E_NOTICE);

Anschließend stelle ich noch den „startseite“ Controller zur Verfügung damit ihr einen noch schnelleren Einstieg habt 😉

// Startseite Controller
class startseite
{
    public function index($id = null)
    {
        echo 'Hallo und willkommen auf meiner tollen Seite!';
    }
}

Mit dieser Grundlage könnt ihr durchaus schon ganz gut arbeiten – Für größere Projekte empfehle ich allerdings ein besseres Routing und eine sicherere Grundlage (z.B. ein Framework). Als Grundlage sollte dieser Blog-Eintrag allerdings hervorragend geeignet sein!

Achtung! Solltet ihr Eingaben aus der URL in die Datenbank bringen (z.B. weil ihr euer Routing in die Datenbank auslagert) solltet ihr unbedingt den Beitrag SQL-Injection vorbeugen lesen!

Mit diesen Worten möchte ich mich für Heute verabschieden. Vielleicht werde ich mal ein paar mehr kleine Tutorials vorbereiten! Ich wünsche euch eine erfolgreiche Rest-Woche!

14 comments on “Basics Tutorial: Einfache SEO URLs

  • Hi there, just became alert to your blog through Google, and found that it’s really informative. I am gonna watch out for brussels. I will be grateful if you continue this in future. A lot of people will be benefited from your writing. Cheers!

  • Danke für deinen Beitrag. Ich werde sicherlich ein paar Stellen daraus für meine Seite übernehmen.
    Ich würde mich noch dafür interessieren, was du mit deinem Satz “ euer Routing in die Datenbank auslagert“ meinst. und wie das funktionieren soll.

    Viele Grüße

    • Hallo Moritz,

      es freut mich das ich dir helfen konnte!
      Der angesprochene Satz bezieht sich auf Content Management Systeme (CMS) wie ProcessWire oder auch WordPress-Installationen (wie dieser Blog). Wenn man über die Web-Oberfläche dynamisch neue Seiten (+ URLs) anlegt, werden diese Informationen üblicherweise in der Datenbank abgelegt…

      Sobald also jemand eine URL eingibt und diese vom System mit dem Inhalt der Datenbank verglichen wird, sollte der Input nicht ohne weitere Prüfung an die Datenbank übergeben werden.

      Mit freundlichen Grüßen
      Leo

  • Hallo, das gefällt mir ganz gut. Ich frage mich nur, wie ich da eine get-Variable einbauen kan, so dass diese auch bei der eigentlichen php Datei ankommt?

    Grüße

    • Hallo Thomas,

      das kann eine Reihe an Ursachen haben – zum Beispiel wenn deine index.php und .htacecss Dateien nicht im obersten Verzeichnis liegen (das von meinedomain.de/) sondern du relativ, z.B. in meinedomain.de/test-eins-zwei/, arbeitest. In diesem Fall müsstest du die RewriteBase entsprechend anpassen (RewriteBase /test-eins-zwei/).
      Eine weitere Ursache könnte sein, dass die Regeln in der .htaccess Datei nicht greifen. Dies kann passieren wenn dein Apache nicht über das „mod_rewrite“ Modul verfügt oder du Nginx einsetzt (anstelle eines Apache Webservers). Du kannst dich diesbezüglich bei deinem Hoster erkundigen, in der Regel helfen diese gern und schnell weiter 🙂

      Ich selbst suche in solchen Fällen meist im Internet nach Lösungen da ich nicht grade der „geborene Administrator“ bin 😉

  • Hi Leo.

    Danke für die rasche Antwort. Habe den Fehler gefunden. Tippfehler:-))
    Wo ich aber noch Probleme habe ist mit der Zeile:
    list($controller, $action, $id) = explode('/', $url);

    Habe ich diese drin, geht Garnichts: „Die Webseite wurde nicht gefunden“. entferne ich diese , gaht’s. Woran liegts? Nutze PHP7.

    Habe gelesen das php7 bei list von rechts nach links liest, also habe ich folgendes versucht:
    list($id, $action, $controller) = explode('/', $url);

    Dann bekomme ich:
    Notice: Undefined offset: 1
    Notice: Undefined offset: 2

    der rest scheint zu gehen.

  • Hi Leo,

    vielen Dank erstmal für Deine einfache und gut erklärte Darstellung zum Thema PHP-Routing.
    Ich bin leider noch nicht sehr fit in diesem Bereich, so dass ich bei dem experimentieren mit Deinem Lösungsansatz ein Problem bekommen habe:

    Wenn ich in der Adresse einen oder mehrere Slashes einbaue, werde die relativen Pfade innerhalb des dann aufgerufenen HTML nicht mehr richtig gesetzt:

    Folgende Situation:

    localhost/meine-seite/index.php ist mein Start- bzw. Standardzugriff

    Wenn ich nun eine lesbare Adresse, z.B.

    localhost/meine-seite/ueber-mich/meine-hobbys

    aufrufe, werden Bilder, css-Dateinen, aber auch relative link-Angaben in dem Verzeichnis localhost/meine-seite/ueber-mich/ gesucht und nicht mehr im „Hauptverzeichnis“.

    Wie kann ich dieses Problem lösen, dass mein Basisverzeichnis immer gleich bleibt. Bin ich der einzige, der dieses Problem hat?

    Liebe Grüße

    Stephan

    • Hallo Stephan, das passiert mir hin und wieder auch selbst noch 😉 Im Grunde musst du nur die relativen Pfade anpassen bzw. diese absolut machen:
      ... href="/style.css" ...

      Das ganze ist natürlich ein wenig nervig wenn man noch mit Unterverzeichnissen (in deinem Fall „meine-seite“) arbeitet. Du hast da folgende Optionen:

      1. RewriteBase angepassen und Pfade absolut machen
      2. Pfade absolut machen (inklusive dem Ordner) ausgehend von deinem Host („localhost“) /meine-seite/style.css
      3. Pfade absolut machen und vhost Eintrag machen

      Am besten ist die dritte Lösung: also wenn du deine Seite lokal so aufrufst, wie es auch später irgendwann online passieren wird. Dazu solltest du dir das Thema „Virtuelle Hosts“ bzw „vhosts“ anschauen, hierbei handelt es sich um eine Apache Konfiguration.
      Dadurch lassen sich lokale virtuelle Hosts erzeugen wie z.B. „http://meine-seite.int“ die dann intern auf deinen definierten Ordner zeigt.

      Dann brauchst du auch nicht viel mit der .htaccess herumprobieren und hast später (beim Livegang) keine Probleme.

      VG Leo

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.