【问题标题】:How to soft delete related records when soft deleting a parent record in Laravel?在 Laravel 中软删除父记录时如何软删除相关记录?
【发布时间】:2023-03-26 21:55:01
【问题描述】:

我有这个具有以下结构的发票表

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 表上的 softDelete 运行良好,但其相关记录(付款)仍然存在。如何使用 softDelete 删除它们?

【问题讨论】:

    标签: php laravel laravel-5 eloquent soft-delete


    【解决方案1】:

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

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

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

    这样就可以了:

    class MyModel extends Model {
      protected static function boot() {
        parent::boot();
    
        static::deleted(function ($invoice) {
          $invoice->payments()->delete();
        });
      }
    }
    

    【讨论】:

    • 不起作用! Invoice.php 第 18 行中的 FatalErrorException:无法使静态方法 Illuminate\Database\Eloquent\Model::boot() 在类 App\Models\Invoice 中成为非静态方法
    • 修复,函数缺少静态修饰符
    • 效果很好!非常感谢!当软删除大约 100 条记录时,这是否会导致任何性能问题?
    • 您必须获取所有这些然后保存它们中的每一个,所以这是额外的 101 个查询......在这种情况下,您可以为相关模型手动设置 deleted_at,这会不太干净,但会只运行一个 SQL 查询。我会在一秒钟内更新答案
    • 我知道这是旧的,但我真的建议使用观察者来实现这一点。
    【解决方案2】:

    您可以选择以下两种方式之一。

    最简单的方法是覆盖 Eloquents delete() 方法并包含相关模型,例如:

    public function delete()
    {
        $this->payments()->delete();
        return parent::delete();
    } 
    

    上面的方法应该只是 find 可以工作,但它似乎有点脏,我想说这不是社区内的首选方法。

    更简洁的方式 (IMO) 是利用 Eloquents 事件,例如:

    public static function boot()
    {
        parent::boot();
    
        static::deleting(function($invoice) { 
             $invoice->payments()->delete();
    
        });
    }
    

    上述两种方法中的任何一种(但不是两种)都可以在您的Invoice 模型中使用。 另外,我假设您在模型中设置了关系,但是,我不确定您是否允许一张发票进行多次付款。无论哪种方式,您都可能需要将示例中的 payments() 更改为您在发票模型中命名的关系。

    希望这会有所帮助!

    【讨论】:

    • 直接在支付关系上调用delete()会绕过模型,不会触发相关模型上的SoftDelete。它们将从数据库中删除。为了使它们被软删除,您需要在每个相关模型上调用 delete()。
    • 您还缺少覆盖删除方法中的 return 语句 - 它应该执行“return parent::delete();”,否则您将丢失从 delete() 返回的值,如果您没有覆盖它。
    • @jedrzej.kurylo,我刚刚进行了测试,以确保您可以对关系使用软删除!
    • 没错,刚刚检查了代码。很高兴知道未来:)
    • 是否保证交易@RossWilson
    【解决方案3】:

    我知道你很久以前就问过这个问题,但我发现this package 非常简单明了。

    或者你也可以使用this package,它也很有用。

    记住根据你的 laravel 版本安装正确的版本。

    你必须通过 composer 安装它:

     composer require askedio/laravel5-soft-cascade ^version
    

    在第二个包装中:

     composer require iatstuti/laravel-cascade-soft-deletes
    

    在你的 config/app.php 中注册服务提供者。

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

    如果您删除一条记录,此包会识别其所有子项并软删除它们。

    如果您的子模型中有另一个关系,那么也可以在该模型中使用它的特征。这比手动操作要容易得多。

    第二个包的好处是删除模型的孙子。在某些情况下,我说这是一种更好的方法。

    【讨论】:

    • 我使用了第二个包,它对 delete() 非常有用,但我可以让它也适用于恢复吗?
    【解决方案4】:

    如果您的数据库的关系不超过一层,那么您可以简单地使用 Laravel 事件在 Model boot() 方法中处理您的软删除,如下所示:

    <?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 跟随兔子洞,无论它走多深。

    【讨论】:

      猜你喜欢
      • 2010-09-09
      • 2018-06-20
      • 1970-01-01
      • 1970-01-01
      • 2019-08-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-16
      相关资源
      最近更新 更多