Sexy URLs mit CakePHP oder: das Sluggable-Behavior
Worum geht’s?
Hier möchte ich das Sluggable-Behavior vorstellen und zeigen, wie man es einbindet und auf den deutschen Sprachraum anpasst.
Warum das Ganze
Richtig hübsche und zudem suchmaschinenoptimierte URLs sehen meiner Meinung nach nicht aus wie “doma.in/artikel/view/32″ sondern eher wie “doma.in/artikel/ich-bin-ein-toller-artikel”. Auch wenn es in der Suchmaschinenoptimierung nur ein kleiner Faktor ist, so kann es der entscheidende Faktor sein. Der weit größere Vorteil einer solchen “spechenden URL” ist aber, dass sie in den Suchergebnissen auch häufiger angeklickt werden wird, da der Nutzer eine konkretere Vorstellung davon hat, was ihn erwartet.
Die ID zu ersetzen wird besonders sinnvoll, wenn man UUIDs arbeitet, denn Artikel 23 kann sich der User wohl noch merken und auch nachvollziehen (es gibt 1 bis x Artikel, fortlaufend nummeriert), aber welcher User kann sich denn unter 48ed2b16-24e0-43be-88d5-0504a1877158 etwas vorstellen oder sich gar die ID merken? Ich bin ein Fan von UUIDs, aber ich finde, sie haben nichts in den URLs verloren…
So geht’s
Ich schlage also vor, die ID durch eine deskriptive Zeichenkette zu ersetzen. Wie läßt sich das mit CakePHP realisieren? Natürlich ziemlich einfach, denn auf die Idee sind natürlich schon längst andere gekommen, man muss sich nur noch in der Bakery bedienen: Dort gibt es Mariano Iglesias’ Sluggable-Behavior. Es generiert uns zu jedem Eintrag ein Slug, also z.B. zu einem Eintrag namens “Ich bin ein toller Artikel” bekommen wir den Slug “ich-bin-ein-toller-artikel”.
Das Einbinden ist denkbar einfach:
- Die sluggable.php in models/behaviors/ kopieren
- Die entsprechenden Datenbank-Tabellen, z.B. ‘article’ um eine Spalte ’slug’, z.B. varchar(100), damit die Slugs gespeichert werden können.
- Das Model sluggable machen:
1 2 3 4 5 6 7 | class Article extends AppModel { var $name = 'Article'; var $actsAs = array('Sluggable'); ... } |
So einfach wäre es im englischsprachigen Raum. Aber die deutsche Sprache hat ja noch ein paar Tücken parat, deshalb brauchen wir folgende Ergänzung, damit die Sonderzeichen ü,ä,ß,.. nicht einfach durch einen Bindestrich ersetzt werden:
1 2 3 4 5 6 | class Article extends AppModel { var $name = 'Article'; var $actsAs = array('Sluggable' => array('translation' => 'utf-8')); ... } |
Jetzt wird mit jedem neuen Article ein Slug passender Slug erstellt.
Links und View-Actions anpassen
Unseren Link auf den Artikel können wir nun in den views mit
1 | echo $html->link('Klick hier', array('action'=>'view', $article['Article']['slug'])); |
setzen.
Nicht vergessen: Noch schnell im Controller die view-Funktion anpassen:
1 2 3 4 | function view($slug) { $article = $this->Article->findBySlug($slug); $this->set('article', $article); } |
Stolperfallen soweit
Falls das Model keinen “title” besitzt, muss konfiguriert werden, woraus der Slug sonst generiert werden soll. Das geht so:
1 | var $actsAs=array('Sluggable' => array( 'label' => 'name'); |
Falls es schon Einträge gibt, wird kein Slug beim Edit erzeugt. Um dies zu ändern muss folgendes konfiguriert werden:
1 | var $actsAs=array('Sluggable' => array('overwrite'=>true)); |
Das Standardverhalten false macht duraus Sinn, denn die URLs zum Artikel sollten sich ja nicht ändern…
Finetuning für DE
Das Sluggable-Behavior ersetzt jedoch ü durch u, ö durch o, ß durch s, und so weiter. Ideal wäre ü zu ue, ö zu oe, ß zu ss, etc., denn so wären die Slugs ideal lesbar und auch die Suchmaschine muss sich nicht zu sehr anstrengen. Dazu müße man eine kleine Änderung an der sluggable.php vornehmen. Ich habe die Änderungen hier zum Copy-Paste vorbereitet, einfach an der entsprechenden Stelle einfügen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 'utf-8' => array( array( // Decompositions for Latin-1 Supplement chr(195).chr(128) => 'A', chr(195).chr(129) => 'A', chr(195).chr(130) => 'A', chr(195).chr(131) => 'A', chr(195).chr(132) => 'AE', chr(195).chr(133) => 'A', chr(195).chr(135) => 'C', chr(195).chr(136) => 'E', chr(195).chr(137) => 'E', chr(195).chr(138) => 'E', chr(195).chr(139) => 'E', chr(195).chr(140) => 'I', chr(195).chr(141) => 'I', chr(195).chr(142) => 'I', chr(195).chr(143) => 'I', chr(195).chr(145) => 'N', chr(195).chr(146) => 'O', chr(195).chr(147) => 'O', chr(195).chr(148) => 'O', chr(195).chr(149) => 'O', chr(195).chr(150) => 'OE', chr(195).chr(153) => 'U', chr(195).chr(154) => 'U', chr(195).chr(155) => 'U', chr(195).chr(156) => 'UE', chr(195).chr(157) => 'Y', chr(195).chr(159) => 'ss', chr(195).chr(160) => 'a', chr(195).chr(161) => 'a', chr(195).chr(162) => 'a', chr(195).chr(163) => 'a', chr(195).chr(164) => 'ae', chr(195).chr(165) => 'a', chr(195).chr(167) => 'c', chr(195).chr(168) => 'e', chr(195).chr(169) => 'e', chr(195).chr(170) => 'e', chr(195).chr(171) => 'e', chr(195).chr(172) => 'i', chr(195).chr(173) => 'i', chr(195).chr(174) => 'i', chr(195).chr(175) => 'i', chr(195).chr(177) => 'n', chr(195).chr(178) => 'o', chr(195).chr(179) => 'o', chr(195).chr(180) => 'o', chr(195).chr(181) => 'o', chr(195).chr(182) => 'o', chr(195).chr(182) => 'oe', chr(195).chr(185) => 'u', chr(195).chr(186) => 'u', chr(195).chr(187) => 'u', chr(195).chr(188) => 'ue', chr(195).chr(189) => 'y', chr(195).chr(191) => 'y', // Decompositions for Latin Extended-A |
Das ist alles. Ich hoffe, mein erster Post hat euch gefallen. Happy Baking!
1 Kommentar
Schreibe ein KommentarKommentare RSS Feed TrackBack URL
Juni 12th, 2009 at 22:50
Ein Klasse Artikel von dir. Hat mir super weitergeholfen. Die Beschreibung in der bakery ist nämlich ziemlich kurz und irgendwie fehlt da die Hälfte.
Vielen Dank!