Laravel 5: cascade soft delete

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Relational Integrity: Implementing Cascade Soft Deletes in Laravel

As developers working with relational databases in frameworks like Laravel, managing data integrity across related models is paramount. When you introduce soft deletion—a pattern where records are marked as deleted rather than physically removed—you face a crucial architectural decision: how do you ensure that dependent records are handled consistently?

This post dives into a common scenario: handling cascading soft deletes between a parent model (like Offer) and its child models (like Service). We will explore the best practices, moving from simple database constraints to sophisticated Eloquent event handling to achieve seamless data management.

The Challenge: Cascading Soft Deletes

You have established an offers table and an services table, linked by a foreign key. Both tables utilize Laravel's built-in softDeletes() trait. Your goal is to ensure that when an Offer record is soft-deleted, all associated Service records are also marked as deleted, maintaining complete data consistency without manual intervention.

The setup you provided demonstrates the correct foundational steps:

  1. Migrations: Both tables correctly include the softDeletes() timestamp columns.
  2. Relationships: The Offer model has a hasMany relationship to Service, and vice versa, establishing the necessary Eloquent connections.
  3. Deletion Logic: You are currently handling the soft delete on the parent (Offer) manually in the controller/service layer.

The missing piece is the cascade—the automatic action that triggers when the parent record changes state.

Strategy 1: Database-Level Cascading (The Foundation)

The most direct way to handle cascading behavior is at the database level using Foreign Key constraints. While Laravel excels at ORM operations, ensuring data integrity first happens in the database layer.

You can modify your services migration to include a cascade action on the foreign key:

php
// Migration for services table (Example)
Schema::table('services', function($table) {
    $table->foreign('offer_id')
          ->references('id')
          ->on('offers')
          ->onDelete('cascade'); // <-- This is the critical line
});

A Note on Soft Deletes: When using onDelete('cascade') with soft deletes, you must be careful. In a standard database setup, cascading deletion physically removes rows. With Laravel's softDeletes, we want to ensure that when an offer is deleted, the services are also soft-deleted. While some advanced database setups can handle this via triggers, relying solely on application logic (Eloquent) often provides better abstraction and portability across different database systems.

Strategy 2: Eloquent Event Handling (The Laravel Way)

For robust, application-level control—which is highly recommended when dealing with complex business rules like soft deletes—we leverage Eloquent Events and Observers. This approach keeps the logic tightly coupled within your models, which aligns perfectly with the principles of clean code advocated by the Laravel community.

We will use the deleting or deleted event on the Offer model to trigger the deletion logic in the related Service models.

Step 1: Implement the Observer

Create an Observer for the Offer model. This observer will listen for the moment an offer is marked as deleted and queue the necessary actions for its services.

php
// app/Observers/OfferObserver.php

class OfferObserver
{
    public function deleting(Offer $offer)
    {
        // When the offer is about to be soft-deleted, find all related services.
        $services = $offer->services;

        foreach ($services as $service) {
            // Perform the soft delete on the service record
            $service->delete(); 
        }
    }
}

Step 2: Register the Observer

Ensure you register this observer in your Offer model's boot method:

php
// app/Offer.php
use Illuminate\Support\Facades\Route;
use App\Observers\OfferObserver;

class Offer extends Model
{
    use SoftDeletes;

    public function deleting(array $models = [])
    {
        foreach ($models as $offer) {
            // Logic defined above goes here, or you can call a dedicated service.
            $this->handleServiceDeletion($offer); 
        }
    }
    
    // ... other methods
}

Step 3: The Execution Flow

When you execute $offer->delete() in your controller, the deleting event fires, iterates through the related services, and calls $service->delete(), successfully cascading the soft delete operation down to the child records. This pattern demonstrates how powerful Eloquent relationships are when combined with lifecycle events—a core concept in efficient data management within Laravel.

Conclusion

Managing relational data integrity, especially with soft deletes, requires a layered approach. While database constraints offer a safety net, implementing custom logic via Eloquent Observers provides the necessary control and application-specific business rules. By mastering the interplay between migrations, relationships, and events, you ensure that your Laravel applications maintain perfectly synchronized and reliable data structures. For further deep dives into these powerful features, always refer to the official documentation on the Laravel Company website.