PAYATHON 2026

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() 时,模型验证规则不会阻止状态更新。
  • 如果订单更新涉及其他关联数据,应考虑使用数据库事务。

备注:内容仅供参考。