SPIP-Contrib

SPIP-Contrib

عربي | Deutsch | English | Español | français | italiano | Nederlands

288 Plugins, 197 contribs sur SPIP-Zone, 190 visiteurs en ce moment

Accueil > Navigation > Recherche > Indexer > Indexer : La boucle SPHINX

Indexer : La boucle SPHINX

24 janvier 2017 – par cam.lafit, Fil, Matthieu Marcillaud, RastaPopoulos – 15 commentaires

8 votes

Avant de commencer :
Pour comprendre l’intérêt du plugin, nous invitions à lire l’article suivant Indexer : Introduction. La configuration et l’installation du serveur Sphinx sont expliquées dans l’article Indexer : Installation et Configuration ; l’usage de la recherche dans Indexer : Rechercher. Le paramétrage de l’indexation dans Indexer : Indexation.

Comment construire ses boucles SPHINX

Sur la page des résultats de recherche, on distinguera en général :
— la boucle principale, qui affiche les résultats de la recherche
— le rappel des éléments de la requête
— le nombre de résultats et les outils de tri
— les suggestions
— la pagination
— les facettes.

Le plugin Indexer offre des briques pour construire tous ces éléments en fonction des spécificités de votre système d’indexation.

À noter : Ce plugin est exploité sur de grosses bases de contenus, parfois réparties sur plusieurs sites SPIP, ce qui explique certains choix de structuration des données. Par conséquent, les résultats d’une recherche peuvent ne pas figurer dans la base de données du site que l’on est en train de consulter. Ceci implique que tous les éléments nécessaires à leur affichage (par exemple : titre, date, mots-clés, auteur, résumé, URL du contenu et URL du logo) doivent être stockés dans l’index — on n’ira pas faire de boucles SPIP à l’intérieur des boucles SPHINX.

La boucle principale

La boucle principale est celle qui va afficher les résultats de la recherche, sous forme de blocs avec titre et lien, et parfois un extrait où les mots de la requête sont surlignés (balise #SNIPPET.

Cette boucle doit prévoir deux grandes familles de critères :

1. quels sont les éléments de l’URL que l’on va transformer en filtres pour limiter le contenu (par date par auteur). Ce sont des critères de la forme
{filter #ENV*{auteur}, 'IN(properties.authors, @valeurs)', 'LENGTH(properties.authors) = 0'}

Cette syntaxe un peu compliquée se lit comme suit : « si l’URL contient une précision &auteur=Toto, ajouter un critère de recherche Sphinx demandant à ce que la propriété indexée properties.authors contienne la chaîne "Toto" ». La partie qui vient après la virgule correspond au cas particulier : « si l’URL contient une précision &auteur=-, limiter la recherche aux articles ayant properties.authors vide ».

Ces critères sont détaillés ci-dessous, dans la partie « filtres ».

2. Quelles facettes faut-il demander à Sphinx de nous renvoyer.

Ces critères ne servent pas à filtrer les résultats, mais permettent de demander à Sphinx de calculer les facettes qui permettront d’affiner par la suite la recherche. En règle générale, chaque filtre défini au point 1 sera accompagné par une facette équivalente. Mais ce n’est pas une obligation technique, et il est possible de prévoir un jeu limité de facettes, et des critères de filtrage qui seraient activés autrement que par des facettes (par exemple des cases à cocher programmées de manière classique).

Ces critères sont de la forme {facet auteur, properties.authors ORDER BY COUNT(*) DESC}.

On prendra l’habitude d’écrire d’abord la liste des filtres, puis la listes de facettes.

Le rappel des éléments de la requête

En général en tête de la page de recherche, on présente un formulaire de recherche qui rappelle au moins les termes de la requête qui vient d’être effectuée. Ce formulaire peut être incomplet, mais il est très important qu’il rappelle tous les éléments qui ont permis de construire la requête.

Ainsi par exemple si l’utilisatrice a saisi une requête sur les mots (full-texte) « Cheval » et la facette « Année : 2015 », il faut que ces deux éléments soient rappelés dans ce bloc. On se basera sur les paramètres de l’URL pour savoir quoi afficher. Chacun des paramètres qui peuvent constituer un filtre doit y figurer.

Par exemple, pour la facette « par année », on écrira au minimum :
[<p>Année: (#ENV{annee})</p>]

Mais ce rappel est aussi un endroit idéal pour ajouter l’interaction « supprimer la facette d’année », ce qui fait qu’on indiquera plutôt :
[<p><a href="[(#SELF|parametre_url{annee,''})]">x</a> Année: (#ENV{annee})</p>]

Le code se complique un peu, mais l’interface devient plus utilisable. Si on a beaucoup de filtres possibles, c’est un peu répétitif, et on peut passer par une boucle DATA pour lister les différents paramètres, tous traités de la même manière (cf. l’exemple dans content/sphinx.html).

Pour le full texte, par convention (hors cas particuliers), l’input texte libre correspondant est toujours présent, soit sous la forme du #FORMULAIRE_RECHERCHE{#SELF}, soit directement sous la forme :
<input type=search name=recherche value="[(#ENV{recherche}|attribut_html)]" placeholder="Saisir une requête…">

Ces éléments de rappel doivent impérativement se trouver en dehors de la boucle de recherche, de manière à apparaître dans tous les cas, que la recherche ait donné ou non des résultats.

Le nombre de résultats et les outils de tri

Dans la partie optionnelle avant la boucle principale, on va gérer l’affichage du nombre de résultats :
<h2>#GRAND_TOTAL documents trouvés (#TOTAL_BOUCLE affichés)</h2>

On peut aussi proposer ici [<p>(#SPHINX_MESSAGE)</p>] qui contiendra d’éventuelles suggestions d’orthographe.

On peut afficher le nombre de résultats trouvés, en sachant que, comme SPHINX coupe systématiquement à 1000 résultats, il faut éventuellement prévoir le cas où ce nombre est atteint.

[(#SPHINX_META|table_valeur{total}|=={1000}|?{'+ de '}) ][(#SPHINX_META|table_valeur{total}) ]résultats

Au même endroit on peut ajouter des infos de débogage, mais il n’est pas utile de les laisser en production : code technique de la requête Sphinx, et temps mis à répondre.

[<div><tt>(#SPHINX_QUERY|htmlspecialchars)</tt></div>]
requête effectuée en [(#SPHINX_META|table_valeur{time}|mult{1000})] ms

C’est ici aussi qu’on peut ajouter des boucles secondaires qui iront par exemple chercher les auteurs ou les mots-clés correspondant à le requête, de manière à suggérer un filtrage pertinent. Cette recherche peut être faite soit dans l’index sphinx, si vous avez indexés ces objets, soit avec une boucle de recherche SPIP normale (les tables auteurs et mots-clés étant en général plus petites que les tables articles, on n’a pas le même souci de performance). C’est ce que fait Le Monde diplomatique en proposant « Auteurs trouvés : le sous-commandant Marcos » lorsque l’on recherche le mot « Marcos ».

Les outils de tri en général sont par date et par pertinence ; il est bien sûr possible d’ajouter des tris sur d’autres critères en fonction des besoins (prix, notes, places disponibles…).

En termes de code, pas de surprise : il s’agit d’un critère SPIP classique dans la boucle SPHINX :
{par #GET{tri}}{inverse #GET{sens_tri}}

Pour l’interface, il faut élucider ce que l’utilisatrice a demandé ou pas, ce qu’on propose par défaut, etc.

<aside class="tri">
        [(#ENV{order}|match{date}|?{
                [(#SET{tri,date})]
                [(#SET{sens_tri,-1})]
                résultats triés par date
        })]
        <div>
        [(#GET{tri}|?{
                <a href="[(#SELF|parametre_url{order,''})]">trier par pertinence</a>
        ,
                <a href="[(#SELF|parametre_url{order,date})]">trier par date</a>
        })]
        </div>
</aside>

La pagination

C’est du SPIP parfaitement standard : un critère {pagination 10} et une balise [<div class="pagination">(#PAGINATION)</div>] dans la partie optionnelle après de la boucle.

Les facettes

C’est l’apport le plus remarquable du moteur SPHINX : à chaque requête, il permet d’associer des facettes, qui représentent les dimensions secondaires associées à cette requête précise.

Ainsi par exemple si on demande des facettes d’auteurs, d’année et de mots-clés, SPHINX renverra un tableau de trois séries de facettes. La série des facettes d’auteurs indiquera jusqu’à 20 auteurs liés aux mots saisis dans la recherche, et pour chacun de ces auteurs, le nombre d’articles qui correspondent à cette saisie et à cet auteur.

Le tri des auteurs ou mots-clés est fait en général en fonction du nombre de résultats (mais on peut préférer par ordre alphabétique : cela se décide dans l’écriture du critère qui crée la facette). Le tri des dates est en général par ordre alphabétique (trier par nombre de résultats donne un affichage confus).

Les facettes permettent à la fois de 1) montrer quels sont les contextes dans lesquelles un mot ou une expression "full texte" apparaissent dans l’ensemble de la base (par exemple : quels sont les auteurs qui parlent le plus souvent de SPIP ?) ; et 2) dans le même temps, de créer des suggestions d’affinage de la recherche, par simple clic sur les mots en question, qui se transformeront en filtres.

Le code des facettes figure dans le squelette listes/sphinx_facettes.html et est inclus dans la partie optionnelle après de la boucle SPHINX par l’appel suivant :
[(#INCLURE{fond=liste/sphinx_facettes,facets=#SPHINX_FACETS,env})]

***

Des filtres spécifiques à des cas courants de comparaisons sont pré-programmés, ce qui évite de devoir connaître et écrire soi-même le code des tests.

Filtre pour un champ n’ayant qu’une valeur (mono-valué)

{filtermono test, champ, valeur(s)[, comparaison]}

Le test en premier permet de passer n’importe quelle valeur fixe ou dynamique (avec filtre ou pas) qui permettra de dire si on va ajouter le filtre ou pas. Le cas courant est de mettre la valeur d’un paramètre de l’URL, et s’il est présent et rempli, on ajoute le filtre.

La comparaison est optionnelle, et vaut "=" par défaut.

Le champ de valeur peut être une liste de plusieurs valeurs, et dans ce cas le test sera un "OU" sur chacune des comparaisons !

Exemples

  1. // Les documents publiés par défaut, sinon ceux du statut demandé
  2. {filtermono #ENV{statut,publie}, properties.statut, #ENV{statut,publie}}
  3. // Les documents de 2014 ou 2013
  4. {filtermono #ENV{annee}, year(date), #LISTE{2014,2013}}
  5. // Les documents ayant au moins #ENV{favs} partages
  6. {filtermono #ENV{favs}, length(properties.share), #ENV{favs}, '>='}

Télécharger

Filtre pour un champ JSON ayant plusieurs valeurs (multi-valué)

{filtermultijson test, champ, valeur(s)}

Mêmes principes que pour le critère précédent sauf que le critère cherche si les valeurs font partie du tableau "champ" (qui doit donc être une liste de plusieurs valeurs).

Si on donne plusieurs valeurs, le critère fera un "ET" entre les tests. Si l’une de ces valeurs est elle-même un tableau, le critère fera un "OU" (avec la commande Sphinx IN).

-  Si les valeurs sont #LISTE{mot1, mot2, mot3} : ça cherchera les documents qui ont mot1 ET mot2 ET mot3.
-  Si les valeurs sont #LISTE{mot1, #LISTE{mot2, mot3}} : ça cherchera les documents qui ont mot1 ET (mot2 OU mot3).

Exemples

  1. // Un auteur précis parmi ceux du document
  2. {filtermultijson #ENV{auteur}, properties.authors, #ENV{auteur}}
  3. // Les documents ayant tous les tags demandés, par ex si tags[]=truc&tags[]=bidule
  4. {filtermultijson #ENV{tags}, properties.tags, #ENV{tags}}

Télécharger

Filtre de distance

{filterdistance test, point1, point2, distance[, comparaison[, nom du champ]]}

Ce critère sélectionne uniquement les réponses qui font que la distance entre le point 1 et le point 2 correspond à la comparaison demandée avec la distance passée en paramètre.

La comparaison est optionnelle est vaut "<=' par défaut.
Le nom de la distance calculée est optionnelle et vaudra par défaut 'distance_0' pour la première distance demandée, puis 'distance_1', etc.

Ce paramètre permet de maîtriser le nom afin de pouvoir plus facilement
demander un tri par le nom voulu, et récupérer la valeur avec une balise.

Exemple

  1. // Tous les documents qui sont à moins de 5km de Bordeaux, avec comme nom #DISTANCE
  2. #SET{bordeaux, #ARRAY{lat, 44.83717, lon, -0.57403}}
  3. #SET{point_document, #ARRAY{lat, properties.geo.lat, lon, properties.geo.lon}}
  4. {filterdistance , testok, #GET{bordeaux}, #GET{point_document}, 5000, '<=', distance}

Télécharger

Tri des résultats

Comme dans les boucles SPIP habituelles, on peut utiliser le critère {par champ} pour trier les résultats, y compris avec les propriétés JSON {par properties.objet}.

Options pour les requêtes

{option nom, valeur}

Exemple

  1. // Modifier la pondération des champs lors de la recherche libre
  2. {option field_weights, "(title=10, content=5)"}

Télécharger

Un exemple de boucle

 Les criteres de boucle

  • index
  • recherche
  • filter
  • facet
  • pagination

Les balises

  • #SPHINX_META
  • #SPHINX_MESSAGE
  • #SPHINX_QUERY
  • #SPHINX_FACETS
  • #SNIPPET
  • #SUMMARY
  • #PROPERTIES
  • #DATE

Dernière modification de cette page le 25 janvier 2017

Retour en haut de la page

Vos commentaires

  • Le 22 janvier à 16:45, par tcharlss En réponse à : Indexer : La boucle SPHINX

    Hop, dans ma lancée, un autre bug rencontré avec le critère {filtermono}. Soit la boucle :

    1. <BOUCLE_test(SPHINX)
    2. {filtermono #ENV{id_objet}, properties.id_objet, #ENV{id_objet}}
    3. >

    Télécharger

    Quand il y a un id_objet dans l’environnement, ça ne marche pas.

    Requête SQL :

    1. SELECT *, (properties.id_objet=230) AS mono_0 FROM truc WHERE (mono_0=1)

    Erreur SQL :

    1. ERROR 1064 (42000): INDEX truc: parse error: equal operation applied TO part string operands

    En revanche, avec le critère {filtermultijson}, ça fonctionne bien :

    1. <BOUCLE_test(SPHINX)
    2. {filtermultijson #ENV{id_objet}, properties.id_objet, #LISTE{#ENV{id_objet}}}
    3. >

    Télécharger

    Requête SQL :

    1. SELECT *, (IN(properties.id_objet, 230)) AS multi_0 FROM truc WHERE (multi_0=1)
    • Le 22 janvier à 16:52, par Maïeul En réponse à : Indexer : La boucle SPHINX

      Merci ! J’ai exactement le même problème (avec id_rubrique).

      Mais le truc bizarre est que cela ne le fait que sur le serveur distant, pas sur le site local, où ma requete fonctionne parfaitement.

      Ca dépend peut être de la version de SPHINX, et dans ce cas ce serait un bug de SPHINX.

      Je vais tester avec filtermultijson dès que possible.

    • Le 22 janvier à 17:01, par RastaPopoulos En réponse à : Indexer : La boucle SPHINX

      Moi j’ai l’impression que c’est parce que dans votre base il y a des valeurs en string, ou un mélange de valeurs string et int, non ? Et il peut pas faire la comparaison du coup ?

    • Le 22 janvier à 17:12, par Maïeul En réponse à : Indexer : La boucle SPHINX

      non, j’ai bien vérifié ces points : la requete interroge un int, et j’ai en base un int (en tout cas, [(#PROPERTIES|var_dump)] me montre un int.

    • Le 22 janvier à 17:13, par Maïeul En réponse à : Indexer : La boucle SPHINX

      la requete (qui marche en local mais pas sur mon site distant) :

      SELECT *, WEIGHT() AS score, (properties.objet='article') as mono_0, (properties.parents.ids[0]=1) as mono_1, SNIPPET(content, 'rigolo', 'limit=200','html_strip_mode=strip') AS snippet FROM spip WHERE (mono_0=1) AND (mono_1=1) AND (MATCH('rigolo')) LIMIT 0,10
    • Le 22 janvier à 17:30, par tcharlss En réponse à : Indexer : La boucle SPHINX

      Moi j’ai l’impression que c’est parce que dans votre base il y a des valeurs en string, ou un mélange de valeurs string et int, non ? Et il peut pas faire la comparaison du coup ?

      En effet pour moi il y a un mélange de int et de string.
      C’est l’objet « hierarchie » qui est en string → [(#PROPERTIES|var_dump)] :

    • Le 22 janvier à 19:10, par Maïeul En réponse à : Indexer : La boucle SPHINX

      @Rastapopoulos : si le pb était un mélange int/string, comment ce ferait-il que la second requête Tcharlss, avec un IN, fonctionne ?

      @Tcharlss : tu est sur que ton ’id_objet’ contient « mots » ? ca devrait pas être un numéro plutot ?

    • Le 22 janvier à 19:20, par Maïeul En réponse à : Indexer : La boucle SPHINX

      @Tcharlss : qu’elle est ta version de SPHINX ?

      La version locale que j’ai est 2.2.11 -> cela marche.
      La version distante est 2.2.9 -> cela ne marche pas.

      Et le ticket http://sphinxsearch.com/bugs/view.php?id=2375 dit que le problème a été résolue en 2.2.11.

    • Le 22 janvier à 21:07, par RastaPopoulos En réponse à : Indexer : La boucle SPHINX

      Ok donc ça a l’air d’être côté Sphinx en fait

    • Le 23 janvier à 10:12, par tcharlss En réponse à : Indexer : La boucle SPHINX

      @maieul : oui en local j’ai la version 2.2.9. Donc c’est résolu dans les versions postérieures, ticket fermé !

    Répondre à ce message

  • Le 22 janvier à 16:29, par tcharlss En réponse à : Indexer : La boucle SPHINX

    Un petit détail qui pourrait être mentionné dans la doc : les dates sont au format timestamp, donc pour trier sur ce critère il faut faire à l’inverse des boucles « traditionnelles » :

    * du plus récent au plus ancien : {par date}
    * du plus ancien au plus récent : {!par date}

    • Le 22 janvier à 17:08, par RastaPopoulos En réponse à : Indexer : La boucle SPHINX

      Euh, comment ça se fait ? Parce que justement timestamp, c’est pareil : les dates les plus vieilles ont un nombre plus petit, donc si on classe par ordre croissant « par date », ça fait bien du plus vieux au plus récent à priori.

    • Le 22 janvier à 17:39, par tcharlss En réponse à : Indexer : La boucle SPHINX

      Ah mais je raconte n’importe quoi, tu as raison, ça marche bien.
      J’avais mal vérifié, désolé pour le bruit !

    Répondre à ce message

  • Le 22 janvier à 15:35, par tcharlss En réponse à : Indexer : La boucle SPHINX

    Un truc un peu surprenant qui devrait être mentionné dans la doc : sans le critère {pagination}, la boucle ne retourne que 20 résultats, même avec l’option max_matches.

    Donc pour afficher tous les résultats il faut ajouter un truc du genre {pagination 999999}

    Répondre à ce message

  • Le 19 janvier à 12:44, par tcharlss En réponse à : Indexer : La boucle SPHINX

    Bonjour,

    Je rencontre un drôle de problème, comme si des critères de la boucle étaient mis en cache.

    J’ai donc une saisie personnalisée utilisée dans un formulaire, elle contient une boucle SPHINX avec un critère {filtermultijson} qui doit être optionnel, donc pris en compte uniquement s’il y a rubriques dans l’environnement :

    1. <BOUCLE_truc(SPHINX)
    2. {filtermultijson #ENV{rubriques}, properties.rubriques.ids, #ENV{rubriques}}
    3. >

    Télécharger

    Cette saisie est inclue plusieurs fois dans mon formulaire, et je ne veux une restriction par rubriques que dans la 1re :

    1. $saisies = array(
    2. 'saisie' => 'masaisie',
    3. 'options' => array(
    4. 'nom' => 'test1',
    5. 'rubriques' => array(50),
    6. ),
    7. ),
    8. 'saisie' => 'masaisie',
    9. 'options' => array(
    10. 'nom' => 'test2',
    11. 'rubriques' => '',
    12. ),
    13. ),
    14. 'saisie' => 'masaisie',
    15. 'options' => array(
    16. 'nom' => 'test3',
    17. 'rubriques' => '',
    18. ),
    19. ),
    20. );

    Télécharger

    Et là surprise, la restriction par rubriques est appliquée dans les 3 boucles !

    Dans la méthode runQuery(), j’ai fait un var_dump($result['query']['query']); pour voir ce qu’il se passait.
    Et effectivement, dans les 3 requêtes on retouve la même valeur dans le SELECT :

    1. requête 1 : (IN(properties.rubriques.ids, 50)) (ok)
    2. requête 2 : (IN(properties.rubriques.ids, 50)) (pas ok)
    3. requête 3 : (IN(properties.rubriques.ids, 50)) (pas ok)

    En revanche quand je donne explictement des valeurs à rubriques pour chaque saisie, elles sont correctement prises en compte. Par exemple en donnant respectivement 50, 100 et 150 :

    1. requête 1 : (IN(properties.rubriques.ids, 50)) (ok)
    2. requête 2 : (IN(properties.rubriques.ids, 100)) (ok)
    3. requête 3 : (IN(properties.rubriques.ids, 150)) (ok)

    Donc on dirait que ça prend automatiquement une valeur si la boucle est utilisée plusieurs fois, et qu’il n’y a pas de valeur donnée explicitement.

    Enfin, si je ne donne une valeur 100 qu’à la 2e saisie, j’obtiens ces requêtes :

    1. requête 1 : rien (ok)
    2. requête 2 : (IN(properties.rubriques.ids, 100)) (ok)
    3. requête 3 : (IN(properties.rubriques.ids, 100)) (pas ok)

    Donc ça semble ne remplir la valeur que s’il y en a une dans la boucle précédente.

    Pour finir, si je fais des copies des squelettes de ma saisie avec des noms différents, là ça fonctionne correctement.
    Bref, on dirait qu’il y a un espèce de cache quelque part qui entre en jeu.

    Répondre à ce message

Répondre à cet article

Qui êtes-vous ?

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici Les choses à faire avant de poser une question (Prolégomènes aux rapports de bugs. )
Ajouter un document

Retour en haut de la page

Ça discute par ici

  • ScolaSPIP 4

    19 janvier 2016 – 212 commentaires

    ScolaSPIP est plugin-squelette responsive personnalisable pour sites Web d’établissements scolaires basé sur SPIPr Présentation de ScolaSPIP Ce plugin pour SPIP 3 est développé par la Dane de l’académie de Versailles pour les webmestres de cette (...)

  • Refonte de l’identité graphique

    10 juillet – 33 commentaires

    Lors de la SPIP Party 2017 à Toulouse, un nouveau contributeur est venu nous présenter son travail sur une refonte du logo. Au delà de la refonte du logo, c’est une toute nouvelle identité graphique pour SPIP que Jordan nous propose. Voici une (...)

  • Agenda Fullcalendar facile

    29 octobre 2016 – 34 commentaires

    Dans un précédent article, nous expliquions comment afficher un agenda Fullcalendar sur son site avec le plugin agenda. Cependant, ceci nécessite des manipulation de squelettes, ce qui n’est pas toujours évident lorsqu’on débute. La présente (...)

  • La Fabrique

    20 avril 2012 – 315 commentaires

    La Fabrique est un outil pour webmestres ou développeurs qui souhaitent créer des plugins. La Fabrique est capable de générer le code source minimal d’un plugin pour SPIP 3 (elle accélère donc le démarrage d’un plugin) et peut s’occuper également de (...)

  • Formulaire upload

    25 septembre 2012 – 34 commentaires

    Ce plugin permet d’uploader des documents depuis l’espace public Objectifs Ce plugin permet d’ajouter un formulaire dans l’espace public pour uploader et gérer des fichiers. A priori, il peut se greffer sur tous types objets SPIP : articles, (...)

Ça spipe par là