Извлечение данных из таблицы соединений в Yii2
Я пытаюсь получить данные из таблицы соединений в Yii2 без дополнительного запроса. У меня есть две модели (User, Group), связанные через таблицу соединений (user_group). В таблице user_group я хочу сохранить дополнительные данные (флаг администратора,...) для этого отношения.
-
Какой лучший способ добавить данные в таблицу соединений? Метод ссылки принимает параметр extraColumns, но я не могу понять, как это работает.
-
Какой лучший способ получить эти данные? Я написал дополнительный запрос, чтобы получить значения из таблицы соединений. Должен быть более чистый способ сделать это?!
FYI, вот как я определил отношение в моделях:
Group.php
public function getUsers() {
return $this->hasMany(User::className(), ['id' => 'user_id'])
->viaTable('user_group', ['group_id' => 'id']);
}
User.php
public function getGroups() {
return $this->hasMany(Group::className(), ['id' => 'group_id'])
->viaTable('user_group', ['user_id' => 'id']);
}
Ответы
Ответ 1
Короче. Используя ActiveRecord
для таблицы соединений, как вы сказали, это ИМХО правильно, потому что вы можете настроить via()
для использования существующего ActiveRecord
. Это позволяет использовать метод Yii link()
для создания элементов в таблице соединений при одновременном добавлении данных (например, вашего флага администратора).
В официальном руководстве Yii Guide 2.0 указывается два способа использования таблицы соединений: использование viaTable()
и использование via()
(см. здесь). В то время как первое ожидает, что имя таблицы переходов как параметр, последний ожидает имя отношения как параметра.
Если вам нужен доступ к данным внутри таблицы переходов, я бы использовал ActiveRecord
для таблицы соединений, как вы предложили, и используйте via()
:
class User extends ActiveRecord
{
public function getUserGroups() {
// one-to-many
return $this->hasMany(UserGroup::className(), ['user_id' => 'id']);
}
}
class Group extends ActiveRecord
{
public function getUserGroups() {
// one-to-many
return $this->hasMany(UserGroup::className(), ['group_id' => 'id']);
}
public function getUsers()
{
// many-to-many: uses userGroups relation above which uses an ActiveRecord class
return $this->hasMany(User::className(), ['id' => 'user_id'])
->via('userGroups');
}
}
class UserGroup extends ActiveRecord
{
public function getUser() {
// one-to-one
return $this->hasOne(User::className(), ['id' => 'user_id']);
}
public function getGroup() {
// one-to-one
return $this->hasOne(Group::className(), ['id' => 'userh_id']);
}
}
Таким образом вы можете получить данные таблицы соединений без дополнительных запросов, используя отношение userGroups
(например, с любым другим отношением один-ко-многим):
$group = Group::find()->where(['id' => $id])->with('userGroups.user')->one();
// --> 3 queries: find group, find user_group, find user
// $group->userGroups contains data of the junction table, for example:
$isAdmin = $group->userGroups[0]->adminFlag
// and the user is also fetched:
$userName = $group->userGroups[0]->user->name
Все это можно сделать, используя отношение hasMany
. Поэтому вы можете спросить, почему вы должны объявлять отношение "многие ко многим", используя via()
: потому что вы можете использовать метод Yii link()
для создания элементов в таблице соединений:
$userGroup = new UserGroup();
// load data from form into $userGroup and validate
if ($userGroup->load(Yii::$app->request->post()) && $userGroup->validate()) {
// all data in $userGroup is valid
// --> create item in junction table incl. additional data
$group->link('users', $user, $userGroup->getDirtyAttributes())
}
Ответ 2
Поскольку я не получил ответа почти 14 дней, я опубликую, как я решил эту проблему. Это не совсем то, что я имел в виду, но он работает, этого достаточно на данный момент. Итак... это то, что я сделал:
- Добавлена модель UserGroup для таблицы соединений
-
Добавлено отношение к группе
public function getUserGroups()
{
return $this->hasMany(UserGroup::className(), ['user_id' => 'id']);
}
-
Присоединился к UserGroup в моей функции поисковой модели
$query = Group::find()->where('id =' . $id)->with('users')->with('userGroups');
Получите от меня то, что я хотел, группу со всеми пользователями и, представленную моей новой моделью UserGroup, данные из таблицы соединений.
Я подумал о том, чтобы сначала расширить функцию построения Yii2 - это может быть лучшим способом решить эту проблему. Но так как я еще не знаю Yii2, я решил не делать этого.
Пожалуйста, дайте мне знать, если у вас есть лучшее решение.
Ответ 3
Я не знаю наверняка, это лучшее решение. Но для моего проекта это будет хорошо на данный момент:)
1) Левое соединение
Добавить новый атрибут класса в User
model public $flag;
.
Добавьте две строки к своему основному отношению, но не удаляйте viaTable
, это может (и должно) остаться.
public function getUsers()
{
return $this->hasMany(User::className(), ['id' => 'user_id'])
->viaTable('user_group', ['group_id' => 'id'])
->leftJoin('user_group', '{{user}}.id=user_id')
->select('{{user}}.*, flag') //or all ->select('*');
}
leftJoin
позволяет выбрать данные из таблицы соединений и select
, чтобы настроить возвращаемые столбцы.
Помните, что viaTable
должен оставаться, потому что link() полагается на него.
2) запрос подвыбора
Добавить новый атрибут класса в User
model public $flag;
И в Group
измененная модель getUsers()
:
public function getUsers()
{
return $this->hasMany(User::className(), ['id' => 'user_id'])
->viaTable('user_group', ['group_id' => 'id'])
->select('*, (SELECT flag FROM user_group WHERE group_id='.$this->id.' AND user_id=user.id LIMIT 1) as flag');
}
Как вы можете видеть, я добавил суб-выбор для списка выбора по умолчанию. Этот выбор предназначен для негрупповой модели пользователей. Да, я согласен, что это немного уродливо, но делает работу.
3) Соотношения условий
Другим вариантом является создание только одного отношения для администраторов:
// Select all users
public function getUsers() { .. }
// Select only admins (users with specific flag )
public function getAdmins()
{
return $this->hasMany(User::className(), ['id' => 'user_id'])
->viaTable('user_group', ['group_id' => 'id'],
function($q){
return $q->andWhere([ 'flag' => 'ADMIN' ]);
});
}
$Group->admins
- получить пользователей с определенным флагом администратора. Но это решение не добавляет атрибут $flag
. Вам нужно знать, когда вы выбираете только администраторов и всех пользователей. Нижняя сторона: вам нужно создать отдельное отношение для каждого значения флага.
Ваше решение с использованием отдельной модели UserGroup
по-прежнему является более гибким и универсальным для всех случаев. Как вы можете добавить валидацию и базовые материалы ActiveRecord. Эти решения - более одностороннее направление - чтобы получить материал.
Ответ 4
Для этой цели я создал простое расширение, которое позволяет привязывать столбцы в таблице подключений к дочерней модели в отношении как свойств.
Поэтому после настройки этого расширения вы сможете получить доступ к атрибутам таблицы соединений, например
foreach ($parentModel->relatedModels as $childModel)
{
$childModel->junction_table_column1;
$childModel->junction_table_column2;
....
}
Для получения дополнительной информации, пожалуйста, взгляните на
Расширение атрибутов таблицы расширения Yii2
Спасибо.