Ajouter une relation à une Vue de façon programmatique avec Drupal 8

une vue sur la montagne

Pour effectuer des requêtes et construire des pages de listing sur Drupal 8, ou tout autre contenu à collecter, nous disposons sur Drupal 8 des EntityQuery, de la Database API (cf. Faire une requête SQL sur plusieurs tables avec Drupal 8) ou encore du module Views qui nous fournit un requêteur et une interface graphique extrêmement puissant. Chacune de ces méthodes ont leur avantages et inconvénients et peuvent mieux convenir selon certaines situations (complexité de la requête, etc.).

Un des avantages des EntityQuery est de pouvoir faire facilement des requêtes complexes selon de nombreuses conditions dynamiques. Néanmoins les requêtes de Views peuvent aussi être altérées de façon dynamique, même si ce type d'altération peut s'avérer légèrement plus complexe du fait de la puissance de Views.

Un cas d'usage particulièrement utile peut être de rajouter une relation à une Vue de façon dynamique (l'équivalent du $query->join()). Prenons tout de suite un exemple, avec une vue basée sur l'entité Node (base table : node), pour laquelle nous souhaitons lui rajouter une relation basée sur un champ field_user référençant un utilisateur, et ainsi par la suite être en mesure d'altérer la requête sur la base des entités Users référencées par ce champ.

Pour ce faire, nous allons implémenter hook_views_query_alter() qui nous permet d'altérer la requête d'une Vue avant son exécution.

/**
 * Implements hook_views_query_alter().
 */
function my_module_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
  if ($view->id() == 'view_machine_name') {
    $current_user = $view->getUser();
    $condition = _my_module_custom_condition($current_user);
    if ($condition) {
      $definition = [
        'table' => 'node__field_user',
        'field' => 'entity_id',
        'left_table' => 'node',
        'left_field' => 'id',
      ];
      $join = \Drupal::service('plugin.manager.views.join')
        ->createInstance('standard', $definition);
      $query->addRelationship('my_relation', $join, 'node');

      $currentUserId = $current_user->id();
      $query->addWhereExpression('AND',
        'my_relation.field_user_target_id = :currentUid',
        ['currentUid' => $currentUserId]);
    }
  }
}

/**
 * Helper function to test a condition.
 *
 * @param \Drupal\Core\Session\AccountInterface $current_user
 *   The current user.
 *
 * @return bool
 *   TRUE if condition passed.
 */
function _my_module_custom_condition(AccountInterface $current_user) {
  $result = TRUE;
  // Some stuff.
  return $result;
}

Dans cet exemple, selon une condition métier quelconque, nous pouvons instancier une nouvelle relation $join, ajouter cette relation sous la dénomination 'my_relation' à la requête, puis ajouter une condition à notre requête basée sur cette nouvelle relation ajoutée.

N'étant pas un grand utilisateur de Views sauf pour des cas d'usage bien précis, j'ai découvert récemment l'utilisation de ce type de Plugin utilisé par Views pour créer et gérer les relations à une vue. Type de Plugin qui permet de faciliter des altérations complexes d'une vue de façon relativement claire. Nous pouvons ainsi avec l'aide d'un développeur Drupal utiliser des vues conçues par des profils de type site builder, pour ces cas d'usage relativement complexes, et gérées et maintenues par des utilisateurs de profil différent.

Tous les goûts sont dans la nature, et il faut de tout pour faire un monde.

 

Commentaires

Soumis par Condutiarii (non vérifié) le 04/12/2019 à 16:20 - Permalien

Bonjour,

Dans ton cas ne serait-il pas plus intéressant d'implémenter un filtre Views avec le plugin @ViewsFilter ?

Soumis par Felip Manyer i… (non vérifié) le 15/02/2022 à 17:16 - Permalien

Merci beaucoup Fabrice !

Quelques notes :
— Pour un INNER JOIN au lieu d'un LEFT JOIN par défaut, rajouter 'type' => 'INNER' dans la définition de la jointure.
— Tant la valeur de left_table que le troisième argument de addRelationship() correspondent à un alias. Si besoin est, vérifier lequel utilise Views en faisant en sorte que la requête s'affiche dans la prévisualisation.

Pour mémoire, j'ai tenté de faire l'inverse, à savoir définir des relations et un argument dans la configuration de la vue, et les retirer dynamiquement ($view->removeHandler($view->current_display, 'relationship' | 'argument', ID)), mais ça ne fonctionne que dans un hook_views_pre_view()... curieux, et surtout trop tard, puisque dans certaines conditions des sous-requêtes ont déjà pu être générées sur la base de la configuration initiale.

Ajouter un commentaire