在Laravel中软删除父记录时如何软删除相关记录?

26

我有这样一个发票表,其结构如下:

id | name | amount | deleted_at
2    iMac   1500   | NULL

并带有以下结构的付款表格

id | invoice_id | amount | deleted_at
2    2            1000   | NULL

发票模板

class Invoice extends Model {

    use SoftDeletes;

}

这里是删除发票的代码

public function cance(Request $request,$id)
{
    $record = Invoice::findOrFail($id);
    $record->delete();
    return response()->json([
        'success' => 'OK',
    ]);
}

支付模型

class Payment extends Model {

    use SoftDeletes;

}

Invoice表的软删除功能运作正常,但其相关记录(付款)仍然存在。如何使用软删除删除它们?

5个回答

25

Eloquent 不提供自动删除相关对象的功能,因此您需要自己编写一些代码。幸运的是,这很简单。

Eloquent 模型 在模型生命周期的不同阶段触发不同的事件,如创建、创建完成、删除、删除完成等 - 您可以在此处阅读更多信息:http://laravel.com/docs/5.1/eloquent#events。您需要的是一个侦听器,在触发删除事件时运行-该侦听器应然后删除所有相关对象。

您可以在模型的boot()方法中注册模型侦听器。侦听器应遍历要删除的发票的所有付款,并逐个删除它们。批量删除在此处不起作用,因为它会直接执行 SQL 查询,绕过模型事件。

这样做就可以解决问题:

class MyModel extends Model {
  protected static function boot() {
    parent::boot();

    static::deleted(function ($invoice) {
      $invoice->payments()->delete();
    });
  }
}

1
无法运行!Invoice.php第18行的FatalErrorException: 无法使Illuminate\Database\Eloquent\Model类的静态方法boot()在App\Models\Invoice类中非静态。 - user3407278
修复了一个问题,该函数缺少了静态修饰符。 - jedrzej.kurylo
那个运行得非常好!非常感谢!如果软删除大约100条记录,会有任何性能问题吗? - user3407278
你必须获取它们中的所有内容,然后保存每个内容,因此需要额外的101个查询... 在这种情况下,您可以手动为相关模型设置deleted_at,这样做可能不太干净,但只会运行一个SQL查询。我将在第二个中更新答案。 - jedrzej.kurylo
我知道这个方法有点老了,但是我真的强烈推荐使用观察者模式来实现这个功能。 - stetim94
运行得非常顺利! - Edinaldo Ribeiro

12
你有两种选择方法。
最简单的方法是重写Eloquent的delete()方法并包含相关的模型,例如:
```php public function delete() { $this->relatedModels()->delete(); parent::delete(); } ```
public function delete()
{
    $this->payments()->delete();
    return parent::delete();
} 

上述方法应该可以正常工作,但似乎有点不太规范,我认为这不是社区中首选的方法。

更清晰的方式(在我看来)是利用Eloquent的事件,例如:

public static function boot()
{
    parent::boot();

    static::deleting(function($invoice) { 
         $invoice->payments()->delete();

    });
}

以上两种方法中的任意一种(但不是两种都要)将放在你的Invoice模型中。 另外,我假设你已经在你的模型中设置了关系,但是我不确定你是否允许一个发票有多个付款。无论如何,你可能需要将示例中的payments()更改为你在发票模型中命名的关系。

希望这可以帮助你!


直接在payments关系上调用delete()将绕过模型,并不会触发相关模型的SoftDelete,它们将从数据库中删除。为了使其软删除,您需要在每个相关模型上调用delete()。 - jedrzej.kurylo
你在重写的delete方法中也缺少了return语句 - 它应该是"return parent::delete();",否则你会失去从delete()返回的值,如果你没有覆盖它。 - jedrzej.kurylo
1
@jedrzej.kurylo,我刚刚测试了一下,确实可以在关系中使用软删除! - Rwd
没错,我刚才检查了代码。以后知道这个信息还是很好的 :) - jedrzej.kurylo
@ TomonsoEjang 如果你将调用包装在事务中,它应该仍然表现出相同的方式。没有任何异步行为,因此它仍将启动事务,在模型中运行代码,然后结束事务...如果有问题,则事务将被回滚。 - Rwd
显示剩余3条评论

7

我知道您很久以前就提出了这个问题,但是我发现这个软件包非常简单明了。

或者您可以使用这个软件包,它也很有用。

请记住根据您的Laravel版本安装正确的版本。

您必须通过composer进行安装:

 composer require askedio/laravel5-soft-cascade ^version

在第二个包中:
 composer require iatstuti/laravel-cascade-soft-deletes

请在config/app.php文件中注册服务提供商。

您可以在GitHub页面上阅读文档。

如果您删除一个记录,此软件包会识别其所有子级并对它们进行软删除。

如果您的子模型中有另一种关系,请在该模型中也使用其特征。这比手动执行要容易得多。

第二个软件包的好处是删除模型的孙子级。在某些情况下,我认为这是更好的方法。


我使用了第二个包,它在 delete() 方面表现得非常好,但是我能否让它也支持 restore() 呢? - hassanrazadev

2
如果你的数据库关系只有一层,那么你可以在模型的boot()方法中使用Laravel事件来处理软删除,如下所示:
<?php
//...

    protected static boot() {
        parent::boot();

        static::deleting(function($invoice) { 
             $invoice->payments()->delete();

        }); 
    }

然而,如果你的结构不止一层,你将不得不调整那段代码。

举个例子,假设你不想删除发票的付款记录,而是要删除给定用户的整个付款历史记录。

<?php

// ...

class Invoice extends Model
{
    // ...

    /**
     * Holds the methods names of Eloquent Relations
     * to fall on delete cascade or on restoring
     * 
     * @var array
     */
    protected static $relations_to_cascade = ['payments']; 

    protected static boot()
    {
        parent::boot();

        static::deleting(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->delete();
                }
            }
        });

        static::restoring(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->withTrashed()->restore();
                }
            }
        });
    }

    public function payments()
    {
        return $this->hasMany(Payment::class);
    }
}

<?php

// ...

class User extends Model
{
    // ...

    /**
     * Holds the methods names of Eloquent Relations 
     * to fall on delete cascade or on restoring
     * 
     * @var array
     */
    protected static $relations_to_cascade = ['invoices']; 

    protected static boot()
    {
        parent::boot();

        static::deleting(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->delete();
                }
            }
        });

        static::restoring(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->withTrashed()->restore();
                }
            }
        });
    }

    public function invoices()
    {
        return $this->hasMany(Invoice::class);
    }
}

这种范式确保 Laravel 无论深入多深的兔子洞,都能跟随其脚步。

0

你也可以使用模型观察器:

php artisan make:Observer InvoiceOberser --model=Invoice

它将在/app/Observers/InvoiceObserver.php中创建一个新文件,其中包含以下方法:

  • created
  • updated
  • deleted
  • restored
  • forceDeleted

您只需要将删除方法更新为以下内容:

public function deleted(Invoice $invoice)
{
    $invoice->payments()->delete();
}

最后,在/app/Providers/EventServiceProvider.php文件中添加以下行:
// On the top
use App\Models\Invoice;
use App\Observers\InvoiceObserver;

// On boot method
Invoice::observe(InvoiceObserver::class);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接