DEV Community

Cover image for Laravel: Softdelete Eloquent Relationships onDelete Cascade
WAIYL KARIM
WAIYL KARIM

Posted on • Updated on

Laravel: Softdelete Eloquent Relationships onDelete Cascade

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

Enter fullscreen mode Exit fullscreen mode

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();
                }
            }
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
                }
            }
        });
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
mah3uz profile image
Mahfuz Shaikh

Thank you. Great approch.
One thing I can confirm with Laravel 8 that restore would not work,
It should be like:

static::restoring(function($resource) {
    foreach (static::$cascade_relations as $relation) {
        foreach ($resource->{$relation}()->withTrashed()->get() as $item) {
            $item->restore();
        }
    }
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
waiylkarim profile image
WAIYL KARIM • Edited

With Laravel 8, it would be better to use Observers instead

Collapse
 
nabil_kadimi profile image
Nabil Kadimi

Nice generic approach. It's the perfect candidate for a trait.