[Symfony 2] Implémentation du moteur de recherche Zend Lucene

Dans l’article précédant, nous avons vu comment installer EWZSearchBundle, le bundle d’intégration de Zend Lucene. La documentation officielle de bundle est très pauvre, donc pour un newbie, c’est difficile de se trouver. Dans cet article, on va voir en détails comment utiliser ce bundle.

Indexation

Pour pouvoir utiliser le moteur de recherche, on doit construire en premier un fichier d’index qui contiendra les textes qui seront utilisés dans les recherches.

Le fichier index sera enregistré sous le path définis dans la configuration de bundle. L’indexation sera faite au fur et à mesure de l’enregistrement des données.

Création d’un index

Pour ce tutorial, on va utiliser comme exemple l’Entity Poste :

class Poste
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string $title
     *
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;

    /**
     * @var text $body
     *
     * @ORM\Column(name="body", type="text")
     */
    private $body;
}

L’indexation d’un post est simple : la clé primaire est stockée pour un référencement ultérieur lors de la recherche des postes et les colonnes title et body sont indexées, mais pas stockées dans l’index, car nous allons utiliser les objets réels pour afficher les résultats.

Donc à chaque fois qu’un post est créé, modifié ou supprimé, l’index doit être mis à jour.
Nous allons modifiers les actions createAction, updateAction et deleteAction de notre controlleur PosteController

Tout d’abort, nous ajoutons les indépendances suivante au début de PosteController.php :

use EWZ\Bundle\SearchBundle\Lucene\Document;
use EWZ\Bundle\SearchBundle\Lucene\Field;

Par la suite, nous ajoutons une méthode de création des indexes, elle sera utilisé dans les actions createAction et updateAction.

Zend Lucene a 5 façons pour indexer des champs :

  • Keyword : Le champ est stocké ET indexé. Cela signifie qu’il peut être aussi bien cherché dans l’index qu’affiché dans les résultats de la recherche.
  • UnIndexed : Le champ ne peut pas être utilisé dans la recherche. En revanche, il peut être retourné dans les résultats.
  • Text : Le champ est stocké, indexé et « tokenizé » (devisé en plusieurs mots).
  • Binary : Le champ n’est ni « tokenizé », ni indexé, mais il est stocké dans le but d’être retourné dans les résultats de recherche.
  • UnStored : Le champ est « tokenizé » et indexé, mais pas stocké dans l’index..

Dans notre exemple, nous allons utilisé, seulement « Keyword » pour stoker l’id du Post, et « UnStored » pour enregistrer « title » et « body » du post.

Voici la méthode de création d’index :

private function createIndex($post)
{
    $search = $this->get('ewz_search.lucene');

    $document = new Document();
    $document->addField(Field::Keyword('key', $post->getId()));
    $document->addField(Field::UnStored('title', $post->getTitle()));
    $document->addField(Field::UnStored('body', $post->getBody()));

    $search->addDocument($document);
    $search->updateIndex();
}

Comme on a déjà dit, on ajoute cette fonction dans createAction et updateAction après la validation de formulaire comme suit :

        if ($form->isValid()) {
            $this->createIndex($entity);
            $em->persist($entity);
            $em->flush();

             return $this->redirect($this->generateUrl('posts_show', array(
                        'id' => $entity->getId()
                     )));
            $query = $form->getData();

        }

Lancer une recherchet

Maintenant que nous avons tout mis en place, nous passons à l’implémentation de l’action recherche, la méthode $this->get(‘ewz_search.lucene’)->find(), interroge l’index pour trouver des mots correspondantes à notre mot cherchée :

    /**
     * 
     * @param \Symfony\Component\HttpFoundation\Request $request
     * @Route("/search", name="search_poste")
     * @Template()
     */
    public function searchAction(Request $request)
    {
        $form = $this->createFormBuilder()->add('search', 'text')->getForm();

        $posts = new \Doctrine\Common\Collections\ArrayCollection;

        if($request->getMethod() === "POST") {

            $form->handleRequest($request);

            $query = $form->getData();

            $results = $this->get('ewz_search.lucene')->find($query['search']);

            $em = $this->getDoctrine()->getManager();

            foreach($results as $hit)
            {
                $document = $hit->getDocument();
                $post = $em->getRepository('BlobBundle:Poste')->find($document->key);
                $posts->add($post);
            }
        }

        return array(
            "posts" => $posts,
            "form" => $form->createView()
         );
    }

Voici une simple Template twig pour l’action :

<form action="{{ path("search_poste") }}" method="post" {{ form_enctype(form) }}>
    {{ form_errors(form) }}
    {{ form_rest(form) }}
    <input type="submit" />
</form>

{% if posts.count > 0 %}
    <h1>Search result</h1>
    <table class="records_list">
        <thead>
            <tr>
                <th>Id</th>
                <th>Title</th>
                <th>Body</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
        {% for entity in posts %}
            <tr>
            <td><a href="{{ path('poste_show', { 'id': entity.id }) }}">{{ entity.id }}</a></td>
            <td>{{ entity.title }}</td>
            <td>{{ entity.body }}</td>
            <td>
                <ul>
                    <li>
                       <a href="{{ path('poste_show', { 'id': entity.id }) }}">show</a>
                    </li>
                    <li>
                        <a href="{{ path('poste_edit', { 'id': entity.id }) }}">edit</a>
                    </li>
                </ul>
            </td>
        </tr>
        {% endfor %}
        </tbody>
</table>
{% endif %}

Supprimer un index

On arrive à la fin, il nous reste que la mise à jours de l’index après la surpression d’un post, nous allons créer une methode deleteIndex:


    
    private function deleteIndex($post) 
    {
        $search = $this->get('ewz_search.lucene');

        $document = new Document();
        $document->addField(Field::Keyword('key', $post->getId()));

        $search->deleteDocument($document);
    }

Cet méthode sera appelé dans l’action deleteAction() juste après la suppression du poste :


        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $entity = $em->getRepository('BlobBundle:Poste')->find($id);

            if (!$entity) {
                throw $this->createNotFoundException('Unable to find Poste entity.');
            }

            $em->remove($entity);
            $em->flush();
            
            $this->deleteIndex($entity);
        }
Advertisements

Une réflexion sur “[Symfony 2] Implémentation du moteur de recherche Zend Lucene

  1. Cet article m’a beaucoup aidée, je cherchais un bundle qui ne necessitait pas d’installation serveur et vos explications sont assez claires.
    Merci!

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s