'(Laravel) How to delete multiple models including optional relationships?
Summary
I'm trying to let my model controller delete multiple models (selected by an array of IDs) including their selected related models (selected by an array of relationship names).
Situation
Model
Postwith relationships:- (One-to-Many) with
Commentmodel under$post->comments() - (One-to-Many) with
Imagemodel under$post->images() - (Many-to-One) with
Usermodel under$post->user()
- (One-to-Many) with
- In
PostControllerthere's thedestroy_multiplemethod that handles the deletion, and where I have:- Array
$idswith IDs ofPostmodels to delete (e.g.[1,2,4]) - Array
$related_modelswith relationship names to delete as well (e.g.['user','comments']but could be a different selection in each call)
- Array
Attempts
1) Iterate and delete:
Post::findMany($ids)->each(function($item) use ($related_models) {
foreach ($related_models as $relation) {
$item->{$relation}()->delete();
}
$item->delete();
});
Problem: All models have to be retrieved first, and for each model, all selected related models have to be deleted. This is a LOT of overhead.
2) Delete related models and models:
// For every selected relationship, delete related models
foreach ($related_models as $relation) {
$class = 'App\\' . studly_case(str_singular($relation));
$class::whereIn('post_id', $ids)->delete();
}
// Delete the models
Post::destroy($ids);
Problem: This only works for the one-to-many relationships and only provided the database columns are named according to the Laravel standard.
Question
What is the most efficient way to do this, while:
- using the defined relationships to ensure the right database naming, columns, etc (as in Attempt 1)
- keeping the performance (without having to retrieve the models) (as in Attempt 2)
- keeping the optionality of choosing the
$idsand$related_models
?
Notes
- I know that deleting a parent model (
Userin this case) is not very good practice, but it's there for the sake of the question. ;) - Using the database
CASCADEconstraint on the foreign keys of the related models' tables (in the migration) makes it lose the optionality. Related models are now always deleted, and also in other instances besides thedelete_multiplemethod.
Solution 1:[1]
You can update your migration like this:
Schema::table('comments', function (Blueprint $table) {
...
$table->unsignedInteger('posts_id');
$table->foreign('posts_id')
->references('id')
->on('posts')
->onDelete('cascade');
});
The above migration will auto remove every comment related to the post you are deleting.
Solution 2:[2]
You should delete the Relationship models first
Add this method in your Post model:
public function delete()
{
$this->images()->delete();
$this->comments()->delete();
return parent::delete();
}
Call from your controller logic This will delete the relationship model first and then delete itself
Post::findMany($ids)->each(function ($item) {
$item->delete();
});
Solution 3:[3]
How about declaring the delete method in your model.
class Post extends Eloquent
{
public function comments()
{
return $this->hasMany(Comment::class);
}
public function images()
{
return $this->hasMany(Images::class);
}
public static function boot() {
parent::boot();
static::deleting(function($post) {
$post->comments()->delete();
$post->images()->delete();
// or call another method here after you declare above
});
}
}
So in your controller, call the simple delete function.
$post = Post::find($id);
$post->delete();
I use this in my delete relationship function from another answer https://stackoverflow.com/a/20108037/15909588
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | dqhuy78 |
| Solution 2 | Halfacht |
| Solution 3 | Karl Hill |
