Ceux qui me suivent savent que je suis quelque peu passionné par les moteurs de recherche en règle générale, que ce soit via mes expériences en tant que référenceur (SEO) mais aussi en tant que développeur. Nous allons apprendre à réaliser en PHP un petit algorithme de restitution de résultats d'un moteur de recherche sur plusieurs tables et plusieurs colonnes (par tables) en MySQL (pourrait s'adapter à d'autres SGBD dans les faits). L'idée est d'obtenir une requête SQL finie et utilisable pour retourner des résultats.
Vers un projet de moteur de recherche PHP plus ambitieux...
La fonction que nous allons voir va donc créer une requête MySQL complète pour un moteur de recherche PHP. J'ai commencé à la travailler dans l'objectif de refonte du moteur de recherche PHP que j'ai déjà proposé sur ce blog depuis plusieurs années (voir aussi la version avec de l'Ajax ou la correction orthographique notamment). Cela fait plusieurs mois que je cherche à revoir tout le moteur pour en proposer une version plus évoluée, plus complète, et avec d'autres surprises (je réfléchis à une API de recherche, etc.). Il s'agit d'un projet entièrement bénévole donc ça prend de fait plus de temps, au milieu des activités que me font vraiment manger... :-)
Pour notre exemple, nous allons nous concentrer sur la méthode LIKE de MySQL, mais nous pourrions tout-à-fait imaginer le même type de procédé avec la méthode REGEXP (moins connue mais souvent plus efficace et précise) ou encore avec la recherche FULLTEXT (sûrement la meilleure, mais qui impose quelques petits réglages côté serveur pour être totalement efficace, notamment sur les mots de 3 caractères et moins).
Souvent, nous savons comment réaliser une requête de recherche simple, que ce soit d'ailleurs en PHP ou via un autre langage de programmation. Avec mon autre moteur de recherche, de nombreux lecteurs m'ont souvent demandé la fonctionnalité multi-tables, qui peut s'avérer bien pratique il faut bien l'admettre. J'ai donc cherché à proposer une fonction d'introduction à ce procédé la plus concise possible, qui permettra de réaliser plusieurs actions :
- recherche multi-tables grâce à UNION en SQL : cela peut fonctionner sur une seule table si nécessaire, mais vous pouvez aussi effectuer une recherche dans plusieurs tables pour restituer davantage de résultats ;
- recherche multi-colonnes grâce à AND et OR en SQL : il est bien rare de ne vouloir chercher que dans une seule colonne, c'est ici la base d'un moteur de recherche standard dans une table ;
- ordonner les résultats pour chaque table : il suffit de signifier le ORDERBY et le ORDER pour classer les résultats comme vous le souhaitez pour chaque table.
J'insiste toutefois, il s'agit d'une fonction introductive, qui peut (et qui sera) encore largement améliorée. Notez aussi qu'on parle de recherche multi-tables avec UNION, et non avec INTERSECT (ou équivalent avec MySQL), c'est-à-dire que la requête va restituer les résultats cumulés de plusieurs tables, et non les résultats existants uniquement dans les diverses tables (cela impose bien d'autres contraintes d'écriture, mais c'est une variante à envisager à terme dans le moteur de recherche PHP final).
Sans plus attendre, voici la fonction PHP pour obtenir la requête SQL finie :
<?php /* $tables = array( "search_table" => array( "colonnes" => array(), // @colonnes array noms des colonnes "orderby" => "", // @orderby string nom de la colonne "order" => "" // @order string ASC/DESC ), "search_table_2" => array( "colonnes" => array(), // @colonnes array noms des colonnes "orderby" => "", // @orderby string nom de la colonne "order" => "" // @order string ASC/DESC ) ); $mots = array() // @mots array mots à rechercher */ function querySearchEngine(array $tables, array $mots):string { $nb = 0; $query = $orderby = ""; // Pour chaque table... foreach($tables as $table => $donnees) { // Ajoute UNION avant chaque table supplémentaire dans la laquelle rechercher if($nb != 0) { $query.= " UNION "; } // Pour chaque mot, on recherche... $nb2 = 0; // Nombre de mots $search_query = "("; foreach($mots as $mot) { $nb3 = 0; // Nombres de colonnes // Pour chaque colonne... foreach($donnees["colonnes"] as $colonne) { if($nb3 != 0) { $search_query.= " OR "; } $search_query.= $colonne." LIKE '%".$mot."%'"; // Recherche dans chaque colonne // Ordre des résultats par table if(!empty($donnees["orderby"])) { $orderby = " ORDER BY ".$donnees["orderby"]." ".$donnees["order"]; } $nb3++; } $nb2++; if(count($mots) != $nb2) { $search_query.= " AND "; } } $search_query.= ")"; $query.= "SELECT * FROM ".$table." WHERE ".$search_query.$orderby; // Recherche dans chaque table $nb++; } return $query; } ?>
La fonction, hors commentaire introductif, fait 42 lignes, cela reste donc relativement court pour s'en imprégner. Elle va restituer une chaîne de caractère constituant la requête finale du moteur de recherche en PHP, quel que soit le nombre de tables, de colonnes ou de mots...
Voici un exemple, que j'ai volontairement présenté avec de la couleur pour que ce soit plus parlant, pour une requête finale basée sur le code suivant (vous noterez que je n'ai pas précisé d'ordre, il n'y a donc pas d'ORDER BY à la fin de la requête).
$tables = array( "search_table" => array( "colonnes" => array("colonne_1_table_1", "colonne_2_table_1"), // @colonnes array noms des colonnes "orderby" => "", // @orderby string nom de la colonne "order" => "" // @order string ASC/DESC ), "search_table_2" => array( "colonnes" => array("colonne_1_table_2", "colonne_2_table_2"), // @colonnes array noms des colonnes "orderby" => "", // @orderby string nom de la colonne "order" => "" // @order string ASC/DESC ) ); $mots = array("mot_1", "mot_2", "mot_3"); // @mots array mots à rechercher echo querySearchEngine($tables, $mots); // Retourne la requête MySQL
Le résultat en image montre bien que la requête SQL de constitue avec les diverses tables, colonnes et pour chaque mot recherché.
Il ne s'agissait ici que d'une introduction, que je vais encore compléter et améliorer à l'avenir pour la refonte de mon moteur de recherche PHP. J'espère que cela vous permettra peut-être de ne pas attendre pour faire vous-mêmes vos propres projets. ;-)