Indexation et recherche simple sur Elasticsearch et Symfony

Share Button

Read the English version

Cet article présente comment configurer et rechercher avec Elasticsearch sur un projet Symfony :


Jeu de données utilisé pour cet article

Pour réaliser notre recherche, nous allons créer des entités “basiques” que nous pourrons indexer : des articles de blog.

<?php

namespace Obtao\BlogBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use FOS\ElasticaBundle\Configuration\Search;

/**
 * Article
 *
 * @ORM\Table(name="article")
 * @Search(repositoryClass="Obtao\BlogBundle\Entity\SearchRepository\ArticleRepository")
 * @ORM\HasLifecycleCallbacks
 * @ORM\Entity(repositoryClass="Obtao\BlogBundle\Entity\Repository\ArticleRepository")
 */
class Article
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    protected $id;

    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=250, nullable=false)
     */
    protected $title;

    /**
     * @var string
     *
     * @ORM\Column(type="text", nullable=false)
     */
    protected $content;

    /**
     * @ORM\Column(name="created_at", type="datetime")
     */
    protected $createdAt;

    /**
     * @ORM\Column(name="published_at", type="datetime", nullable=true)
     */
    protected $publishedAt;

    /**
    * @ORM\PrePersist
    */
    public function prePersist()
    {
        $this->createdAt = new \DateTime();
    }

    public function isPublished()
    {
        return (null !== $this->getPublishedAt());
    }

    // others getters and setters

}

Créer et configurer le fichier de mapping

Pour réaliser l’indexation, vous devez déclarer le format de vos documents. Le nom des champs, leur type, les filtres appliqués à l’indexation et à la recherche, … 

Nous n’allons parler ici que du mapping des entités et pas de la configuration de l’index en lui-même. Pour plus d’informations sur la manière d’indexer et rechercher les documents, lisez notre article sur le sujet.

Dans le config.yml, ajouter l’import d’un fichier fos_elastica.yml qui contiendra toute la configuration, et ajouter dans le parameters.yml les paramètres requis (host et port). 

# app/config/config.yml

imports:
    - { resource: fos_elastica.yml }

# app/config/parameters.yml.dist

parameters:
    elastic_host : localhost
    elastic_port : 9200

# app/config/fos_elastica.yml

fos_elastica:
    clients:
        default: { host: %elastic_host%, port: %elastic_port% }
    indexes:
        obtao_blog:
            client: default
            types:
                article:
                    mappings:
                        id:
                            type: integer
                        createdAt :
                            type : date
                        publishedAt :
                            type : date
                        published : 
                            type : boolean
                        title : ~
                        content : ~
                    persistence:
                        driver: orm
                        model: Obtao\BlogBundle\Entity\Article
                        finder: ~
                        provider: ~
                        listener: ~

  • Clients : Définit les clients disponibles pour la recherche (ici un seul client “default”)
  • Indexes : Nom de l’index dans Elasticsearch. On peut l’apparenter au nom d’une base en SQL. Il va regrouper des types de document au même titre qu’une base de données regroupe des tables.
  • Types : Déclare les différents types de documents qui seront indexés. Nous n’aurons dans cet exemple qu’un seul type : l’article. Un type peut être apparenté à une table de base de données.
    • mapping : Déclarations des valeurs présentes sur le document. Cela peut être assimilé à un champ de base de données SQL
    • persistence : Définit la manière dont FOSElasticaBundle va indexer vos documents en fonction de vos entités Symfony.
      • Driver : Driver à utiliser (ici, comme souvent, ORM)
      • Model : Permet de définir un document Elasticsearch à partir d’une entité symfony. C’est la manière la plus simple d’indexer les documents : en utilisant les modèles déjà définis pour votre application. 
      • Finder : Interface de recherche. Nous utilisons pour l’instant l’interface par défaut. C’est le service qui vous permet de lancer une recherche vers Elasticsearch
      • Provider : Interface d’indexation. Nous utilisons pour l’instant l’interface par défaut. C’est le service qui vous permet de définir “la manière” d’indexer dans Elasticsearch
      • Listener : Liste les listeners pour lesquels l’indexation intervient. (Par défaut : insert, update, delete. Cas utilisé majoritairement)

Vous connaissez donc maintenant la configuration “de base”, et la définition de vos documents Elasticsearch en fonction de vos entités Doctrine.

Indexer / Voir le résultat de son indexation

Le moment est venu d’insérer en base quelques données de test, puis de remplir votre index Elasticsearch via la commande : 

$ app/console fos:elastica:populate

Cette commande utilise le provider, et boucle sur tous vos objets doctrine présents en base pour remplir l’index. 

La méthode est simple :  pour chaque entrée définie dans le mapping lors du paramétrage (fos_elastica.yml) le getter sera appelé et la valeur retournée insérée côté Elasticsearch.

Vous pouvez donc insérer des données calculée, n’existant pas côté doctrine, simplement en définissant un getter (comme ici le published/isPublished).  

Une fois l’indexation terminée, vous pouvez visualiser vos documents dans l’onglet “Navigateur” du plugin head (http://localhost:9200/_plugin/head/)

Grâce au listener, à chaque insert/update doctrine, votre document sera mis à jour dans l’index Elastisearch.

Créer un objet de recherche Symfony

Nous allons créer un objet de recherche sur lequel sera basé le formulaire qui nous permettra de faciliter la gestion de la recherche.

Cet objet contiendra diverses propriétés qui seront associés à nos critères de filtres, de tri et de pagination. Voici à quoi il pourrait ressembler :

<?php

namespace Obtao\BlogBundle\Model;

use Symfony\Component\HttpFoundation\Request;

class ArticleSearch
{
    // begin of publication range
    protected $dateFrom;

    // end of publication range
    protected $dateTo;

    // published or not
    protected $isPublished;

    protected $title;

    public function __construct()
    {
        // initialise the dateFrom to "one month ago", and the dateTo to "today"
        $date = new \DateTime();
        $month = new \DateInterval('P1Y');
        $date->sub($month);
        $date->setTime('00','00','00');

        $this->dateFrom = $date;
        $this->dateTo = new \DateTime();
        $this->dateTo->setTime('23','59','59');
    }

    public function setDateFrom($dateFrom)
    {
        if($dateFrom != ""){
            $dateFrom->setTime('00','00','00');
            $this->dateFrom = $dateFrom;
        }

        return $this;
    }

    public function getDateFrom()
    {
        return $this->dateFrom;
    }

    public function setDateTo($dateTo)
    {
        if($dateTo != ""){
            $dateTo->setTime('23','59','59');
            $this->dateTo = $dateTo;
        }

        return $this;
    }

    public function clearDates(){
        $this->dateTo = null;
        $this->dateFrom = null;
    }

    public function getDateTo()
    {
        return $this->dateTo;
    }

    public function getIsPublished()
    {
        return $this->isPublished;
    }

    public function setIsPublished($isPublished)
    {
        $this->isPublished = $isPublished;

        return $this;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }
}

Dans cet objet, nous avons défini nos critères de recherche. Il ne s’agit pas forcément de reprendre tous les champs de l’objet à rechercher tels quels. Par exemple, nous ne voulons pas autoriser la recherche sur la propriété “content”, c’est pourquoi elle ne fait pas partie de notre objet. A l’inverse, la propriété “publishedAt” de l’objet Article, devient “dateFrom” et “dateTo” dans l’objet ArticleSearch car nous voulons rechercher les Articles publiés entre deux dates données. Nous avons également mis une propriété “isPublished” car nous voulons seulement retrouver les articles publiés ou non. Nous aurions pu ajouter deux propriétés “createdFrom” et “createdTo” pour retrouver tous les articles créés entre deux dates.

En fait, les filtres “date” (“dateFrom”/”dateTo”) et “isPublished” sont incompatibles : spécifier une date implique qu’on recherche des articles publiés (puisqu’on filtre sur la date de publication). Donc spécifier une plage de date et chercher les articles non publiés ne renverra jamais rien. Ce n’est donc pas un super exemple mais le but n’est pas de faire l’appli du siècle mais de montrer un peu ce qu’on peut faire.

Vous pouvez évidemment rajouter d’autres critères selon vos envies et besoins.

Créer le formulaire de recherche associé

Cet objet sera associé à un formulaire qui nous permettra de choisir nos critères. Voici ce formulaire (du grand classique comme vous le voyez), qu’il faudra déclarer comme service :

<?php

namespace Obtao\BlogBundle\Form\Type;

use Obtao\BlogBundle\Model\ArticleSearch;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class ArticleSearchType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title',null,array(
                'required' => false,
            ))
            ->add('dateFrom', 'date', array(
                'required' => false,
                'widget' => 'single_text',
            ))
            ->add('dateTo', 'date', array(
                'required' => false,
                'widget' => 'single_text',
            ))
            ->add('isPublished','choice', array(
                'choices' => array('false'=>'non','true'=>'oui'),
                'required' => false,
            ))
            ->add('search','submit')
        ;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        parent::setDefaultOptions($resolver);
        $resolver->setDefaults(array(
            // avoid to pass the csrf token in the url (but it's not protected anymore)
            'csrf_protection' => false,
            'data_class' => 'Obtao\BlogBundle\Model\ArticleSearch'
        ));
    }

    public function getName()
    {
        return 'article_search_type';
    }
}

Rechercher avec Elasticsearch

Maintenant, nous pouvons implémenter notre fonction de recherche. Le principe est simple : dans un contrôleur, on instancie le formulaire de recherche et, en cas de soumission, on appelle la méthode search(ArticleSearch $articleSearch) de la classe ArticleRepository. En effet, afin de bien faire les choses et de garder notre code bien organisé, nous mettons tout ce qui concerne la construction de requêtes de recherche dans une classe dédiée. Souvenez-vous, nous avons comparé la recherche dans Elasticsearch avec une requête en base de données. Vous ne mettriez jamais de requête Doctrine dans un contrôleur, pas vrai? Ben là c’est pareil (ceux qui ont répondu “oui” peuvent courir nu dans des orties pendant 20 minutes).

Voici le contrôleur (simplifié) :

<?php

namespace Obtao\BlogBundle\Controller;

use Obtao\BlogBundle\Form\Type\ArticleSearchType;
use Obtao\BlogBundle\Model\ArticleSearch;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class ArticleController extends Controller
{

    public function listAction(Request $request)
    {
        $articleSearch = new ArticleSearch();

        $articleSearchForm = $this->get('form.factory')
            ->createNamed(
                '',
                'article_search_type',
                $articleSearch,
                array(
                    'action' => $this->generateUrl('obtao-article-search'),
                    'method' => 'GET'
                )
            );
        $articleSearchForm->handleRequest($request);
        $articleSearch = $articleSearchForm->getData();
        
        $elasticaManager = $this->container->get('fos_elastica.manager');
        $results = $elasticaManager->getRepository('ObtaoBlogBundle:Article')->search($articleSearch);

        return $this->render('ObtaoBlogBundle:Article:list.html.twig',array(
            'results' => $results,
            'articleSearchForm' => $articleSearchForm->createView(),
        ));
    }
}

Cela devrait fonctionner à quelques détails près. Enfin, voici la méthode search qui construit la requête pour Elasticsearch. Si vous lisez cet article, c’est surement pour ça que vous êtes là, et merci d’être resté(e?).

<?php

namespace Obtao\BlogBundle\Entity\SearchRepository;

use FOS\ElasticaBundle\Repository;
use Obtao\BlogBundle\Model\ArticleSearch;

class ArticleRepository extends Repository
{
    public function search(ArticleSearch $articleSearch)
    {
        // we create a query to return all the articles
        // but if the criteria title is specified, we use it
        if ($articleSearch->getTitle() != null && $articleSearch != '') {
            $query = new \Elastica\Query\Match();
            $query->setFieldQuery('article.title', $articleSearch->getTitle());
            $query->setFieldFuzziness('article.title', 0.7);
            $query->setFieldMinimumShouldMatch('article.title', '80%');
            //
        } else {
            $query = new \Elastica\Query\MatchAll();
        }
         $baseQuery = $query;

        // then we create filters depending on the chosen criterias
        $boolFilter = new \Elastica\Filter\Bool();

        /*
            Dates filter
            We add this filter only the getIspublished filter is not at "false"
        */
        if("false" != $articleSearch->getIsPublished()
           && null !== $articleSearch->getDateFrom()
           && null !== $articleSearch->getDateTo())
        {
            $boolFilter->addMust(new \Elastica\Filter\Range('publishedAt',
                array(
                    'gte' => \Elastica\Util::convertDate($articleSearch->getDateFrom()->getTimestamp()),
                    'lte' => \Elastica\Util::convertDate($articleSearch->getDateTo()->getTimestamp())
                )
            ));
        }

        // Published or not filter
        if($articleSearch->getIsPublished() !== null){
            $boolFilter->addMust(
                new \Elastica\Filter\Terms('published', array($articleSearch->getIsPublished()))
            );
        }

        $filtered = new \Elastica\Query\Filtered($baseQuery, $boolFilter);

        $query = \Elastica\Query::create($filtered);

        return $this->find($query);
    }

}

Voilà. Je vous épargne le template qui n’a pas grand intérêt (vous pouvez le trouver )mais nous avons maintenant une liste d’articles avec un petit formulaire de recherche qui nous permet de filtrer les résultats.

Dans le prochain article, nous verrons comment paginer cette liste et y ajouter des tris (en plus de nos filtres).

Pour en savoir plus sur Elasticsearch dans un projet Symfony (mais pas que), lisez nos autres articles sur le sujet

Share Button

40 thoughts on “Indexation et recherche simple sur Elasticsearch et Symfony

  1. Merci beaucoup pour ce tutoriel, vraiment très utile.
    Je suis entrain de le mettre en place également, mais j’ai un problème. En effet, lorsque que je définis le client avec “default: { host: localhost, port: 9200 }” j’obtiens un message d’erreur quand je lance la commande :
    >> php app/console fos:elastica:populate
    [Elastica\Exception\Connection\HttpException]
    Couldn’t connect to host, ElasticSearch down?

    Avez vous déjà rencontré ce problème. Je l’ai solutionner en mettant 80 en valeur de port mais je ne suis pas convaincu que ce soit la bonne méthode. De plus quand j’applique cette modification et que je veux effectuer une recherche j’obtiens un résultat vide…
    $itemType = $this->get(‘fos_elastica.index.website.item’);

    $resultSet = $itemType->search($term);
    foreach($resultSet as $res){
    print_r($res);
    }
    d’ou mon hésitation sur le changement de port.
    Merci par avance pour votre aide

    • Bonjour et merci pour votre commentaire !

      Cette erreur survient lorsque votre serveur elasticsearch n’est pas démarré (ou inaccessible)

      Pour le démarrer si vous êtes sous linux :
      “sudo service elasticsearch start”
      (de manière générale, pour connaitre le status du service : “sudo service elasticsearch status”)

      Lorsque vous changez de port pour le port 80, toutes vos requêtes vers ElasticSearch sont envoyées vers votre serveur Apache (qui répond, vous n’avez donc plus d’erreur. Mais vos requêtes ne sont pas traitées par ElasticSearch, Apache ne sait rien en faire)

      Le serveur ElasticSearch répond bien par défaut sur le port 9200. Une fois le service démarré vous pourrez vous en rendre compte en allant à cette adresse : http://localhost:9200/

      François

  2. Bonjour Francois,
    Merci pour votre retour. J’avais effectivement une erreur avec le serveur elasticsearch que j’avais mal installé. Je passe par une VM linux sur un mac et la VM était capricieuse :).
    Le problème est résolu.
    Merci encore

  3. Bonjour,
    j’ai suivis votre tuto il est super bien, est il possible d’utiliser jquery pour donner une suggestion des titres possible, si oui si vouz pouvez m’aider je serai trop reconnaisson,
    pour le fos_elestica.yml je l’ai changé un petit peut:

    fos_elastica:
        clients:
            default: { host: %elastic_host%, port: %elastic_port% }
        indexes:
           moteur:
                client: default
                finder: ~
                settings:
                   index:
                     analysis:
                       analyzer:
                         custom_search_analyzer:
                          type: custom
                          tokenizer: standard
                          filter   : [standard, lowercase, asciifolding]
                         custom_index_analyzer:
                          type:  custom
                          tokenizer: standard
                          filter   : [standard, lowercase, asciifolding, custom_filter]
    
                       filter:
                        custom_filter:
                           type: edgeNGram
                           side: front
                           min_gram: 1
                           max_gram: 20
                types:
                    article:
                        mappings:
                            title :   { search_analyzer: custom_search_analyzer, index_analyzer: custom_index_analyzer, type: string }
                            id:
                                type: integer
                            createdAt :
                                type : date
                            publishedAt :
                                type : date
                            published : 
                                type : boolean
    
                            content : ~
                        persistence:
                            driver: orm
                            model: Obtao\BlogBundle\Entity\Article
                            elastica_to_model_transformer:
                                service: obtao.transformers.elastica.article
                            finder: ~
                            provider: ~
                            listener: ~
    • Bonjour et merci pour le commentaire.
      Implémenter une telle fonctionnalité est très simple sur le principe : à chaque fois que l’utilisateur appuie sur une touche, le client lance un appel ajax au serveur qui va chercher des titres correspondants. Le retour de l’a requête ajax est ensuite affiché sous le champ afin que l’utilisateur puisse sélectionner un titre dans la liste proposée.
      Sinon il existe des plugins tout faits pour cela (ça s’appelle de l’autocomplete). Par exemple : http://www.devbridge.com/projects/autocomplete/jquery/

  4. bonjour,
    est ce que vous pouvez m’aider j’ai essayé votre example ça marche bien maintenant j’ai passé à mon projet et j’ai cette erreur Error: Call to undefined method FOS\ElasticaBundle\Repository::search()

    Cordialement

    • Bonjour,
      La méthode search n’existe pas par défaut, il faut la créer vous même et implémenter votre propre logique.
      Dans notre exemple ci-dessus, il a 2 choses à faire :

      • Spécifier à FOSElasticaBundle que votre entité va utiliser un dépôt personnalisé. Cela se fait avec l’annotation @Search(repositoryClass=”Obtao\BlogBundle\Entity\SearchRepository\ArticleRepository”) dans le fichier Article.php (ne pas oublier l’import de la classe Search)
      • Créer la classe ArticleRepository et y implémenter la méthode Search

      Tout le code se trouve dans l’article, bon courage.

      • Bonjour,
        J’ai le même problème aussi et j’arrive pas à le résoudre alors que j’ai suivi vos instructions.
        SVP si vous pouvez bien m’aider.
        Merci.

          • L’erreur: FatalErrorException: Error: Call to undefined method FOS\ElasticaBundle\Repository::search()
            J’ai suivi la solution que vous avez proposé mais j’ai toujours la même erreur.

          • Il vous manque l’utilisation de l’annotation @Search(…) dans votre entité. Regardez bien la classe Article dans notre exemple, au début du post.

  5. Bonjour,

    merci pour tout le tuto qui est très clair et précis ! J’aurais une petite question, l’utilisation d’elasticsearch nécessite l’installation d’elasticSearch sur le serveur.

    Sur un serveur mutualisé type OVH où on ne peut qu’envoyer nos vendors par FTP, il n’est donc pas possible d’utiliser ElasticSearch ?

    Cordialement

    • Bonjour,

      Effectivement, impossible d’installer Elasticsearch sur un mutualisé d’OVH !
      Il faut passer aux offres dédiées ou VPS (Qui sont relativement peu cheres chez OVH en ce moment) au moins pour y installer ES.

      François

  6. Bonjour,

    merci pour le tuto, je me suis demandé si vous pouvez m’aider en essayant votre example ça marche bien mais quand j’ai voulu créer mon formulaire dans une vue la soumission m’affiche seulement 10 résultats et ne sont pas les résultats attendu

    pour l’action

     public function listAction(Request $request)
        {
            $imageSearch = new ImageSearch();
    
            $request = $this ->get('request');
    
            $elasticaManager = $this->container->get('fos_elastica.manager');
    
            $results = $elasticaManager->getRepository('EcommerceBlogBundle:Image')->search($imageSearch);
               
            return $this->render(
                 "EcommerceBlogBundle:Default:list.html.twig",
                 array('results' => $results)
            );   
        }
    

    pour le formulaire

    et la vue pour l’affichage

    Results ({{ results|length }})

        
       {% for image in results %}
           {{ image.url }}
       {% endfor %}
    
    • Bonjour,

      Si des résultats sont affichés, on peut considérer que le cycle controller/formulaire/repository/vue fonctionne. Le fait qu’il n’y ait que 10 résultats est un comportement par défaut d’Elasticsearch. Si vous en voulez plus, il faut en demander plus. Du coup si je comprends bien, votre problème est que les résultats ne sont pas ceux attendus. Dans ce cas, pouvez vous en dire plus (exemple retourné, exemple attendu). Il se peut que ce soit un problème de configuration, ou bien votre filtre qui n’est pas bon. (d’ailleurs, vous n’avez pas copié le code du formulaire ni celui du repository)

  7. Bonjour,

    merci pour votre réponse je m’excuse j’ai cru copier le formulaire pour le repository:

    class ImageRepository extends Repository
    {
        public function search(ImageSearch $imageSearch)
        {
            /
            if ($imageSearch->getUrl() != '' && $imageSearch->getUrl() != null) {
                $query = new \Elastica\Query\Match();
            $query->setFieldQuery('image.url', $imageSearch->getUrl());
                $query->setFieldFuzziness('image.url', 0.7);
                $query->setFieldMinimumShouldMatch('image.url', '80%');
                
            }else {
                $query = new \Elastica\Query\MatchAll();
            }
             
            return $this->find($query);
        }
    

    et le formulaire:

    je crois pas qu’il soit un problème de configuration puisque j’ai essayé la configuration de cette article,aussi je n’ai pas utilisé un filtre

    Cordialement

  8. Bonsoir,
    en voyant une question sur l’autocompletion je crois qu’Elasticsearch utilise le complete suggester

  9. enfait je debute en symfony 2 , j’ai suivi ce tuto , mais j’ai cette probléme aprés avoir copier le controlleur
    ” Could not load type “article_search_type ” aidez moi svp ; merci

    • Bonjour, il faut déclarer votre formulaire en tant que service.
      En XML:

      <service id="obtao.form.type.article_search"
          class="Obtao\BlogBundle\Form\Type\ArticleSearchType">
          <tag name="form.type" alias="article_search_type" />
      </service>

      Ou en Yaml:

      services:
          obtao.form.type.article_search:
              class: Obtao\BlogBundle\Form\Type\ArticleSearchType
              tags:
                  - { name: form.type, alias: article_search_type }
      
  10. Bonjour j’ai suivi votre tuto j’arrive à la fin mais j ai un soucis

    Call to undefined method FOS\ElasticaBundle\Repository::search()

    j’ai donc creer mon repository ainsi qu’ajouter le @search de celui ci mais vous dites importer la classe search et la je ne vois pas du tout quoi importer

    il s’agit d’un use quelque chose mais je ne vois pas ce qu il faut mettre exactement.

    Merci de votre réponse.

    • La classe Search est celle de l’annotation que vous utilisez pour dire à FosElastica que vous voulez utiliser un Repository personnalisé.
      Dans notre exemple, le Use à utiliser est dans la classe Obtao\BlogBundle\Entoty\Article.php, en haut du fichier, ajouter l’import :

      use FOS\ElasticaBundle\Configuration\Search;

  11. Bonsoir
    Je vous remercie pour ce tutoriel très pertinent.
    Toutefois, après avoir reproduit votre exemple à la lettre et peuplé ma base de données, en effectuant le recherche, je n’ai aucun résultat, à chaque essai, j’ai toujours le message “Results (0) No result for this search”
    devrai je créer un autre template pour l’affichage des resultats??

    Merci pour vos précisions

    • Bonjour,
      Avez-vous installé le plugin Head ? Si oui, commencez par vérifier que vos données sont bien indexées (via l’onglet “navigateur”) et si c’est le cas, exécutez votre requête dans l’onglet “autres requêtes”. Supprimez les filtres un à un pour voir lequel empêche la requête de retourner des résultats.

  12. Bonsoir,

    Votre tutoriel est vraiment parfait. Par contre, j’ai une question : dans la barre de debug, je vois que Doctrine est appelé pour populer les objets je pense. Est-il nécessaire de faire ces requêtes ?

    L’intérêt d’utiliser ElasticSearch est de ne pas faire de requêtes Sql inutiles, et de tout récupérer du moteur de recherche, non ?

    • Bonjour,

      En fait l’intérêt d’Elasticsearch n’est pas forcément de se substituer à une base de données. Evidemment, ça peut l’être dans certains cas. Ce qu’il faut éviter, c’est le double travail (hydrater un objet avec Doctrine alors que les informations sont déjà fournies dans Elasticsearch).

      Dans le cas d’une recherche par exemple, le boulot d’Elasticsearch serait de retourner une série d’id. Vous recherchez les articles qui contiennent “css html”, il vous donne les id 3, 14 et 56 par exemple, et vous utilisez juste ces ids pour requêter dans les BDD et trouver les articles correspondant. Ainsi, les requêtes sont optimisées et très peu gourmandes (par rapport à un WHERE LIKE). Dans ce cas de figure, je ne trouve pas d’intérêt à tout stocker dans Elasticsearch.

      En revanche, si vous êtes dans un cas précis où vous voulez vraiment éviter d’appeler la BDD, il faut passer par l’index d’Elasticsearch et non pas par le finder :

      // Remplacer
      $results = $elasticaManager->getRepository('ObtaoBlogBundle:Article')->search($articleSearch);
      
      // par quelque chose comme
      $results = $this->get('fos_elastica.index.obtao_blog.article')->search($yourElasticsearchQuery)->getResponse()->getData();
      

      Je n’ai pas testé, j’espère que ça fonctionnera. Dites moi si vous avez dû faire des ajustement que je corrige pour les prochains lecteurs. Merci.

  13. Bonjour,

    Je suis tombé sur cet excellent article en cherchant une solution à mon porblème, et je vois que j’ai exactement le même cod que vous. Pourtant chez moi ça ne marche pas. J’essaye de récupérer des medias (entité doctrine indexée dans ES avec FOSElasticSearch). qui ont été publiés jusqu’à une date. La partie du code qui nous intéresse est la suivant, je ne mets pas tout, juste de quoi comprendre :

    use Elastica\Query as EQ;
    use Elastica\Filter as EF;

    $query = new EQ\MatchAll();
    $filters = new EF\Bool();

    if ($criteria->getDateEnd() !== null)
    {
    $filters->addMust(
    new EF\Range(‘media.publishedAt’, array (
    ‘gte’ => \Elastica\Util::convertDate(0),
    ‘lte’ => \Elastica\Util::convertDate($criteria->getDateEnd()->getTimestamp())
    ))
    );
    };

    Mais avec ou sans le ‘gte’, je n’ai aucun résultat. Sans la condition sur publishedAt j’ai tous mes médias.

    J’ai l’impression qu’il n’arrive pas à comparer les dates. Une idée ?

    • Bonjour,

      Peut être que cela vient de la manière dont sont indexées vos dates. Le code semble effectivement correct (en admettant qu’une autre condition que vous n’auriez pas mis dans votre commentaire ne filtre les résultats). Du coup, je me dis que soit $criteria->getDateEnd() ne renvoit pas un DateTime (mais à ce moment là, getTimestamp() renverrait une erreur), soit vos dates sont mal indexées. Vérifiez bien le “type:date”

  14. Bonjour ,
    Excelent article ! merci beaucoup.
    Une petite question , la visiblement vous faites une recherche que sur l entite article. Est il possible de faire une recherche sur plusieurs entites en meme temps?
    Je suis entrain de construire un site d annonce qui plein de dependance. Exemple :
    L article peux avoir 1 ou plusieurs images, et si c est un article voiture il a une entite articleVoit qui contient tous les details comme la marque le km…. Si c est un article appartement il a une autre entite nomme artileAppart mais l article n a pas d entite articleVoit bien entendu !
    Dois je faire plusieurs indexes differents (1 pour les voitures, 1 pour les appartements) sachant que l entite maitre est commune (article)?
    De plus dans ce fichier routing , comment faire par exemple pour lier l article et l’articleVoit?

    Merci pour votre future reponse !

    • Bonjour, pour la partie indexation, il vous faut effectivement indexer l’entité Article. Puisque les Voitures et Appartements en héritent, alors ce sera facile car les méthodes seront les mêmes.
      Pour retrouver l’objet lié, vous pouvez faire un truc du genre :

      public function getExtraEntity()
      {
          if ($this instanceof Voiture) {
              return $this->getArticleVoit();
          } elseif ($this instanceof Appartement) {
              return $this->getArticleAppart();
          } else {
              return null;
          }
      }

      Ce n’est pas propre à 100% mais ça règle facilement le problème tout en étant compréhensible. Cela vous permet aussi d’indexer des informations supplémentaires.
      Vous pourriez indexer par exemple article.extraEntity.info où la méthode getInfo() de ArticleVoit renvoit le nombre de kilomètres au compteur, et où getInfo() de ArticleAppart renvoit la surface en m2. Par exemple…

  15. Bonjour,

    J’essaye de reproduire votre article pour chercher dans une liste d’idées toutes celles qui correspondent a un thème donné. Les étapes suivantes seront de trouver toutes celles dont le titre, la description ou le contenu contiennent un mot.

    Sur le permier point, j’ai mis dans IdeeRepository le code suivant :

    public function search(IdeeSearch $ideeSearch)
    {
    $query_part = new \Elastica\Query\MatchAll();
    $filters = new \Elastica\Filter\Bool();

    if($ideeSearch->getTheme() !== null){
    $filters->addMust(
    new \Elastica\Filter\Term(array(‘theme’=>$ideeSearch->getTheme()->getNom()))
    );
    }

    $filtered = new \Elastica\Query\Filtered($query_part, $filters);
    return $this->find($filtered,50);
    }

    Il me retourne bien toutes mes idées dans le cas ou je n’ai pas de theme définis.
    Si j’ai un theme, il me retourne 0 élément.

    La requete elastic search générée est la suivante :

    Voyez-vous une raison que ca ne fonctionne pas ?
    Bien sur, aucune erreur n’est montrée, juste aucun résultats.

    Merci,
    Pierre

    • la requete en question (il y a un pb avec le filtre anti spam (il n aime pas C*lture)

      {“query”:{“filtered”:{“query”:{“match_all”:{}},”filter”:{“bool”:{“must”:[{"term":{"theme":"Transports"}}]}}}},”size”:50}

      Pierre

      • Bonjour,

        La requête devrait fonctionner. Avez-vous installé le plugin head? Si oui, vérifiez via l’onglet “navigateur” que votre contenu est bien indexé et que le terme “Transports” y figure bien (peut être faut il enlever la majuscule?

        • Bonjour,

          Désolé mais non, je n’ai jamais travaillé directement avec les highlights donc je n’ai pas d’idée sur la cause du problème.

  16. Bonjour,

    Merci pour vos tutos qui me permettent souvent de me sortir de situation parieuses !
    En revanche, j’ai pour cette fois une erreur dont je ne trouve pas l’origine.
    J’ai bien installer ES comme service, j’ai mis à jour mon fichier config.yml avec uniquement 1 entité pour le moment pour tester.
    Mais quand je fais un fos:elastica:populate j’obtiens une erreur : Maximum function nesting level of ’100′ reached, abording!
    Tout cela accompagné de dizaines de numéros de lignes faisant références à des fichiers de JMS\Serializer et FOL\ElasticaBundle.

    Merci d’avance pour votre aide qui m’est précieuse.

    • Cette erreur est due à xdebug. Enfin… il y a une erreur quelque part mais vous devriez désactiver xdebug le temps d’avoir un message plus parlant qui permettrait d’en savoir plus sur l’erreur d’origine.

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