Correction automatique des requêtes d’un moteur de recherche en PHP objet (POO)

Mathieu Chartier Programmation 14 commentaires

Les moteurs de recherche fourmillent d'options toujours plus intéressantes que les autres. Bien que la plupart semblent être un acquis depuis plusieurs années, force est de constater que même les fonctionnalités les plus simples demandent parfois de nombreuses lignes de code.

Nous allons nous intéresser ici à la correction automatique des requêtes de recherche. Cette fonctionnalité implique de nombreux paramètres mais permet souvent de se sortir d'un bien mauvais pas quand notre niveau d'orthographe décide de s'approcher du néant (et comme cela arrive à tout le monde, l'option de correction devient fort intéressante).

L'exemple ci-dessous digne des perles trouvées sur le site Bescherelle ta mère active la fonctionnalité de correction des requêtes de Google. Elle illustre ce qu'il est possible de réaliser en matière de correction en PHP objet, comme nous le verrons plus bas dans cet article...

Correction automatique des requêtes par Google

Pour les impatients, vous pouvez télécharger le pack complet en PHP 5.5 ici (les versions antérieures du moteur de recherche ont été abandonnées) :

Télécharger “Pack complet - moteur de recherche PHP 5.5 - PHP 7”moteurPHP5.5.zip – Téléchargé 42508 fois – 171,49 Ko

Télécharger “Classe PHP 5.5 et PHP 7 du moteur de recherche”moteur-php5.5.class-inc.zip – Téléchargé 4774 fois – 13,47 Ko

Comment corriger automatiquement une requête de recherche en PHP ?

La correction automatique des requêtes est présente depuis si longtemps sur Google que l'on en oublierait presque qu'il est très difficile d'atteindre un tel niveau de performance en matière de correction orthographique. Bing utilise également ce type de procédé et a même expliqué comment le moteur de recherche faisait pour corriger les requêtes via SearchEngineWatch.

En fait, de nombreuses méthodes permettent de corriger l'orthographe des mots tapés dans les requêtes de recherche. En voici quelques-unes :

  • Utiliser un tableau de correspondance avec le mot bien orthographié et les variantes possibles. Cette méthode est lourde et ne permet pas avec certitude de trouver un maximum de correspondances.
  • Utiliser la distance de Levenshtein pour mesurer la "distance" entre les mots (en réalité, le nombre de modifications de lettres à effectuer pour obtenir un mot exact). Par exemple, l'indice de Levenshtein entre "Google" et "Googli" est 1, car il suffit de modifier une seule lettre pour retomber sur le bon mot. La distance de Levenshtein est une technique intéressante mais ne doit pas être utilisée seule car elle ne vérifie pas vraiment l'orthographe, mais fait plutôt des correspondances entre des suites de lettres. Une variante consiste à utiliser une fonction comme similar_text() en PHP, mais le rendu n'est pas bien meilleur...
  • Utiliser l'algorithme phonétique Soundex pour comparer la phonétique des mots sans faute avec celle des mots erronés.
  • Utiliser l'algorithme phonétique Metaphone pour comparer les mots. Cet indice agit comme Soundex mais est plus précis en termes de phonétique. Son créateur a même inventé l'algorithme double metaphone non disponible en natif en PHP (mais qui apporte plus de précisions dans la phonétique de certaines langues, contrairement à Soundex et Metaphone qui utilisent l'anglais).

Certaines méthodes présentées ici peuvent être couplées avec d'autres pour augmenter le nombre de possibilités. Nous pouvons déjà imaginer que Google et Bing ne se contentent pas de ces techniques "simples", ils utilisent sûrement des analyses sémantiques et thématiques avant les corrections (pour restreindre les champs de correction, etc.).

Limites de la correction orthographique des requêtes en PHP

Chaque méthode impose un index de mots et expressions "sans faute d'orthographe" pour être utilisé comme base de comparaison. C'est la qualité et la taille de cet index inversé qui permet une plus grande précision du correcteur orthographique.

Qu'on se le dise, même avec un index inversé de la puissance de celui de Google ou Bing, des fautes passeront toujours entre les mailles du filet. La phonétique a ses limites et même un tableau de correspondance idéal ne pourra jamais répondre à tous les cas de figures. Ce type de fonctionnalités tend donc à une meilleure interprétation des requêtes sans pour autant garantir 100% d'exactitude.

Protocole de test de la correction automatique des requêtes

Les méthodes PHP objet (POO) de la correction orthographique automatique ont été ajoutées au sein du moteur de recherche PHP objet que j'ai créé il y a plusieurs mois. Ainsi, nous bénéficierons de tous les atouts du moteur existant avec les nouvelles fonctionnalités.

Plusieurs méthodes permettent de corriger l'écriture des requêtes de recherche (la méthode principale teste les algorithmes soundex et metaphone), de créer son propre index inversé, de vérifier si l'index existe, etc. Pour être totalement honnête, il est prévu que je réécrive le moteur complet avec plus de méthodes qu'auparavant afin de le découper encore davantage et le rendre plus modulaire qu'il ne l'est déjà, j'ai donc déjà entrepris ce travail avec ces nouveaux objets PHP.

Il est possible de personnaliser l'affichage qui précède ou encadre la requête corrigée. Dans la capture suivante (sans CSS donc à l'état brut), il est inscrit "Tentez avec une autre orthographe : " suivi de la requête corrigée (correspondant à l'ancre du lien réécrit en HTML).

Correction orthographique des requêtes de recherche en PHP Objet

Les expressions exactes recherchées (entre guillemets) sont récupérées ainsi que les mots indépendants (comme "amet" et "dolor" ici) afin de correspondre au maximum à la recherche originelle.

Il est possible également d'utiliser une méthode particulière (à placer obligatoirement après la méthode de correction !) pour afficher directement les résultats de la requête corrigée, comme c'est le cas sur Google notamment. Si cette méthode n'est pas appliquée, on risque d'obtenir le message "Pas de résultats pour cette recherche." si les fautes d'orthographes sont trop bloquantes pour la recherche.

Voici l'exemple du code du fichier moteur-normal-php5.5.php présent dans le pack (voir en haut de l'article). Les lignes surlignées correspondent aux ajouts spécifiques concernant la correction orthographique automatique.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Moteur de recherche - Mode normal</title>
</head>

<body>
<form id="searchForm" name="moteurSubmit" method="GET" action="">
	<label for="q">Rechercher :</label>
	<input type="text" value="<?php if(isset($_GET['q'])) { echo htmlspecialchars($_GET['q']); } ?>" name="q" id="moteur" />
	<input type="submit" value="Envoyer" />
</form>
<?php
include_once("class.inc/BDD-PHP5.5.class-inc.php"); // Class PHP 5.5 (avec mysqli)
include_once("class.inc/stopwords.php");
include_once("class.inc/moteur-php5.5.class-inc.php"); // Class PHP 5.5

//
// $link se trouve dans BDD.class-inc.php, il s'agit de la variable de connexion
// Attention, elle doit absolument être utilisée (sous ce nom ou un autre) en PHP 5.5 ou supérieur
// N.B. : elle s'ajoute en début d'appel des class moteurRecherche($link...), autoCompletion($link...) et alterTableFullText($link...)
//

if(isset($_GET) && !empty($_GET['q'])) {
    $moteur = new moteurRecherche($link, stripslashes($_GET['q']), 'search', 'regexp', $stopwords, $exclusion = '', $encoding = 'utf-8', $exact = true, $accent = true);
    $colonnesWhere = array('title', 'description');
	$alterTable = new alterTableFullText($link, 'table_search', 'search', $colonnesWhere);
    $moteur->moteurRequetes($colonnesWhere);
}

if(isset($moteur)) {
    // Affichage de la requête avec $moteur->requete
    echo '<h3>Résultats de la recherche : <em>'.$moteur->requete.'</em></h3>';
	
	// Création de la table des mots corrects
	if($moteur->isIndex("correctindex", "table_search") == false) {
		// Créé l'index correct
		$moteur->createIndex();
	}

	// Tableau des mots puis ajout dans la table
	$motsCorrects = array("lorem", "dolor", "amet", "sit", "ipsum", "dolor amet", "lorem ipsum");
	$moteur->setIndex($motsCorrects);

	// Affichage de la correction des résultats
	$corrections = $moteur->getCorrection();
	$moteur->getCorrectedResults();
	if(!empty($corrections)) {
		echo "<p>Tentez avec une autre orthographe : ".$corrections."</p>\n";
	}

	// Fonction d'affichage des résultats (fonction callback appelée ensuite)
	function display($requete, $nbResults, $mots) {
		if($nbResults == 0) { // Si aucun résultat n'est retourné
			echo "<p>Aucun résultat, veuillez effectuer une autre recherche !</p>";    
		} else { // Sinon on affiche les résultats en boucle
			
			// Affichage du nombre de résultats (optionnel)
			$affichageResultats = new affichageResultats();
			echo $affichageResultats->nbResultats(true);
			
			// Instanciation des ID (et du numéro de résultat si besoin)
			$nb = 0;
			
			// Comptage dynamique du nombre de résultats
			if(isset($_GET['p'])) {
				// $nb = 0 + (limite * (numéro de page - 1))
				$nb = $nb + (10 * ($_GET['p'] - 1));
			}
			
			while(($key = mysqli_fetch_assoc($requete))) {
				$nb++; // Incrémentation de l'ID
				
				// On encode chaque clé/valeur de résultats en UTF-8
				foreach($key as $k => $v) {
					 $key[$k] = utf8_encode($v);
				}

				// Résultats de recherche à afficher (à personnaliser)
				$texte  = "<div class='results' id='".$nb."'>\n";
				$texte .= "\t<h3>".$nb." - ".$key['title']."</h3>\n";
				$texte .= "\t<p>".$key['description']."</p>\n";
				$texte .= "</div>\n";

				// Affichage du contenu après surlignage des mots recherchés
				// N.B. : optionnel --> possibilité de remplacer par echo $texte;
				$surlignage = new surlignageMot($mots, $texte);

				echo $surlignage->contenu;
			} // Fin de la boucle while
		}
	} // Fin de la fonction display (callback)
	
	// Nombre de résultats par "tranche d'affichage"
	$limit = 10;
	
	// Numéro de page récupéré dynamiquement
	if(isset($_GET['p'])) {
		$page = htmlspecialchars($_GET['p']);
	} else {
		$page = 0;
	}
	
	// Lancement de la fonction d'affichage avec paramètres
	$moteur->moteurAffichage('display', '', array(true, $page, $limit, true));
	
	// Ajout de la fonction de pagination
	// N.B. : le second paramètre correspond au "name" de $_GET['p']
	$moteur->moteurPagination($page, 'p');
}
?>
</body>
</html>

Liste des méthodes de correction en PHP Objet du moteur de recherche

Le moteur de recherche contient 9 nouvelles méthodes dont trois objets privés, nous allons les décrire rapidement ici, bien que tout soit noté dans les commentaires de la class PHP à télécharger :

  • getCorrection($tableIndex = "", $parametre = "q", $select = true) : méthode principale de la correction automatique qui retourne un lien réécrit avec la bonne requête corrigée. Il est important de faire correspondre le paramètre $paramètre utilisé pour le moteur ("q" par défaut", "s" sur WordPress, etc.). Enfin, $select (true ou false) permet de récupérer les valeurs de comparaison directement dans l'index (true) ou en les calculant à la volée (false - plus gourmant).
  • getCorrectedResults() : objet à placer après getCorrection() (car il utilise sa valeur retournée) pour afficher directement les résultats issus de la requête corrigée (si des résultats correspondent bien entendu). Il suffit de supprimer ou commenter la ligne de cette méthode pour retirer la fonctionnalité.
  • isIndex($tableIndex, $databaseName) : vérifie si la table de l'index ($tableIndex) existe dans la base de données ($databaseName). La méthode permet d'enregistrer le nom de la table pour ne pas avoir à la répéter ultérieurement (comme dans l'exemple présent dans cet article).
  • getIndex($tableIndex = "") : retourne un tableau contenant toutes les informations relatives à la table de l'index inversé ($tableIndex), comme les mots présents, les valeurs métaphones et soundex, etc.
  • createIndex($tableIndex = "") : crée la table de l'index. En réalité, l'idéal est de l'utiliser une seule fois pour améliorer les performances globales du moteur, car une fois l'index inversé créé, la méthode devient obsolète.
  • setIndex($arrayWords = array(), $tableIndex = "") : ajoute un tableau de mots ou expressions ($arrayWords) dans l'index. Comme pour createIndex(), il convient d'utiliser cette méthode uniquement quand on veut remplir l'index inversé.
  • queryStringToLink($string, $queryArg = "q") - méthode privée : fonction de modification automatique du lien présent derrière la requête corrigée (afin de correspondre à cette nouvelle requête).
  • setQuery($query) - méthode privée : mutateur qui permet de modifier la requête de recherche à la volée (utile pour prendre en compte la correction automatiquement).
  • getCleanQuery($query = "") - méthode privée : objet qui permet de nettoyer la requête afin de la rendre utilisable en cas de pagination automatique (scroll infini) ou par trigger (clic sur un bouton "Afficher plus de résultats").

14 commentaires

  • Anoh Abbah dit :

    Vraiment merci pour ce moteur de recherche.

  • ANOBA dit :

    Bonjour,
    J'ai un problème avec votre moteur de recherche.
    C'est que le moteur de recherche fonction parfaitement bien en local mais refuse en ligne.
    Svp pouvez vous m'aider???
    Merci

    • Bonjour,
      Dites-moi ce qui ne va pas. En gros, le moteur fonctionne sur Wamp, mais quand vous le passez en ligne sur un "vrai" serveur, ça ne fonctionne plus ? Et qu'est-ce qui ne fonctionne plus exactement ?

      • Schoenmaeker dit :

        Je pense que le problème de Anoba était au niveau de la requête SQL de l’auto complétion. En local la requête fonctionne et en ligne sur une version plus stricte la requête bloque au niveau de "idindex DESC" qui n'est pas sélectionné par le "select"...

  • WhiteRoro dit :

    Sympa ce petit moteur...
    Je le trouve très bien pour un seul mot de recherche mais pas très précis pour deux mots ou plus. La liste de suggestion disparait lorsque l'on saisi un espace entre deux mots...

    • Le moteur est aussi pertinent pour un ou plusieurs mots puisque ce dernier cherche de la même manière en réalité. On pourrait même dire qu'il est forcément plus pertinent avec plusieurs mots puisqu'il n'affiche que les résultats qui les contiennent tous. Ensuite, tout dépend du réglage que vous en avez fait, car il est possible d'activer ou non l'algorithme de pertinence, de changer les types de tri et les priorités, etc.
      Concernant les suggestions, là encore c'est juste un paramètre à changer car il est possible d'avoir des suggestions pour chaque mot tapé, comme dans le moteur de recherche de ce blog par exemple (c'est basé sur ce moteur justement).
      Après, ce moteur n'a aucunement la prétention de rivaliser avec Qwant ou Google, ou même Elastic Search qui possède des équipes complètes... ^^

      • WhiteRoro dit :

        J'ai juste un dernier petit souci au niveau des réglages des accents. L'auto complétion ne fonctionne pas si "$field" contient des accents. Je voudrais avoir des noms de colonne de table de base de données avec accents du type : "Faits_de_société", "Géographie"... J'ai activer l'option mais cela ne suffit pas -> ne renvoi aucune erreur...

      • Vous voulez mettre des accents dans le nom des tables de base de données ? Normalement ça ne fonctionne pas, au même titre que les tirets. Il faut des noms de table sans accents, sans tirets, seuls les lettres non accentuées et underscores sont acceptés il me semble.

  • carde dit :

    bonjour, les sources ne sont pas accessibles pour la version 7+ de php,
    J'ai déjà utilisé votre moteur dans une version antérieure, il est génial, bravo pour votre travail :)
    cordialement,
    Benoît

    • Bonjour,
      La version 5.5 fonctionne jusqu'à PHP 7, c'est pour cela qu'il n'y a pas de mise à jour ou de nouvelles versions. Passer cela en PHP 7 ne changerait pas grand chose côté code, hormis rajouter quelques types de données et de méthodes. PHP 7 lit très bien cette version, je l'utilise d'ailleurs dans cet environnement depuis plus de deux ans.

      • benoit dit :

        Entendu Mathieu, merci.
        Pour une raison que je n'arrive pas à élucider, la ligne 420 de moteur-php5.5.class-inc.php me renvoie sur une page 404 après avoir minutieusement controlé tous mes paramètres de connection. J'ai pensé à ma version de php, mais vous balayez cette option ce qui est rageant vu qu'il s'agit d'un problème en apparence très simple. Probablement aurais-je oublié quelque chose, je m'y remets !!!

      • Ah, le problème est très étonnant en ligne 420, car cela correspond au comptage du nombre de résultats, qui ressemble en tout point à celle de la ligne 422 (qui ne semble pas planter) à un détail près à la requête de la ligne 418. En revanche, j'ai vu que les lignes 421 et 423 pour les "or die()" devraient être mises à la suite en théorie, et pas sur des lignes à part. Testez ça en collant la ligne 421 à la fin de la 420, et la 423 à la fin de la 422. En gros, vous devez avoir la requête suivie du "or die()".
        Pour le reste, le plus étonnant serait que la requête ne fonctionne pas pour le comptage, mais fonctionne très bien pour la récupération des résultats (requête de la ligne 418), alors qu'elles s'inspirent des mêmes paramétrages, etc. :-S

      • carde dit :

        Bon j'ai trouvé si ca peut aider quelqu'un le moteur plantait sans raison valable : mon erreur était dans la base de données, mes champs étant encodés en UTF8-unicode au lieu d'UTF8-general ci...
        Voilà :)
        pffff ^^

      • Les joies de l'encodage des caractères... ^^

  • Déposer un commentaire

    Répondre à carde Annuler la réponse

    L'adresse de messagerie ne sera pas publiée.* Champs obligatoires