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ć?


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.


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

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.


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;

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.


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

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

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

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

-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”.


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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.