必须掌握with()方法的精确用法:需模型正确定义关联getter,支持单层、多关联及嵌套(如'category.publisher')预加载;可配合匿名函数或数组参数筛选字段与条件,但select须含外键;with()生成两条sql,join条件筛选必须用joinwith()。

要在Yii应用中一次性加载主模型及其关联数据,避免N+1查询问题,必须掌握with()方法的精确用法和边界条件。这个方法不是简单加个括号就能生效,它依赖模型层的正确声明、查询链的合理构建,以及对缓存机制的清醒认知。
基础用法:单层关联预加载
在控制器中调用find()后直接链式调用with(),传入模型中定义的关联方法名(不带get前缀)。
例如Article模型已定义getCategory(),则写法为:Article::find()->with('category')->all()。
这一步必须确保模型中已存在对应名称的getter方法,否则会抛出Unknown property异常。
多关联同时预加载
传入数组即可一次加载多个关联:
Article::find()->with(['category', 'author', 'tags'])->asArray()->all();
注意:数组元素顺序不影响SQL执行顺序,但会影响最终返回数组中键的排列顺序。
若某个关联未在模型中正确定义,整个查询会失败,不会静默跳过。
嵌套关联:三级及以上深度加载
使用点号语法可穿透多层关系:
第一步:确认Article模型中有getCategory(),Category模型中有getPublisher();
第二步:调用Article::find()->with('category.publisher')->all();
第三步:访问时可用$article->category->publisher->name直接取值;
【publisher必须是Category模型中定义的合法关联名,且返回ActiveQuery对象】;
若中间某一层缺失关联定义,如Category中没写getPublisher(),则运行时报错,不会返回null或空对象。
带条件与字段筛选的with()
方法一:匿名函数回调方式
在with()中传入关联名和闭包,对子查询做定制:
Article::find()->with(['category' => function ($query) { $query->select(['id', 'name'])->where(['status' => 1]); }])->all();
方法二:使用数组参数形式(Yii 2.0.14+)
Article::find()->with(['category' => [ 'select' => ['id', 'name'], 'where' => ['status' => 1], ]])->all();
注意:如果用了select,必须包含关联外键字段(如category表的id),否则关联结果无法绑定到主模型上。
with()与joinWith()的本质区别
with()生成两条独立SQL:一条查主表,一条用IN语句查关联表;
joinWith()生成一条JOIN SQL,底层默认用LEFT JOIN;
当需要WHERE条件作用于关联表字段时,必须用joinWith(),因为with()的二次查询无法被主查询的where过滤;
例如要查“分类名为‘PHP’的文章”,只能写:Article::find()->joinWith('category')->where(['category.name' => 'PHP'])->all();;
【用with()加where筛选关联字段会导致SQL错误或结果为空】。











