Obtenez tous les utilisateurs avec des rôles spécifiques en utilisant EntityFieldQuery

35

Je pensais que c'était une tâche facile, mais il ne semble pas y avoir de méthode Drupal pour cela. Je suis allé aussi loin en sachant que je devais utiliser EntityFieldQuerypour cela - parce que l'API dit que les conditions user_load_multiple()sont obsolètes.

Alors j'ai essayé ceci:

  $query = new EntityFieldQuery;
  $query
    ->entityCondition('entity_type', 'user')
    ->propertyCondition('rid',array(1,2,3);

  $result = $query->execute();

Pourtant j'ai eu ceci:

PDOException: SQLSTATE [42S22]: Colonne introuvable: 1054 Colonne inconnue 'users.rid' dans 'clause where': SELECT utilisateurs.uid AS id_entité,: type_entité AS, type_entité, NULL AS revision_id,: bundle AS bundle FROM {utilisateurs} utilisateurs WHERE (users.rid =: db_condition_placeholder_0); Tableau ([: db_condition_placeholder_0] => 3 [: type_entité] => utilisateur [: bundle] => utilisateur) dans EntityFieldQuery-> execute ()

Ma première pensée était que je dois joindre à users_roles-Table et ainsi de suite, mais ce sera conduire à des doublons.

Est-ce que quelqu'un a une idée comment le faire?

Nils Riedemann
la source
user_load_multiple () est également un non-aller. Espérons que nous aurons un moyen de faire cela dans D8.
Dalin
Vous pouvez utiliser mon sandbox EntityFieldQueryExtra dans la mesure où il le permet ->propertyCondition('rid', array(1, 2, 3));
mikeytown2

Réponses:

32

Pour être honnête, je ne sais pas comment y parvenir. Il est difficile de trouver de bons exemples d'utilisation de EntityFieldQuery. Étant donné que personne n'a encore répondu à cette question et que la solution, je vais essayer de vous aider. Décrit ci-dessous est ma meilleure hypothèse.

Une chose à garder à l'esprit: les rôles sont stockés dans une table différente de celle des utilisateurs. Ils sont ajoutés en utilisant UserController :: attachLoad .

Il semble y avoir trois conditions différentes que vous pouvez utiliser avec un EntityFieldQuery:

  1. entityCondition (Conditions spécifiques à l'entité: 'type_entité', 'bundle', 'id_vision' ou 'id_entité')
  2. fieldCondition (Conditions sur les champs gérés par l'API de champ)
  3. propertyCondition (Conditions sur les propriétés de l'entité)

L’option la plus logique (s’il en existe une) serait d’utiliser une propriétéCondition, mais à mesure que les rôles sont ajoutés à l’utilisateur dans UserController :: attachLoad, je ne pense pas que EntityFieldQuery y ait accès. Je pense que EntityFieldQuery utilise simplement le schéma défini dans hook_schema ().

Cela me porte à croire que ce que vous essayez de réaliser est impossible. Une solution de contournement serait d'obtenir tous les uids avec une requête normale:

Je n'ai pas accès à Drupal pour le moment, le code ci-dessous est peut-être désactivé. Je suis sûr que quelqu'un corrigera les erreurs.

// Use $query for readability
$query = 'SELECT DISTINCT(ur.uid) 
  FROM {users_roles} AS ur
  WHERE ur.rid IN (:rids)';
$result = db_query($query, array(':rids' => array(1,2,3)));

$uids = $result->fetchCol();

$users = user_load_multiple($uids);

S'il est possible d'atteindre ce que vous voulez avec EntityFieldQuery, je serai éclairé.

Bart
la source
1
Ouais, c'est comme ça que je suis allé. Pourtant, je suis très curieux de savoir comment y parvenir avec EFQ et la question reste ouverte. Il est dommage que les EFQ ne soient pas encore bien documentés, car ils pourraient remplacer totalement View pour moi à long terme.
Nils Riedemann le
EFQ ne prend en charge que les propriétés de base d'une entité, qui font partie de la table d'entités. Les rôles d'un utilisateur ne sont en aucun cas exposés au système d'entité.
Berdir
1
Aussi, indice d'optimisation: vous pouvez remplacer l'initalisation $ uids et le foreach par $uids = $result->fetchColumn().
Berdir
N'hésitez pas à éditer le code Berdir :)
Bart
19

Ça devrait faire l'affaire

  $query = new EntityFieldQuery;
  $query
    ->entityCondition('entity_type', 'user')
    ->addTag('role_filter');
  $results = $query->execute();

/**
* Implement hook_query_TAG_alter
* 
* @param QueryAlterableInterface $query
*/
function MY_MODULE_query_role_filter_alter(QueryAlterableInterface $query) {
  $query->leftJoin('users_roles', 'r', 'users.uid = r.uid');  
  $and = db_and()
            ->condition('r.rid', MY_ROLE_INT, '=');
  $query
    ->condition($and);
}
alf
la source
Je me demande pourquoi cette réponse a si peu de votes positifs. C'est la meilleure solution. Bien que je devrais noter que cela $query->leftJoin('users_roles', 'r', 'users.uid = r.uid');pourrait causer une colonne inconnue 'uid'. Dans ce cas, nous devons ajouter $query->propertyCondition('uid', 0, '!=')pour que la table des utilisateurs soit dans la requête.
Elaman
1
Cela devrait certainement être la réponse acceptée.
Frank Robert Anderson
16

Il y a en fait un moyen de le faire. En son cœur, EntityFieldQuery (EFQ) est simplement une requête de base de données qui peut être modifiée avec les hooks alter alter de requête.

Exemple le plus simple possible:

function mymodule_get_users_by_rolename($rolename){
  $query = new EntityFieldQuery;
  $query->entityCondition('entity_type', 'user');
  $query->addTag('rolequery');
  $query->addMetaData('rolename', $rolename);

  return $query->execute();
}

function mymodule_query_rolequery_alter(QueryAlterableInterface $query) {
  $rolename = $query->getMetaData('rolename');

  $role_subquery = db_select("role", "role");
  $role_subquery->condition('role.name', $rolename, '=');
  $role_subquery->join('users_roles', "users_to_include", "role.rid = users_to_include.rid");
  $role_subquery->fields('users_to_include', array('uid' => 'uid'));
  $role_subquery->where('users_to_include.uid = users.uid');
  $query->exists($role_subquery);
}

Cependant, il y a quelques petites mises en garde qui nécessiteraient un codage supplémentaire. Par exemple, dans le cas où la seule condition présente dans EFQ est une condition de champ, la base-utilisateur ne sera pas présente. Ainsi, lorsque vous recherchez des utilisateurs par rôle et une seule condition de champ, vous devez également vous assurer que La table users est jointe et si ce n’est pas le cas, joignez-la manuellement, ce qui est un processus fastidieux.

Mais pour vos besoins, cela fera l'affaire. Réaliser que vous pouvez modifier manuellement les requêtes EFQ ouvre un monde immense d'opportunités pour créer des requêtes très puissantes tout en maintenant l'interface propre et Drupal.

Faites-moi savoir si vous avez des questions ou des problèmes.

Edit: J'ai en fait trouvé un moyen un peu plus performant de faire cela et modifié mon code en conséquence.

Tommi Forsström
la source
Une façon de "pirater" l'exigence fondamentale est de toujours ajouter une fausse propriété property comme $ query-> propertyCondition ('uid', 0, '! ='); à la requête. Cela oblige la base à être liée, mais nuit légèrement aux performances, car EFQ ne la laisse pas dans le cas d’un seul champ. La condition est qu’il est beaucoup plus rapide d’interroger la table de données du champ.
Tommi Forsström
2
Je ne pense pas que ce soit l'exemple le plus simple possible, comme l'a affirmé. Ne pouvez-vous pas abandonner la sous-requête en faveur de deux jointures et d'une condition directement $query? Et je pourrais aussi suggérer de changer mymodule_get_users_by_rolenamepour ne pas renvoyer le résultat de la requête elle-même, mais pour déréférencer la clé 'user'.
Peter Taylor
6
$user_list = array();
$roles = array('role_1', 'role_2');
$query = db_select('users_roles', 'ur');
$query->join('users', 'u', 'u.uid = ur.uid');
$query->join('role', 'r', 'r.rid = ur.rid');
$query->fields('u',array('uid'));
$query->fields('u',array('mail'));
$query->fields('u',array('name'));

$query->condition('r.name', $roles, 'IN');
$result = $query->execute();

if($result){
    foreach($result as $row ) {
        $uid = $row->uid;
        $user_list[$uid]['mail'] = $row->mail;
        $user_list[$uid]['name'] = $row->name;
        $user_list[$uid]['uid'] = $uid;
    }
}

return $user_list;
Dhanesh Haridas
la source
5

Vous pouvez toujours utiliser EntityFieldQuery () une fois que vous avez obtenu les ID utilisateur de votre rôle, si vous le souhaitez, par exemple parce que vous souhaitez utiliser fieldCondition () ou une autre méthode.

Utilisation du tableau $ uids dans l'exemple ci-dessus:

$role = user_role_load_by_name('my_role_name');
$result = db_select('users_roles', 'ur')
->fields('ur', array('uid'))
->condition('rid', $role->rid)
->execute();

foreach($result as $record) {
  $uids[] = $record->uid;
}

$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'user')
->entityCondition('entity_id', $uids, 'IN')
->execute();

Il serait préférable que EntityFieldQuery ait une méthode de jointure.

JUPITER
la source
1
Si vous avez plus de quelques dizaines d'utilisateurs avec ce rôle, cet EFQ aura des performances terribles.
Dalin
5

/**
 * Get users by specific role
 */
function mymodule_get_users_by_role() {
    $role = user_role_load_by_name('rolename');
    $uids = db_select('users_roles', 'ur')
        ->fields('ur', array('uid'))
        ->condition('ur.rid', $role->rid, '=')
        ->execute()
        ->fetchCol();
    $users = user_load_multiple($uids);
}
je mord
la source
Je pense que c'est probablement la réponse la plus efficace. En guise de légère amélioration, j’ajouterais un paramètre $rolenamequi serait utilisé dans l’appel à user_role_load_by_nameet une vérification pour nous assurer user_role_load_by_namequ’elle ne renvoie pas la valeur false.
stefgosselin
4

OK Ceci est un ancien, mais vous pouvez faire quelque chose comme ceci:

$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'user')

$roles_subquery = db_select('users_roles', 'ur');
$roles_subquery->fields('ur', array('uid'));
$roles_subquery->condition('rid', $my_role_id);

$query->propertyCondition('uid', $roles_subquery, 'IN');
Fabio Epifani
la source
J'ai changé d'avis c'est la meilleure réponse. Sauf qu'il manque un point-virgule et que $ my_role_id n'est pas défini.
Frank Robert Anderson
2

C'est un peu tard pour le jeu, mais je pense que c'est ce que l'OP demandait. par exemple pas de sql, tous drupal. J'espère que d'autres le trouveront utile. La recherche de cela m’a conduit ici et c’est ce que j’ai trouvé.

    /**
     * Users with role
     *
     * @param $role mixed The name or rid of the role we're wanting users to have
     * @param $active_user boolean Only return active accounts?
     *
     * @return array An array of user objects with the role
     */
    function users_with_role($role, $active_user = TRUE) {
      $uids = array();
      $users = array();
      if (is_int($role)) {
        $my_rid = $role;
      }
      else {
        $role_obj = user_role_load_by_name($role);
      }
      $result = db_select('users_roles', 'ur')
        ->fields('ur')
        ->condition('ur.rid', $role_obj->rid, '=')
        ->execute();
      foreach ($result as $record) {
        $uids[] = $record->uid;
      };
      $query = new EntityFieldQuery();
      $query->entityCondition('entity_type', 'user')
        ->propertyCondition('uid', $uids, 'IN');
      if ($active_user) {
        $query->propertyCondition('status', 1);
      }
      $entities = $query->execute();
      if (!empty($entities)) {
        $users = entity_load('user', array_keys($entities['user']));
      }
      return $users;
    }
Or
la source
Si vous avez plus de quelques dizaines d'utilisateurs avec ce rôle, cet EFQ aura des performances terribles.
Dalin
@Dalin, étant donné la qualification initiale (c’est-à-dire pas de sql, tous les drupal), que suggéreriez-vous comme méthode pour obtenir de meilleures performances?
Or
1
Hey Gold! On dirait que la condition db_select () s'attend à ce que $ role soit une chaîne, et si vous transmettez un entier, il sera capturé si $ role_obj n'est pas défini lorsqu'il fait référence à $ role_obj-> rid. EDIT: Je peux modifier les réponses, woo.
Chris Burgess
0

C'est en effet un sujet plus ancien et j'ai trouvé beaucoup de bonnes approches.

J'améliorerais un peu la solution fournie par @alf, car je pense qu'une requête avec join est la meilleure approche. Cependant, j'aime aussi que mes fonctions aient des paramètres et ne dépendent pas d'une constante. Alors voilà:

function MY_MODULE_load_users_by_role($rid) {
  $query = new EntityFieldQuery;
  $query
    ->entityCondition('entity_type', 'user')
    ->addMetaData('account_role', user_role_load($rid))
    ->addTag('role_filter');
  $results = $query->execute();

  if (isset($results['user'])) {
    $uids = array_keys($results['user']);
    $users = entity_load('user', $uids);
  }
  else {
    $users = array();
  }

  return $users;
}

/**
 * Implement hook_query_TAG_alter
 *
 * @param QueryAlterableInterface $query
 */
function MY_MODULE_query_role_filter_alter(QueryAlterableInterface $query) {
  $rid = $query->alterMetaData['account_role']->rid;
  $query->leftJoin('users_roles', 'r', 'users.uid = r.uid');
  $and = db_and()
    ->condition('r.rid', $rid, '=');
  $query
    ->condition($and);
}
benelori
la source
-1

Vous trouverez ici de bons exemples d’explications sur l’utilisation de EntityFieldQuery:

mais, comme Berdir l'a dit pour le moment, "EFQ ne prend en charge que les propriétés de base d'une entité, qui font partie de la table d'entités. Les rôles d'un utilisateur ne sont en aucune manière exposés au système de l'entité".

k_zoltan
la source
-2

Je ne doute pas que cela puisse être simplifié. Cela peut être fait cependant pour Drupal 8:

//retrieve all "VIP" members
$query = \Drupal::entityQuery('user');
$nids = $query->execute();
foreach ($nids as $nid){
    $user = \Drupal\user\Entity\User::load($nid);
    if ($user->hasRole('VIP'))
        $emails_VIP_members[]=$user->getEmail();
}
 $send_to=implode(',',$emails_VIP_members);
Matoeil
la source
Mieux vaut utiliser une requête directement pour collecter les mails des utilisateurs, au lieu de les charger tous
Codium
cette solution est-elle dans l'une de ces réponses?
Matoeil le