You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Yii2 Active Record多对多嵌套关联查询及正确数据格式获取

如何通过一次Yii2 Active Record查询获取关联产品的属性及对应属性项?

问题背景

我有4张关联表:products、properties、property_items、property_product(中间表),关联逻辑如下:

  • 一个Product对应多个Properties
  • 一个Property对应多个PropertyItems
  • 一个Product对应多个PropertyItems(通过中间表property_product关联)

表数据

products表

Id  name
1   Arrabiata
2   Bolognese

properties表

Id  name
1   Pasta type
2   Size

property_items表

Id  name              property_id
1   Spinach spaghetti 1
2   Linguine          1
3   Spaghetti         1
4   Small             2
5   Medium            2
6   Large             2

property_product表(中间表)

product_id  property_id  property_item_id
1           1            1
1           1            2
1           2            4
1           2            6
2           1            2
2           1            3
2           2            5

现有模型关联

我用Gii生成了对应模型及基础关联:

class Products extends \yii\db\ActiveRecord {
    // 其他业务代码...
    public function getProperties() {
        return $this->hasMany(Properties::className(), ['id' => 'property_id'])
            ->viaTable('property_product', ['product_id' => 'id']);
    }
}

class Properties extends \yii\db\ActiveRecord {
    // 其他业务代码...
    public function getPropertyItems() {
        return $this->hasMany(PropertyItems::className(), ['property_id' => 'id']);
    }
}

遇到的问题

我希望通过一次Active Record查询拿到指定格式的JSON响应,但尝试两种方式都踩了坑:

  1. 嵌套循环查询:性能极差,每一层循环都要发起新的数据库请求
    Menus::find()->with(['products' => function ($query) {
        $query->with(['properties']);
    }])
    ->where(['>', 'status', 0])
    ->andWhere(['resid' => $id])->asArray()->all();
    
    foreach ($menus as &$menu) {
        foreach ($menu['products'] as &$product) {
            foreach ($product['properties'] as &$property) {
                $propertyItems = PropertyItems::find()->where(['property_id' => $property['id']] )
                    ->leftJoin('property_product', 'property_items.id = property_product.property_item_id')
                    ->where(['property_product.product_id' => $product['id'], 'property_product.property_id' => $property['id']])
                    ->asArray()->all();
                $property['propertyItems'] = $propertyItems;
            }
        }
    }
    
  2. 多层with关联查询:返回的property_items是该属性下的所有选项,没有关联到当前Product,不符合需求
    Menus::find()->with(['products' => function ($query) {
        $query->with(['properties' => function ($query) {
            $query->with(['propertyItems']);
        } ]);
    } ])->asArray()->all();
    

期望的JSON格式

[
    {
        "id": "1",
        "name": "Arrabiata",
        "properties": [
            {
                "id": "1",
                "name": "Pasta type",
                "property_items" : [
                    { "id": "1", "name": "Spinach spaghetti" },
                    { "id": "2", "name": "Linguine" }
                ]
            },
            {
                "id": "2",
                "name": "Size",
                "property_items" : [
                    { "id": "4", "name": "Small" },
                    { "id": "6", "name": "Large" }
                ]
            }
        ]
    },
    {
        "id": "2",
        "name": "Bolognese",
        "properties": [
            {
                "id": "1",
                "name": "Pasta type",
                "property_items" : [
                    { "id": "2", "name": "Linguine" },
                    { "id": "3", "name": "Spaghetti" }
                ]
            },
            {
                "id": "2",
                "name": "Size",
                "property_items" : [
                    { "id": "5", "name": "Medium" }
                ]
            }
        ]
    }
]

请问如何通过一次Active Record查询实现该需求?


解决方案

核心思路是给Properties模型新增一个带上下文条件的关联方法,专门用来关联「当前属性下属于指定Product的PropertyItems」,然后通过嵌套with加载这个关联,利用Yii2的关联上下文自动绑定对应的Product ID,既保证性能又能拿到符合要求的数据结构。

步骤1:修改Properties模型,新增关联方法

在Properties类中添加getProductPropertyItems方法,这个方法会通过中间表过滤出属于当前关联Product的属性项:

class Properties extends \yii\db\ActiveRecord {
    // 原有代码...
    public function getPropertyItems() {
        return $this->hasMany(PropertyItems::className(), ['property_id' => 'id']);
    }

    // 新增:关联当前属性下属于对应Product的PropertyItems
    public function getProductPropertyItems() {
        // 通过中间表关联,同时利用Yii的关联上下文获取当前绑定的Product ID
        return $this->hasMany(PropertyItems::className(), ['id' => 'property_item_id'])
            ->viaTable('property_product', ['property_id' => 'id'], function ($query) {
                // 从关联上下文拿到当前的Product模型ID
                $product = $this->getRelatedRecords()['products'][0] ?? null;
                if ($product) {
                    $query->andWhere(['product_id' => $product['id']]);
                }
            });
    }
}

步骤2:调整查询语句,加载新关联

修改查询代码,在加载properties时指定加载我们新增的productPropertyItems,最后调整键名匹配期望的JSON格式:

$result = Menus::find()
    ->with(['products' => function ($query) {
        $query->with(['properties' => function ($query) {
            // 加载带Product过滤条件的属性项关联
            $query->with(['productPropertyItems']);
        }]);
    }])
    ->where(['>', 'status', 0])
    ->andWhere(['resid' => $id])
    ->asArray()
    ->all();

// 把关联名重命名为期望的property_items
foreach ($result as &$menu) {
    foreach ($menu['products'] as &$product) {
        foreach ($product['properties'] as &$property) {
            $property['property_items'] = $property['productPropertyItems'];
            unset($property['productPropertyItems']);
        }
    }
}

// 输出符合要求的JSON
echo json_encode($result, JSON_PRETTY_PRINT);

原理说明

Yii2的Active Record关联机制会自动维护嵌套关联的上下文关系,当我们在Properties的关联方法中调用getRelatedRecords()时,能拿到当前绑定的Products实例,从而动态过滤出属于该Product的属性项。整个查询只会发起3-4次SQL请求(远少于嵌套循环的N次),性能得到极大提升,同时返回的数据结构完全匹配需求。


内容的提问来源于stack exchange,提问作者user3500294

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:41:14