Yii2 出现 Creating default object from empty value
结论
这个错误通常是因为 Checkout::findOne() 没有查到记录,返回了 null。此时再执行:
$order->payment_status = 1;
就会出现 Creating default object from empty value。在较新的 PHP 版本中,可能会报类似 Attempt to assign property "payment_status" on null 的错误。
更新前需要确认查询条件是否正确,同时处理记录不存在的情况。如果同一个订单号对应多条记录,可以直接使用 updateAll(),不需要递归。
正确更新单条记录
如果 $invoice->order_id 对应 Checkout 表的主键,可以这样写:
'successCallback' => function ($invoice) {
$order = \common\models\Checkout::findOne($invoice->order_id);
if ($order === null) {
Yii::warning(
'Checkout record not found. Order ID: ' . $invoice->order_id,
__METHOD__
);
return;
}
$order->payment_status = 1;
if (!$order->save()) {
Yii::error([
'message' => 'Failed to update payment status.',
'order_id' => $invoice->order_id,
'errors' => $order->getErrors(),
], __METHOD__);
}
}
save() 会执行模型验证。如果保存失败,可以通过 $order->getErrors() 查看具体原因。
确认查询字段是否正确
这段代码:
Checkout::findOne($invoice->order_id);
默认会按 Checkout 模型的主键查询,大致相当于:
Checkout::findOne([
Checkout::primaryKey()[0] => $invoice->order_id,
]);
如果 $invoice->order_id 对应的不是 Checkout 表主键,而是 order_id 字段,就要明确写出查询条件:
$order = \common\models\Checkout::findOne([
'order_id' => $invoice->order_id,
]);
完整代码如下:
'successCallback' => function ($invoice) {
$order = \common\models\Checkout::findOne([
'order_id' => $invoice->order_id,
]);
if ($order === null) {
Yii::warning(
'Checkout record not found. Order ID: ' . $invoice->order_id,
__METHOD__
);
return;
}
$order->payment_status = 1;
if (!$order->save()) {
Yii::error($order->getErrors(), __METHOD__);
}
}
这类问题最常见的原因就是查询字段不匹配。
不需要验证时直接更新
如果这里只修改支付状态,不需要执行模型验证,可以使用 updateAttributes():
'successCallback' => function ($invoice) {
$order = \common\models\Checkout::findOne([
'order_id' => $invoice->order_id,
]);
if ($order === null) {
Yii::warning(
'Checkout record not found. Order ID: ' . $invoice->order_id,
__METHOD__
);
return;
}
$order->updateAttributes([
'payment_status' => 1,
]);
}
updateAttributes() 不会执行验证,只更新指定字段。它通常也不会执行完整的 beforeSave()、afterSave() 流程。如果模型依赖这些事件或行为,应该继续使用 save()。
也可以这样写:
$order->payment_status = 1;
$order->save(false, ['payment_status']);
这种写法会跳过验证,但仍会经过 ActiveRecord 的保存流程。
同一个订单号对应多条记录
如果 order_id 不是唯一字段,同一个订单号可能对应多条 Checkout 记录。findOne() 只会取出其中一条,这时可以直接批量更新:
'successCallback' => function ($invoice) {
$updatedRows = \common\models\Checkout::updateAll(
['payment_status' => 1],
['order_id' => $invoice->order_id]
);
if ($updatedRows === 0) {
Yii::warning(
'No Checkout records were updated. Order ID: ' . $invoice->order_id,
__METHOD__
);
}
}
updateAll() 会生成一条批量 UPDATE SQL,比逐条查询和保存更高效。不过,它不会执行模型验证、ActiveRecord 事件或行为逻辑。
如果每条记录都需要执行模型验证或保存事件,就要逐条处理:
'successCallback' => function ($invoice) {
$orders = \common\models\Checkout::findAll([
'order_id' => $invoice->order_id,
]);
foreach ($orders as $order) {
$order->payment_status = 1;
if (!$order->save()) {
Yii::error([
'checkout_id' => $order->getPrimaryKey(),
'errors' => $order->getErrors(),
], __METHOD__);
}
}
}
注意事项
支付平台可能会重复调用支付成功回调,所以更新逻辑最好具备幂等性。重复将 payment_status 设置为 1 通常没有问题。但如果回调还会发货、增加余额或生成流水,就必须检查订单当前状态,避免重复处理。
还需要确认:
- 回调已经通过支付平台的签名或真实性校验。
$invoice->order_id的值与 Checkout 表中实际使用的查询字段一致。payment_status是允许写入的数据库字段。- 使用
save()时,模型验证规则不会阻止状态更新。 - 如果订单更新涉及其他关联数据,应考虑使用数据库事务。
备注:内容仅供参考。
