Laravel 软删除父记录时如何 soft delete 关联记录?
支付阿杰
结论
Laravel 的软删除不会自动作用于关联模型。对 Invoice 执行 delete() 时,Laravel 只会更新当前记录的 deleted_at。要同时软删除付款记录,需要先定义 payments 关联,再在 Invoice 的删除事件中调用关联记录的 delete()。
定义关联并处理级联软删除
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Invoice extends Model
{
use SoftDeletes;
public function payments(): HasMany
{
return $this->hasMany(Payment::class);
}
protected static function booted(): void
{
static::deleting(function (Invoice $invoice) {
if ($invoice->isForceDeleting()) {
$invoice->payments()
->withTrashed()
->forceDelete();
return;
}
$invoice->payments()->delete();
});
}
}
Payment 模型同样需要启用 SoftDeletes:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Payment extends Model
{
use SoftDeletes;
public function invoice(): BelongsTo
{
return $this->belongsTo(Invoice::class);
}
}
之后执行:
$invoice = Invoice::findOrFail($id);
$invoice->delete();
Laravel 会同时更新 invoices.deleted_at 和相关记录的 payments.deleted_at,不会真正删除这些数据。
建议使用事务
如果父记录和关联记录必须同时完成删除,可以放在同一个事务中:
use App\Models\Invoice;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
public function cancel(Request $request, $id)
{
DB::transaction(function () use ($id) {
$invoice = Invoice::findOrFail($id);
$invoice->delete();
});
return response()->json([
'success' => 'OK',
]);
}
其中任何一步发生异常,父记录和子记录的修改都会回滚。
如果子记录还需要触发模型事件
$invoice->payments()->delete() 是批量更新,通常不会为每条 Payment 记录触发 deleting、deleted 等模型事件。
如果 Payment 本身有删除监听器,或者还要继续处理更深层的关联,应逐条删除:
static::deleting(function (Invoice $invoice) {
if ($invoice->isForceDeleting()) {
$invoice->payments()
->withTrashed()
->eachById(function (Payment $payment) {
$payment->forceDelete();
});
return;
}
$invoice->payments()
->eachById(function (Payment $payment) {
$payment->delete();
});
});
关联记录较多时,使用 eachById() 可以避免一次性加载全部数据,占用的内存更少。
注意事项
数据库外键的 ON DELETE CASCADE 只在真正执行 DELETE 时生效。软删除只是更新 deleted_at,因此数据库外键无法自动软删除关联记录。
删除时还应使用模型实例:
$invoice->delete();
不要改用批量语句:
Invoice::whereKey($id)->delete();
批量删除不会触发 Invoice 的模型删除事件,因此前面定义的级联逻辑也不会执行。
如果之后需要恢复 Invoice,是否同步恢复 Payment 应由具体业务决定。直接恢复所有关联 Payment,可能会把删除 Invoice 之前已经单独删除的付款记录一并恢复。更稳妥的做法是记录删除批次,或者明确每条关联记录的删除来源。
备注:内容仅供参考。
