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

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

$user->delete();

是否有一种方法来附加一个类型的回调,这样它就会自动这样做:

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

最好是在模型类内部。

349130 次浏览

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

希望能有所帮助。

你可以在你的迁移中设置这个:

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

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

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

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

或者你也可以这样做,只是另一个选择:

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

我相信这是Eloquent事件(http://laravel.com/docs/eloquent#model-events)的一个完美用例。你可以使用"deleting"事件来进行清理:

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

你可能也应该把整个东西放在一个事务中,以确保引用的完整性。

用户模型中的关系:

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

删除相关记录:

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


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


$user->delete();

在我的情况下,这是相当简单的,因为我的数据库表是InnoDB与外键级联删除。

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

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

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

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

这里有一个例子:

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

有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. 使用雄辩的事件观察者 (ref: 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.使用外键约束 (ref: https://laravel.com/docs/5.7/migrations#foreign-key-constraints)

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

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

只有在模型实例上使用delete函数时,才会触发事件。

所以,有人说:

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

为了在删除用户时删除post标签,我们必须遍历$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模型甚至没有加载)

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

你可以使用这种方法作为替代。

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

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

最好是为此重写delete方法。这样,你可以在delete方法本身中合并DB事务。如果你使用事件方式,你将不得不在每次调用delete方法时用一个DB事务来覆盖它。

在你的User模型中。

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


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


$result = parent::delete();


\DB::commit();


return $result;
}

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

我在《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();中删除不适合我…

在定义模型迁移时最好使用onDelete级联。它负责为你删除模型的关系:

如。

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

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

这里有一些完美的解决方案。

# 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',
]);
  1. 在你想删除的模型上添加删除函数

  2. 定义模型之间的关系

例如在这个例子中:

/**
* @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');
}
$table->foreignId('user_id')->constrained('user')->cascadeOnDelete();

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