27.7. Erweiterbarkeit

27.7.1. Textanalyse

Die Zend_Search_Lucene_Analysis_Analyzer Klasse wird vom Indexer verwendet, um die Textfelder des Dokuments in Token aufzuteilen.

Die Methoden Zend_Search_Lucene_Analysis_Analyzer::getDefault() und Zend_Search_Lucene_Analysis_Analyzer::setDefault() werden verwendet, um den Standardanalysator zu erhalten oder zu festzulegen.

Deshalb kannst Du deinen eigene Textanalysator festlegen oder ihn aus den vordefinierten Analysatoren auswählen: Zend_Search_Lucene_Analysis_Analyzer_Common_Text und Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive (Standard). Beide interpretieren einen Token als eine Sequenz aus Buchstaben. Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive konvertiert Token in Kleinbuchstaben.

Um zwischen Analysatoren zu wechseln:

<?php
Zend_Search_Lucene_Analysis_Analyzer::setDefault(
    new Zend_Search_Lucene_Analysis_Analyzer_Common_Text());
...
$index->addDocument($doc);

Zend_Search_Lucene_Analysis_Analyzer_Common wurde als Vorgänger für alle benutzerdefinierten Analysatoren entwickelt. Benutzer sollten nur die reset() und nextToken() Methoden definieren, die einen String der $_input Eigenschaft nimmt und einen Token schrittweise zurück gibt (null gibt das Ende des Stroms an).

Die nextToken() Methode sollte die normalize() Methode auf jeden Token anwenden. Dies erlaubt die Verwendung von Tokenfiltern in deinem Analysator.

Hier ist ein Beispiel für einen eigenen Analysator, welcher Wörter mit Ziffern als Begriffe verwendet:

Beispiel 27.1. Eigener Textanalysator

<?php
/** Hier ist ein eigener Textanalysator, der Worte mit Ziffern als einen Begriff behandelt */


/** Zend_Search_Lucene_Analysis_Analyzer Hirarchie */
require_once 'Zend/Search/Lucene/Analysis/Analyzer.php';

class My_Analyzer extends Zend_Search_Lucene_Analysis_Analyzer_Common
{
    private $_position;

    /**
     * Reset token stream
     */
    public function reset()
    {
        $this->_position = 0;
    }

    /**
     * Tokenization stream API
     * Get next token
     * Returns null at the end of stream
     *
     * @return Zend_Search_Lucene_Analysis_Token|null
     */
    public function nextToken()
    {
        if ($this->_input === null) {
            return null;
        }

        while ($this->_position < strlen($this->_input)) {
            // skip white space
            while ($this->_position < strlen($this->_input) &&
                   !ctype_alnum( $this->_input[$this->_position] )) {
                $this->_position++;
            }

            $termStartPosition = $this->_position;

            // read token
            while ($this->_position < strlen($this->_input) &&
                   ctype_alnum( $this->_input[$this->_position] )) {
                $this->_position++;
            }

            // Empty token, end of stream.
            if ($this->_position == $termStartPosition) {
                return null;
            }

            $token = new Zend_Search_Lucene_Analysis_Token(
                                      substr($this->_input,
                                             $termStartPosition,
                                             $this->_position - $termStartPosition),
                                      $termStartPosition,
                                      $this->_position);
            $token = $this->normalize($token);
            if ($token !== null) {
                return $token;
            }
            // Continue if token is skipped
        }

        return null;
    }
}

Zend_Search_Lucene_Analysis_Analyzer::setDefault(
    new My_Analyzer());

?>

27.7.2. Filtern von Tokens

Der Zend_Search_Lucene_Analysis_Analyzer_Common Analyzer bietet auch einen Mechanismus zum Filtern von Tokens.

Die Zend_Search_Lucene_Analysis_TokenFilter Klasse ist eine abstrakte Ebene für solche Filter. Sie sollte vor den eigenen Filtern verwendet werden.

Eigene Filter müssen die normalize() Methode implementieren, welche den Eingabe Token verändern oder signalisieren, dass der Token übersprungen werden kann.

Es gibt bereits drei im Analyzer Unterpaket definierte Filter:

  • Zend_Search_Lucene_Analysis_TokenFilter_LowerCase Filter.

  • Zend_Search_Lucene_Analysis_TokenFilter_ShortWords Filter.

  • Zend_Search_Lucene_Analysis_TokenFilter_StopWords Filter.

Der LowerCase Filter wird bereits standardmäßig für den Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive Analyzer verwendet.

ShortWords und StopWords können mit bereits definierten oder einem eigenen Analyzer wie folgt verwendet werden:

<?php
$stopWords = array('a', 'an', 'at', 'the', 'and', 'or', 'is', 'am');
$stopWordsFilter = new Zend_Search_Lucene_Analysis_TokenFilter_StopWords($stopWords);

$analyzer = new Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum_CaseInsensitive();
$analyzer->addFilter($stopWordsFilter);

Zend_Search_Lucene_Analysis_Analyzer::setDefault($analyzer);

<?php
$shortWordsFilter = new Zend_Search_Lucene_Analysis_TokenFilter_ShortWords();

$analyzer = new Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum_CaseInsensitive();
$analyzer->addFilter($shortWordsFilter);

Zend_Search_Lucene_Analysis_Analyzer::setDefault($analyzer);
?>

Der Zend_Search_Lucene_Analysis_TokenFilter_StopWords Konstruktor nimmt ein Array mit Stopwörtern als Eingabe entgegen. Aber Stopwörter können auch aus einer Datei geladen werden:

<?php
$stopWordsFilter = new Zend_Search_Lucene_Analysis_TokenFilter_StopWords();
$stopWordsFilter->loadFromFile($my_stopwords_file);

$analyzer = new Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum_CaseInsensitive();
$analyzer->addFilter($stopWordsFilter);

Zend_Search_Lucene_Analysis_Analyzer::setDefault($analyzer);

Die Datei sollte eine normale Textdatei mit einem Wort pro Zeile sein. '#' markiert eine Zeile als Kommentar.

Der Zend_Search_Lucene_Analysis_TokenFilter_ShortWords Konstruktor hat ein optionales Argument. Es ist das Limit für die Wortlänge. Der Standardwert ist 2.

27.7.3. Algorithmen für Punktwertermittlung

Der Punktwert einer Abfrage q für das Dokument d ist wie folgt definiert:

score(q,d) = sum( tf(t in d) * idf(t) * getBoost(t.field in d) * lengthNorm(t.field in d) ) * coord(q,d) * queryNorm(q)

tf(t in d) - Zend_Search_Lucene_Search_Similarity::tf($freq) - ein Punktwertfaktor, der auf der Häufigkeit des Begriffes oder der Phrase innerhalb des Dokuments basiert.

idf(t) - Zend_Search_Lucene_Search_SimilaritySimilarity::tf($term, $reader) - ein Punktwertfaktor für einen einfachen Begriff eines spezifischen Indizes.

getBoost(t.field in d) - Verstärkungsfaktor für das Begriffsfeld.

lengthNorm($term) - der Normalisierungswert für ein Feld, der die Gesamtzahl der Begriffe innerhalb eines Fields enthält. Dieser Wert wird im Index abgelegt. Diese Wert werden zusammen mit dem Verstärkungsfaktor im Index abgelegt und vom Suchcode für alle Treffer eines Feldes zu Punktwerten multipliziert.

Treffer in längeren Feldern sind weniger präzise, so dass Implementierungen dieser Methode normalerweise kleinere Werte zurückgeben, wenn numTokens groß ist, und größere Werte, wenn numTokens klein ist.

coord(q,d) - Zend_Search_Lucene_Search_Similarity::coord($overlap, $maxOverlap) - ein Punktwertfaktor, der auf dem Anteil aller Abfragebegriffe basiert, die ein Dokument enthält.

Das Vorhandensein eines grossen Teils der Abfragebegriffe gibt einen besseren Treffer für die Abfrage an, so dass Implementierungen dieser Methode normalerweise größere Werte zurückgeben, wenn das Verhältnis zwischen diesen Parametern groß ist, und kleinere Werte, wenn es klein ist.

queryNorm(q) - der Normalisierungswert für eine Abfrage, welcher die Summe der quadrierten Gewichtungen jedes Begriffes eine Abfrage enthält. Dieser Wert wird für das Gewicht jedes Abfragebegriffes multipliziert. term.

Dieses wirkt sich nicht auf die Reihenfolge ist, versucht aber, die Punktwerte für verschiedenen Abfragen vergleichbar zu machen.

Algorithmen für Punktwertermittlung können durch die Definition einer eigenen Ähnlichkeitsklasse angepasst werden. Hierfür muss die Zend_Search_Lucene_Search_Similarity Klasse wie unten angegeben erweitert werden und dann die Zend_Search_Lucene_Search_Similarity::setDefault($similarity); Methode für das Setzen des Standards verwendet werden.

<?php

class MySimilarity extends Zend_Search_Lucene_Search_Similarity {
    public function lengthNorm($fieldName, $numTerms) {
        return 1.0/sqrt($numTerms);
    }

    public function queryNorm($sumOfSquaredWeights) {
        return 1.0/sqrt($sumOfSquaredWeights);
    }

    public function tf($freq) {
        return sqrt($freq);
    }

    /**
     * Es wird jetzt nicht verwendet. Berechnet den Wert eines Treffers 
     * für eine ungenauen Phrasenanfrage.
     */
    public function sloppyFreq($distance) {
        return 1.0;
    }

    public function idfFreq($docFreq, $numDocs) {
        return log($numDocs/(float)($docFreq+1)) + 1.0;
    }

    public function coord($overlap, $maxOverlap) {
        return $overlap/(float)$maxOverlap;
    }
}

$mySimilarity = new MySimilarity();
Zend_Search_Lucene_Search_Similarity::setDefault($mySimilarity);

?>

27.7.4. Storage Container

Eine abstrakte Klasse Zend_Search_Lucene_Storage_Directory definiert Funktionalitäten für Verzeichnisse.

Der Zend_Search_Lucene Konstruktur verwendet als Eingabe entweder einen String oder ein Zend_Search_Lucene_Storage_Directory Objekt.

Die Zend_Search_Lucene_Storage_Directory_Filesystem Klasse implementiert Verzeichnisfunktionalitäten für das Dateisystem.

Wenn ein String als Eingabe für den Zend_Search_Lucene Konstruktur verwendet wird, behandelt der Indexleser (das Zend_Search_Lucene Objekt) es wie einen Dateipfad und instanziiert ein Zend_Search_Lucene_Storage_Directory_Filesystem Objekt.

Du kannst deinen eigenen Verzeichnisimplementation durch die Erweiterung der Zend_Search_Lucene_Storage_Directory Klasse definieren.

Zend_Search_Lucene_Storage_Directory Methoden:

<?php

abstract class Zend_Search_Lucene_Storage_Directory {
/**
 * Schließt den Speicher
 *
 * @return void
 */
abstract function close();


/**
 * Erstellt im Verzeichnis eine neue, leere Datei mit dem übergebenen Dateinamen $filename.
 *
 * @param string $name
 * @return void
 */
abstract function createFile($filename);


/**
 * Entfernt eine vorhande Datei $filename aus dem Verzeichnis.
 *
 * @param string $filename
 * @return void
 */
abstract function deleteFile($filename);


/**
 * Gibtv true zurück, wenn eine Datei mit dem übergebenen Dateinamen $filename existiert
 *
 * @param string $filename
 * @return boolean
 */
abstract function fileExists($filename);


/**
 * Gibt die länge eine Datei $filename im Verzeichnis zurück
 *
 * @param string $filename
 * @return integer
 */
abstract function fileLength($filename);


/**
 * Gibt den UNIX Zeitstempel für die letzte Änderung der Datei $filename zurück.
 *
 * @param string $filename
 * @return integer
 */
abstract function fileModified($filename);


/**
 * Benennt eine vorhandene Datei im Verzeichnis um.
 *
 * @param string $from
 * @param string $to
 * @return void
 */
abstract function renameFile($from, $to);


/**
 * Ändert die Änderungstzeit der Datei $filename auf jetzt um
 *
 * @param string $filename
 * @return void
 */
abstract function touchFile($filename);


/**
 * Gibt ein Zend_Search_Lucene_Storage_File Objekt für den Dateinamen $filename aus dem 
 * Verzeichnis zurück.
 *
 * @param string $filename
 * @return Zend_Search_Lucene_Storage_File
 */
abstract function getFileObject($filename);

}

?>

Die getFileObject($filename) Methode der Zend_Search_Lucene_Storage_Directory Klasse gibt ein Zend_Search_Lucene_Storage_File Objekt zurück.

Die abstrakte Klasse Zend_Search_Lucene_Storage_File implementiert einfache Funktionen für Dateiabstraktion und das Lesen von Indexdateien.

Du musst außerdem Zend_Search_Lucene_Storage_File für deine Verzeichnisimplementation erweitern.

Nur zwei Methoden der Zend_Search_Lucene_Storage_File Klasse müssen in deiner Implementation überladen werden:

<?php

class MyFile extends Zend_Search_Lucene_Storage_File {
    /**
     * Setzt den Indikator für die Dateiposition rückt den Dateizeiger
     * voran. Die neue Position, gemessen in Bytes vom Dateianfangm 
     * wird erreicht durch das Hinzufügen eines Versatzes zu der
     * angegebenen Position. Dessen Werte sind wie folgt definiert: 
     * SEEK_SET - Setze die Position auf den Versatz.
     * SEEK_CUR - Setze die Position auf die aktuelle Position plus Versatz.
     * SEEK_END - Setze die Position aufs Dateisende plus Versatz. (Um den 
     * Zeiger auf eine Position vor dem Dateiende zu bewegen, übergebe einen
     * negativen Wert als Versatz.)
     * Bei Erfolg wird 0, andernfalls -1 zurückgegeben
     *
     * @param integer $offset
     * @param integer $whence
     * @return integer
     */
    public function seek($offset, $whence=SEEK_SET) {
        ...
    }

    /**
     * Lese $length Bytes aus der Datei und setze den Dateizeiger vor.
     *
     * @param integer $length
     * @return string
     */
    protected function _fread($length=1) {
        ...
    }
}

?>