3

There are many related questions but unfortunately I can't find working solution

I have Laravel model and when this model is deleted I want to

  1. delete some related models
  2. run custom SQL query when deleting model

My Laravel's model class looks like (as you can see models can have different relation types)

class ModelA extends Model
{
  public functions modelsB() {
    return $this->hasMany(ModelB:class);
  }

  public functions modelsC() {
    return $this->belongsToMany(ModelC:class);
  }

  // other related models
 
  // place where I am expecting actual deleting is happening
  public static function boot() {
        parent::boot();

        self::deleting(function($modelA) { 

          $modelA->modelsB()->get()->each->delete(); 
         
          foreach($modelA->modelsC as $modelC){
            $modelC->delete();
          }
    });
  }
}

ModelA is deleted but all related data stays, and I am not sure that it is even being called. Maybe I missed something? Should I extend some class for my ModelA? Or this boot function should be placed somewhere else?

moonvader
  • 19,761
  • 18
  • 67
  • 116

5 Answers5

4

What about https://laravel.com/docs/7.x/eloquent#events-using-closures? Something like

class ModelA extends Model
{
    public functions modelsB()
    {
        return $this->hasMany(ModelB::class);
    }

    /**
     * The "booted" method of the model.
     *
     * @return void
     */
    protected static function booted()
    {
        static::deleted(function ($modelA) {
            $modelA->modelsB()->delete();
            // ...
        });
    }
}
aleksejjj
  • 1,715
  • 10
  • 21
3

Did you cascade it in your migration file of your ModelB like so:

$table->foreignId('modelA_id')
    ->onDelete('cascade');
    ->constrained()

The `onDelete('cascade') is the important part, mind you this is a shorthand for:

$table->unsignedBigInteger('modelA_id');

$table->foreign('modelA_id')->references('id')
    ->on('modelA')->onDelete('cascade');

This should do most of the heavy lifting for you and you can find more info at Laravel Docs Migrations

If you don't like this approach and would prefer an event-based approach then try doing this in your ModelA class:

protected static function booted()
{
    static::deleted(function ($modelA) {
        $modelA->modelsB()->delete();
        // ...
    });
}
Prateik Darji
  • 2,297
  • 1
  • 13
  • 29
tsommie
  • 470
  • 4
  • 13
2

Did you try use static instead of self

In the documentation, Laravel recommends to using static keyword when binding event or observer

Emre Kaya
  • 121
  • 4
2

If you are trying to delete the related model from within the model itself. You only need to call $this->reletedModel()->delete(); There is really no need of foreach() loop.

Given you have a function defining a relation with following declaration.

public function relatedModel()
{
    return $this->hasOne(RelatedModel::class);
    //OR
    //return $this->hasMany(RelatedModel::class);
}

/** Function to delete related models */
public function deleteRelatedModels()
{
    return $this->relatedModel()->delete();
}
Ankit Singh
  • 922
  • 9
  • 16
2

I would suggest using Observers to listen for ModelA deleted event.

  1. run php artisan make:observer ModelAObserver --model=ModelA
  2. in AppServiceProvider in boot() metohd add ModelA::observe(ModelAObserver::class);
  3. now in your ModelAObserver in deleted() method you will delete associated models and perform your custom SQL queries.
/**
 * Handle the ModelA "deleted" event.
 *
 * @param  \App\ModelA  $modelA
 * @return void
 */
public function deleted(ModelA $modelA)
{
    // delete associated models
    
    $modelA->modelsB()->delete();
    $modelA->modelsC()->delete()

    ... 

    // perform custom SQL queries

    ... 
}

you also, need to add a database transaction wherever you call delete on ModelA to make sure that action is completely done or not.

DB::transaction(function () use ($modelA) {
   $modelA->delete();
});
mmabdelgawad
  • 2,385
  • 2
  • 7
  • 15