Créer un moteur de recherche avec Symfony
Introduction
Pour mettre en place un moteur de recherche sur votre application vous pouvez utiliser des solutions comme ElasticSearch qui est une base de données non relationnel pour les recherches. Cependant cette solution est plus couteuse en ressources et en temps de développement. Pour réaliser un moteur de recherche simple et rapide vous pouvez suivre ce tutoriel.
Ce moteur de recherche se base sur une requete SQL de la mort. Je l’ai écris de sorte à ce qu’il soit facile à implémenter dans d’autres projets. L’implémentation avec Symfony se situe à la fin de l’article.
Cette solution restera tout de même moins performante qu’un moteur de recherche plus classique.
Code source
La structure de la requete SQL
La requete SQL se divise en plusieurs sous requetes relisé par des UNION.
Skelete de la requete
SELECT *, SUM(coeff) AS pertinence
FROM (
-- ARTICLE
-- ARTICLE BY name (exact) 100
SOUS-REQUETE 1
UNION
-- APPLICATION 50
SOUS-REQUETE 2
UNION
-- COMMENT
-- COMMENT BY name (exact) 100
SOUS-REQUETE 3
UNION
-- COMMENT 50
SOUS-REQUETE 4
UNION
) as A
group by id, type
ORDER BY pertinence DESC, name ASC
LIMIT $max
Toutes les sous requetes définie un critère de sélection. On attribue à chaque critère un score (ex: la recherche est égale au nom de l’article +100). Ensuite on calcule la pertinence avec la somme des scores que la recherche aura validée. On trie tout par pertinence et voilà la requête SQL est ok.
Exemple de sous requête : score de 100 pour la recherche qui est égale au nom de l’article
SELECT id, name, description, 100 as coeff, 'article' as type
FROM article
WHERE UPPER(name) LIKE '$value'
Requete globale
SELECT *, SUM(coeff) AS pertinence
FROM (
-- ARTICLE
-- ARTICLE BY name (exact) 100
SELECT id, name, description, 100 as coeff, 'article' as type
FROM article
WHERE UPPER(name) LIKE '$value'
UNION
-- APPLICATION 50
SELECT id, name, description, 50 as coeff, 'article' as type
FROM article
WHERE UPPER(name) LIKE '$value' OR description LIKE '%$value%'
UNION
-- COMMENT
-- COMMENT BY name (exact) 100
SELECT id, name, content as description, 100 as coeff, 'comment' as type
FROM comment
WHERE UPPER(name) LIKE '$value'
UNION
-- COMMENT 50
SELECT id, name, content as description, 50 as coeff, 'comment' as type
FROM comment
WHERE UPPER(name) LIKE '$value' OR content LIKE '%$value%'
UNION
) as A
group by id, type
ORDER BY pertinence DESC, name ASC
LIMIT $max
ATTENTION Plus la base de données sera grande plus le temps de recherche sera plus long. Et plus il y a de sous requetes, plus le moteur mettra du temps à chercher.
Implementation avec Symfony
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
class SearchController extends AbstractController
{
/**
* @Route("/search", name="search")
*/
public function index(Request $request)
{
$search = $request->query->get('q');
$result = $this->search($search);
return new JsonResponse($result, 200);
}
private function search($value, $max = 100)
{
$connection = $this->getDoctrine()->getManager()->getConnection();
$query = $connection->prepare("
SELECT *, SUM(coeff) AS pertinence
FROM (
-- ARTICLE
-- ARTICLE BY name (exact) 100
SELECT id, name, description, 100 as coeff, 'article' as type
FROM article
WHERE UPPER(name) LIKE '$value'
UNION
-- APPLICATION 50
SELECT id, name, description, 50 as coeff, 'article' as type
FROM article
WHERE UPPER(name) LIKE '$value' OR description LIKE '%$value%'
UNION
-- ARTICLE
-- ARTICLE BY name (exact) 100
SELECT id, name, content as description, 100 as coeff, 'comment' as type
FROM comment
WHERE UPPER(name) LIKE '$value'
UNION
-- APPLICATION 50
SELECT id, name, content as description, 50 as coeff, 'comment' as type
FROM comment
WHERE UPPER(name) LIKE '$value' OR content LIKE '%$value%'
UNION
) as A
group by id, type
ORDER BY pertinence DESC, name ASC
LIMIT $max
");
// $query->execute(['val' => strtoupper($value)]);
$query->execute();
$result = $query->fetchAll();
// POST TRAITEMENT
// On ajoute les liens
foreach ($result as $key => $value)
{
switch ($value['type']) {
case 'article':
$result[$key]['link'] = $_ENV['ARTICLE_URL'] . $result[$key]['id'];
break;
case 'comment':
$result[$key]['link'] = $_ENV['COMMENT_URL'] . $result[$key]['id'];
break;
default:
$result[$key]['link'] = null;
break;
}
}
return $result;
}
}
J’ai rajouté un post traitement (qui est biensur optionnel) pour ajouter d’autres infos (ici les liens des article et commentaires) aux résultats.
La variable $_ENV['COMMENT_URL']
se situe dans le .env