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


158

当我使用以下语法删除一行时:

$user->delete();

有没有一种方法可以附加各种回调,以便例如自动执行此操作:

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

最好在模型类内部。

Answers:


205

我相信这是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...
        });
    }
}

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


7
注意:我花了一些时间才开始工作。我需要添加first()到查询中,以便可以访问模型事件,例如User::where('id', '=', $id)->first()->delete(); Source
Michel Ayres

6
@MichelAyres:是的,您需要在模型实例上而不是在查询生成器上调用delete()。Builder有它自己的delete()方法,该方法基本上只运行DELETE sql查询,所以我认为它对orm事件
一无所知

3
这是进行软删除的方法。我相信新的/首选Laravel方法是通过以下方式将所有这些粘贴到AppServiceProvider的boot()方法中:\ App \ User :: deleting(function($ u){$ u-> photos()-> delete( );});
Watercayman,2017年

4
我几乎在Laravel 5.5中工作过,我必须添加一个foreach($user->photos as $photo),然后$photo->delete()确保每个孩子在各个级别上都删除了自己的孩子,而不是因为某种原因而只删除了一个孩子。
乔治

9
但是,这不会进一步级联。例如,如果Photoshas tags和您在Photos模型中执行相同的操作(即在deletingmethod:上$photo->tags()->delete();),则永远不会触发。但是,如果我将其for循环并执行类似的操作,for($user->photos as $photo) { $photo->delete(); }那么the tags也将被删除!仅供参考
-supersan

199

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

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

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

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

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

是的,我想我应该澄清这种依赖性。
克里斯·施密茨

62
但是,如果您使用的是软删除,则不会,因为实际上并不会真正删除行。
tremby 2014年

7
另外-这将删除数据库中的记录,但不会运行您的delete方法,因此,如果您要对删除进行额外的工作(例如-删除文件),它将不会运行
amosmos

10
这种方法依靠数据库来进行级联删除,但是并非所有的数据库都支持此删除,因此需要格外小心。例如,没有配备MyISAM引擎的MySQL,或者默认设置中没有任何NoSQL DB,SQLite等。另外的问题是,工匠不会在运行迁移时就此警告您,它只会在MyISAM表上不创建外键,并且当您以后删除记录时,将不会发生级联。我曾经遇到过这个问题,并且相信我很难调试。
ivanhoe

1
@kehinde您显示的方法不会在要删除的关系上调用删除事件。您应该遍历该关系并单独调用delete。
汤姆(Tom)

51

注意:此答案是为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();
    }
}

希望能帮助到你。


1
您必须使用:foreach($ this-> photos as $ photo)($ this-> photos而不是$ this-> photos())否则,提示不错!
Barryvdh

20
为了提高效率,请使用以下查询:Photo :: where(“ user_id”,$ this-> id)-> delete(); 不是最好的方式,但只有1个查询,方式更好的性能,如果用户有1,000,000照片的。
德克(Dirk)2013年

5
实际上,您可以调用:$ this-> photos()-> delete(); 无需循环–艾芬豪
艾芬豪2013年

4
@ivanhoe我注意到,如果删除集合,则删除事件不会在照片中触发,但是,如akhyar所建议的那样进行迭代,将导致触发删除事件。这是一个错误吗?
adamkrell 2013年

1
@akhyar几乎可以做到$this->photos()->delete()。在photos()返回查询生成器对象。
Sven van Zoelen 2014年

32

用户模型中的关系:

public function photos()
{
    return $this->hasMany('Photo');
}

删除记录及相关:

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

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

$user->delete();

4
这可行,但是如果涉及到一个piviot表(在hasMany / belongsToMany关系的情况下),请小心使用$ user()-> relation()-> detach(),否则您将删除引用而不是该关系。
詹姆斯·贝利

laravel 6,这对我有用。@Calin您能解释更多吗?
Arman H

19

有3种解决方法:

1.在模型启动时使用雄辩事件(ref: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.使用口才事件观察者(参考: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');

1
我认为这3个选项最优雅,因为是将约束内置到数据库本身中。我对其进行了测试,并且效果很好。
吉尔伯特'18

14

从Laravel 5.2开始,文档指出应在AppServiceProvider中注册以下类型的事件处理程序:

<?php
class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        User::deleting(function ($user) {
            $user->photos()->delete();
        });
    }

我什至甚至想将它们移到单独的类而不是闭包上,以改善应用程序的结构。


1
Laravel 5.3建议将它们放在称为Observers的单独的类中-尽管虽然仅在5.3中进行了说明,但该Eloquent::observe()方法在5.2中也可用,并且可以从AppServiceProvider中使用。
利斯

3
如果您有任何“的hasMany”的关系,从你的photos(),你还需要小心-这个过程将不会删除孙子,因为你没有加载模型。您将需要遍历photos(注意,不是photos())并将delete()其作为模型触发它们,以触发与删除相关的事件。
利斯

1
@Leith观察方法在5.1中也可用。
泰勒·里德

2

最好重写此delete方法。这样,您可以将数据库事务合并到delete方法本身中。如果您使用事件方式,您将不得不支付delete每次调用它时使用数据库事务方法的调用。

在您的User模型中。

public function delete()
{
    \DB::beginTransaction();

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}

1

在我的情况下,这非常简单,因为我的数据库表是带有外键的InnoDB,并带有Cascade on Delete。

因此,在这种情况下,如果您的照片表包含该用户的外键参考,而您要做的就是删除酒店,并且清理工作将由数据库进行,则数据库将从数据中删除所有照片记录基础。


如其他答案中所述,使用软删除时,在数据库层进行级联删除将不起作用。买家当心。:)
本·约翰逊

1

在删除对象本身之前,我将遍历集合中的所有内容,并进行迭代。

这是一个例子:

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();

0

或者,您也可以根据需要执行以下操作:

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();

0

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

您可以轻松处理高阶消息

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();
        });
    }
}

-1

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

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

所以,这只蜜蜂说:

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

为了在删除用户时删除帖子标签,我们必须迭代$user->posts并调用$post->delete()

foreach($user->posts as $post) { $post->delete(); } ->这将触发发布上的删除事件

VS

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


-2

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

将会发生的是,我们将所有与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可以解决这个问题。
7rust
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.