Laravel 中自动删除相关行(Eloquent ORM)

Posted

技术标签:

【中文标题】Laravel 中自动删除相关行(Eloquent ORM)【英文标题】:Automatically deleting related rows in Laravel (Eloquent ORM) 【发布时间】:2012-12-19 21:22:45 【问题描述】:

当我使用这种语法删除一行时:

$user->delete();

有没有办法附加各种回调,例如自动执行此操作:

$this->photo()->delete();

最好在模型类中。

【问题讨论】:

【参考方案1】:

注意:此答案是为 Laravel 3 编写的。因此,在最新版本的 Laravel 中可能会或可能不会很好地工作。

您可以在实际删除用户之前删除所有相关照片。

<?php

class User extends Eloquent


    public function photos()
    
        return $this->has_many('Photo');
    

    public function delete()
    
        // delete all related photos 
        $this->photos()->delete();
        // as suggested by Dirk in comment,
        // it's an uglier alternative, but faster
        // Photo::where("user_id", $this->id)->delete()

        // delete the user
        return parent::delete();
    

希望对你有帮助。

【讨论】:

你必须使用:foreach($this->photos as $photo) ($this->photos 而不是 $this->photos()) 否则,好的提示! 为了提高效率,使用一个查询:Photo::where("user_id", $this->id)->delete();不是最好的方式,但只有 1 个查询,如果用户有 1.000.000 张照片,方式性能会更好。 其实可以调用:$this->photos()->delete();无需循环——艾芬豪 @ivanhoe 我注意到如果您删除集合,删除事件不会在照片中触发,但是,按照 akhyar 的建议进行迭代会导致删除事件触发。这是一个错误吗? 是的,我在 L4。不工作。删除事件不会触发。我试了几次。我还没来得及梳理 Laravel 的代码找出原因。【参考方案2】:

您实际上可以在迁移中进行设置:

$table-&gt;foreign('user_id')-&gt;references('id')-&gt;on('users')-&gt;onDelete('cascade');

来源:http://laravel.com/docs/5.1/migrations#foreign-key-constraints

您还可以为“on delete”和“on”指定所需的操作 更新”约束的属性:

$table->foreign('user_id')
      ->references('id')->on('users')
      ->onDelete('cascade');

【讨论】:

是的,我想我应该澄清这种依赖关系。 但如果您使用软删除,则不会,因为这些行并没有真正删除。 另外 - 这将删除数据库中的记录,但不会运行您的删除方法,因此如果您在删除方面做额外的工作(例如 - 删除文件),它将不会运行跨度> 这种方式依赖于 DB 进行级联删除,但并非所有 DB 都支持,因此需要格外小心。例如,带有 MyISAM 引擎的 mysql 没有,任何 NoSQL DB、默认设置中的 SQLite 等都没有。另外的问题是,当你运行迁移时,artisan 不会警告你,它不会在 MyISAM 表上创建外键和当您稍后删除记录时,不会发生级联。我曾经遇到过这个问题,相信我很难调试。 @kehinde 您展示的方法不会在要删除的关系上调用删除事件。您应该遍历关系并单独调用 delete。【参考方案3】:

如果你愿意,你也可以这样做,只是另一种选择:

try 
    DB::connection()->pdo->beginTransaction();

    $photos = Photo::where('user_id', '=', $user_id)->delete(); // Delete all photos for user
    $user = Geofence::where('id', '=', $user_id)->delete(); // Delete users

    DB::connection()->pdo->commit();

catch(\Laravel\Database\Exception $e) 
    DB::connection()->pdo->rollBack();
    Log::exception($e);

请注意,如果您没有使用默认的 laravel db 连接,那么您需要执行以下操作:

DB::connection('connection_name')->pdo->beginTransaction();
DB::connection('connection_name')->pdo->commit();
DB::connection('connection_name')->pdo->rollBack();

【讨论】:

【参考方案4】:

我相信这是 Eloquent 事件 (http://laravel.com/docs/eloquent#model-events) 的完美用例。您可以使用“删除”事件进行清理:

class User extends Eloquent

    public function photos()
    
        return $this->has_many('Photo');
    

    // this is a recommended way to declare event handlers
    public static function boot() 
        parent::boot();

        static::deleting(function($user)  // before delete() method call this
             $user->photos()->delete();
             // do the rest of the cleanup...
        );
    

您可能还应该将整个内容放入事务中,以确保引用完整性..

【讨论】:

注意:我花了一些时间直到我得到这个工作。我需要将first() 添加到查询中,以便我可以访问 model-event 例如User::where('id', '=', $id)-&gt;first()-&gt;delete();Source @MichelAyres:是的,您需要在模型实例上调用 delete(),而不是在查询生成器上。 Builder 有它自己的 delete() 方法,它基本上只是运行一个 DELETE sql 查询,所以我认为它对 orm 事件一无所知...... 这是软删除的方式。我相信新的/首选的 Laravel 方法是以这种方式将所有这些粘贴在 AppServiceProvider 的 boot() 方法中: \App\User::deleting(function ($u) $u->photos()->delete( ); ); 几乎在 Laravel 5.5 中工作,我必须添加一个 foreach($user-&gt;photos as $photo),然后是 $photo-&gt;delete() 以确保每个孩子在所有级别上都删除了它的孩子,而不是像某些人那样只删除一个原因。 这并没有进一步级联。例如,如果 Photos 具有 tags 并且您在 Photos 模型中执行相同操作(即在 deleting 方法上:$photo-&gt;tags()-&gt;delete();)它永远不会被触发。但是,如果我将其设为for 循环并执行for($user-&gt;photos as $photo) $photo-&gt;delete(); 之类的操作,那么tags 也会被删除!仅供参考【参考方案5】:

用户模型中的关系:

public function photos()

    return $this->hasMany('Photo');

删除记录及相关:

$user = User::find($id);

// delete related   
$user->photos()->delete();

$user->delete();

【讨论】:

这行得通,但如果涉及到数据透视表(在 hasMany / belongsToMany 关系的情况下),请小心使用 $user()->relation()->detach(),否则你将删除引用,而不是关系。 这适用于我 laravel 6。@Calin 你能解释更多吗?【参考方案6】:

就我而言,这非常简单,因为我的数据库表是带有外键的 InnoDB,删除时带有 Cascade。

因此,在这种情况下,如果您的照片表包含用户的外键引用,那么您只需删除酒店并且清理工作将由数据库完成,数据库将删除所有照片记录来自数据库。

【讨论】:

正如其他答案中所述,使用软删除时,数据库层的级联删除将不起作用。买家小心。 :)【参考方案7】:

从 Laravel 5.2 开始,documentation states 表示这些类型的事件处理程序应该在 AppServiceProvider 中注册:

<?php
class AppServiceProvider extends ServiceProvider

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    
        User::deleting(function ($user) 
            $user->photos()->delete();
        );
    

我什至想将它们移动到单独的类而不是闭包中以获得更好的应用程序结构。

【讨论】:

Laravel 5.3 建议将它们放在名为 Observers 的单独类中 - 虽然它只记录在 5.3 中,但 Eloquent::observe() 方法在 5.2 中也可用,并且可以从 AppServiceProvider 中使用。跨度> 如果您有任何“hasMany”关系来自您的photos(),您还需要小心 - 此过程将不会删除孙子,因为你没有加载模型。您需要循环 photos(注意,不是 photos())并在它们上触发 delete() 方法作为模型,以便触发与删除相关的事件。 @Leith 观察方法在 5.1 中也可以使用。【参考方案8】:

我会遍历集合,在删除对象本身之前分离所有内容。

这是一个例子:

try 
        $user = User::findOrFail($id);
        if ($user->has('photos')) 
            foreach ($user->photos as $photo) 

                $user->photos()->detach($photo);
            
        
        $user->delete();
        return 'User deleted';
     catch (Exception $e) 
        dd($e);
    

我知道它不是自动的,但它非常简单。

另一种简单的方法是为模型提供方法。像这样:

public function detach()
       try 
            
            if ($this->has('photos')) 
                foreach ($this->photos as $photo) 
    
                    $this->photos()->detach($photo);
                
            
           
         catch (Exception $e) 
            dd($e);
        

然后您可以在需要的地方简单地调用它:

$user->detach();
$user->delete();

【讨论】:

【参考方案9】:

有 3 种方法可以解决这个问题:

1.在模型启动时使用 Eloquent 事件(参考:https://laravel.com/docs/5.7/eloquent#events)

class User extends Eloquent

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

        static::deleting(function($user) 
             $user->photos()->delete();
        );
    

2。使用 Eloquent 事件观察者(参考:https://laravel.com/docs/5.7/eloquent#observers)

在您的 AppServiceProvider 中,像这样注册观察者:

public function boot()

    User::observe(UserObserver::class);

接下来,像这样添加一个 Observer 类:

class UserObserver

    public function deleting(User $user)
    
         $user->photos()->delete();
    

3.使用外键约束(参考:https://laravel.com/docs/5.7/migrations#foreign-key-constraints)

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

【讨论】:

我认为这 3 个选项是最优雅的,因为它将约束构建到数据库本身中。我对其进行了测试,效果很好。 你最好提一下有什么区别,第一个和第二个非常适合一次只进行一个更改,而不是批处理,而且当你使用不支持级联的数据库时,第三个可以处理多个行,但不是在所有数据库上。【参考方案10】:

是的,但正如@supersan 在评论中所述,如果您在 QueryBuilder 上 delete(),则不会触发模型事件,因为我们没有加载模型本身,然后在该模型上调用 delete()。

仅当我们在模型实例上使用删除函数时才会触发事件。

所以,这只蜜蜂说:

if user->hasMany(post)
and if post->hasMany(tags)

为了在删除用户时删除帖子标签,我们必须遍历 $user-&gt;posts 并调用 $post-&gt;delete()

foreach($user-&gt;posts as $post) $post-&gt;delete(); -> 这将在 Post 上触发删除事件

VS

$user-&gt;posts()-&gt;delete() -> 这不会在 post 上触发删除事件,因为我们实际上并没有加载 Post 模型(我们只运行类似的 SQL:DELETE * from posts where user_id = $user-&gt;id,因此甚至没有加载 Post 模型)

【讨论】:

【参考方案11】:

要详细说明所选答案,如果您的关系也有必须删除的子关系,您必须先检索所有子关系记录,然后调用delete() 方法,以便它们的删除事件也被正确触发。

您可以使用higher order messages 轻松做到这一点。

class User extends Eloquent

    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    public static function boot() 
        parent::boot();

        static::deleting(function($user) 
             $user->photos()->get()->each->delete();
        );
    

您还可以通过仅查询关系 ID 列来提高性能:

class User extends Eloquent

    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    public static function boot() 
        parent::boot();

        static::deleting(function($user) 
             $user->photos()->get(['id'])->each->delete();
        );
    

【讨论】:

【参考方案12】:

您可以使用此方法作为替代方法。

会发生什么,我们取所有与 users 表关联的表,并使用循环删除相关数据

$tables = DB::select("
    SELECT
        TABLE_NAME,
        COLUMN_NAME,
        CONSTRAINT_NAME,
        REFERENCED_TABLE_NAME,
        REFERENCED_COLUMN_NAME
    FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
    WHERE REFERENCED_TABLE_NAME = 'users'
");

foreach($tables as $table)
    $table_name =  $table->TABLE_NAME;
    $column_name = $table->COLUMN_NAME;

    DB::delete("delete from $table_name where $column_name = ?", [$id]);

【讨论】:

我不认为所有这些查询都是必要的,因为如果你清楚地指定它,雄辩的 orm 可以处理这个问题。【参考方案13】:

最好覆盖delete 方法。这样,您可以将 DB 事务合并到 delete 方法本身中。如果使用事件方式,则每次调用delete 方法时都必须使用数据库事务覆盖。

在您的 User 模型中。

public function delete()

    \DB::beginTransaction();

     $this
        ->photo()
        ->delete()
    ;

    $result = parent::delete();

    \DB::commit();

    return $result;

【讨论】:

【参考方案14】:

使用Constrained()

Laravel 7 之后,新的foreignId()constrained() 方法可用于定义数据库中的关系约束。在这些方法上可以使用OnDelete()方法自动删除相关记录。

旧式

$table->unsignedBigInterer('user_id');

$table->foreign('user_id')
    ->references('id')
    ->on('users')
    ->onDelete('cascade');

新风格

$table->foreignId('user_id')
      ->constrained()
      ->onDelete('cascade');

【讨论】:

【参考方案15】:

这种方式在 Laravel 8 上对我有用:

public static function boot() 

    parent::boot();
    
    static::deleted(function($item)
        $item->deleted_by = \Auth::id(); // to know who delete item, you can delete this row
        $item->save();  // to know who delete item, you can delete this row
        foreach ($item->photos as $photo)
            $photo->delete();
        
    );


public function photos()

    return $this->hasMany('App\Models\Photos');

注意:以这种语法删除 $user-&gt;photos()-&gt;delete(); 对我不起作用...

【讨论】:

【参考方案16】:

在定义模型的迁移时最好使用 onDelete 级联。这会为您删除模型的关系:

例如

 $table->foreign(’user_id’)
  ->references(’id’)->on(’users’)
  ->onDelete(’cascade’);

如果您碰巧发现自己正在考虑如何将模型及其关系删除到大于 3 或 4 个嵌套关系的级别,那么您应该考虑重新定义模型的关系。

【讨论】:

【参考方案17】:

这里有完美的解决方案。

# model

public function order_item_properties()

    return $this->hasMany(OrderItemProperty::class, 'order_id', 'id');


public function order_variations()

    return $this->hasMany(OrderItemVariation::class, 'order_id', 'id');


# controller

$order_item = OrderItem::find($request->order_id);

$order_item->order_item_properties()->delete();
$order_item->order_variations()->delete();

$order_item->delete();

return response()->json([
    'message' => 'Deleted',
]);

【讨论】:

以上是关于Laravel 中自动删除相关行(Eloquent ORM)的主要内容,如果未能解决你的问题,请参考以下文章

Laravel eloquent 获取相关表的最新行

Laravel Eloquent 级联删除

Laravel Eloquent 在 whereHas() 上进行选择

Laravel Eloquent,添加有限行的关系变为空

Eloquent - 如果外键有行,则防止删除数据 - Laravel

Laravel Eloquent - 从相关模型中检索特定列