Я пытаюсь получить данные из таблицы соединений в Yii2 без дополнительного запроса. У меня есть две модели (User, Group), связанные через таблицу соединений (user_group). В таблице user_group я хочу сохранить дополнительные данные (флаг администратора, …) для этого отношения.
Каков наилучший способ добавления данных в таблицу соединений? Метод link принимает параметр 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']); }
Короче : использование ActiveRecord
для таблицы переходов, как вы предложили, является IMHO правильным способом, поскольку вы можете настроить via()
для использования существующего ActiveRecord
. Это позволяет использовать метод link()
Yii для создания элементов в таблице соединений при одновременном добавлении данных (например, вашего флага администратора).
В официальном руководстве 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()
: поскольку вы можете использовать метод link()
Yii для создания элементов в таблице соединений:
$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()) }
Поскольку я не получил ответа почти 14 дней, я опубликую, как я решил эту проблему. Это не совсем то, что я имел в виду, но это работает, этого достаточно. Итак … это то, что я сделал:
Добавлено отношение к группе
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, я решил не делать этого.
Пожалуйста, дайте мне знать, если у вас есть лучшее решение.
Я не знаю наверняка, это лучшее решение. Но для моего проекта это будет хорошо на данный момент 🙂
Добавить новый атрибут класса в User
шаблон 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
leftJoin
для настройки возвращаемых столбцов. Помните, что viaTable
должен оставаться, потому что ссылка () полагается на него.
Добавить новый атрибут класса в User
шаблон 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'); }
Как вы можете видеть, я добавил дополнительный выбор для списка выбора по умолчанию. Этот выбор предназначен для негрупповой модели пользователей. Да, я согласен, что это немного уродливо, но делает работу.
Другим вариантом является создание только одного отношения для администраторов:
// 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. Эти решения – более одностороннее направление – чтобы получить материал.
Для этого я создал простое расширение, которое позволяет привязывать столбцы в таблице подключений к дочерней модели в качестве свойств. Поэтому после настройки этого расширения вы сможете получить доступ к атрибутам таблицы соединений, например
foreach ($parentModel->relatedModels as $childModel) { $childModel->junction_table_column1; $childModel->junction_table_column2; .... }
Для получения дополнительной информации ознакомьтесь с расширением атрибутов таблицы Yii2
Благодарю.