我正在尝试从Yii2中的连接表中获取数据而无需其他查询。我有2个模型(用户,组)通过联结表(user_group)关联。在user_group表中,我想存储此关系的额外数据(admin flag,...)。
将数据添加到联结表的最佳方法是什么?链接方法接受参数extraColumns但我无法弄清楚它是如何工作的。
检索此数据的最佳方法是什么?我写了一个额外的查询来从结表中获取值。必须有一个更清洁的方法来做到这一点?!
仅供参考,这是我在模型中定义关系的方式:
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']);
}
答案 0 :(得分:8)
简而言之:使用ActiveRecord
建议的联结表是恕我直言,因为您可以设置via()
来使用现有的ActiveRecord
}。这允许您使用Yii的link()
方法在联结表中创建项目,同时添加数据(如管理标志)。
官方Yii指南2.0说明了两种使用联结表的方法:使用viaTable()
并使用via()
(请参阅here)。虽然前者期望联结表的名称作为参数,但后者需要将关系名称作为参数。
如果您需要访问联结表中的数据,我会按照您的建议使用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())
}
答案 1 :(得分:3)
由于我差不多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,我现在决定不这样做。
如果您有更好的解决方案,请与我们联系。
答案 2 :(得分:3)
我不确定这是最好的解决方案。但是对于我的项目来说,现在好了:)
在&
模型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
必须保留,因为link()依赖它。
在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
的解决方案仍然更灵活,适用于所有情况。就像你可以添加验证和基本的ActiveRecord东西。这些解决方案更多的是单向方向 - 将东西拿出来。
答案 3 :(得分:1)
为此,我创建了一个简单的扩展,允许将联结表中的列作为属性附加到子模型中。 因此,在设置此扩展后,您将能够访问连接表属性,如
foreach ($parentModel->relatedModels as $childModel)
{
$childModel->junction_table_column1;
$childModel->junction_table_column2;
....
}
欲了解更多信息,请查看 Yii2 junction table attributes extension
感谢。