Как реализовать отношение Yii2 через строку, а не массив?

У меня есть класс с именем ChatMessage. В БД есть 2 основных столбца (from_user, to_user). Я хочу связать один из этих 2 столбцов с идентификатором модели пользователя. Как я могу использовать запрос отношения, который выглядит следующим образом

public function getChattedUser()
{
    return $this->hasOne(User::className(), '(case when(chat_message.from_user={Yii::$app->user->id}) then chat_message.to_user else chat_message.from_user END)=user.id');
}

2 ответа

Краткий ответ: это невозможно.

Отношения ActiveRecord сложнее, чем вы думаете, и поддержка таких условий надежным способом действительно сложна / невозможна. Проблема в том, что отношение может использоваться в разных контекстах:

  1. Присоединиться: select * from chat_messages join users on users.id = chat_messages.from_user,
  2. Ленивая загрузка: select * from users where users.id = 1,
  3. Стремительная загрузка: select * from users where users.id IN (1, 2, 3, 4),

Как вы видите, трудно конвертировать ваши on условие, чтобы соответствовать каждому из этих трех типов запросов ( см. эту проблему на GitHub).

Вы можете попытаться переопределить ActiveQuery поддерживать ваше состояние, но гораздо проще (и, вероятно, достаточно хорошо) использовать два отношения и создать несколько помощников, чтобы сделать доступ к этим отношениям более удобным:

public function getUserFrom() {
    return $this->hasOne(User::className(), ['id' => 'user_from']);
}

public function getUserTo() {
    return $this->hasOne(User::className(), ['id' => 'user_to']);
}

public function getUser() {
    return Yii::$app->user->id == $this->user_from ? $this->userFrom : $this->userTo;
}

Триггер нетерпеливо загружается:

ChatMessage::find()->with(['userFrom', 'userTo'])->all();

Этого должно быть достаточно для простых случаев. Для более сложных сценариев вам нужно будет писать запросы вручную.


Обратите внимание, что с помощью Yii::$app->user->id в вашей модели это считается плохой практикой

В итоге, модели /.../

  • НЕ должен напрямую обращаться к запросу, сеансу или любым другим данным об окружающей среде. Эти данные должны быть введены контроллерами в модели;

https://www.yiiframework.com/doc/guide/2.0/en/structure-models

Вы можете достичь этого, просто добавив оператор if в свою функцию отношения. Просто напишите case логика в выражении if, как написано ниже:

public function getChattedUser()
{
    if ($this->from_user == Yii::$app->user->id) {
        return $this->hasOne(User::className(), ['id' => 'to_user']);
    }
    return $this->hasOne(User::className(), ['id' => 'from_user']);
}
Другие вопросы по тегам