Overview

In Laravel’s Eloquent ORM, you can automatically delete related rows when a parent model is deleted by defining relationships and cascading delete behavior. Here’s how you can do it:


1. Using onDelete('cascade') in Migrations

When defining the foreign key constraints in your database schema, use the onDelete('cascade') method. This ensures that related rows in the database are automatically deleted at the database level.

  • a. Using constrained()

    If you define the foreign key using the constrained() method, Laravel will automatically infer the table name from the field name. For example:

    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        // Laravel infers 'users' as the related table from 'user_id'
        $table->foreignId('user_id')->constrained()->onDelete('cascade');
        $table->string('title');
        $table->timestamps();
    });
    

    Using the onDelete('cascade') method in your migrations, you need to define the table name explicitly when setting up foreign key constraints unless you use the constrained() method, which infers the table name. In this case, you don’t need to define the table name explicitly.

  • b. Explicitly Defining the Table Name

    If you do not use the constrained() helper or the column name doesn’t follow Laravel’s conventions, you must specify the table name explicitly. For example:

    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('author_id');
        // You must explicitly define the table name as 'users'
        $table->foreign('author_id')->references('id')->on('users')->onDelete('cascade');
        $table->string('title');
        $table->timestamps();
    });
    

    Here:

    • The foreign key is author_id.
    • The related table is explicitly defined as users.

    Recommendations:

    • If you do not specify onDelete('cascade'), the database will not automatically delete related rows, and you will need to handle it manually or in your Eloquent model logic.
    • Use constrained() for cleaner and more concise migration definitions, as long as your column names follow Laravel’s conventions.
    • Define the table name explicitly if the foreign key or table name deviates from Laravel’s naming standards.

2. Using Eloquent Model Events

In your Eloquent model, you can use the deleting event to handle cascading deletes for relationships not enforced at the database level.

For example:

class User extends Model {     
    public function posts()     
    {         
        return $this->hasMany(Post::class);     
    }      
    
    protected static function boot()     
    {         
        parent::boot();          
        static::deleting(function ($user) {             
            // Delete related posts when a user is deleted
            $user->posts()->delete();         
        });     
    } 
}

3. Defining cascadeDeletes in Relationships

Laravel provides the delete method for soft-deletes or non-constrained cascading:

class User extends Model {
    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    public function delete()
    {
        // Delete related posts first         
        $this->posts()->delete();

        // Call the parent delete method         
        parent::delete();  
    } 
}

4. Using Laravel Soft Deletes

If you use soft deletes, you can cascade soft delete relationships by overriding the delete method:

use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Model {

    use SoftDeletes;     

    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    public function delete()
    {
        $this->posts()->each->delete();
        parent::delete();
    }
}

5. Multiple Foreign Keys with Cascading Deletes

If a table references multiple other tables, you can define cascading deletes for each foreign key individually.

Schema::create('order_items', function (Blueprint $table) {     
    $table->id();     
    $table->foreignId('order_id')->constrained()->onDelete('cascade'); // References 'orders' table     
    $table->foreignId('product_id')->constrained()->onDelete('cascade'); // References 'products' table     
    $table->integer('quantity');     
    $table->timestamps(); 
});

In above example:

  • Deleting an order will automatically delete the associated order_items.
  • Deleting a product will also remove the corresponding order_items.

6. Composite Foreign Keys

Laravel doesn’t natively support composite foreign keys, but you can define them using raw SQL in migrations.

Schema::create('role_user', function (Blueprint $table) {
    $table->unsignedBigInteger('role_id');
    $table->unsignedBigInteger('user_id');
    $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    $table->primary(['role_id', 'user_id']); // Composite primary key 
});

In above example:

  • If a role or user is deleted, the corresponding rows in the pivot table role_user are automatically removed.

7. Cross-Database Relationships

If your foreign keys reference a table in a different database, you need to specify the schema explicitly.

Schema::create('comments', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('post_id');
    $table->foreign('post_id')->references('id')->on('blog_database.posts')->onDelete('cascade');
    $table->text('content');
    $table->timestamps();
});

In above example:

  • post_id references the posts table in the blog_database schema.
  • Cascading deletes will still work across databases.

8. Using setNull Instead of Cascade

If you don’t want to delete related rows but instead set the foreign key to NULL, use onDelete('set null').

Schema::create('tasks', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('assigned_to')->nullable();
    $table->foreign('assigned_to')->references('id')->on('users')->onDelete('set null');
    $table->string('description');
    $table->timestamps();
});

In above example:

  • Deleting a user will set the assigned_to column to NULL for their tasks instead of deleting the tasks.

9. Foreign Key with Soft Deletes

Cascading deletes at the database level won’t work for soft-deleted rows. You can handle this with Eloquent’s events.

Migration:

Schema::create('comments', function (Blueprint $table) {
    $table->id();
    $table->foreignId('post_id')->constrained()->onDelete('cascade');
    $table->text('content');
    $table->timestamps();
    $table->softDeletes(); // Adds `deleted_at` column
});

Eloquent Model:

class Post extends Model {
    use SoftDeletes;

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

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

        static::deleting(function ($post) {

            if ($post->isForceDeleting()) {
                // Permanently delete comments                 
                $post->comments()->forceDelete();
            } else {
                // Soft delete comments
                $post->comments()->delete();
            }
        });
    }
}

Notes:

  • Use onDelete('cascade') for simple relationships directly managed by the database.
  • For complex business logic (e.g., soft deletes or conditional deletes), handle cascading behavior in Eloquent models using deleting and deleted events.
  • Ensure you test migrations thoroughly, especially when dealing with composite keys, cross-database relationships, or custom logic.
  • Use database-level constraints (onDelete('cascade')) for better performance and consistency.
  • Use model-level events if you need additional business logic when deleting related rows.
  • Test your delete logic thoroughly to avoid accidentally deleting unintended data