Laravel: Controller function to delete the data

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Data Deletion in Laravel: Handling Complex Relationships with Controllers

As senior developers, one of the most common and critical tasks we face when building robust applications is managing data integrity, especially during deletion operations. When you delete a parent record, you must ensure that all related child records are handled correctly—either deleted automatically (cascading) or manually via explicit commands.

This post will walk you through a practical scenario using Laravel: deleting a Client and ensuring that all associated Projects and their translations are also cleanly removed from the database. We will explore how to approach this problem, contrasting Eloquent methods with direct database queries, which is essential for writing clean, efficient code on platforms like laravelcompany.com.

Understanding Your Database Schema

Before diving into the controller logic, understanding the relationships defined by your tables is paramount. You have established a clear relationship structure:

  • projects: Stores core project details (id, slug, etc.).
  • project_translations: Stores localized data for each project (linked via project_id).
  • clients: Stores client information (id, name, etc.).
  • client_project: This is the crucial pivot or junction table, linking clients to projects (many-to-many relationship).

When you delete a client, you need to address three levels of dependency: the direct link (client_project), the project itself, and its localized translations.

The Challenge: Cascading Deletions

The goal is simple: when a Client is deleted, all associated client_project entries must go, and subsequently, all related projects and project_translations must also be removed.

If you rely solely on default database constraints (like ON DELETE CASCADE), the deletion might work perfectly for the pivot table, but managing nested Eloquent relationships can sometimes require explicit management, especially when dealing with complex object graphs.

Implementation Strategies in the Controller

We will look at two primary ways to execute this multi-level deletion within your controller.

Method 1: Direct Query Builder (The Explicit Approach)

Your current implementation uses the Laravel Query Builder (DB::table()) to manually handle the deletion steps. This method is highly explicit and gives you precise control over which tables are affected, which can be very efficient for complex scenarios where Eloquent relationships might become overly complicated.

Here is how your function looks:

use Illuminate\Support\Facades\DB;
use App\Models\Client; // Assuming you are using Eloquent models

public function destroyClient($id)
{
    // 1. Find the client (Good practice for existence check)
    $cliente = Client::find($id);

    if (!$cliente) {
        abort(404, 'Client not found.');
    }

    // 2. Delete the pivot relationship first
    DB::table('client_project')
        ->where('client_id', $id)
        ->delete();

    // 3. Delete related projects and their translations (This requires sub-queries or cascading logic)
    // To ensure we delete the project data, we find the related project IDs first.
    $projectIds = DB::table('client_project')
        ->where('client_id', $id)
        ->pluck('project_id');

    if ($projectIds->isNotEmpty()) {
        // Delete translations for those projects
        DB::table('project_translations')
            ->whereIn('project_id', $projectIds)
            ->delete();

        // Delete the projects themselves
        DB::table('projects')
            ->whereIn('id', $projectIds)
            ->delete();
    }

    // 4. Finally, delete the client
    $cliente->delete(); 

    return redirect()->route('admin.clients');
}

Developer Insight: This method is robust because it forces you to explicitly manage every step of the deletion based on your defined schema. It bypasses relying solely on Eloquent's default cascading rules and ensures data integrity, regardless of how complex the relationships are.

Method 2: Leveraging Eloquent Relationships (The Idiomatic Approach)

In a well-structured Laravel application, the most "Laravel" way to handle this is by defining proper Eloquent relationships with with or cascading constraints at the database level. If you define your models correctly, deleting the parent model often handles the children automatically.

For instance, if you set up your foreign keys with ON DELETE CASCADE in your migration files:

  1. client_project.client_id should cascade delete to clients.id.
  2. projects.client_project (if defined as a relation) should handle the rest.

If these rules are set up correctly, your controller code simplifies dramatically:

public function destroyClient($id)
{
    // If ON DELETE CASCADE is set up in migrations, this single line handles everything!
    $cliente = Client::findOrFail($id);
    $cliente->delete(); 

    return redirect()->route('admin.clients');
}

Best Practice: Always prioritize setting up your database migrations with appropriate foreign key constraints (ON DELETE CASCADE or ON DELETE SET NULL) before writing complex controller logic. This keeps your business logic clean and defers the heavy lifting to the database engine, which is often faster and more reliable than custom PHP queries.

Conclusion

Deleting data across multiple related tables requires careful architectural planning. While the raw query builder method provides explicit control over every deletion step—as demonstrated in Method 1—the most elegant solution in a Laravel environment is to establish robust foreign key constraints (ON DELETE CASCADE) within your migrations. This allows Eloquent and the database itself to manage cascading deletions efficiently, leading to cleaner, more maintainable code. Always aim for the most idiomatic approach when working with frameworks like Laravel on platforms such as laravelcompany.com.