Uzyskaj wszystkich użytkowników z określonymi rolami za pomocą EntityFieldQuery

35

Myślałem, że to łatwe zadanie, ale wydaje się, że nie ma do tego metody Drupala. Doszedłem do tego stopnia, że ​​wiedziałem, że muszę do tego użyć EntityFieldQuery- ponieważ API powiedział, że warunki user_load_multiple()są przestarzałe.

Więc próbowałem tego:

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

  $result = $query->execute();

Ale mam to:

PDOException: SQLSTATE [42S22]: Nie znaleziono kolumny: 1054 Nieznana kolumna „users.rid” w klauzuli „where”: SELECT users.uid AS entity_id,: entity_type AS entity_type, NULL AS revision_id,: pakiet AS pakiet OD {users} użytkowników GDZIE (users.rid =: db_condition_placeholder_0); Tablica ([: db_condition_placeholder_0] => 3 [: entity_type] => użytkownik [: pakiet] => użytkownik) w EntityFieldQuery-> execute ()

Więc moją pierwszą myślą było to, że będę musiał przyłączyć się users_roles-Tabela i tak dalej, ale to będzie prowadzić do duplikatów.

Czy ktoś ma pomysł, jak to zrobić?

Nils Riedemann
źródło
user_load_multiple () jest również nie do przejścia. Mamy nadzieję, że będziemy w stanie to zrobić w D8.
Dalin
Możesz użyć mojej piaskownicy EntityFieldQueryExtra, ponieważ pozwala ->propertyCondition('rid', array(1, 2, 3));
mikeytown2

Odpowiedzi:

32

Szczerze mówiąc, nie mam pojęcia, jak to osiągnąć. Dobre przykłady użycia EntityFieldQuery są trudne do znalezienia. Ponieważ nikt jeszcze nie odpowiedział na to pytanie i jestem również zainteresowany rozwiązaniem, postaram się pomóc. Opisane poniżej moje najlepsze przypuszczenie.

Należy pamiętać: role są przechowywane w innej tabeli niż użytkownicy. Są dodawane przy użyciu UserController :: attachLoad .

Wydaje się, że istnieją trzy różne warunki, których można użyć z EntityFieldQuery:

  1. entityCondition (warunki specyficzne dla encji: „typ_jednostki”, „pakiet”, „id_wersji” lub „id_jednostki”)
  2. fieldCondition (Warunki dotyczące pól obsługiwanych przez Field API)
  3. propertyCondition (Warunki dotyczące właściwości encji)

Najbardziej logiczną opcją (jeśli taka istnieje) byłoby użycie właściwości conditionCondition , ale ponieważ role są dodawane do użytkownika w UserController :: attachLoad , nie sądzę, aby EntityFieldQuery miał do niego dostęp. Myślę, że EntityFieldQuery po prostu używa schematu zdefiniowanego w hook_schema ().

To prowadzi mnie do przekonania, że ​​to, co próbujesz osiągnąć, jest niemożliwe. Obejściem tego problemu byłoby pobranie wszystkich identyfikatorów UID za pomocą zwykłego zapytania:

Nie mam teraz dostępu do Drupala, więc poniższy kod może być wyłączony. Jestem pewien, że ktoś będzie edytować błędy.

// 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);

Jeśli możliwe jest osiągnięcie tego, co chcesz dzięki EntityFieldQuery, będę oświecony.

Bart
źródło
1
Tak, właśnie tak poszedłem. Jestem jednak bardzo ciekawy, jak to osiągnąć za pomocą EFQ i pozostawiam pytanie otwarte. Szkoda, że ​​EFQ nie są jeszcze tak dobrze udokumentowane, ponieważ mogą całkowicie zastąpić dla mnie Widoki na dłuższą metę.
Nils Riedemann,
EFQ obsługuje tylko podstawowe właściwości encji, które są częścią tabeli encji. Role użytkownika nie są w żaden sposób ujawniane systemowi encji.
Berdir
1
Również wskazówka optymalizacyjna: Możesz zastąpić inicjalizację $ uids i foreach na $uids = $result->fetchColumn().
Berdir
Edytuj kod Berdir :)
Bart
19

To powinno wystarczyć

  $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
źródło
Zastanawiam się, dlaczego ta odpowiedź ma tak niewiele głosów. To najlepsze rozwiązanie. Chociaż powinienem zauważyć, że $query->leftJoin('users_roles', 'r', 'users.uid = r.uid');może to powodować nieznane kolumny „UID”. W takim przypadku musimy dodać $query->propertyCondition('uid', 0, '!='), aby tabela użytkowników znajdowała się w zapytaniu.
Elaman
1
Zdecydowanie powinna to być zaakceptowana odpowiedź.
Frank Robert Anderson
16

Jest na to sposób. W swoim sercu EntityFieldQuery (EFQ) jest po prostu zapytaniem do bazy danych, które można zmienić za pomocą haków do zmiany zapytania.

Najprostszy możliwy przykład:

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);
}

Istnieje jednak kilka małych zastrzeżeń, które wymagałyby trochę kodowania. Na przykład pod warunkiem, że jedynym warunkiem występującym w EFQ jest warunek polowy, baza użytkowników nie będzie obecna, więc gdy pobierasz użytkowników według roli i pojedynczego pola warunek, musisz również upewnić się, że Tabela użytkowników jest dołączana, a jeśli nie, ręcznie dołącz do niej, co jest trochę żmudnym procesem.

Ale na twoją potrzebę to załatwi sprawę. Zdanie sobie sprawy z tego, że możesz ręcznie zmieniać zapytania EFQ, otwiera ogromny świat możliwości tworzenia bardzo potężnych zapytań przy jednoczesnym utrzymaniu czystości interfejsu i Drupala.

Daj mi znać, jeśli masz jakieś pytania lub problemy.

Edycja: Znalazłem trochę bardziej wydajny sposób na zrobienie tego i odpowiednio zmieniłem kod.

Tommi Forsström
źródło
Jednym ze sposobów „zhakowania” wymogu bazy jest zawsze dodanie fałszywego warunku właściwości, takiego jak $ query-> propertyCondition ('uid', 0, '! = =); do zapytania. Wymusza to połączenie tabeli bazowej, ale tylko nieco obniża wydajność, ponieważ EFQ pomija tabelę podstawową w przypadku pojedynczego pola. Warunkiem jest to, że o wiele szybsze jest zapytanie tabeli danych pola.
Tommi Forsström
2
Jak sądzę, nie sądzę, że jest to najprostszy możliwy przykład. Czy nie można porzucić podzapytania na korzyść dwóch złączeń i jednego warunku bezpośrednio na $query? Mogę również zasugerować zmianę, mymodule_get_users_by_rolenameaby nie zwracała wyniku samego zapytania, ale aby oderwać klucz '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
źródło
5

Nadal możesz używać EntityFieldQuery () po otrzymaniu identyfikatorów użytkowników dla swojej roli, jeśli chcesz, na przykład dlatego, że chcesz użyć fieldCondition () lub innej metody.

Używając tablicy $ uids w powyższym przykładzie:

$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();

Byłoby ładniej, gdyby EntityFieldQuery miała metodę łączenia.

JOWISZ
źródło
1
Jeśli masz więcej niż kilkudziesięciu użytkowników z tą rolą, ten EFQ będzie miał straszną wydajność.
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);
}
ibit
źródło
Myślę, że jest to prawdopodobnie najbardziej skuteczna odpowiedź. Jako niewielką poprawę dodałbym parametr, $rolenamektóry zostałby użyty w wywołaniu user_role_load_by_namei sprawdzenie, aby upewnić się, user_role_load_by_nameże nie zwraca false.
stefgosselin
4

OK To jest stary, ale możesz zrobić coś takiego:

$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
źródło
Zmieniłem zdanie, to najlepsza odpowiedź. Tyle że brakuje Ci średnika, a $ my_role_id jest niezdefiniowany.
Frank Robert Anderson,
2

Trochę za późno na grę, ale myślę, że o to poprosił OP. np. bez sql, wszystkie drupal. Mam nadzieję, że inni uznają to za przydatne. Poszukiwania tego doprowadziły mnie tutaj i właśnie to wymyśliłem.

    /**
     * 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;
    }
Złoto
źródło
Jeśli masz więcej niż kilkudziesięciu użytkowników z tą rolą, ten EFQ będzie miał straszną wydajność.
Dalin
@Dalin, biorąc pod uwagę początkową kwalifikację (tj. Brak sql, cały drupal), co zaproponowałbyś jako metodę, która uzyskałaby lepszą wydajność?
Złoto
1
Hej, złoto! Wygląda na to, że warunek db_select () oczekuje, że $ rola była ciągiem, a jeśli podasz liczbę całkowitą, zostanie on złapany na tym, że $ rola_obj nie jest ustawiona, gdy odwołuje się do $ rola_obj-> rid. EDYCJA: Mogę edytować odpowiedzi, woo.
Chris Burgess,
0

To jest rzeczywiście starszy temat i znalazłem wiele dobrych podejść.

Poprawiłbym trochę rozwiązanie dostarczone przez @alf, ponieważ myślę, że jedno zapytanie z łączeniem jest najlepszym podejściem. Jednak lubię też, aby moje funkcje miały parametry i nie zależały od stałej. A więc oto:

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
źródło
-1

Dobre objaśnienia dotyczące korzystania z EntityFieldQuery można znaleźć tutaj:

ale tak jak na razie powiedział Berdir: „EFQ obsługuje tylko podstawowe właściwości encji, które są częścią tabeli encji. Role użytkownika nie są w żaden sposób narażone na system encji”.

k_zoltan
źródło
-2

Nie mam wątpliwości, że można to uprościć. Można to jednak zrobić w przypadku drupala 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
źródło
Lepiej używać zapytania bezpośrednio do zbierania wiadomości od użytkowników, zamiast ładowania ich wszystkich
Codium
czy to rozwiązanie jest jedną z tych odpowiedzi?
Matoeil