Laravel Soft Delete – Create a Trash Can using PHP and MySQL

Laravel soft delete allows you to enable some models that will not be permanently deleted by just calling the delete() method. Instead, you need to call the forceDelete() method in order to permanently delete the data.

The deleted models will not be fetched from Eloquent or Query Builder queries. Instead, you need to call a separate function in order to fetch the records along with deleted ones, we will get on that later in this article. Laravel soft delete has several advantages:

  • First, it works like your PC’s recycle bin. If you accidentally deleted from record from your admin panel, it will not be deleted permanently at once.
  • Second, you can display all deleted data on a separate page.
  • You have a backup of your deleted data, so in the future, if you need some old data, you can always get it from recycle bin or trash can.
  • Can display all data to the user and highlight the data that is deleted.
  • Always be able to restore the accidentally deleted records.

Let’s get started

So let’s get started by creating a simple Laravel soft delete module. For the sake of this article, we will be implementing a trash can in our user’s model.

1. Creating a column in the user’s table

First, you need to run the following command to create a migration for soft deleting:

php artisan make:migration add_column_soft_delete_in_users_table

Then you need to open your migration file and add the following line in the Schema::table function inside up() method:

$table->softDeletes();

Then you need to run the migration using the following command:

php artisan migrate

Open your phpMyAdmin and you will see a new column at the end of the user’s table named deleted_at. Its default value will be NULL and it will have a current date & time value once you call the delete() method.

2. Laravel Soft Delete Eloquent Model

Open your user model usually in App\Models\User.php and update it as the following:

use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable, SoftDeletes;

    ///////////
}

After that, whenever you call the delete() method on any user object, you will see that deleted_at the field will be updated to the current date & time of that user.

3. Display all Users with Pagination

Let’s say that you have an admin panel. We will be displaying all users (non-deleted) on one page. So first create a route for this in your routes/web.php file:

use App\Http\Controllers\UserController;

Route::group([
    "prefix" => "users"
], function () {
    Route::get("/", [UserController::class, "index"]);

    // [other routes goes here]
});

We have created a group because we will be adding more routes to it. For example, route to delete a user, route to display all deleted users, route to restore a user, and route to permanently delete a user. Run the following command if you do not have the UserController:

php artisan make:controller UserController

Now, create an index() method in your UserController class. Following is what your UserController should look like:

use App\Models\User;

public function index()
{
    $users = User::orderBy("id", "desc")->paginate();
    $trashed = User::onlyTrashed()->count();

    return view("users/index", [
        "users" => $users,
        "trashed" => $trashed
    ]);
}

Create a folder named users and inside this folder, create a file in resources/views/users/index.blade.php. This file should have the following code:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css" />
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js"></script>

<a href="{{ url('/users/trashed') }}">
    Trash ({{ $trashed }})
</a>

<table class="table table-bordered">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
        </tr>
    </thead>

    <tbody>
        @foreach ($users as $user)
            <tr>
                <td>{{ $user->id }}</td>
                <td>{{ $user->name }}</td>
                <td>{{ $user->email }}</td>
            </tr>
        @endforeach
    </tbody>
</table>

{{ $users->appends(request()->except("page"))->links() }}

Refresh the page now and you will see a list of all users. Now we need to create a button to delete a user.

4. Delete a User

To delete a user, we will first a heading in our <thead> tag:

<th>Actions</th>

Then in <tbody>, we need to create a form which when submit will first ask for confirmation. If confirmed, then it will mark the user as deleted.

<td>
	<form method="POST" action="{{ url('/users/delete') }}" onsubmit="return confirm('Are you sure you want to delete this user ?');">
		{{ csrf_field() }}
		<input type="hidden" name="id" value="{{ $user->id }}" required>
		<button type="submit" class="btn btn-danger">
			Delete
		</button>
	</form>
</td>

Refresh the page and you will see a delete button along with each user. Then create a route on your web.php file:

Route::post("/delete", [UserController::class, "destroy"]);

The above lines go in the [other routes goes here] section of web.php. Then create a method in your UserController to mark the user as deleted:

public function destroy()
{
    $user = User::find(request()->id);
    if ($user == null)
    {
        abort(404);
    }

    $user->delete();
    return redirect()->back();
}

You can delete the user in one line too, like this: User::destroy(request()->id); but fetching the user first has some advantages:

  • You can perform any other sub-functions before deleting a user.
  • Or check if the user exists or not, and display the proper messages accordingly.

Try deleting a user now, you will see that will no longer be displayed in your Bootstrap table. But you can still see the record in your database using phpMyAdmin. However, you will notice that the deleted_at the field is updated and now has the UTC date and time value when the delete operation was performed.

5. Show All Deleted Users

At 3rd step, we create an anchor tag that displays all the trashed user’s counts. Now is the time to create a route for it. To create a route on your routes/web file.

Route::get("/trashed", [UserController::class, "trashed_users"]);

Then create the following method in your UserController:

public function trashed_users()
{
    $trashed = User::onlyTrashed()->orderBy("id", "desc")->paginate();

    return view("users/trashed", [
        "trashed" => $trashed
    ]);
}

After that, create a file resources/views/users/trashed.blade.php. It will have the following code:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css" />
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js"></script>

<table class="table table-bordered">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>Deleted at</th>
            <th>Actions</th>
        </tr>
    </thead>

    <tbody>
        @foreach ($trashed as $user)
            <tr>
                <td>{{ $user->id }}</td>
                <td>{{ $user->name }}</td>
                <td>{{ $user->email }}</td>
                <td>{{ $user->deleted_at }}</td>
            </tr>
        @endforeach
    </tbody>
</table>

{{ $trashed->appends(request()->except("page"))->links() }}

Refresh the page now and you will see all deleted users in a table. You will also be able to view the date and time when they were deleted.

6. Restore the Deleted Records

First, create another <td> tag at the end of <tbody> tag. In this table cell, we will create a button that will ask for confirmation. And when confirmed, will restore the user.

<td>
    <form method="POST"
        action="{{ url('/users/restore') }}"
        onsubmit="return confirm('Are you sure you want to restore this user ?');">

        {{ csrf_field() }}

        <input type="hidden" name="id" value="{{ $user->id }}" required />
        <button type="submit" class="btn btn-success">
            Restore
        </button>
    </form>
</td>

Then create a route in your web/routes.php that will handle this request.

Route::post("/restore", [UserController::class, "do_restore"]);

After that, create the following method in your UserController:

public function do_restore()
{
    $user = User::withTrashed()->find(request()->id);
    if ($user == null)
    {
        abort(404);
    }

    $user->restore();
    return redirect()->back();
}

Refresh the page now and you will see a “Restore” button along with each delete user. On clicking that, you will be asked for confirmation. If confirmed, you will no longer see that record in the trash can. But you will start seeing that record in all users table.

If you open your database using phpMyAdmin, you will see that user will again have the deleted_at column value as NULL.

7. Permanently Delete Records

Where you are displaying all soft-deleted records in your trash can, you are already displaying a button to restore the record. Now is the time to display another button that will permanently delete the record from the database. Create a button along with the “Restore” button in your trash can:

<form method="POST"
    action="{{ url('/users/delete-permanently') }}"
    onsubmit="return confirm('Are you sure you want to permanently delete this user ?');">

    {{ csrf_field() }}

    <input type="hidden" name="id" value="{{ $user->id }}" required />
    <button type="submit" class="btn btn-danger">
        Delete
    </button>
</form>

Then, you need to create a route that will handle this request.

Route::post("/delete-permanently", [UserController::class, "delete_permanently"]);

Then you need to create a method in your UserController that will permanently delete that record from the database.

public function delete_permanently()
{
    $user = User::withTrashed()->find(request()->id);
    if ($user == null)
    {
        abort(404);
    }

    $user->forceDelete();
    return redirect()->back();
}

Refresh the page now and click on the delete button from the trash can. You will be asked for confirmation. Once confirmed, you will no longer see that record in the trash can nor in all users table. If you check the database, you will see that, that user’s row has been permanently deleted from the database.

Get our social networking site project developed in Laravel:

Social Networking Site in Laravel – Node JS, Socket IO

So that’s how you can implement a complete Laravel soft delete module in your web application.

[wpdm_package id=’1545′]