从Yii2中的结表中检索数据


Retrieve data from junction table in Yii2

我正在尝试从Yii2中的联接表中获取数据,而不需要额外的查询。我有两个通过连接表(User_Group)关联的模型(User,Group)。在user_group表中,我想为这个关系存储额外的数据(admin标志,…)。

  • 向连接表中添加数据的最佳方式是什么链接方法接受一个参数extraColumns,但我不知道它是如何工作的。

  • 检索此数据的最佳方式是什么我编写了一个额外的查询来获取连接表中的值。一定有更干净的方法可以做到这一点?!

仅供参考,这就是我如何定义模型中的关系:

Group.php

public function getUsers() {
    return $this->hasMany(User::className(), ['id' => 'user_id'])
        ->viaTable('user_group', ['group_id' => 'id']);
}

用户.php

public function getGroups() {
    return $this->hasMany(Group::className(), ['id' => 'group_id'])
        ->viaTable('user_group', ['user_id' => 'id']);
}

简而言之:像您建议的那样为连接表使用ActiveRecord是IMHO的正确方法,因为您可以将via()设置为使用现有的ActiveRecord。这允许您使用Yii的link()方法在连接表中创建项目,同时添加数据(如管理标志)。


YiiGuide2.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())
}

我不确定这是否是最佳解决方案。但对于我的项目来说,它现在会很好:)

1) 左联接

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自定义返回列。请记住,viaTable必须保留,因为link()依赖于它。

2) 子选择查询

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');
}

如您所见,我为默认选择列表添加了子选择。此选择适用于用户而非组模型。是的,我同意这有点难看,但确实有效。

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内容一样。这些解决方案更多的是单向的——把东西拿出来。

由于我已经将近14天没有收到回复了,我将发布我是如何解决这个问题的。这并不完全是我的想法,但它是有效的,这就足够了。所以…这就是我所做的:

  1. 为连接表添加了一个模型UserGroup
  2. 添加了与的关系

    public function getUserGroups()
    {
        return $this->hasMany(UserGroup::className(), ['user_id' => 'id']);
    }
    
  3. 在我的搜索模型功能中加入UserGroup

    $query = Group::find()->where('id =' . $id)->with('users')->with('userGroups');
    

这就是我想要的,拥有所有用户的,以及由我的新模型UserGroup表示的连接表中的数据。

我想先扩展查询构建Yii2函数,这可能是解决这个问题的更好方法。但由于我还不太了解Yii2,我决定暂时不做。

如果您有更好的解决方案,请告诉我

为此,我创建了一个简单的扩展,允许将连接表中的列作为属性附加到子模型。因此,在设置此扩展后,您将能够访问连接表属性,如

foreach ($parentModel->relatedModels as $childModel)
{
    $childModel->junction_table_column1;
    $childModel->junction_table_column2;
    ....
}

有关更多信息,请查看Yii2结表属性扩展

谢谢。