How to perform Soft Delete in Laravel [Tutorial]


Deepak Prasad

Laravel

In this tutorial we’re going to cover how to implement Laravel soft deletes. You’ll learn how to handle and test soft deleted records, address some common pitfalls, and also get best practices for maintaining the integrity of your data and your users privacy in your applications.

Soft delete allows users to delete records from a database without totally wiping the record from the system. This feature is becoming more important as businesses and organizations try to keep their information as safe and accurate as possible. For example, let’s say you have an employee record system. With soft delete implemented in your database, employees can delete old records without removing them completely. So if they ever need to pull up that old deleted record it will always be there.

 

Steps to perform Soft Deletes in Laravel

Create Project (Optional)

If you already have a project where you plan to perform soft delete then you can skip this step. But let me create a project to demonstrate the implementation. We can use below command to create a project:

sudo mkdir -p /opt/projects
sudo chmod 777 /opt/projects
cd /opt/projects
composer create-project --prefer-dist laravel/laravel SoftDeleteDemo

Navigate to your project directory:

cd SoftDeleteDemo

Set up your .env file for database connection by editing the .env file with your database credentials. I have already created and configured MariaDB to use as my backend DB for Laravel projects:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=my_laravel_db
DB_USERNAME=user1
DB_PASSWORD=Passw0rd

 

Create a Migration for Soft Deletes

Generate a new migration file for a table, e.g., users, where soft deletes will be implemented. You can modify this file based on your environment which contains the table where you want to implement soft delete.

php artisan make:migration create_users_table --create=users
How to perform Soft Delete in Laravel [Tutorial]

Open the newly created migration file in the database/migrations directory which in my case is 2024_02_18_144746_create_users_table. You'll need to add a deleted_at column to your table schema within the migration's up method. Laravel uses this column to mark records as deleted. Here's how you can modify the table structure:

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();
    $table->softDeletes(); // This line adds the soft delete column
});

 

Run the Migration

After modifying the migration, apply the changes to your database by running:

php artisan migrate
How to perform Soft Delete in Laravel [Tutorial]

This will add a deleted_at column to your users table, which Laravel will use to mark records as soft deleted.

 

Enabling Soft Deletes in the Model

To enable soft deletes for a model, you need to use the SoftDeletes trait provided by Eloquent. Open the model file you wish to enable soft deletes for, typically located in app/Models. For a User model, the file would be app/Models/User.php. Within your model, import the SoftDeletes trait and use it within the class. Here's an example for the User model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; // Import the SoftDeletes trait

class User extends Model
{
    use HasFactory, SoftDeletes; // Use the SoftDeletes trait

    // Your model's properties and methods
}

Huzzah! Your users table is now decked out with soft deletes, and it only took a few steps. When you hit delete() on a User model instance, Laravel won’t scrap the record from your database. Rather, it’ll slap the current timestamp into the deleted_at column of that record — which marks it as “deleted.” Magic! You can still get to these finicky records by adding them to explicit model queries. And if you ever need to bring your deleted records back from the grave? No problemo.

 

Performing Soft Delete, Restoration and Verification

I have added some dummy users in my table to be able to test the soft delete operation in Laravel. Here is a list of all the users in my table:

php artisan tinker
> App\Models\User::get();
How to perform Soft Delete in Laravel [Tutorial]

 

Soft Deleting a Record

Soft deleting a record marks it as deleted without actually removing it from the database. This is achieved by setting a deleted_at timestamp for the record.

php artisan tinker
> App\Models\User::find(1)->delete();

This command soft deletes the user with id 1. You can replace 1 with any other user id you wish to soft delete.

How to perform Soft Delete in Laravel [Tutorial]

 

List Only Soft Deleted Records

Use the below command to only list the soft deleted users:

App\Models\User::onlyTrashed()->get();
How to perform Soft Delete in Laravel [Tutorial]

 

Restoring a Soft Deleted Record

Restoring a soft-deleted record makes it available again for queries that don't explicitly include soft-deleted models.

App\Models\User::withTrashed()->find(1)->restore();

This command restores the user with id 1. Adjust the id as necessary for different users.

How to perform Soft Delete in Laravel [Tutorial]

 

Permanently Deleting a Record

Permanently deleting a record removes it from the database entirely, ignoring the soft delete functionality.

App\Models\User::withTrashed()->find(1)->forceDelete();

This command completely removes the user with id 1 from the database. Be cautious with this operation as it cannot be undone.

How to perform Soft Delete in Laravel [Tutorial]

As expected, we don't see any user with id 1 in our records any more as it is permanently deleted:

App\Models\User::withTrashed()->get();
How to perform Soft Delete in Laravel [Tutorial]

 

Writing tests for soft deleted models

To test these functionalities in a more structured way, you can write tests in the tests/Feature directory. For example, create a test file named UserSoftDeletesTest.php:

php artisan make:test UserSoftDeletesTest
How to perform Soft Delete in Laravel [Tutorial]

Then, in your test file located at tests/Feature/UserSoftDeletesTest.php, you can write tests to ensure soft deleting, restoring, and force deleting work as expected:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\User;

class UserSoftDeletesTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function a_user_can_be_soft_deleted_and_restored()
    {
        $user = User::factory()->create();

        $user->delete();
        $this->assertSoftDeleted($user);

        $user->restore();
        $this->assertDatabaseHas('users', ['id' => $user->id, 'deleted_at' => null]);
    }

    /** @test */
    public function a_user_can_be_force_deleted()
    {
        $user = User::factory()->create();

        $user->forceDelete();
        $this->assertDatabaseMissing('users', ['id' => $user->id]);
    }
}
NOTE:
In my case I already had an existing table with the same users name so I had to delete that duplicate table database/migrations/2014_10_12_000000_create_users_table.php or else I was getting 1050 Table 'users' already exists

To run your tests, use the PHPUnit command:

./vendor/bin/phpunit

or simply:

php artisan test
How to perform Soft Delete in Laravel [Tutorial]

 

Implementing Controllers and Route

We can create a Controller and use it to manage User records, including handling soft deletes.

Run the following Artisan command to create a new controller named UserController. This command will create a file named UserController.php in the app/Http/Controllers directory.

php artisan make:controller UserController
How to perform Soft Delete in Laravel [Tutorial]

Open the routes/web.php file to define routes for the actions you want to perform on the User model. Here, we'll specify routes for deleting a user, restoring a user, viewing deleted user and permanently deleting a user.

// Define the route for the index page of users
Route::get('/users', [UserController::class, 'index'])->name('users.index');

// Route to soft delete a user
Route::delete('/users/{user}', [UserController::class, 'delete'])->name('users.delete');

// Route to permanently delete a user
Route::delete('/users/{user}/force', [UserController::class, 'forceDelete'])->name('users.forceDelete');

// Route to show soft deleted users
Route::get('/users/deleted', [UserController::class, 'showDeleted'])->name('users.deleted');

// Route to restore a specific soft deleted user
Route::get('/users/{user}/restore', [UserController::class, 'restore'])->name('users.restore');

Next we will implement methods in UserController to handle the actions defined in our routes in app/Http/Controllers/UserController.php.

class UserController extends Controller
{
    public function index()
    {
        $users = User::all(); // Get all users
        return view('users.index', compact('users')); // Return the view with users
    }

    public function showDeleted()
    {
        $deletedUsers = User::onlyTrashed()->get();
        return view('users.deleted', compact('deletedUsers'));
    }


    // Method to soft delete a user
    public function delete(User $user)
    {
        $user->delete();
        return redirect()->route('users.index')->with('status', 'User deleted successfully.');
    }

    // Method to restore a soft-deleted user
    public function restore($userId)
    {
        $user = User::withTrashed()->findOrFail($userId);
        $user->restore();
        return redirect()->route('users.index')->with('status', 'User restored successfully.');
    }

    // Method to permanently delete a user
    public function forceDelete(User $user)
    {
        $user->forceDelete();
        return redirect()->route('users.index')->with('status', 'User permanently deleted successfully.');
    }
}

Here are the list of implemented methods:

  • index(): Fetches all user records using User::all(). Returns the users.index view, passing the users data to it. This view can display a list of all users, making it useful for an admin dashboard or a user management page.
  • showDeleted(): Retrieves only the soft deleted users with User::onlyTrashed()->get(). This leverages Laravel's soft delete functionality to get users marked as deleted (deleted_at is not null) without permanently removing them from the database. Returns the users.deleted view, providing a list of these soft deleted users, likely with options to restore them.
  • delete(User $user): Accepts a User model instance, implicitly route model binding a user based on the ID passed in the route. Calls $user->delete(), which soft deletes the user (marks deleted_at with a timestamp) without removing the record from the database. Redirects back to the users.index route with a success status message. This informs the user of the successful deletion.
  • restore($userId): Finds a soft-deleted user by their ID using User::withTrashed()->findOrFail($userId). This includes users in the query that are normally hidden because of being soft deleted. Calls $user->restore(), which clears the deleted_at column for that user, effectively "undeleting" them. Redirects back to the users.index route with a success status message indicating the user has been restored.
  • forceDelete(User $user): Similar to the delete method, it accepts a User model instance. However, it calls $user->forceDelete() instead, which permanently removes the user's record from the database, bypassing the soft delete functionality. Redirects back to the users.index route with a success status message, indicating that the user has been permanently deleted.

Next we need to create a view with the forms for soft deleting, restoring, and permanently deleting a user should be placed in the Blade templates. I will create a new directory inside resources/views/ to place my blade files.

mkdir resources/views/users

Next create a view file to list all users resources/views/users/index.blade.php:

<a href="{{ route('users.deleted') }}" class="btn btn-secondary">View Deleted Users</a>

{{-- Loop through each user and display their information along with actions --}}
@foreach ($users as $user)
    <div>
        <p>{{ $user->name }}</p>
        {{-- Soft Delete Form --}}
        <form action="{{ route('users.delete', $user->id) }}" method="POST">
            @csrf
            @method('DELETE')
            <button type="submit">Delete</button>
        </form>
        
        {{-- Restore Form --}}
        {{-- Only show this if the user is soft deleted --}}
        @if($user->trashed())
        <form action="{{ route('users.restore', $user->id) }}" method="POST">
            @csrf
            <button type="submit">Restore</button>
        </form>
        @endif
        
        {{-- Permanent Delete Form --}}
        <form action="{{ route('users.forceDelete', $user->id) }}" method="POST">
            @csrf
            @method('DELETE')
            <button type="submit">Permanently Delete</button>
        </form>
    </div>
@endforeach

Similarly to list the deleted users we create resources/views/users/deleted.blade.php:

{{-- Loop through each deleted user and display them --}}
@foreach ($deletedUsers as $user)
    <div>
        <p>{{ $user->name }}</p>
        {{-- Restore Button --}}
        <a href="{{ route('users.restore', $user->id) }}" class="btn btn-info">Restore</a>
    </div>
@endforeach

Make sure your Laravel application is running. If it's not, you can start it by running php artisan serve in your terminal. If you haven't specifically configured routes to change its location, and assuming you're using the default Laravel development server, the URL might look something like http://127.0.0.1:8000/users based on your route definition in routes/web.php.

How to perform Soft Delete in Laravel [Tutorial]

On the users/index page, you should see a list of users and next to each, the "Delete" button you've set up in your Blade template. Clicking this button will submit the form, sending a POST request with a _method of DELETE to the URL specified in the form's action attribute. This action should correspond to the route you defined for deleting a user, which should be processed by a method in your UserController.

How to perform Soft Delete in Laravel [Tutorial]

 

Cleaning Up Old Soft Deleted Models using Pruning Mechanism

In Laravel, you can use the framework's pruning mechanism to tidy up aging soft-deleted models. You basically get this feature by default and it automatically removes old records that have been soft-deleted after a certain time frame. Here’s how you'd implement it in your User model:

First, ensure your model uses the Illuminate\Database\Eloquent\Prunable trait. This trait provides the necessary functionality to prune (permanently delete) old records. We will update our app/Models/User.php model file:

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Prunable;

class User extends Model
{
    use SoftDeletes, Prunable;

    /**
     * Determine which prunable model records should be pruned.
     */
    protected function prunable()
    {
        // Prune any records that have been soft-deleted for more than 365 days
        return static::onlyTrashed()->where('deleted_at', '<=', now()->subDays(365));
    }
}

Next, you need to schedule the pruning to run at a regular interval. This is done in the app/Console/Kernel.php file within the schedule method. Here you can define how often you want the pruning process to run.

protected function schedule(Schedule $schedule)
{
    // Run model pruning every day at midnight
    $schedule->command('model:prune')->daily();
}

To test or manually trigger the pruning process, you can run the pruning Artisan command:

php artisan model:prune --model=App\Models\User

This command allows you to specify which model(s) you'd like to prune. It's a good way to manually clean up your models or test that your pruning logic is set up correctly.

Views: 61

Deepak Prasad

He is the founder of GoLinuxCloud and brings over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels in various domains, from development to DevOps, Networking, and Security, ensuring robust and efficient solutions for diverse projects. You can reach out to him on his LinkedIn profile or join on Facebook page.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

1 thought on “How to perform Soft Delete in Laravel [Tutorial]”

Leave a Comment


We try to offer easy-to-follow guides and tips on various topics such as Linux, Cloud Computing, Programming Languages, Ethical Hacking and much more.

Programming Languages

JavaScript

Python

Golang

Node.js

Java

Laravel