Получить всех пользователей с конкретными ролями, используя EntityFieldQuery


35

Я думал, что это простая задача, но, похоже, для этого не существует метода Drupal. Я дошел до того, что понял, что должен использовать EntityFieldQueryдля этого - потому что API сказал, что условия для user_load_multiple()устарели.

Итак, я попробовал это:

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

  $result = $query->execute();

Все же я получил это:

PDOException: SQLSTATE [42S22]: столбец не найден: 1054 Неизвестный столбец «users.rid» в «where clause»: SELECT users.uid AS entity_id,: entity_type AS entity_type, NULL AS revision_id,: комплект AS, комплект FROM {пользователи} пользователи ГДЕ (users.rid =: db_condition_placeholder_0); Массив ([: db_condition_placeholder_0] => 3 [: entity_type] => user [: bundle] => user) в EntityFieldQuery-> execute ()

Так что моя первая мысль была, что я должен был бы присоединиться к users_roles-столам и так далее, но это будет приводить к дублям.

У кого-нибудь есть идеи как это сделать?


user_load_multiple () также не допускается. Надеюсь, у нас будет способ сделать это в D8.
Далин

Вы можете использовать мою песочницу EntityFieldQueryExtra, поскольку она позволяет ->propertyCondition('rid', array(1, 2, 3));
mikeytown2

Ответы:


32

Если честно, я понятия не имею, как этого добиться. Хорошие примеры того, как использовать EntityFieldQuery, трудно найти. Поскольку никто еще не ответил на этот вопрос, и я также заинтересован в решении, я постараюсь помочь вам. Описанное ниже - мое лучшее предположение.

Об этом следует помнить: роли хранятся в другой таблице, нежели пользователи. Они добавляются с помощью UserController :: attachLoad .

Кажется, есть три различных условия, которые вы можете использовать с EntityFieldQuery:

  1. entityCondition ( Особые условия объекта: «entity_type», «bundle», «revision_id» или «entity_id»)
  2. fieldCondition (Условия для полей, поддерживаемые Field API)
  3. propertyCondition (Условия на свойствах объекта)

Наиболее логичным вариантом (если он есть) было бы использование propertyCondition, но, поскольку роли добавляются пользователю в UserController :: attachLoad, я не думаю, что EntityFieldQuery имеет к нему доступ. Я думаю, что EntityFieldQuery просто использует схему, определенную в hook_schema ().

Это заставляет меня верить, что то, чего вы пытаетесь достичь, невозможно. Обходной путь - получить все идентификаторы с помощью обычного запроса:

У меня нет доступа к Drupal сейчас, поэтому код ниже может быть отключен. Я уверен, что кто-нибудь исправит ошибки.

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

Если можно достичь желаемого с EntityFieldQuery, я буду просвещен.


1
Да, это путь, которым я пошел. Тем не менее, мне очень интересно, как этого добиться с помощью EFQ, и я оставлю вопрос открытым. Жаль, что EFQ еще недостаточно хорошо документированы, поскольку в конечном итоге они могут полностью заменить Views для меня.
Нильс Ридеманн

EFQ поддерживает только базовые свойства объекта, которые являются частью таблицы объекта. Роли пользователя никоим образом не связаны с системой сущностей.
Бердир

1
Также совет по оптимизации: вы можете заменить инициализацию $ uids и foreach на $uids = $result->fetchColumn().
Бердир

Не стесняйтесь редактировать код Berdir :)
Барт

19

Это должно сделать свое дело

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

Интересно, почему этот ответ имеет так мало голосов. Это лучшее решение. Хотя я должен отметить, что это $query->leftJoin('users_roles', 'r', 'users.uid = r.uid');может привести к неизвестному столбцу «uid». В этом случае нам нужно добавить $query->propertyCondition('uid', 0, '!='), чтобы таблица пользователей была в запросе.
Elaman

1
Определенно это должен быть принятый ответ.
Фрэнк Роберт Андерсон

16

На самом деле есть способ сделать это. По своей сути EntityFieldQuery (EFQ) - это просто запрос к базе данных, который можно изменить с помощью хуков изменения запроса.

Простейший возможный пример:

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

Тем не менее, есть несколько небольших предостережений, которые потребуют еще большего кодирования. Например, в отношении единственного условия, присутствующего в EFQ, являющегося fieldCondition, пользовательская база данных не будет присутствовать, поэтому, когда вы выбираете пользователей по роли и отдельному fieldCondition, вам также необходимо убедиться, что Таблица пользователей присоединяется, а если нет, присоединяется к ней вручную, что немного утомительно.

Но для ваших нужд это поможет. Понимание того, что вы можете вручную изменять запросы EFQ, открывает огромный мир возможностей для создания очень мощных запросов, сохраняя при этом интерфейс в чистоте и Drupal.

Дайте мне знать, если у вас есть какие-либо вопросы или проблемы.

Редактировать: я на самом деле нашел более эффективный способ сделать это и изменил свой код соответственно.


Один из способов «взломать» базовое требование - всегда добавлять поддельное условие свойства, например, $ query-> propertyCondition ('uid', 0, '! ='); на запрос. Это вынуждает связывать базовую таблицу, но лишь немного снижает производительность, поскольку EFQ не учитывает базовую таблицу в случае единственного fieldCondition, а именно в том, что гораздо быстрее просто запрашивать таблицу данных поля.
Томми Форсстрем

2
Я не думаю, что это довольно простой пример, как утверждается. Разве вы не можете отказаться от подзапроса в пользу двух соединений и одного условия $query? И я мог бы также предложить изменить, mymodule_get_users_by_rolenameчтобы не возвращать результат самого запроса, а чтобы разыменовать ключ 'user'.
Питер Тейлор,

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

Вы по-прежнему можете использовать EntityFieldQuery (), как только вы получите идентификаторы пользователей для своей роли, например, если вы хотите использовать fieldCondition () или другой метод.

Используя массив $ uids в приведенном выше примере:

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

Было бы лучше, если бы EntityFieldQuery имел метод соединения.


1
Если у вас есть несколько десятков пользователей с этой ролью, этот EFQ будет иметь ужасную производительность.
Далин

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

Я думаю, что это, вероятно, самый эффективный ответ. В качестве небольшого улучшения я бы добавил параметр $rolename, который будет использоваться при вызове, user_role_load_by_nameи проверку, чтобы убедиться, что user_role_load_by_nameон не возвращает false.
stefgosselin

4

Хорошо, это старый, но вы могли бы сделать что-то вроде этого:

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

Я передумал, это лучший ответ. За исключением того, что вы пропустили точку с запятой, а $ my_role_id не определен.
Фрэнк Роберт Андерсон

2

Это немного поздно для игры, но я думаю, что это то, о чем просил ОП. Например, нет sql, все друпал. Надеюсь, другие найдут это полезным. Поиски этого привели меня сюда, и это то, что я придумал.

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

Если у вас есть несколько десятков пользователей с этой ролью, этот EFQ будет иметь ужасную производительность.
Далин

@Dalin, учитывая начальную квалификацию (т.е. нет sql, все drupal), что бы вы предложили в качестве метода, который позволил бы получить более высокую производительность?
Золото

1
Привет, золото! Похоже, что условие db_select () ожидает, что $ role была строкой, и если вы передадите целое число, оно попадет на $ role_obj, который не устанавливается, когда ссылается на $ role_obj-> rid. РЕДАКТИРОВАТЬ: Я могу редактировать ответы, Ву.
Крис Берджесс

0

Это действительно старая тема, и я нашел много хороших подходов.

Я бы немного улучшил решение, предоставленное @alf, потому что я думаю, что один запрос с объединением - лучший подход. Однако мне также нравится, что мои функции имеют параметры и не зависят от константы. Так что вот так:

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

Вы можете найти хорошие примеры объяснений о том, как использовать EntityFieldQuery здесь:

  • http://drupal.org/node/1343708 (в этой статье вы найдете больше полезных ресурсов, учебных пособий и примеров)

но, как сказал Бердир, на данный момент «EFQ поддерживает только базовые свойства объекта, которые являются частью таблицы объектов. Роли пользователя никоим образом не раскрываются для системы объектов».


-2

Я не сомневаюсь, что это можно упростить. Это может быть сделано для 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);

Лучше использовать запрос напрямую для сбора пользовательских писем, а не загружать их все
Codium

это решение в одном из тех ответов?
Матеил
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.