Eloquent is one of many Laravel features that you just can't deny how cool and useful they are. However, sometimes even the very best things have their flaws. And one of Eloquent's is not being able to autodelete related objects through SoftDelete
when a model has a relationship to another with onDelete('cascade')
.
For this article, I am supposing that you are using Laravel 5
. So let's say you have your default users
table related to the User
model and you have another table called invoices
which is related to the Invoice
model. User
and Invoice
are linked through an Eloquent relationship as followed:
// app/User.php
<?php
namespace App;
class User extends Authenticatable {
// ...
public function invoices() {
return $this->hasMany(Invoice::class);
}
}
I think the code above does not need any explanations. It's a simple Eloquent relationship linking User
and Invoice
.
// app/User.php
<?php
namespace App;
class User extends Model {
// ...
/**
* Holds the methods' names of Eloquent Relations
* to fall on delete cascade or on restoring
*
* @var array
*/
protected static $relations_to_cascade = ['invoices'];
protected static function boot()
{
parent::boot();
static::deleting(function($resource) {
foreach (static::$relations_to_cascade as $relation) {
foreach ($resource->{$relation}()->get() as $item) {
$item->delete();
}
}
});
static::restoring(function($resource) {
foreach (static::$relations_to_cascade as $relation) {
foreach ($resource->{$relation}()->get() as $item) {
$item->withTrashed()->restore();
}
}
});
}
}
Let's now break down this code:
protected static $relations_to_cascade = ['invoices'];
in this array we store the methods' names or relationships we wish to delete (or restore) when a SoftDelete
occurs. If we have more than one relationship, we should push them to the array. For instance:
protected static $relations_to_cascade = ['invoices', 'roles'];
Note that we must have a roles()
relationship declared in our Invoice
model as well in this case or our app won't recognize the roles
method later on the code.
Now that we have defined the Eloquent relationships we want deleted/restored we move on to actually deleting them and/or restoring them.
static::deleting(function($resource) {
foreach (static::$relations_to_cascade as $relation) {
foreach ($resource->{$relation}()->get() as $item) {
$item->delete();
}
}
});
This piece of code calls the deleting
method. Inside this method, We loop through our $relations_to_cascade
array. We then get all the concerned items from our database and delete them.
Now once you understand this process, the next block will make sense
static::restoring(function($resource) {
foreach (static::$relations_to_cascade as $relation) {
foreach ($resource->{$relation}()->get() as $item) {
$item->withTrashed()->restore();
}
}
});
Yes exactly, it does the same thing but this time retrieves the concerned item withTrashed()
(because it was deleted and must include trashed items, duh!) and finally restore the item.
And voilà, c'est fait!
You can repeat the same logic for other models and other relationships.
If you are starting a new Laravel project, it would be a good idea to keep this in mind and apply this paradigm with every model you create in order not to have to get back and change every model of your code — things can get messy, and trust me they will!
Top comments (3)
Thank you. Great approch.
One thing I can confirm with Laravel 8 that restore would not work,
It should be like:
With Laravel 8, it would be better to use Observers instead
Nice generic approach. It's the perfect candidate for a trait.