Yii的save()默认不保存关联数据,仅更新主表;关联模型需手动save()或用link()/unlink()(要求外键定义正确),批量操作须结合事务与显式错误处理。

关联模型更新时,save() 默认不保存关联数据
Yii 的 ActiveRecord 在调用 save() 时,只保存当前模型对应主表的数据,不会自动同步更新关联表(比如 hasMany() 或 hasOne() 定义的关联)。这是新手最常踩的坑:改了关联对象的属性、调了 save(),结果数据库里啥都没变。
根本原因在于 Yii 的关联关系是“读时加载、写时不联动”,必须显式触发关联数据的持久化。
- 如果关联模型已存在(有
id),需手动调用其save() - 如果要批量更新一对多子记录,得自己遍历并保存每个子模型
- 若想“一键保存主从”,得启用事务 + 手动控制顺序,或使用扩展(如
yii2tech/ar-softdelete不适用此场景)
link() 和 unlink() 只适用于外键在当前模型侧的关联
比如 Order 属于 User(user_id 在 order 表),那么 $user->link('orders', $order) 会把 $order->user_id 设为 $user->id 并保存 —— 这本质是更新子表外键字段。
但它**不适用于反向场景**:比如你想通过 $order->link('items', $item) 更新 order_item.order_id,前提是 order_item 模型的外键字段名必须严格匹配关联定义中的 viaTable 或 link() 推导规则(通常要求是 order_id)。否则抛出 InvalidCallException:“Unable to link models: the foreign key is not specified.”
- 检查关联定义中是否漏写了
'foreignKey'(对hasMany/hasOne)或'link'(对viaTable) -
link()不会校验子模型数据合法性,它只设外键、调save();若子模型验证失败,会静默失败或抛异常,需捕获$item->getErrors() - 批量
link()前建议先unlinkAll()再逐个重连,避免残留脏关联
带验证和事务的关联更新推荐写法
真实业务中,主模型和关联模型往往都有验证规则,且必须原子性提交。硬编码多个 save() 调用容易出错,应封装在事务里,并显式检查每一步结果。
// 示例:更新订单及其商品项
$order = Order::findOne($id);
if (!$order) {
throw new NotFoundHttpException();
}
// 先保存主表
if (!$order->load(Yii::$app->request->post()) || !$order->save()) {
return $this->render('update', ['model' => $order]);
}
// 再处理关联项(假设 items 是 hasMany 关联)
$oldItemIds = ArrayHelper::getColumn($order->items, 'id');
$newItems = Yii::$app->request->post('OrderItem', []);
// 删除不在新数据里的旧项
OrderItem::deleteAll(['and', ['order_id' => $order->id], ['not in', 'id', $newItems]]);
// 插入/更新新项
foreach ($newItems as $itemData) {
$item = isset($itemData['id'])
? OrderItem::findOne($itemData['id'])
: new OrderItem(['order_id' => $order->id]);
$item->setAttributes($itemData);
if (!$item->save()) {
// 记录错误或抛出异常,让事务回滚
throw new BadRequestHttpException(json_encode($item->getErrors()));
}
}
注意:这里没用 Transaction::begin() 显式包裹,是因为 OrderItem::deleteAll() 和循环中的 $item->save() 都是独立 SQL,真要强一致性,必须加事务并捕获所有异常。
用 populateRelation() 后不能直接改再 save()
有人会这么写:
$order = Order::findOne(1)->with('items')->one();
foreach ($order->items as $item) {
$item->status = 'shipped';
}
$order->save(); // ❌ 这行完全没用!$order->items 不会被保存
因为 $order->items 是只读关系结果,修改它只是改 PHP 对象内存状态,不触发任何持久化逻辑。要更新这些子记录,必须对每个 $item 单独调 save(),或用 updateAll() 批量 SQL(绕过模型验证)。
复杂点在于:关联数据可能来自 via 中间表、嵌套多层、或含软删除逻辑 —— 这些都得在业务层手动判断,框架不会替你猜意图。


















