Mattiman's avatar

Deleting related models

What is the best way to delete related records (models) in Laravel? I've read the documentation but there's very little in there about deleting records. Also searched elsewhere, but I see lots of different solutions, which mostly look pretty ugly.

In my project I have different models with several oneToMany and ManyToMany relations. For example:

Institution
- hasMany Project (table projects)
- hasMany User (table users)
- etc

Project
- hasMany Contribution (table projectcontributions)
- belongsToMany Animalgroup (table project_animalgroups)
- belongsToMany Category (table project_categories)
- belongsToMany Institution (table project_institutions)
- etc

User
- belongsTo Institution 

Now at the moment I have in my Project model a method delete with several detach() calls to delete the many to many relations records. There are also some FOREIGN KEY ON DELETE CASCADE restraints.

But what if I want to delete an Institution? Besides the Institution itself, also all projects AND relations of projects have to be deleted. And the users belonging to the institution. Should I try to set all Foreign Key ON DELETE CASCADEs in the db tables or is there an elegant way in Laravel?

0 likes
11 replies
pmall's avatar

On this kind of situation, depending on the context, I like to use soft deletes.

The deleted institutions will not be shown anymore but the db stay coherent.

1 like
pmall's avatar

You can use model observers too.

Mattiman's avatar

Three answers very quickly but only more options ... thanks ;)

But seriously: what would be the advantages of 'model events' and 'model observers' over On delete cascade in the db?

And looking at the docs, it's not entirely clear what to do: so if I would choose for events/observers, I'd have to create classes for the observers, which contain methods for the events and those observer classes would have to be registered somewhere?

2 likes
polarcubs's avatar

Hi @Mattiman

I think for the onDelete cascade option, it only works if you delete the parent table record or the records which has a foreign key to the record being deleted. For example If you have a users table and a students table. Deleting a record on the students table (whose student_id is actually a foreign key to users table). Would not delete the corresponding record on the users table. But deleting the record on users table which students table link to will delete the record on students table accordingly.

I would usually go with the onDelete cascade option as it's the simplest. However, sometimes you need more logic to be performed when you delete. For example when someone delete a forum reply, maybe I will also deduct the user forum points by 50. That's a good place where events come in.

If you do not wish to create classes, you may put it inside your model classes like so as seen in the laravel docs

class User extends Eloquent {

    public static function boot()
    {
        parent::boot();

        User::created(function($user)
    {
            // Sends some thank you email
    });
    }

}

Hope it helps! I don't know about laravel observers well enough so I shall leave that to others.

1 like
Mattiman's avatar

Hi @polarcubs

Yes you are right, the On delete cascades only work one way. Delete an Institution and all its projects (with foreign key institution_id) will be deleted. But deleting a project will not delete the institution. In this case that's what I want.

Good to know about the option in your example. Can imagine you need that kind of logic sometimes. In another project I'm working on it might actually be very useful.

Thanks for your reply.

JarekTkaczyk's avatar

There are 2 major approaches: DB event constraints, domain code or both.

You need to consider whether you want to use soft deletes or not.

  1. SoftDeletes -> no DB events are triggered, because in fact the record is not deleted, but only updated (set deleted_at = timestamp). That said, the only way you can handle related models, is in the code.
  2. hard deletes - you can rely on DB event constraints. But mind that you cannot control it later, once they are defined. If that would be a problem, then, again, you need to handle this in the code.

For flexibility, when using Eloquent, I strongly suggest the 2nd approach with no DB constraints applied.

Now, you can do it leveraging Eloquent events or overriding delete method. I'm not pasting the code here, just a few hints you need to take into consideration:

  1. If model uses soft deletes, then related should use it as well
  2. if that's the case, remember to handle restored too
  3. If you use hasManyThrough, read this http://stackoverflow.com/a/23274040/784588
  4. for belongsToMany relation you don't want to touch related models most of the time. Possibly you could do that if current models is the only one attached to the related one
  5. Probably the best way is something like this:
$relatedIds = $this->relation()->lists('table.id');
Related::whereIn($relatedIds)->delete();
7 likes
4jZW7jVSdS4U6PC's avatar

You can do something like this.

Assuming that you have a User table with Articles and inside the User.php model you have a getTrashedArticles method which returns all trashed articles.

    /**
     * Inside User.php
     */
    public static function boot()
    {
        parent::boot();

        static::deleted(function ($user) {
            foreach($user->articles as $article) {
                $article->delete();
            }
        });

        static::restored(function ($user) {
            foreach($user->getTrashedArticles() as $article) {
                    $article->restore();
            }
        });
    }
3 likes
Mattiman's avatar

OK lots of things to think about it seems. Thanks for the answers JarekTkaczyk and ludo237!

@JarekTkaczyk: At the moment I probably want hard deletes. Hadn't even considered soft deletes are possible. If I would use those, I will have to change all the database tables (add column deleted_at). Not that big of a deal probably, but I'm not sure I need the soft deletes.

I'll look at your explanation of option 2 variant B (hard deletes using eloquent) a bit more to see what it all means. What I'm afraid of, since it's an existing (live) application, is having to change a lot of code in a dozen or more models and forgetting something or making a mistake somewhere. The relations are pretty complex with many many-to-many and related models having many-to many to others again, etc.
If I use the mysql FK On delete cascades, it's relatively easy to do a db dump, look at all the foreign keys and restraints and see if something is missing.

But at the moment I'm using both approaches (db on delete cascades and some model->children->detach(), each for different relations) at the same time, so that's not ideal.

@ludo237: Your example is a bit confusing. In your example, do you mean that a UserController class is like:

class UserController {
    public function articles() {
        return $this->hasMany('Article');
    }
    public function delete($id)
    {
        User::destroy($id);
    }
} 

and then if I do UserController->delete($id), not only the user record is (soft)deleted but also the articles related to that user?

JarekTkaczyk's avatar

@ludo237 That's an option, but mind that calling delete on each model is slow. Doing it with a single query is the way.

1 like
kumarmann's avatar

i have a little bit confussion here in user table suppose i delete user_id =5,And this user table relation with other two table like as demo table ,test table .So i want if user_id=5 is deleted then all user_id = data are delete from other two table .Then it is possible here.Please suggest me

Please or to participate in this conversation.