简体   繁体   English

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

[英]Automatically deleting related rows in Laravel (Eloquent ORM)

When I delete a row using this syntax:当我使用此语法删除一行时:

$user->delete();

Is there a way to attach a callback of sorts, so that it would eg do this automatically:有没有办法附加各种回调,以便它会自动执行此操作:

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

Preferably inside the model-class.最好在模型类中。

I believe this is a perfect use-case for Eloquent events ( http://laravel.com/docs/eloquent#model-events ).我相信这是 Eloquent 事件 ( http://laravel.com/docs/eloquent#model-events ) 的完美用例。 You can use the "deleting" event to do the cleanup:您可以使用“删除”事件进行清理:

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

You should probably also put the whole thing inside a transaction, to ensure the referential integrity..您可能还应该将整个事情放在一个事务中,以确保参照完整性。

You can actually set this up in your migrations:您实际上可以在迁移中进行设置:

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

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

You may also specify the desired action for the "on delete" and "on update" properties of the constraint:您还可以为约束的“删除时”和“更新时”属性指定所需的操作:

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

Note : This answer was written for Laravel 3 .注意:此答案是为Laravel 3编写的。 Thus might or might not works well in more recent version of Laravel.因此在更新的 Laravel 版本中可能会也可能不会很好地工作。

You can delete all related photos before actually deleting the user.您可以在实际删除用户之前删除所有相关照片。

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

Hope it helps.希望能帮助到你。

Relation in User model:用户模型中的关系:

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

Delete record and related:删除记录及相关:

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

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

$user->delete();

There are 3 approaches to solving this:有3种方法可以解决这个问题:

1. Using Eloquent Events On Model Boot (ref: https://laravel.com/docs/5.7/eloquent#events ) 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. Using Eloquent Event Observers (ref: https://laravel.com/docs/5.7/eloquent#observers ) 2. 使用 Eloquent Event Observers (参考: https : //laravel.com/docs/5.7/eloquent#observers

In your AppServiceProvider, register the observer like so:在您的 AppServiceProvider 中,像这样注册观察者:

public function boot()
{
    User::observe(UserObserver::class);
}

Next, add an Observer class like so:接下来,添加一个 Observer 类,如下所示:

class UserObserver
{
    public function deleting(User $user)
    {
         $user->photos()->delete();
    }
}

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

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

As of Laravel 5.2, the documentation states that these kinds of event handlers should be registered in the AppServiceProvider:从 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();
        });
    }

I even suppose to move them to separate classes instead of closures for better application structure.我什至想将它们移动到单独的类而不是闭包以获得更好的应用程序结构。

It is better if you override the delete method for this.最好为此覆盖delete方法。 That way, you can incorporate DB transactions within the delete method itself.这样,您可以将 DB 事务合并到delete方法本身中。 If you use the event way, you will have to cover your call of delete method with a DB transaction every time you call it.如果您使用事件方式,则每次调用它时都必须用数据库事务覆盖对delete方法的调用。

In your User model.在您的User模型中。

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

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}

To elaborate on the selected answer, if your relationships also have child relationships that must be deleted, you have to retrieve all child relationship records first, then call the delete() method so their delete events are fired properly as well.为了详细说明所选答案,如果您的关系也有必须删除的子关系,则必须首先检索所有子关系记录,然后调用delete()方法,以便它们的删除事件也正确触发。

You can do this easily with 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();
        });
    }
}

You can also improve performance by querying only the relationships ID column:您还可以通过仅查询关系 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. Add delete function on model that you want to delete add delete function on model 你要删除

  2. Define relations of models定义模型关系

for example in this instance:例如在这种情况下:

/**
 * @return bool|null
 */
public function delete(): ?bool
{
    $this->profile()->delete();
    $this->userInterests()->delete();
    $this->userActivities()->delete();
    $this->lastLocation()->delete();

    return parent::delete();
}

And relations in user model are:与用户 model 的关系是:

public function profile()
{
    return $this->hasOne(Profile::class, 'user_id', 'id');
}

public function userInterests()
{
    return $this->hasMany(userInterest::class, 'user_id', 'id');
}

public function userActivities()
{
    return $this->hasMany(userActivity::class, 'user_id', 'id');
}

public function lastLocation()
{
    return $this->hasOne(LastLocation::class, 'user_id', 'id');
}

I would iterate through the collection detaching everything before deleting the object itself.在删除对象本身之前,我会遍历集合以分离所有内容。

here's an example:这是一个例子:

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

I know it is not automatic but it is very simple.我知道这不是自动的,但它非常简单。

Another simple approach would be to provide the model with a method.另一种简单的方法是为模型提供一种方法。 Like this:像这样:

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

Then you can simply call this where you need:然后你可以简单地在你需要的地方调用它:

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

Using Constrained()使用Constrained()

After Laravel 7 , new foreignId() and constrained() methods are available for defining relationship constraint in database.Laravel 7 之后,新的foreignId()constrained()方法可用于在数据库中定义关系约束。 OnDelete() method can be used on these methods to automatically delete related records. OnDelete()方法可用于这些方法以自动删除相关记录。

Old style老款式

$table->unsignedBigInterer('user_id');

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

New style新风格

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

In my case it was pretty simple because my database tables are InnoDB with foreign keys with Cascade on Delete.就我而言,这非常简单,因为我的数据库表是带有外键的 InnoDB,删除时带有级联。

So in this case if your photos table contains a foreign key reference for the user than all you have to do is to delete the hotel and the cleanup will be done by the Data Base, the data base will delete all the photos records from the data base.因此,在这种情况下,如果您的照片表包含用户的外键引用,那么您所要做的就是删除酒店,清理工作将由数据库完成,数据库将从数据中删除所有照片记录根据。

Here are the perfect solutions.这里有完美的解决方案。

# 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',
]);

Or you can do this if you wanted, just another option:或者您可以根据需要执行此操作,只是另一种选择:

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

Note if you are not using the default laravel db connection then you need to do the following:请注意,如果您没有使用默认的 laravel db 连接,那么您需要执行以下操作:

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

This way worked for me on Laravel 8:这种方式在 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');
}

Note: deleting in this syntax $user->photos()->delete();注意:在这个语法中删除$user->photos()->delete(); not worked for me...没有为我工作...

It's better to use onDelete cascade when defining your model's migration.在定义模型的迁移时最好使用 onDelete 级联。 This takes care of deleting the model's relations for you:这会为您删除模型的关系:

eg例如

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

If you happen to find yourself thinking about how to delete a model and its relations to a level greater than 3 or 4 nested relations, then you should consider redefining your model's relationships.如果您碰巧发现自己正在考虑如何将模型及其关系删除到超过 3 或 4 个嵌套关系的级别,那么您应该考虑重新定义模型的关系。

$table->foreignId('user_id')->constrained('user')->cascadeOnDelete();

or或者

$table->foreignId('user_id')->constrained()->cascadeOnDelete();

yeah, but as @supersan stated upper in a comment, if you delete() on a QueryBuilder, the model event will not be fired, because we are not loading the model itself, then calling delete() on that model.是的,但是正如@supersan 在评论中所说的那样,如果您在 QueryBuilder 上使用 delete(),则不会触发模型事件,因为我们没有加载模型本身,然后在该模型上调用 delete()。

The events are fired only if we use the delete function on a Model Instance.仅当我们在模型实例上使用删除函数时才会触发事件。

So, this beeing said:所以,这只蜜蜂说:

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

in order to delete the post tags when deleting the user, we would have to iterate over $user->posts and calling $post->delete()为了在删除用户时删除帖子标签,我们必须遍历$user->posts并调用$post->delete()

foreach($user->posts as $post) { $post->delete(); } foreach($user->posts as $post) { $post->delete(); } -> this will fire the deleting event on Post foreach($user->posts as $post) { $post->delete(); } -> 这将触发 Post 上的删除事件

VS VS

$user->posts()->delete() -> this will not fire the deleting event on post because we do not actually load the Post Model (we only run a SQL like: DELETE * from posts where user_id = $user->id and thus, the Post model is not even loaded) $user->posts()->delete() -> 这不会在 post 上触发删除事件,因为我们实际上并没有加载 Post 模型(我们只运行一个 SQL,如: DELETE * from posts where user_id = $user->id ,因此,甚至没有加载 Post 模型)

You can use this method as an alternative.您可以使用此方法作为替代方法。

What will happen is that we take all the tables associated with the users table and delete the related data using looping将会发生的事情是我们将所有与 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]);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM