Réaliser des tris sur des vues indexées de Drupal Search API

Photo de lego

J'ai été confronté récemment à un bug pour le moins atypique. J'obtenais des contenus dupliqués et/ou manquants sur différentes paginations d'une même vue basée sur Search API. Un bug présent sur le site de production, sur le site de preprod mais différement, et quasi-absent de l'instance de développement. Et le tout apparaissant de façon aléatoire. Après avoir chercher sans relâche sur les termes "drupal search api duplicate sorting" ou "drupal search api duplicate pagination" ou toutes les déclinaisons possibles, je souhaite partager le résultat d'un debug bien corsé.

Description et origine du problème

Si à l'occasion vous avez à réaliser un site e-commerce comportant des filtres à facettes et des fonctions de tri, vous aurez très certainement l'occasion d'utiliser les excellents modules Search Api, Facet API et Search Api sorts pour implémenter rapidement des vues de produits avec des filtres à facettes et une fonction de tri.

Vous pouvez être alors confronté à devoir proposer des filtres de tri basés sur des champs booléens, ou des champs dont les valeurs sont potentiellement identiques, par exemple un champ promotion implémenté avec hook_entity_property_info_alter() qui calcule le montant de la promotion afin de pouvoir trier les produits en fonction du montant de la promotion.

Vous risquez alors d'être confronté à un problème de produits dupliqués et/ou manquants, en utilisant la fonction de tri combinée à une vue avec pagination. En effet, le propre du module Search API sorts est qu'il nécessite pour fonctionner une vue originelle sans tri initial. Mais comment une requête trie-elle ses résultats si ses critères de tri sont tous égal à zéro, ou une valeur identique ? Vous alllez obtenir des résultats consistants si la requête est unique, mais aléatoires dès lors que vous mettrez en place une pagination. 

Le propre de la pagination est de proposer des requêtes limitées, améliorant l'expérience utilisateur et les ressources du serveur, en effectuant une requête depuis un décalage et pour un nombre limité d'éléments. Si le tri d'une requête peut être consistant sans pagination, il en va autrement à chaque requête appelant les éléments pour une pagination précise, si la requête ne dispose pas d'un ordre de tri consistant de la totalité des éléments au travers de toutes les paginations.

Le plus ardu a été d'identifier la source de ce bug. Une fois fait, c'est 90% du travail accompli.

La solution

Search API sorts nécessitant que la vue originielle ne dispose pas de tri, pour lui appliquer le sien propre, pour palier à ce problème il suffit d'affecter un second critère de tri, après celui ajouté par Search API sorts, à une vue sur des contenus indéxés.

Autrement dit, nous allons ajouter à chaque requête de Search API sorts notre propre critère de tri, qui permettra d'assurer la consistance du tri quelque soit la pagination et le critère de tri utilisé.

Nous allons donc implémenter le HOOK hook_search_api_query_alter(). Et utiliser le nid de nos contenus, qui devra bien sûr être indexé, pour s'assurer d'un tri consistant quelque soit le critère de tri utilisé. Le choix du nid n'est pas anodin, car nous avons l'assurance avec celui-ci de son unicité. Ce qui n'est pas le cas par exemple avec la date de création, car lors d'un import en masse des produits, certains d'entre eux auront des dates identiques à la seconde près.

Tout simplement cela ce traduit par :

 /**
 * Implements hook_search_api_query_alter
 */
function MYMODULE_search_api_query_alter(SearchApiQueryInterface $query) {
  // Add extra default sort on nid to ensure consistancy for query between differents pager
  $options = $query->getOptions();
  switch ($options['search id']) {
    case 'search_api_views:VIEW_NAME:VIEW_DISPLAY':
      $query->sort('nid', 'DESC');
      break;
  }
} 

Cinq lignes de code ridicules, vu à posteriori, mais qui pourront vous épargner peut-être quelques nuits blanches. Un autre élément important est que vous devez vous assurer que l'altération de la requête de Search API se produise après l'altération effectuée par Search API sorts. Pour cela, vous devrez implémenter le HOOK hook_module_implements_alter().

 /**
 * Implements hook_module_implements_alter().
 */
function MYMODULE_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'search_api_query_alter' && isset($implementations['MYMODULE'])) {
    $group = $implementations['MYMODULE'];
    unset($implementations['MYMODULE']);
    $implementations['MYMODULE'] = $group;
  }
} 

Avec ces quelques lignes de code, vous aurez résolu cette question épineuse de tri sur des critères potentiellement égaux et problématiques si vos pages de résultats proposent une pagination. Si cela peut aider, j'en serai ravi.

 

Commentaires

Soumis par JuanMvr (non vérifié) le 22/05/2019 à 11:51 - Permalien

Bonjour Flocon,
je déterre un peu le sujet, mais je rencontre le même soucis sur Drupal 8 avec Search api et une vue qui est paginé, je n'utilise pas Search Api Sort
A tu un retour d'expérience sur D8 ?

Soumis par JuanMvr (non vérifié) le 23/05/2019 à 14:43 - Permalien

En réponse à par JuanMvr (non vérifié)

Retour d'expérience pour drupal 8 avec Search Api + Facets + Une vue paginer sur des Porduits Commerce:

/**
* Implements hook_search_api_query_alter
*
* @param \Drupal\search_api\Query\QueryInterface $query
*/
function site_base_search_api_query_alter(QueryInterface $query) {

$serachId = $query->getSearchId();
switch ($serachId) {
case 'views_page:VIEW_NAME:VIEW_DISPLAY':
$query->sort('product_id', 'DESC');
break;
}
}

et la fonction implements ne change pas

/**
* Implements hook_module_implements_alter().
*/
function MYMODULE_module_implements_alter(&$implementations, $hook) {
if ($hook == 'search_api_query_alter' && isset($implementations['MYMODULE'])) {
$group = $implementations['MYMODULE'];
unset($implementations['MYMODULE']);
$implementations['MYMODULE'] = $group;
}
}

Ajouter un commentaire