27.7. Extensibilité

27.7.1. Analyse textuelle

La classe Zend_Search_Lucene_Analysis_Analyzer est utilisée par l'indexeur pour fractionner les champs texte du document en token.

Les méthodes Zend_Search_Lucene_Analysis_Analyzer::getDefault() et Zend_Search_Lucene_Analysis_Analyzer::setDefault() sont utilisées pour récupérer et définir l'analyseur par défaut.

Ainsi, vous pouvez assigner votre propre analyseur de texte, ou alors en choisir un dans le jeu des analyseurs prédéfinis : Zend_Search_Lucene_Analysis_Analyzer_Common_Text et Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive (par défaut). Les deux interpètrent le token comme une séquence de lettres. Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive convertit les tokens en minuscule.

Pour passer d'un analyseur à un autre, utilisez le code suivant :

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

Zend_Search_Lucene_Analysis_Analyzer_Common a été créée pour être un parent de tous les analyseurs définis par l'utilisateur. L'utilisateur ne devrait définir que les méthodes reset() et nextToken(), qui prennent une chaîne de caractères issu du membre $_input et les tokens un par un (null indique la fin du flux).

La méthode nextToken() devrait appliquer la méthode normalize() à tous les tokens. Cela permettra d'utiliser des filtres sur les tokens avec votre analyseur.

Voici un exemple d'Analyseur personnalisé, qui prend pour termes des mots contenant des chiffres :

Exemple 27.1. Analyseur de texte personnalisé

<?php
/** Voici un analyseur de texte personnalisé, qui traite les mots contenant des chiffres comme un seul terme */

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

class Mon_Analyseur 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 Mon_Analyseur());
			    


27.7.2. Filtrer les tokens

L'analyseur Zend_Search_Lucene_Analysis_Analyzer_Common offre aussi un méchanisme de filtrage des tokens.

La classe Zend_Search_Lucene_Analysis_TokenFilter est une classe abtraite pour ces filtres. Elle peut être utilisée comme parent pour vos propres filtres.

Les filtres personnalisé doivent implémenter la méthode normalize() qui peut transformer les tokens d'entrée ou signaler les tokens qui doivent être évités.

Il existe trois fltres déjà définis dans le sous-paquet Analysis :

  • le filtre Zend_Search_Lucene_Analysis_TokenFilter_LowerCase ;

  • le filtre Zend_Search_Lucene_Analysis_TokenFilter_ShortWords ;

  • le filtre Zend_Search_Lucene_Analysis_TokenFilter_StopWords.

Le filtre LowerCase est déjà utilisé par l'analyseur par défaut Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive.

ShortWords et StopWords peuvent être utilisé avec les analyseurs déjà définis ou les vôtres comme ceci :

<?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);
    		

Le constructeur Zend_Search_Lucene_Analysis_TokenFilter_StopWords prend un tableau de "stop-words" comme entrée. Mais les "stop-words" peuvent être chargés à partir d'un fichier :

<?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);
    		

Les fichiers devraient des fichiers textes communs avec un mot dans chaque chaîne. Le caractère "#" déclare un chaîne en commentaire.

Le constructeur Zend_Search_Lucene_Analysis_TokenFilter_ShortWords a un paramètre facultatif. Il s'agit de la taille limite d'un mot. La valeur par défaut est 2.

27.7.3. Algorithme de calcul du score

Le score d'une requête q pour un document d est défini comme ceci :

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) - un facteur de score basé sur la fréquence des termes ou des phrases dans le document.

idf(t) - Zend_Search_Lucene_Search_SimilaritySimilarity::tf($term, $reader) - un facteur de score pour un terme simple, pour l'index specifié.

getBoost(t.field in d) - facteur de boost pour le champ du terme.

lengthNorm($term) - La valeur de normalisation pour un champ, à partir du nombre total de termes contenus dans ce champ. Cette valeur est stockée dans l'index. Ces valeurs, avec fieldBoost, sont stockées dans un index et multipliés dans les scores de hits, sur chaque champ, par le code de recherche.

Les recherches dans des champs plus longs sont moins précises, donc les mises en oeuvre de cette méthode rendent d'habitude des valeurs plus petites quand numTokens est grand et plus grandes valeurs quand numTokens est petit.

coord(q,d) - Zend_Search_Lucene_Search_Similarity::coord($overlap, $maxOverlap) - un facteur de score basé sur la fraction de tous les termes de requête qu'un document contient.

La présence d'une large portion de termes de requête indique une meilleure correspondance avec la requête, ainsi les implémentations de cette méthode retournent de plus grandes valeurs quand le ratio entre ces paramètres est élevé, et des plus petites valeurs quand le ratio entre eux est faible.

queryNorm(q) - la valeur de normalisation pour une requête, à partir de la somme des carrés des poids de chacun des termes de la requête. Cette valeur est ensuite multipliée par le poid de chaque terme de requête.

Cela n'affecte pas le ranking, mais tente plutôt de rendre comparable des scores provenant de requêtes différentes.

L'algorithme de scoring peut-être personnalisé en définissant votre propre classe Similarity. Pour ce faire étendez la classe Zend_Search_Lucene_Search_Similarity comme montré ci-dessous, et utilisez ensuite la méthode Zend_Search_Lucene_Search_Similarity::setDefault($similarity); pour la définir par défaut.

<?php
class MaSimilarite 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);
    }

    /**
     * It's not used now. Computes the amount of a sloppy phrase match,
     * based on an edit distance.
     */
    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 MaSimilarite();
Zend_Search_Lucene_Search_Similarity::setDefault($mySimilarity);
    	

27.7.4. Conteneurs de stockage

Une classe abstraite Zend_Search_Lucene_Storage_Directory définit les fonctionnalités relatives aux répertoires.

Le constructeur de Zend_Search_Lucene utilise soit une chaine soit un objet Zend_Search_Lucene_Storage_Directory comme entrée.

La classe Zend_Search_Lucene_Storage_Directory_Filesystem implémente les fonctionnalités de répertoires pour le système de fichier.

Si une chaîne est utilisée comme entrée dans le constructeur de Zend_Search_Lucene, alors le lecteur d'index (objet Zend_Search_Lucene) le traite comme un chemin du système de fichier, et instantie de lui-même un objet Zend_Search_Lucene_Storage_Directory_Filesystem.

Vous pouvez faire votre propre implémentation de répertoires en étendant la classe Zend_Search_Lucene_Storage_Directory.

Les méthodes de Zend_Search_Lucene_Storage_Directory :

<?php
abstract class Zend_Search_Lucene_Storage_Directory {
/**
 * Closes the store.
 *
 * @return void
 */
abstract function close();


/**
 * Creates a new, empty file in the directory with the given $filename.
 *
 * @param string $name
 * @return void
 */
abstract function createFile($filename);


/**
 * Removes an existing $filename in the directory.
 *
 * @param string $filename
 * @return void
 */
abstract function deleteFile($filename);


/**
 * Returns true if a file with the given $filename exists.
 *
 * @param string $filename
 * @return boolean
 */
abstract function fileExists($filename);


/**
 * Returns the length of a $filename in the directory.
 *
 * @param string $filename
 * @return integer
 */
abstract function fileLength($filename);


/**
 * Returns the UNIX timestamp $filename was last modified.
 *
 * @param string $filename
 * @return integer
 */
abstract function fileModified($filename);


/**
 * Renames an existing file in the directory.
 *
 * @param string $from
 * @param string $to
 * @return void
 */
abstract function renameFile($from, $to);


/**
 * Sets the modified time of $filename to now.
 *
 * @param string $filename
 * @return void
 */
abstract function touchFile($filename);


/**
 * Returns a Zend_Search_Lucene_Storage_File object for a given $filename in the directory.
 *
 * @param string $filename
 * @return Zend_Search_Lucene_Storage_File
 */
abstract function getFileObject($filename);

}
    		

La méthode getFileObject($filename) de la classe Zend_Search_Lucene_Storage_Directory retourne un objet Zend_Search_Lucene_Storage_File.

La classe abstraite Zend_Search_Lucene_Storage_File implémente l'abstraction des fichiers et de la lecture des fichiers d'index.

Vous devez bien sur étendre la classe Zend_Search_Lucene_Storage_File pour votre implémentation de répertoires.

Seules deux méthodes de la classe Zend_Search_Lucene_Storage_File doivent être surchargée dans votre implémentation:

<?php
class MyFile extends Zend_Search_Lucene_Storage_File {
    /**
     * Sets the file position indicator and advances the file pointer.
     * The new position, measured in bytes from the beginning of the file,
     * is obtained by adding offset to the position specified by whence,
     * whose values are defined as follows:
     * SEEK_SET - Set position equal to offset bytes.
     * SEEK_CUR - Set position to current location plus offset.
     * SEEK_END - Set position to end-of-file plus offset. (To move to
     * a position before the end-of-file, you need to pass a negative value
     * in offset.)
     * Upon success, returns 0; otherwise, returns -1
     *
     * @param integer $offset
     * @param integer $whence
     * @return integer
     */
    public function seek($offset, $whence=SEEK_SET) {
        ...
    }

    /**
     * Read a $length bytes from the file and advance the file pointer.
     *
     * @param integer $length
     * @return string
     */
    protected function _fread($length=1) {
        ...
    }
}