ChatGPT解决这个技术问题 Extra ChatGPT

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

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

$user->delete();

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

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

最好在模型类内部。


P
Paras

我相信这是 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)->first()->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->photos as $photo),然后是 $photo->delete() 以确保每个孩子在所有级别上都删除了它的孩子,而不是因为某种原因而只删除一个。
不过,这并没有进一步级联。例如,如果 Photos 具有 tags 而您在 Photos 模型中执行相同操作(即在 deleting 方法:$photo->tags()->delete(); 上)它永远不会被触发。但是,如果我将其设为 for 循环并执行 for($user->photos as $photo) { $photo->delete(); } 之类的操作,则 tags 也会被删除!仅供参考
N
NathanOliver

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

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

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

您还可以为约束的“on delete”和“on update”属性指定所需的操作: $table->foreign('user_id') ->references('id')->on('users') - >onDelete('级联');


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

注意:这个答案是为 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 的代码来找出原因。
C
Calin Blaga

用户模型中的关系:

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 你能解释更多吗?
P
Paras

有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 Event Observers(参考: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 个选项是最优雅的,因为它将约束构建到数据库本身中。我测试它并且工作得很好。
您最好提及有什么区别,第一个和第二个非常适合一次仅进行一次更改,而不是批处理,并且当您使用不支持级联的数据库时,第三个适用于多行但不适用于所有数据库.
m
mpen

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

<?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 中使用。
如果您有任何 来自您的 photos() 的“hasMany”关系,您还需要小心 - 此过程将不会删除孙子,因为您没有加载模型.您需要循环 photos(注意,不是 photos())并将它们作为模型触发 delete() 方法,以便触发与删除相关的事件。
@Leith 5.1 中也提供了观察方法。
b
budhajeewa

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

在您的 User 模型中。

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

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

    $result = parent::delete();

    \DB::commit();

    return $result;
}

S
Steve Bauman

要详细说明所选答案,如果您的关系还具有必须删除的子关系,则必须先检索所有子关系记录,然后调用 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();
        });
    }
}

B
BABAK ASHRAFI

使用约束()

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

a
agm1984

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

这是一个例子:

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

A
AnasSafi

这种方式在 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->photos()->delete(); 对我不起作用...


C
Cyber5h13ld

在要删除的模型上添加删除功能定义模型的关系

例如在这种情况下:

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

    return parent::delete();
}

用户模型中的关系是:

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

A
Alex

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

因此,在这种情况下,如果您的照片表包含用户的外键引用,那么您所要做的就是删除酒店并且清理将由数据库完成,数据库将从数据中删除所有照片记录根据。


正如其他答案中所述,使用软删除时,数据库层的级联删除将不起作用。买家小心。 :)
P
Pri Nce

这里有完美的解决方案。

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

定义“完美解决方案”。它不会自动删除相关数据
D
Darren Powers

或者您可以根据需要执行此操作,只是另一种选择:

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

L
Leena Patel

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

例如

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

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


R
Rajib Bin Alam
$table->foreignId('user_id')->constrained('user')->cascadeOnDelete();

或者

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

a
ascripter

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

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

所以,这只蜜蜂说:

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

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

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

VS

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


D
Daanzel

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

将会发生的是我们获取与用户表关联的所有表并使用循环删除相关数据

$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 可以处理这个问题。