Autocomplétion et recherche full-text temps réel avec php et javascript

Comment implémenter simplement une recherche temps réel pour votre site internet en conservant la maîtrise de vos données/statistiques ?

18 août 2015

exemple d'une recherche temps réel

Introduction

Plusieurs solutions existent et permettent d'intégrer une recherche temps réel sur un site internet.

Certaines sont payantes et vos données / statistiques sont sur leurs serveurs, par exemple Algolia.

D'autres sont gratuites, mais nécessitent un serveur physique dédié (qui lui n'est pas gratuit) et peuvent être complexes à mettre en place pour un prototype (ça prend du temps), par exemple ElasticSearch.

Comment faire si on souhaite intégrer rapidement la recherche temps réel sur un site internet tout en conservant la maîtrise des données/statistiques ?

Nous avons commencé à développer une solution : js-php-real-time-search.

Elle est utilisée sur notre Portail Web Lyonnais.

Pré-requis

Un serveur http du type Apache, c'est un logiciel libre.

PHP doit être installé, php est un langage de programmation libre principalement utilisé dans le développement internet.

Composer est un gestionnaire libre de dépendances pour php, il permet de déclarer et d'installer automatiquement des librairies tierces.

Fonctionnement

php inclut par défaut la base de données sqlite.

sqlite a une extension fts4 qui permet d'effectuer des recherches plein texte.

sqlite est suffisant pour un site de petite et moyenne taille, surtout si on accède la plupart du temps à la base de données en lecture seule.

Côté serveur

Optimisation d'Apache

Activer l'option Keep-Alive dans votre fichier .htaccess.

Cette option permet de transmettre plusieurs requêtes via la même connexion TCP et peut diviser par deux les temps de latence.

<IfModule mod_headers.c>
    Header set Connection Keep-Alive
</IfModule>

Installation

Composer

Exécuter dans un invité, la ligne de commande ci-dessous :

php -r "readfile('https://getcomposer.org/installer');" | php

Cela affiche :

#!/usr/bin/env php
All settings correct for using Composer
Downloading...

Composer successfully installed to: C:\tmp\composer.phar
Use it: php composer.phar

Un fichier composer.phar est maintenant présent dans le répertoire courant.

Configurer le fichier composer.json

Créer un fichier composer.json dans le répertoire courant contenant :

{
    "require": {
        "glicer/search": "dev-master"
    }
}

Installer les librairies

À partir du même répertoire que composer.json, exécuter dans un invité, la ligne de commande ci-dessous :

php composer.phar install

Cela affiche :

Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing symfony/stopwatch (v2.3.31)
    Loading from cache

  - Installing symfony/yaml (v2.3.31)
    Loading from cache

  - Installing symfony/filesystem (v2.3.31)
    Loading from cache

  - Installing symfony/finder (v2.3.31)
    Loading from cache

  - Installing symfony/console (v2.3.31)
    Loading from cache

  - Installing glicer/search (dev-master af5dee8)
    Cloning af5dee89a0c30955bcef954283b670dae226151e

symfony/console suggests installing symfony/event-dispatcher ()
Writing lock file
Generating autoload files

Structure du répertoire

Après installation, le répertoire courant contient les fichiers et répertoires ci-dessous :

composer.json
composer.phar
composer.lock
vendor
 ├───composer
 ├───glicer
 │     └───search
 │            ├───doc
 │            ├───samples
 │            ├───src
 │            └───tests
 └───symfony

Importer les données

Les données à indexer doivent être pour le moment au format yaml et encodées en utf8.

Un exemple ci-dessous :

cinema_comoedia:
    title : "Cinéma Comoedia"
    link: "http://www.cinema-comoedia.com"
    sociallink: "https://www.facebook.com/cinemacomoedia"
    feedlink: "http://www.cinema-comoedia.com/rss/"
    feedlimit: 5
    tags: "cinéma"
    gps: "45.7473766,4.8355764"
    description: |
                  Cinéma indépendant art et essai, lieu de rencontres dans lequel
                  projections de films, expositions, conférences et débats sont également possibles ...
    address: "13 Avenue Berthelot"
    city: "Lyon 7"
    phone: "04 26 99 45 00"
jp_coureur_des_berges:
    title : "Jean-Pierre, le coureur des berges du Rhône"
    sociallink: "https://fr-fr.facebook.com/pages/Jean-Pierre-le-coureur-des-berges-du-Rh%C3%B4ne/101738059870725"
    rel: ""
    tags: "athlète papy joggeur short rouge"
    description: |
                  Courir, courir, et encore courir... sans jamais s'arrêter ...

Exécuter le code php ci-dessous pour indexer les données dans la base de données sqlite.

Il faut notamment indiquer :

<?php
use Symfony\Component\Console\Output\ConsoleOutput;
use GlSearchEngine\GlServerEngine;

$output    = new ConsoleOutput();

$yamlFiles      = [__DIR__ . "/data/web.yml", __DIR__ . "/data/web2.yml"];  //liste des fichiers yaml à importer
$dbname         = __DIR__ . "/data/web.db"; //chemin de la base sqlite où seront stockées les données
$table          = "web";    //préfixe des tables créées
$fieldsFullText = ['title', 'tags', 'description', 'address', 'city'];  //liste des champs yaml utilisés pour la recherche plein texte
$fieldsFilter   = ['gps']; //liste des champs yaml éventuellement utilisés pour filtrer une recherche

$engine = new GlServerEngine($dbname, $output, true);
$engine->importYaml(
       $table,
       $fieldsFilter,
       $fieldsFullText,
       $yamlFiles,
       function () use ($output) { //fonction appelée à chaque importation
           $output->write(".");
       }
);

Le contrôleur

Le client effectue un HTTP GET sur le serveur

Le serveur répond dans le format json.

Ci-dessous un exemple search.php :

<?php
include('php/vendor/glicer/search/src/GlServerSearch.php');

use GlSearchEngine\GlServerSearch;

$query = null;
$filter = null;

if (isset($_GET['q'])) {
    $query  = $_GET['q'];
}

if (!$query) {
    return;
}

if (isset($_GET['f'])) {
    $filter = $_GET['f'];
}

$dbname = __DIR__ . "/web.db";
$fields = ['title', 'tags', 'description', 'address', 'city'];  //réindiquer la liste des champs destinés à la recherche full-text

$search = new GlServerSearch($dbname, "web", $fields);
$json = $search->queryJson($query, $filter);

header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json;charset=utf-8;');
echo $json;

Côté client

Fonctionnement

La partie client est réalisée en typescript : glsearch.ts

Intégrer glsearch.js dans votre fichier .html.

Utiliser la fonction query de glsearch pour interroger le serveur, ses paramètres sont :

Exemple d'utilisation

La bibliothèque jquery et le fichier glsearch.js sont nécessaires.

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script src="js/jquery.min.js"></script>
    <script src="js/glsearch.js"></script>
    <style>
        b {
            color: blue;
        }
    </style>
</head>

<body>
<form>
    <fieldset>
        <label for="target">Search : </label>
        <input id="target" type="text">
    </fieldset>
</form>
<div id="result">

</div>
<script>
    var view = function (fields) {
        var html = "";
        for (var field in fields) {
            html += fields[field] + "<br>";
        }
        return html;
    };

    var client = new glSearch("http://lyon.glicer.com/search/search.php?q={q}&f={f}");

    $("#target").keyup(function (event) { //événement déclenché à chaque frappe sur le clavier
        var val = $(this).val();
        var result = $('#result');
        var html = "<div>";
        client.query(val,
                function (value) {  //ajoute chaque élément pour construire le code html à intégrer
                    html += "<div>";
                    html += view(value);
                    html += "</div>";
                    html += "<br>";
                },
                function (values) {
                    if (values == null) {
                        result.empty(); //si aucun résultat, efface l'affichage
                    } else {
                        html += "</div>";
                        result.empty();
                        result.append(html); //affiche le résultat final
                    }
                });
    });
</script>
</body>
</html>

Performances

Performance sous firefox

Versions des logiciels et langages utilisés


Laisser un commentaire

Auracom - 12/10/2015 11:52

Bonjour Pourriez vous faire un test avec 100.000 enregistrements ? Merci


dev_glicer - 12/10/2015 22:38

Nous n'avons pas à disposition une telle base, nous ne pensons pas que notre solution soit pour l'instant pertinente pour autant de données.
Nous l'avons utilisé pour notre prototype.