Как фильтровать по условиям для связанных моделей?

У меня есть ассоциация belongsToMany для пользователей и контактов.

Я хотел бы найти Контакты данного Пользователя. Мне нужно что-то вроде

$this->Contacts->find()->contain(['Users' => ['Users.id' => 1]]);

В кулинарной книге говорится о предоставлении условий для содержания, пользовательских методах поиска и пения через ключ ассоциации, но я не узнал, как их собрать.

Ответ 1

Используйте Query :: Match() или Query :: innerJoinWith()

При запросе из таблицы Contacts вы ищете Query::matching() или Query::innerJoinWith(), а не (только) Query::contain().

См. Cookbook> Доступ к базе данных & ORM> Построитель запросов> Фильтрация по связанным данным

Вот пример использования ваших таблиц:

$this->Contacts
    ->find()
    ->matching('Users', function(\Cake\ORM\Query $q) {
        return $q->where(['Users.id' => 1]);
    });

Это автоматически добавит необходимые условия объединения + в сгенерированный запрос, так что будут извлечены только те контакты, которые связаны хотя бы с одним пользователем с идентификатором 1.

Таргетинг на таблицу соединений

В случае, если вам нужно было вручную установить связь "многие ко многим" через hasMany и belongsTo, вы можете напрямую нацелить таблицу объединения:

$this->Contacts
    ->find()
    ->matching('ContactsUsers', function(\Cake\ORM\Query $q) {
        return $q->where(['ContactsUsers.user_id' => 1]);
    });

Включая защитные оболочки

Если вы действительно хотите, чтобы все ассоциации были возвращены в ваших результатах, просто продолжайте использовать contain():

$this->Contacts
    ->find()
    ->contain('Users')
    ->matching('Users', function(\Cake\ORM\Query $q) {
        return $q->where(['Users.id' => 1]);
    });

Это будет содержать всех пользователей, принадлежащих контакту.

Ограничение содержания

В тех случаях, когда у вас есть несколько совпадений, и вы хотите содержать только эти совпадения, вам также придется отфильтровать содержимое. В этом примере это не имеет особого смысла, поскольку будет только одно совпадение, но в других ситуациях это может быть полезно, например, если вы хотите сопоставить все контакты, у которых есть активные пользователи, и извлечь контакты, включая только Активные связанные пользователи:

$this->Contacts
    ->find()
    ->contain(['Users' => function(\Cake\ORM\Query $q) {
        return $q->where(['Users.active' => true]);
    }])
    ->matching('Users', function(\Cake\ORM\Query $q) {
        return $q->where(['Users.active' => true]);
    })
    ->group('Contacts.id');

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

Глубокие ассоциации

Таким способом вы также можете нацелить более глубокие ассоциации, используя синтаксис пути с точечной нотацией, известный из Query::contain(). Например, у вас была ассоциация Users hasOne Profiles, и вы хотите сопоставлять только тем пользователям, которые хотят получать уведомления, которые могут выглядеть примерно так:

->matching('Users.Profiles', function(\Cake\ORM\Query $q) {
    return $q->where(['Profiles.receive_notifications' => true]);
})

Это автоматически создаст все необходимые дополнительные объединения.

Вместо этого выберите из другой таблицы

С этими ассоциациями и вашими простыми требованиями вы также можете легко запрашивать данные с другой стороны, то есть через таблицу Users и использовать просто Query::contain() для включения связанных контактов, например

$this->Users
    ->find()
    ->contain('Contacts')
    ->where([
        'Users.id' => 1
    ])
    ->first();

Все контакты можно затем найти в свойстве entity contacts.

Ответ 2

В то время как @ndm answer позволил мне справиться с подобной проблемой, решение потребовало небольшой настройки. Проблема заключалась в том, что пользователи не фильтровали данные в сопоставлении при добавлении joinTable и не возвращали пользователей. Это то, чем я закончил, надеюсь, это кому-нибудь поможет.

$this->Contacts
->find()
->contain('Users', function(\Cake\ORM\Query $q) {
    return $q->matching('ContactsUsers', function(\Cake\ORM\Query $q) {
        return $q->where(['ContactsUsers.some_field' => 1]);
    }
});

Это дало мне контакты с пользователями, у которых some_field установлено в 1 в таблице сопоставлений. Или я слишком усложняю и есть лучшее решение?