PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for November, 2009

Wave Einladungen für Interessierte

with 16 comments

Ich kann mich noch nicht dazu durchringen, ein umfassendes Urteil über Google Wave abzugeben, aber der erste Eindruck ist sehr ernüchternd. Es kommt mir so vor wie ein webbasierter Messenger, bei dem man mehrere Personen in Räume einladen kann und viel Zeit verbraten kann. Nebenbei noch Dateien tauschen. Nichts was man mit Threaded Emails oder ICQ/Jabber nicht auch machen könnte.

Mag sein, dass da noch mehr ist, oder mittels Gadgets da noch mehr möglich ist, aber bisher hatte ich noch nicht soviel Zeit, es im Detail zu betrachten. Deshalb noch kein ausführlicher Artikel.

Wer sich selbst ein Bild machen möchte, kann hier gern in den Kommentaren einen Pieps sagen, habe noch 7 Einladungen zu verschenken an Interessierte.

wave overview

Written by Michael Kliewe

November 30th, 2009 at 6:31 pm

Posted in Allgemein

Tagged with , ,

Blog URL Struktur geändert

with 19 comments

So, nun habe ich etwas Zeit gehabt und auf die suchmaschinenfreundliche URL-Struktur umgestellt. Vorher hatte ich umgestellt auf /%post_id% , sodass Artikel unter
https://www.phpgangsta.de/630
verfügbar waren. Das war primär dafür gemacht, dass ich diese kurzen URLs in Twitter nutzen kann, da ich Short-URL-Dienste hasse (man weiß nie was dahinter versteckt ist).

Nun habe ich die Struktur umgestellt auf /%postname% , sodass der selbe Artikel nun standardmäßig so verlinkt ist:

Taugt Zend_Queue etwas?

ABER: Da ich die kurzen URLs nicht verlieren wollte (und die Links in Google/Twitter weiterhin funktionieren sollen) habe ich meine .htaccess noch angepasst, sodass ich nun beide Formate nutzen kann. In Twitter kann ich also weiterhin meine geliebten Kurz-URLs nutzen, im Blog wird allerdings der Postname genutzt. Meine .htaccess sieht nun so aus:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /

RewriteRule ^([0-9]+)$ /?p=$1 [R=301,L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

Falls also in der URL nur Zahlen vorkommen, sende ich einen „301 Permanent Redirect“ auf die URL ?p=<id> , diese URL wandelt WordPress dann nochmals um und leitet auf die URL mit dem Postnamen um. In Google sollten nun also bald die Artikel nur noch mit dem Postnamen erscheinen und die ID-URLs bald verschwinden. Ich bin gespannt, ob das so klappt.

Um nur einen 301 Redirect zu nutzen hätte ich ein kleines Script schreiben müssen, welches aus einer ID die Postname-URL herausfindet (Datenbank?) und dann redirected. Ob das einfach ist und vor allem bei zukünftigen WordPress-Updates noch funktioniert würde ich bezweifeln, und soweit ich weiß sind 2 Redirects kein Problem, oder hat jemand andere Informationen?

Jedenfalls funktioniert es nun so wie ich möchte, und Google werde ich in den nächsten Tagen mal beobachten.

Written by Michael Kliewe

November 29th, 2009 at 1:36 am

Posted in Allgemein

Tagged with , , ,

Taugt Zend_Queue etwas?

with 2 comments

In einem älteren Artikel schrieb ich bereits über asynchrone Aufgaben. Heute möchte ich Zend_Queue vorstellen, womit man Aufgaben speichern kann, die später erledigt werden sollen.

Eine solche Message-Queue hat zwei Aufgaben: Aufgaben sollen erstellt werden können inklusive Details was zu tun ist, sowie Möglichkeiten bereitstellen, diese Aufgaben später wieder auslesen zu können, um sie abzuarbeiten.

Diese Aufgaben können wir nun in verschiedenen Backends speichern, Zend_Queue bietet einige Adapter an, wie beispielsweise eine Datenbank, ZendPlatformJobQueue, Apache ActiveMQ und MemcacheQ (bald wohl auch Amazon SQS).

Diese asynchrone Abarbeitung hat viele Vorteile. Für den Webseiten-Besucher erscheint die Webseite schneller, da einige Aufgaben später (wenige Sekunden, vielleicht auch sehr viel später) abgearbeitet werden. Für uns als Entwickler und Betreiber des Services besteht die Möglichkeit, Aufgaben in lastarmen Zeiträumen zu erledigen, sie auf mehrere Server aufzuteilen, die Last von den Frontendservern zu nehmen usw. Beim ZendPlatformJobQueue-Adapter kann man Jobs auch priorisieren, zu bestimmten Zeiten erledigen lassen, Abhängigkeiten zwischen den Jobs definieren und einiges mehr.

Mit einer Queue können wir auch regelmäßige Aufgaben abarbeiten lassen, beispielsweise das Aufräumen der Datenbank jede Nacht um 3 Uhr, pausieren von Aufgaben und spätere Wiederaufnahme der Tätigkeit, oder das Aufschieben von Aufgaben um x Stunden, beispielsweise soll immer exakt 2 Stunden nach dem Eintreffen eines Ereignisses eine Email versendet werden, wenn der Nutzer bis dahin nicht auf der Webseite war. Das geht allerdings wie gesagt nicht mit jedem Adapter.

Allgemein lässt sich sagen, dass wir alles offline abarbeiten sollten was nicht für ein Feedback des Besuchers benötigt wird.

Ein gutes Beispiel wäre ein Forum. Wenn ein Besucher einen neuen Beitrag schreibt, gibt es viel zu tun: Der Beitrag muss in der Datenbank gespeichert werden, der neue Inhalt muss in den Index gepumpt werden, Themenabonenten müssen benachrichtigt werden, RSS-Feeds müssen aktualisiert werden, und am Ende soll dem Benutzer sein fertiger Beitrag gezeigt werden. Das schreit doch nach Parallelität bzw. Queuing:

jobqueue

Wie funktioniert nun Zend_Queue? Hier ein einfaches Beispiel, wo wir eine Datenbank als Backend nutzen und einen Job eintragen. Zuerst müssen wir die Tabellen anlegen, die dazu benötigt werden. Das entsprechende SQL-Script befindet sich unter Zend/Queue/Adapter/Db/mysql.sql

Die entsprechenden PHP-Scripte zum Eintragen von Jobs und zum Auslesen von Jobs sehen so aus:

initQueue.php

<?php
include('Zend/Loader/Autoloader.php');
$autoloader = Zend_Loader_Autoloader::getInstance();

$config = array(
	'name' => 'myqueue',
	'driverOptions' => array(
		'host'     => 'localhost',
		'port'     => '3306',
		'username' => 'root',
		'password' => '',
		'dbname'   => 'Queue',
		'type'     => 'pdo_mysql'
	),
	'options' => array(
		// use Zend_Db_Select for update, not all databases
		// can support this feature.
		Zend_Db_Select::FOR_UPDATE => true
	)
);

// Create a database queue
$queue = new Zend_Queue('Db', $config);
$queue->createQueue('myqueue');

insert.php

<?php
require('initQueue.php');

$queue->send(serialize(array(	
			'action' => 'new article',
			'articleId' => 13))
);

receive.php

<?php
require('initQueue.php');

$messages = $queue->receive(5);
foreach($messages as $msg) {
	$jobInfo = unserialize($msg->body);

	// do the work
	var_dump($jobInfo);

	$queue->deleteMessage($msg);
}

Die Datenbank unterstützt nur einfache Jobs, es ist also nur für einfache Entkopplung zu gebrauchen, aber hier soll es als Beispiel reichen. Es werden aktuell keine Prioritäten, Ausführungszeiten oder ähnliches unterstützt, es ist eine reine Message-Queue. Der ZendPlatformJobQueue-Adapter unterstützt beispielsweise diese Features, sodass darauf zurückgegriffen werden sollte bei komplexeren Aufgaben. Soweit ich das bisher gelesen habe wird die Job Queue aber nur im kostenpflichtigen ZendServer enthalten sein, nicht in der Community Edition. Das ist echt schade, denn von den Features her ist das echt spitze was da geboten wird.

Wer also eine einfache Message-Queue benötigt, kommt mit Zend_Queue super klar, wer mehr braucht, sollte den ZendPlatformJobQueue-Adapter und den ZendServer nutzen (und vorher kaufen), oder sich bei Projekten wie Gearman oder Dropr umschauen, die ich in naher Zukunft auch noch vorstellen möchte.

Written by Michael Kliewe

November 27th, 2009 at 9:58 am

Die eigene Suchmaschine in PHP leicht gemacht: Lucene

with 10 comments

Nicht nur Google hat ausgereifte Suchalgorithmen, jeder Programmierer kann sich auch seine eigene Volltextsuche auf die Webseite bauen. Das können zum Beispiel alle Unterseiten sein, die durchsucht werden sollen, aber auch Dateien, Emails, Dokumente und Texte jeglicher Art.

Ich werde am Ende auch kurz aufzeigen, warum der Mysql-Volltextindex kein guter bzw. schneller Index ist, und warum Lucene und andere Suchengines ihre Daseinsberechtigung haben.

In diesem Artikel soll es also um Lucene gehen. Lucene ist ein Open-Source-Suchalgorithmus, der als Apache-Projekt weiterentwickelt wird und auf den viele weitere Produkte aufbauen (das bekannteste ist wohl Solr). Der Grundaufbau einer solchen „Suchmaschine“ besteht aus 2 Teilen: Dem Indexer und der Suche.

Der Indexer ist zum Befüllen des Datenbestandes (des Indexes) zuständig. Ihm übergibt man also alle Texte und Dokumente, und sagt ihm dabei, welche Felder und Daten davon wichtig sind, und eventuell noch wie wichtig die einzelnen Dokumente sind. Lucene ist zum Beispiel auch in der Lage, HTML-Dateien zu parsen und daraus title, meta-tags, header usw zu extrahieren. Man spart also Arbeit, und kann die Suche später auf die entsprechenden Bereiche beschränken. Der Index wird dann im Dateisystem abgelegt.

Die Suche spuckt dann die Ergebnisse aus, wenn man sie mit mehr oder minder komplexen Suchaufgaben befeuert. Dabei sind nicht nur einfache Stichwortsuchen möglich, sondern auch „ungefähre Treffer“, man erhält einen Relevanzwert(Score) und noch einige weitere Informationen.

Wenn wir nun in PHP einen solchen Index aufbauen wollen, nutzen wir am besten die Zend_Search_Lucene Klassen dafür. Hier ein einfaches Beispiel, wie man den Index füllt:

<?php

include_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

$index = Zend_Search_Lucene::create('/tmp/index');

$document = new Zend_Search_Lucene_Document();
$document->addField(Zend_Search_Lucene_Field::Text('title', 'Titel 1 des Dokuments'));
$document->addField(Zend_Search_Lucene_Field::Text('content', 'Hier ist ein toller Text'));
$index->addDocument($document);

$document = new Zend_Search_Lucene_Document();
$document->addField(Zend_Search_Lucene_Field::Text('title', 'Das hier ist der zweite Titel'));
$document->addField(Zend_Search_Lucene_Field::Text('content', 'Und hier steht der Inhalt eines Buches'));
$index->addDocument($document);

Wir definieren also ein Verzeichnis, in dem der Index abgelegt werden soll. Dann erstellen wir ein Dokument, zu dem wir dann ein Feld hinzufügen, in diesem Fall ein Textfeld. Dieses wird gesplittet und jedes Wort kann als Suchwort genutzt werden. Text-Felder werden zum Index hinzugefügt und komplett gespeichert, um sie bei den Ergebnissen auszugeben. Es gibt auch noch weitere Feldtypen, die zum beispiel nur indiziert aber nicht gespeichert werden, oder nur gespeichert und nicht indiziert. Hier gibt es eine Übersicht der Feldtypen.
Zum Schluss fügen wir das Dokument noch zum Index hinzu. Um die Suche nachher etwas interessanter zu machen, fügen wir noch ein weiteres Dokument hinzu. Das Ergebnis sieht dann so aus:

index

Reingucken brauchen wir da nicht, denn der Inhalt ist relativ kryptisch. Wir wollen ja auch nicht direkt auf diese Dateien zugreifen, sondern mittels der Suche. Das geht wie folgt:

<?php

include_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

$index = Zend_Search_Lucene::open('/tmp/index');

$queries = array('Buch', 'toller', 'ist', 'title:ist');

foreach ($queries as $query) {
	$results = $index->find(
	  		Zend_Search_Lucene_Search_QueryParser::parse($query)
	);
	echo "Suche: " . $query . "\n";
	echo count($results) . " Ergebnisse \n\n";
	foreach ($results as $result) {
		echo 'Inhalt: ' . $result->content . "\n";
		echo 'Score: ' . $result->score . "\n";
		echo  "\n";
	}
}

Die Abfragen können normale Sucheworte sein, man kann nur in bestimmten Feldern suchen, boolsche Operatoren (AND/OR) nutzen als auch noch viel komplexere Abfragen starten.

Die Ausgabe sieht wie folgt aus:

luceneresult

Es ist also wirklich kein Hexenwerk, mit knapp 30 Zeilen haben wir sowohl den Index gefüllt als auch einige Suchabfragen gestartet und die Ergebnisse ausgegeben.
Mit Lucene kann man noch sehr viel mehr machen, alles hier aufzuzählen würde den Rahmen sprengen. Einfach mal im Zend Framework Manual gucken, dann bekommt man einen Eindruck, was alles möglich ist.

Achso, ich erwähnte ja noch, dass ein Mysql-Volltextindex nicht so sinnvoll ist. Sobald große Mengen an Daten anfallen, wird Mysql langsam. Hier gibt es ein wunderbares PDF-Dokument mit Benchmarks.

Interessant sieht auch Sphinx aus, habe mich allerdings noch nicht damit beschäftigt.

Lucene ist also besonders interessant bei Daten, die nicht bereits in der Datenbank vorhanden sind. Als Beispiele wären da Dokumente, Twitter-Nachrichten, Emails oder statische HTML-Dateien genannt. Bevor man das also in seine Datenbank pumpt, nur um eine langsame Volltextsuche zu erhalten, sollte man lieber Lucene benutzen.

Written by Michael Kliewe

November 26th, 2009 at 9:48 am

Kinotipp: Paranormal Activities

with 5 comments

Heute ein kleiner Artikel, der absolut garnichts mit PHP zu tun hat.

Wir waren gerade im Kino, und ich kann jedem nur empfehlen: Geht auch rein und guckt euch den Film an. Paranormal Activities.

Zwei von uns vier Leuten sind keine wirklichen Horrorfans, aber wir entschlossen uns doch, reinzugehen. Und es hat uns umgehauen. Trotz Blair Witch Project, The Ring und einigen wenigen weiteren Filmen, die uns bekannt waren, hat dieser doch noch etwas herausgekitzelt, das uns allen die Haare zu Berge stehen ließ. Wirklich atemberaubend.

Schon während des Films ein flaues Gefühl im Magen, man merkte auch die Anspannung und den erhöhten Blutdruck. Auch wenn man sich darauf vorbereitete, dass etwas passiert, passierte etwas anderes unerwartetes.

Empfehlung: Guckt ihn euch an, und zwar im Kino. Denn nur im Kino kommt das wichtigste richtig gut rüber: Der Sound. Der ist mit das wichtigste. Außerdem sollte man unbedingt darauf achten, keinen Ton zu sagen und zu hoffen, keine Laberköpfe um sich herumsitzen zu haben. Außerdem solltet ihr euch keinesfalls irgendwelche Trailer angucken oder Webseiten dazu zu besuchen, das würde die Überraschung evtl. verderben.

Ich habe mich gerade dabei ertappt, ohne Grund 2 Mal zur Tür geguckt zu haben, ob sie auch zu ist. Unglaublich.

Falls ihr ihn auch schon gesehen habt, wäre es toll wenn ihr eure Meinung hier kurz hinterlassen würdet.

Gute Nacht.

Written by Michael Kliewe

November 23rd, 2009 at 11:07 pm

Posted in Privates

Tagged with , ,