Symfony – Eine Volltextsuche muss her!

Nur wie? Eine Möglichkeit wäre es, dass Ganze, wie gewohnt, über die Datenbank laufen zu lassen. Voraussetzung dafür ist allerdings, dass die Datenbank Volltexindexierung unterstützt, wie etwa MySQL in MyISAM Tabellen. Ein solche Abhängigkeit würde die Symfony Datenbankabstraktion allerdings mit Füßen treten, also machte ich mich auf die Suche und wurde fündig.

Eine Entscheidung

Symfony selbst bietet an diversen Stellen Infos, von der selbst geschriebenen PHP basierten Suchmaschine, bis zum sfLucenePlugin. Nun heißt es, im mittlerweile in die Jahre gekommen Askeet Tutorial, Zend würde für sein Framework noch etwas länger brauchen. Seit der Veröffentlichung des Zend Frameworks in der Stable Version 1.0, kann davon natürlich keine Rede mehr sein. Da ich das Zend Framework sehr schätze – Warum? Verdammt starkes Argument, aber: Es kommt von Zend! – viel mir die Entscheidung leicht.

Das sfLucenePlugin arbeitet mit der Zend Search Lucene Klasse aus dem Zend Framework und stellte damit genug Anreiz für mich da, die Sache mal auszuprobieren. Eine Alternative zum Plugin, in dessen Beschreibung ausdrücklich auf das
Alpha Stadium hingewiesen wird, ist die manuelle Integration der Klasse aus dem Zend Framework. Den Weg dazu beschreibt Johannes Schmidt hier im t8d-Blog oder auch hier Dave Dash auf Englisch. Wohl der zuverlässigere Weg, mit dem ich mich auch noch beschäftigen werde, aber das Plugin sollte erst einmal seine Chance bekommen.

Woher kommt dieses „Lucene“ eigentlich?

Bei der Lucene Search Komponente des Zend Frameworks handelt es sich um eine in PHP5
implementierte Version der ursprünglich von Apache in Java programmierten Volltextsuchmaschine Lucene. Mehr Informationen zu diesem Ursprung bei Apache finden sich hier. Also, ziemlich starke Namen, die da auftauchen und nun endlich ran an den Speck!

Plugin Installation

Die Installation gestaltete sich gewohnt einfach.

  1. Installieren des Plugins:
    symfony plugin-install http://plugins.symfony-project.com/sfLucenePlugin
  2. Konfigurationdateien anlegen:
    symfony lucene-init myapp
  3. Symfony Cache leeren:
    symfony cc

Die grundlegende Konfiguration

Um eine erstes Ergebnis zu erzielen, habe ich mich einfach an die Konfigurationvorschläge auf der Plugin Seite gehalten. Das gesamte Plugin wird über die Datei search.yml im config Ordner des Projekts (z.B. projekt/config/search.yml) konfiguriert. Also, Datei geöffnet und folgendes rein geschrieben:

index:
  name: MyIndex
  encoding: UTF-8
  short_words: 2
  cultures: [en]

Der name des Indexes wird vom Plugin benötigt, aber scheint sonst keine sonderliche Bedeutung zu haben.

Unter encoding ist prinzipiell auch eine andere Kodierung möglich, in der Doku wird aber ausdrücklich darauf hingewiesen, dass UTF8 generell die beste Wahl darstellt.

Über short_words ist dem Plugin mitzuteilen, bei welcher Zeichenlänge es Wörter gar nicht erst in den Index aufnehmen soll. In diesem Fall also 2 Zeichen.

Ein großer Vorteil des Plugins: Es unterstützt die Internationalisierung bereits. So sind über den Parameter cultures die Sprachen anzugeben, für die ein Index erstellt werden soll. Zumindest eine Sprache ist hier Pflicht, sonst kann der Index nicht erstellt werden.

Infos zu weiteren Konfigurationmöglichkeiten finden sich auf der Projekt Seite.

Die Applikation konfigurieren

Nun geht es darum, dem Plugin zu sagen, welche Daten es in den Index aufnehmen soll. Ich entschied mich für die ORM-Layer Variante und habe dazu die search.yml Datei von eben (also im Beispiel projekt/config/search.yml) um den folgenden Bereich erweitert:

models:
  Post:
    fields:
      id: unindexed
      title:
        boost: 1.5
        type: text
      excerpt: text
      body: text
    description: body
    title: title

Im Bereich fields werden die einzelnen Felder angegeben, welche in den Index übernommen werden sollen. Genauere Infos zu den verwendeten Typen, finden sich hier in der Zend Dokumentation.

Zusätzlich ist es noch möglich, bestimmten Feldern eine höhere Gewichtung im Suchergebnis zu verschaffen. Im Beispiel geschehen beim Feld title, mit der Angabe boost: 1.5

Eigentlich ist die Konfiguration für das Modul damit abgeschlossen, da das Plugin eigentlich automatisch heraus finden soll, welches Feld als Titel und welches als Beschreibung in der Trefferliste angezeigt werden soll. Bei mir ging das nicht so wirklich und so habe ich mit den Angaben description: body und title: title angeben können, welche Felder verwendet werden sollen.

Routing der Applikation

Jetzt muss noch definiert werden, worauf in den späteren Ergebnislisten verlinkt werden soll. Hier kommt die search.yml Datei im Config Ordner der Applikation zum Einsatz. Der Eintrag:

models:
  Post:
    route: post/show?id=%id%

gibt den Pfad an, der bei einem Ergebnis für Post genutzt werden soll. %id% wird dabei automatisch durch die ID des Eintrages ersetzt. Voraussetzung dazu ist allerdings, dass sich die ID auch im Index befindet.

Einrichten Behavior

Nun muss das Model selbst noch so erweitert werden, dass bei Änderungen der Daten automatisch der Index aktualisiert wird. Zurzeit Arbeitet das Plugin leider nur mit Propel zusammen, Doctrine soll folgen. Die Anpassung der Model Klasse, in diesem Beispiel lib/model/Post.php, erfolgt über folgende Zeile, am Ende der Datei:

sfLucenePropelBehavior::getInitializer()->setupModel('Post');

Durch diesen Aufruf werden Behavior für Pre Save, Post Save, Pre Delete und Post Delete initialisiert. Mehr zum Thema Model Behavior findet sich hier in der Dokumentation, im Abschnitt „Using Model Behaviors“.

Ein kleines, aber überaus wichtiges Detail: Propel unterstützt in Standardkonfiguration keine Behavior. Daher muss in der Datei project/config/propel.ini der Wert von propel.builder.addBehaviors von false auf true gesetzt werden. Nach dem ändern dieses Wertes muss das Model mit einem symfony propel-build-model neu gebaut werden, sonst ist die Frustration groß… ich weiß von was ich spreche ;)

So, endlich: Die Volltextsuche!

Alles ist so weit konfiguriert und es kann an das Erstellen des Indexes gehen:

symfony lucene-rebuild myapp

Initialisiert den Index, wobei myapp mit dem Namen der Applikation ersetzt werden muss, für die der Index erstellt werden soll.

Lucene SuchmaskeNun fehlt nur noch die Oberfläche, um das Ganze auch zu testen. Hierzu bietet das Plugin bereits ein Standardmodul. Um diese zur Verfügung zu stellen, muss die settings.yml, im Config Ordner der Applikation, um folgende Zeilen erweitert werden, damit das mitgelieferte Modul des Plugins verfügbar ist:

all:
  .settings:
    enabled_modules: [default, sfLucene]

Ein Aufruf der Applikation, mit dem Modul sfLucene, zaubert jetzt auch die lang ersehnte Suchmaske auf den Bildschirm.

Keine Wildcards?!

Ok, eine Suche nach Packungen funktioniert einwandfrei, die Suche nach Packung führt jedoch zu keinem Ergebnis. Hm, dumm, aber, siehe da, nach der Zend Doku versteht die Suche durchaus Wildcards. Also nach Packung* gesucht und nach etwas Testen erst einmal dumm aus der Wäsche geguckt: Kein Ergebnis. Wem soll ich denn bitte diese Suche andrehen?

Die Lösung des Problems war dann ein wenig Tricky. Ich stieß auf eine Diskussion, in der es um die fehlende Wildcard Unterstützung geht, samt der Lösung des Problems!

Diese sieht wie folgt aus: Auch wenn Zend die Wildcards bereits in die Dokumentation übernommen hat, unterstützt die Suche diese in der Stable Version bisher noch nicht, im Nightly Build aber! Also, aktuelles Nightly Build runter geladen, die Search Komponente aus library/Zend/ kopiert und in den Ordner plugins/sfLucenePlugin/lib/vendor/Zend/ über die bestehenden Dateien kopiert. Und Zack, nach einem symfony lucene-rebuild: Die Suche nach Packung* führte zu dem gewünschten Ergebnis und ich konnte wieder aufatmen :)

Was soll nun werden, hm?

Ich könnte nun noch weiter ins Detail gehen, würde dabei aber letztlich nur noch das wiederholen, was eh schon auf der Projektseite beschrieben wird. Es gibt noch diverse Konfiguratonsmöglichkeiten wie Resulthighlighting, Stopwords, eigene spezielle Indexierung und so weiter, aber erst einmal habe ich meine Suche und will wissen wie sie tickt, aber ich sehe dem Ganzen optimistisch entgegen. Erster Eindruck: Ein starkes Plugin!

P.S.: Ich will in dem Blog eigentlich keine News bloggen, die bereits zu Hauf gebloggt wurden, aber wo ich schon mal dabei bin: Heute ist Symfony 1.0.8 erschienen! Die Änderungen gibt es hier.

3 thoughts on “Symfony – Eine Volltextsuche muss her!”

  1. Hallo,
    cooler Beitrag, Danke. Ich bin gerade dabei mich mit dem System von symfony vertraut zu machen, da ist jede Doku nützlich, da es noch nicht soviel Beiträge existieren. Naja jedenfalls schonmal ein kleiner Einblick auf was man sich so einlässt…
    Gruss, mach weiter so!

Kommentare sind geschlossen.