Kategorie-Archive: Programmiersprachen

Saubere URLs mit „lesbaren“ Titeln erzeugen

Die als „Permalink“ oder „Slug“ bekannten Endungen von URLs, die meist den Titel abbilden, kennen wohl die meisten Internetnutzer. Dieser Beitrag hat zum Beispiel den Slug saubere-urls-mit-lesbaren-titeln-erzeugen.

Diese Art und Weise des Aufrufs ist eine leserfreundliche Alternative zum Aufruf via ID (index.php?id=3242) und bietet vor allem den Vorteil, dass der Leser sofort anhand der URL erkennen kann, welchen Artikel er aufgerufen hat.

Das Erzeugen eines URL Slugs ist nicht ganz so trivial. Zwar gibt es sehr einfache Methoden (zum Beispiel alle Zeichen zu verwerfen, die nicht a-z oder 0-9 sind), aber dann verliert die URL besonders im Deutschen viel Sinn. Denn Umlaute oder das ß würden komplett unter den Tisch fallen.

Bei Matteo Spinelli habe ich eine schöne Funktion gefunden, die ich jetzt benutze, um eigene Slugs zu erstellen. Alle nicht darstellbaren Zeichen werden bei dieser Funktion durch ähnliche Buchstaben ersetzt, also zum Beispiel das ß durch Doppel-S, ö und Ö durch o und so weiter.

Ich habe die Funktion für meine Zwecke angepasst und erweitert. Sie erzeugt jetzt einzigartige Slugs, vorausgesetzt niemand nutzt den gleichen Titel in exakt der gleichen Sekunde (das genügt als „uniqueness“ für meinen Anwendungsfall).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * Generate a readable (URL-valid) slug from a given string
 * @param string $str
 * @return string
 */
public static function slug($str){
 
	setlocale(LC_ALL, 'en_US.UTF8');
	$slug = iconv('UTF-8', 'ASCII//TRANSLIT', $str);
	$slug = preg_replace("/[^a-zA-Z0-9\/_|+ -]/", '', $slug);
	$slug = strtolower(trim($slug, '-'));
	$slug = preg_replace("/[\/_|+ -]+/", '-', $slug);
	$slug = (time()-1388530800).'-'.$slug; //1388530800 => 2014-01-01
	return $slug;
 
}

Direkter Joomla-Logout ohne Zwischenseite (J!2.5 und höher)

Bei Joomla gibt es ein Problem, wenn man sich direkt ausloggen möchte. Ich nutze beispielsweise ein Menü (Modul), in dem ich gerne einen Abmelden-Link haben möchte.

Da gibt es dann 3 Optionen:

  1. Ich baue eine Form ein, die den direkten Logout erlaubt. Das ist lässt sich dann sehr schlecht mit CSS stylen.
  2. Ich leite den Nutzer auf eine Zwischenseite von com_users weiter, wo er sich ausloggen kann. Das ist umständlich.
  3. Ich baue einen direkten Logout-Link mit dem nötigen Token.

Der 3. Weg ist natürlich die beste Lösung, allerdings habe ich ihn nirgendwo beschrieben gesehen. Daher jetzt hier kurz und knapp:

<a href="<?php echo JRoute::_('index.php?option=com_users&task=user.logout&'.JSession::getFormToken().'=1'); ?>"><?php echo JText::_('JLOGOUT'); ?></a>

Von iFrames auf Parent-Window zugreifen (cross-domain)

Will man eine Funktion im Elternfenster eines iFrame aufrufen, so ist das normalerweise kein Problem.

1
window.parent.functionname();

und fertig ist die Kiste. Ganz anders sieht es aber aus, wenn der iFrame nicht in der gleichen Domain liegt. Cross-Domain-Zugriff auf Funktionen (und Eigenschaften) ist nämlich verboten.

Hierfür gibt es seit einiger Zeit eine Lösung, die inzwischen viele Browser implementieren: postMessage. Wenn man nicht gerade IE6/7 unterstützen muss, kann man diese Funktion nutzen, um sich das Leben einfacher zu machen.

Im iFrame sendet man dabei eine Nachricht an das Hauptfenster:

1
window.parent.postMessage('close', '*');

und im Hauptfenster empfängt man die Nachricht und wertet sie aus:

1
2
3
4
window.addEventListener("message", function(event) {
    if(event.data === 'close'){
	doSomething();
    }

Unterstützt wird diese praktische Funktion von Firefox 3+, IE 8+, Chrome 4+, Safari 4+, Opera 10+ und allen derzeit gängigen Mobilbrowsern.

caniuse
(Bild von caniuse.com)

Lange Zend DB-Queries bringen PHP/Apache zum Absturz

Beim Programmieren einer Anwendung mit Zend und IBM DB2 bin ich auf ein Phänomen gestoßen, das zunächst unerklärlich schien. Führte ich einen langen Query aus (in diesem Fall sollte ein längerer XML-String in ein XML-Datenfeld der IBM DB2 Express-C geschrieben werden), so stürzte Apache ohne Fehlermeldung ab. In den Logs tauchte nur „restarting“ auf, sonst nichts.

Nach etwas Recherche stellte sich heraus, dass wohl eine fehlerhafte Implementierung der PCRE-Erweiterung Schuld an dem Desaster ist. Ist Backtracking auf einen zu hohen Wert eingestellt, so konsumiert PCRE (Zend Framework benutzt preg_match(), um SQL-Queries auf Korrektheit zu prüfen) den gesamten Stack, wodurch der Prozess sang- und klanglos abgeschossen wird.

Eine Erklärung zu PCRE-Backtracking gibt es hier: http://www.regular-expressions.info/catastrophic.html.

Für PHP ist die Lösung relativ simpel, der voreingestellte Wert von 100.000 für pcre.backtrack_limit und pcre.recursion_limit muss (entgegen einiger Aussagen in diversen Bugreports und Tutorials) heruntergesetzt werden. In meiner PHP.ini sieht das zurzeit folgendermaßen aus:

1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
[Pcre]
;PCRE library backtracking limit.
; http://php.net/pcre.backtrack-limit
pcre.backtrack_limit=1000
 
;PCRE library recursion limit.
;Please note that if you set this value to a high number you may consume all
;the available process stack and eventually crash PHP (due to reaching the
;stack size limit imposed by the Operating System).
; http://php.net/pcre.recursion-limit
pcre.recursion_limit=1000

Damit laufen meine Queries (~1800 Zeichen pro Query) anstandslos durch. Bei Bedarf kann dieser Wert auch weiter abgesenkt werden, auf 500 zum Beispiel. Da die PCRE-Funktion einen Aufwand von O(2^n) hat, bringt auch diese relativ kleine Änderung wieder recht viel.

10 falsche Vorstellungen über PHP

Ich mag PHP. Habe es schon immer gemocht, seit ich in meiner Schulzeit von HTML(3)-Markup – was ich davor für Programmieren hielt – zu echten Programmiersprachen gewechselt bin. PHP ist einfach zu erlernen, bietet viel Komfort und, unter Beachtung verschiedener Voraussetzungen, eine hohe Performance bei Nutzung relativ weniger Systemressourcen. Zudem ist es (fast) überall im Web verfügbar, egal ob beim Free-Hoster mit Werbefinanzierung oder auf dem 800-Euro-pro-Monat-Rootserver.

Hartnäckig halten sich allerdings zahlreiche Gerüchte über PHP, von schlechter Performace (sehr häufig) über Bedenken wegen fehlender Objektorientierung bis zur Angst, dass Zend aufgrund seiner Vormachtstellung im Development von PHP die Sprache vollständig kontrolliert. Manuel Lemos hat dazu auf phpclasses.org einen sehr guten Artikel verfasst, den sich jeder Interessierte mal zu Gemüte führen sollte. Sogar als erfahrener Developer kann man dort noch das ein oder andere, das man vielleicht noch nicht wusste, erfahren. Oder wussten Sie, dass man mit PHP Windows-Dienste erstellen kann?

MySQL und Index – oder: warum %-Zeichen böse sind

Heute bin ich auf eine interessante Sache gestoßen, die eigentlich ganz logisch ist (wenn man ein wenig über Bäume Bescheid weiß), auf die man aber von allein trotzdem erst nach viel Nachdenken kommt wie ich finde. Daher will ich sie niemandem vorenthalten 🙂

Es fing alles damit an, dass MySQL-Queries des XMLArsenal (siehe Projekte) unglaublich lange brauchten. Ein JOIN über 3 Tabellen mit WHERE-Clause auf einer und Primärschlüsseln in allen Tabellen (die zum JOIN verwendet wurden) brauchte teilweise bis zu 2 Sekunden. Nun ist der Datenbestand recht groß, etwa 170.000 Zeilen (~2GByte) in der größten Tabelle. Trotzdem konnte es eigentlich nicht angehen zumal auf der Namensspalte, die ich in der WHERE-Clause verwende, bereits ein Index definiert war. An dieser Stelle muss ich mich bei Silvan Mühlemann von techblog.tilllate.com bedanken, sein Artikel „Optimierung von MySQL-Abfragen: Verwendung des Index“ hat mich auf die richtige Spur bzw. eigentlich gleich zur richtigen Lösung gebracht: Nutzt man

1
LIKE %Name%

kann MySQL den definierten Index nicht nutzen, d.h. wieder Scan über die ganze Tabelle. Lässt man das vordere % weg wird das Ergebis zwar kleiner aber es steht der Nutzung des Index nichts mehr im Wege – Speedup in diesem Fall: 50.000-fach. Merke: kein % vorn wo es nicht unbedingt nötig ist. Faszinierend. 🙂

Flash „drängelt sich vor“

Beim Erstellen von Websites, die Flash beinhalten, ist mir schon des öfteren eine kuriose Eigenschaft des Flash-Objekts aufgefallen: Es ignoriert die natürliche Reihenfolge der Elemente (z-Index) und wird grundsätzlich als oberster Layer angezeigt. Damit überdeckt es ggf. auch absolut positionierte Elemente, bei denen einen z-Index definiert ist. Dieses Verhalten wird von Adobe sogar in einem extra Knowledgebase-Artikel beschrieben (warum auch immer es nicht einfach gleich dem Standard angepasst wird): tn_15523.

Um dieses Verhalten zu umgehen muss man nur einen zusätzlichen Parameter

1
<param name="wmode" value="opaque" />

einfügen, gültige Werte für value sind dabei window (default), opaque, und transparent. Bei den letztgenannten tritt dabei die Missachtung der z-Reihenfolge nicht auf.

mod_rewrite für ein „MVC“-ähnliches Verhalten

mod_rewrite sollte jedem, der schon etwas Erfahrung im Bereich Webprogrammierung hat, ein Begriff sein. Es wird häuftig verwendet, um „SEO-friendly“ – also suchmaschienenoptimierte – URLs für dynamische Websites zu erzeugen. Auch das MVC-Modell sollte als geläufiges Entwurfsmuster bekannt sein. Nun braucht man aber nicht bei jedem Projekt MVC, denn kleine Projekte werden dadurch schnell sperrig; oft reicht auch einfach ein Umschreiben auf bestimmte URLs und die Verwendung einer Template-Engine. Leider konnte ich nirgends im Web ein Beispiel finden wie man jetzt genau http://domain.com/user/add in http://domain.com/user.php?action=add umschreibt.

Daher nun hier mein Bespiel einer .htaccess, die genau das ermöglicht:

1
2
3
4
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule   ^([^/]+)/?([^/]*)/?    /$1.php?action=$2

Die RewriteCond verhindert hier, dass Dateien, die wirklich über diese URL erreichbar sind, ebenfalls umgeschrieben werden (wäre schlecht für Bilder, CSS usw.). Wenn man das Muster ([^/]*)/? wiederholt anhängt kann das genutzt werden, um noch tiefere Pfade in weitere Parameter umzusetzen, z.B. würde

1
2
3
4
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule   ^([^/]+)/?([^/]*)/?([^/]*)/?    /$1.php?action=$2&param=$3

dafür sorgen, dass man auch http://domain.com/user/view/45 in http://domain.com/user.php?action=view¶m=45 umsetzen kann. So sind beliebige Pfadtiefen nutzbar.

Zu beachten ist, dass man im verarbeitenden Script die Parameter in $_GET nicht auf Existenz sondern mit Hilfe der Funktion empty() testen sollte, da automatisch leere Strings als Parameter übergeben werden falls der Pfad nicht die maximale Tiefe erreicht.

css.php