Indexation avancée avec FOSElasticabundle (ModelToElasticaTransformer)

Share Button

Read the English version

La configuration XML du FOSElasticaBundle est très performante. Il existe quelques rares cas ou cela ne s’avère pas suffisant.

Pour résoudre ce problème, FOS permet de surcharger la transformation entre un Objet Doctrine (ORM ou ODM) et un Document ElasticSearch : le ModelToElasticaTransformer.

Quand utiliser le ModelToElasticaTransformer

Dans la majorité des cas, ce problème peut être résolu en créant un ou plusieurs getter spécifiques (voir Indexation et recherche simple avec Elasticsearch et Symfony).
Mais il est cependant possible que la complexité de votre problème vous pousse à personnaliser totalement la transformation.
Nous montrons rapidement dans cet article comment procéder.
Comme le cas présenté ici n’a que peu d’intérêt dans le cadre de notre projet de blog bac à sable, nous n’avons pas mis les sources sur le dépôt Github.

Note : Alors que l’article Elasticsearch et Symfony : Hydrater son objet expliquait comment personnaliser la transformation d’un document Elasticsearch en objet Doctrine, cet article présente l’opération inverse : transformer un (ou plusieurs) objet Doctrine en un document Elasticsearch.

La première chose à faire est de créer un service associé :

    <! -- services.xml -->
    <service id="obtao.transformers.model.article" class="Obtao\BlogBundle\Transformer\ArticleToElasticaTransformer" />

Création du Transformer

Nous créons ensuite la classe. Pour coder le transformer nous avons choisi d’aplatir l’objet à l’aide d’un tableau.
Il est aussi envisageable de s’inspirer du ModelToElasticaAutoTransformer ou d’utiliser le propertyAccessor pour automatiser le traitement.

<?php

namespace Obtao\BlogBundle\Transformer;

use Elastica\Document;
use FOS\ElasticaBundle\Transformer\ModelToElasticaTransformerInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

class ArticleToElasticaTransformer implements ModelToElasticaTransformerInterface
{
    /**
     * Transforme un article en object elasticsearch avec les clés requises
     *
     * @param article $article the object to convert
     * @param array  $fields the keys we want to have in the returned array
     *
     * @return Document
     **/
    public function transform($article, array $fields)
    {
        $identifier = $article->getId();

        $authors = array();

        // aplatis les nested "auteurs"
        // il s'agit d'un exemple pour le besoin de ce post. En fait, nous aurions
        // pu (et dû) créer une propriété "flattenAuthor" dans la conf d'Elasticsearch
        // puis créer la méthode getFlattenAuthor dans Article.php avec le code ci-dessous
        foreach($article->getAuthors() as $author){
            $nestedAuthor = array(
                'firstname' => $author->getFirstname(),
                'surname' => $author->getSurname(),
                'id' => $author->getId(),
            );
            $authors[] = $nestedAuthor;
        }

        // ici nous formatons la date, encore une fois, cela devrait être fait dans le getter
        $publishedAt = $article->getPublishedAt();
        $publishedAt = $publishedAt ? $publishedAt->format('c'):null;

        $values = array(
            'id' => $article->getId(),
            'category' => $article->getCategory()->__toString(),
            'createdAt' => $article->getCreatedAt()->format('c'),
            'content' => $article->getContent(),
            'publishedAt' => $publishedAt,
            'published' => $article->isPublished(),
            'tags' => $article->getTags(),
            'title' => $article->getTitle(),
            'authors' => $authors,
        );

        // Crée le document à indexer
        $document = new Document($identifier,$values);
        $document->setParent($article->getCategory()->getId());

        return $document;
    }
}

La dernière étape est de déclarer le Transformer dans la configuration du FOSElastica

#config/fos_elastica.yml

fos_elastica:
    indexes:
        obtao_blog:
            types:
                article:
                    persistence:
                        model_to_elastica_transformer:
                            service: obtao.transformers.model.article

Lors de votre indexation, le Transformer sera appelé et votre objet sera toujours transformé grâce à cette logique.

Share Button

6 thoughts on “Indexation avancée avec FOSElasticabundle (ModelToElasticaTransformer)

  1. Salut !

    Je viens de découvrir ce blog et je le trouve vraiment cool ! J’utilise le couple Symfony2 / ElasticSearch dans beaucoup de mes projets perso depuis 2 ans et j’ai également mis ca en place a mon boulot. Je vais donc suivre ce blog de très prés ;-)

    Juste une question en ce qui concerne l’indexation. Pour un de mes besoins, j’ai besoin de créer un index / mois pour plus de performance (d’après les conseils des dev d’Elastic search lors de la formation que j’ai faite sur Melbourne). Vu qu’il m’a été impossible de faire ca avec FosElastica, j’ai cree des scripts d’indexation a la main (commande SF2) et je run ca dans un crontab tous les jours (donc pas de sync en temps reel mais c’est pas bien grave). Connaitrais-tu un moyen de faire ca proprement avec Fos ? en gros j’ai besoin de créer des index dynamiquement a la place de créer des index static venant de fichiers de config. Mes index comporte bien sur l’année et le mois concerné like: entityname_201405.

    • Bonjour,

      Je n’ai jamais utilisé d’index dynamique. Je me dis qu’il très probablement possible d’indexer tes objets comme tu l’as commencé. Pour cela, tu devras rajouter votre nouvel index dynamique via l’IndexManager de FOS (que tu devras peut être surcharger pour ajouter une méthode addIndex()). Ensuite, il suffit de s’inspirer de la commande PopulateCommand de FOS, mais c’est surement ce que tu as déjà fait.
      Par contre, si le fichier de configuration de contient pas les nouveaux index, je me dis que cela peut poser problème par la suite. Je ne sais pas comment tu as géré ça. Que faire si on veut changer la conf d’indexation ou celle de recherche? Si tu veux changer de provider ou de transformer?

      Une autre solution serait de générer la configuration du nouvel index. Comme ça, le fonctionnement de l’ElasticaBundle ne change pas. Je ne sais pas quelle est la meilleure solution entre les deux. Je te laisse choisir entre les besoins du projet (budget, besoin d’évolution, …)

  2. Salut,

    En fait je n’utilise pas du tout FosElastica pour mes index dynamiques (ni pour l’indexation, ni pour le requetage). J’utilise FosElastica pour les objets qui nécessite l’utilisation des trigger de FosElastica (très pratique d’ailleurs). Pour mon script d’indexation, j’ai cree un mapping dynamique en fonction de ce que je récupère de mon objet en question avec l’em et la méthode getClassMetadata().

    Je n’ai pas regardé comment marche le script d’indexation de FOS, mais mes index dynamique sont deja en très grand nombre (quasiment 50) donc je me vois mal générer a la volée la config pour chacun d’entre eux. Je perds en flexibilité certes, mais ca fit parfaitement mon besoin pour ce projet. Si ca t’intéresse je peux écrire un article pour ton blog pour expliquer ma démarche.

  3. Salut,

    tout d’abord merci pour ce post très intéressant (ainsi que les autres posts!).

    Par contre, je me demande s’il n’y aurait pas une coquille dans le transformer:
    ******************
    $authors = array();

    // aplatis les nested “auteurs”
    // il s’agit d’un exemple pour le besoin de ce post. En fait, nous aurions
    // pu (et dû) créer une propriété “flattenAuthor” dans la conf d’Elasticsearch
    // puis créer la méthode getFlattenAuthor dans Article.php avec le code ci-dessous
    foreach($article->getAuthors() as $author){
    $nestedAuthor = array(
    ‘firstname’ => $author->getFirstname(),
    ‘surname’ => $author->getSurname(),
    ‘id’ => $author->getId(),
    );
    $authors[] = $author;
    }
    ******************

    $authors[] = $author; ?? C’était pas plutot $authors[] = $nestedAuthor; ?

    De plus, après le foreach, $authors n’est plus du tout utilisé, et j’avoue que je ne comprends pas très bien pour le coup.

    Merci d’avance

    • Bonjour,

      Désolé pour la réponse tardive et merci d’avoir relevé ces erreurs. J’avoue que ce post date un peu et ma mémoire étant ce qu’elle est, je ne suis pas très sûr du résultat.
      Néanmoins, j’ai mis à jour le post avec ce qu’il aurait dû être (à mon avis). En tout cas, ça me parait plus logique comme ça, que ce que qui était avant.

      J’espère que ça aura corrigé vos problèmes.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Protected by WP Anti Spam