Talk de Fabien PAPET sur " Construire un moteur de recherche avancé avec Elastica et API Platform "

Ahmed Eben Hassine
Ahmed Eben hassine
4 octobre 2024

 

Construire un moteur de recherche avancé avec Elastica et API Platform -

Présenté par Fabien Papet 

Expert Symfony certifié, Tech lead et DevOps (Azure / AWS / K8s / Terraform)

Fabien Papet

 

Introduction

Cette session se concentre sur le développement d'un moteur de recherche centralisé pour un vaste catalogue de produits. Avec un volume impressionnant de 3 millions de produits répartis sur 290 fabricants, l’objectif est d’intégrer un moteur de recherche avancé au sein d'une API existante. Nous allons explorer les techniques et les outils pour améliorer la recherche multi-champs et multi-critères, tout en offrant des fonctionnalités avancées pour une expérience utilisateur optimale.

 

Objectif de la Conférence

Le principal objectif de cette conférence est d’ajouter un moteur de recherche à l'API actuelle, permettant ainsi une recherche multi-champs et multi-critères. Les fonctionnalités visées incluent :

Fonctionnalités ciblées : 

  • Tolérance aux erreurs typographiques
  • Gestion des références partielles et exactes
  • Possibilité de marquer des produits comme favoris

Les filtres proposés permettent de :

  • Filtrer par fabricant
  • Filtrer les produits publics ou privés, selon les clients
  • Filtrer les produits avec ou sans photo

Les priorités d'affichages sont les suivantes :

  • Accorder une priorité spécifique à des critères clés dans l’affichage des résultats.
  • Privilégier l’affichage des produits illustrés par des images.
  • Augmenter la visibilité des produits marqués comme étant des favoris.

 

Implémentation Technique

Utilisation d’Elastica

La solution recommandée par Fabien consiste à remplacer le Data Provider natif d'API Platform, qui repose sur la bibliothèque elasticsearch/elasticsearch, par un nouvel ElasticaProvider personnalisé utilisant Elastica comme client Elasticsearch.
Le Data Provider proposé par Api Platform présente des limitations en matière de recherche avancée, notamment l'impossibilité d'intégrer l'API Count, car il ne prend en charge que l'API Search d'Elasticsearch. C'est pourquoi Fabien a opté pour Elastica, un package PHP qui offre une API flexible et performante, permettant d'exécuter des requêtes complexes de manière optimale.

Exemple de requête avec Elastica :

Voici un exemple de requête utilisant Elastica :

$boolQuery = new Query\BoolQuery();
$boolQuery->addMust(new Query\Match('name', 'Casque Audio Pro'));
$boolQuery->addMust(new Query\Term(['category' => 'Électronique']));

// Filtre de plage de prix
$rangeFilter = new Query\Range('price', ['gte' => 100, 'lte' => 500]);
$boolQuery->addMust($rangeFilter);

// Définition de la requête principale
$query = new Query($boolQuery);

Cela permet d'interroger l'API Elasticsearch avec la requête suivante :

{
    "query":{
        "bool":{
            "must":[
                {
                    "match":{
                        "name":"Casque Audio Pro"
                    }
                },
                {
                    "term":{
                        "category":"Électronique"
                    }
                },
                {
                    "range":{
                        "price":{
                            "gte":100,
                            "lte":500
                        }
                    }
                }
            ]
        }
    }
}

Avantages d'Elastica

  • Autocomplétion par les IDE : Réduit le temps de développement et minimise les erreurs.
  • Intégration avec Symfony HttpClient : Optimise la gestion de compression et traitement asynchrone. (Plus de détails https://github.com/elastic/elasticsearch-php/issues/1241)
  • Flexibilité des requêtes :  Permet de construire facilement des requêtes complexes.
  • Abstraction orientée objet des détails techniques : Masque les détails de communication avec Elasticsearch, permettant aux développeurs de se concentrer sur la logique métier.
  • Support des fonctionnalités avancées : Inclut des fonctionnalités comme le regroupement, les facettes et les filtres.

Exemple de Configuration dans API Platform :

#[Entity(repositoryClass: ProductRepository::class)]
#[ApiResource(
    operations: [
        new GetCollection(
            uriTemplate: '/products/search',
            provider: ElasticaProvider::class,
            stateOptions: new StateOptions(
                indexName: 'products',
                trackTotalHits: true,
            )
        ),
        new GetCollection(),
        new Get(),
    ],
)]
#[ApiFilter(filterClass: ProductFilter::class)]
#[ApiFilter(filterClass: TermFilter::class, properties: ['brand' => 'brand'])]
#[ApiFilter(filterClass: RangeFilter::class, properties: ['price' => 'min_price'])]
#[ApiFilter(filterClass: CountFilter::class, properties: ['count'])]
class Product

 

Fonctionnalités Avancées

     1. Count filter :

Le Count Filter est une classe qui étend AbstractFilter et remplace l'API de recherche d'Elasticsearch pour retourner uniquement le nombre total de résultats d'une requête.

#[ApiFilter(filterClass: CountFilter::class, properties: ['count'])]
class Product

Application du Filtre : La méthode apply active le filtre de comptage dans le QueryBuilder. Elle vérifie l'existence du filtre count et, si présent et valide, renvoie le nombre total de résultats.

use App\ApiPlatform\Elasticsearch\QueryBuilder;

class CountFilter extends AbstractFilter
{
    public function apply(QueryBuilder $queryBuilder, Operation $operation, array $uriVariables = [], array $context = []): void
    {
        if (!$this->filterExists('count')) {
            return;
        }

        $value = filter_var($this->getRequestValue('count'), FILTER_VALIDATE_BOOLEAN);

        if (!$value) {
            return;
        }

        $queryBuilder->count();   ...

     2. Filtres personnalisés :

Les filtres personnalisés permettent des recherches complexes, adaptées aux besoins spécifiques. Voici un exemple de filtre :

Exemple de filtre : ProductFilter

class ProductFilter extends AbstractFilter
{
    private const FILTER_NAME = 'query';

    public function apply(QueryBuilder $queryBuilder, Operation $operation, array $uriVariables = [], array $context = []): void
    {
        if (!$this->filterExists(self::FILTER_NAME)) {
            return;
        }
        
        $searchQuery = $this->getRequestValue(self::FILTER_NAME);
        if ('' === $searchQuery) {
            return;
        }

        $boolQuery = new BoolQuery();
        $boolQuery->addShould(QueryHelper::matchExactly($searchQuery, 'code', 10));
        $boolQuery->addShould(QueryHelper::matchByRegex($searchQuery, 'code', 2));
        $boolQuery->addShould(QueryHelper::matchPartiallyOnMultipleFields($searchQuery, ['name^3', 'brand^2', 'model^1']));
        $boolQuery->addShould(QueryHelper::matchPartiallyWithFaultTolerance($searchQuery, ['name', 'brand', 'model']));

        $queryBuilder->getQuery()->addMust($boolQuery);
    }
}

     3. Lecture seule avec filtrage :

Voici un exemple de filtre pour les produits publics ou privés, basé sur company_id :

class CompanyFilter extends AbstractFilter
{
    public function apply(QueryBuilder $queryBuilder, Operation $operation, array $uriVariables = [], array $context = []): void
    {
        $user = $this->getUser();
        $company = $user->getCompany();
        
        $boolQuery = new BoolQuery();
        if ($company) {
            $boolQuery->addShould((new Term())->setTerm('company_id', $company->getId()));
        }
        $boolQuery->addShould((new BoolQuery())->addMustNot(['exists' => ['field' => 'company_id']]));

        $queryBuilder->getQuery()->addFilter($boolQuery);
    }
}

 

Conclusion 

L'adoption d'Elastica dans le développement du moteur de recherche a permis de concevoir un système à la fois robuste et flexible, capable de gérer efficacement un volume important de données tout en intégrant des fonctionnalités avancées. Cette stratégie renforce non seulement la performance et la maintenabilité du code, mais favorise également une intégration optimisée avec Symfony.

 

Pour plus de détails, vous pouvez consulter le code source de la démonstration : Elastica Provider Demo.

À la recherche d'une nouvelle opportunité ? Voir toutes les offres
Découvrez quelques uns de nos projets Symfony Voir les projets

A lire aussi

Voir tous les articles