Laravel Soft Delete is a useful option from Laravel Eloquent if you want to use it as a recycle bin before permanently deleting data. In case some data gets deleted accidentally, Laravel has the option to restore the deleted data. So, you can use this feature as trash data. You can also permanently delete the data using the Laravel force delete option.
To implement the Soft-Delete feature in Laravel, follow these:
- While creating the migration use
$table->softDeletes();
for the table you want to use Soft-Delete. This will add "deleted_at" column in the table. - In the model add
use Illuminate\Database\Eloquent\SoftDeletes;
and adduse SoftDeletes;
trait. - In the controller use
delete()
method on the selected row to move data to trash. It updates the "deleted_at" column with the timestamp. - To Restore - Use
restore()
method in the controller to restore deleted data. - To Permanently Delete - use
forceDelete()
method in the controller for the selected rows from Trashed data.
Please read the entire topic to implement all these. We will develop a Laravel application to show how Soft Delete works. We will have a list of training courses with the option to delete each course. After deleting you can view the trashed data with the option to restore it or delete it permanently.
Watch YouTube Video
Create a Laravel project using composer
We will create a Laravel project using the below command. We are using the project name as lara_soft_delete.
composer create-project --prefer-dist laravel/laravel lara_soft_delete
This command will create a lara_soft_delete folder and install all necessary files in it. So, our project root folder is "lara_soft_delete".
Create Laravel migration
We will create a table named 'training_courses' using Laravel migration. This table will store the details of all courses. Go to your project root folder and run the below command from the VS code terminal. It will create the model, controller and migration.
php artisan make:model TrainingCourse -mc
The option "mc" will create the model (TrainingCourse.php), controller (TrainingCourseController.php) and the migration file (create_training_courses_table.php with a datetime prefix).
Create a database named "lara_demo" for this project using phpMyAdmin and update the .env
file for database details as below:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=lara_demo
DB_USERNAME=root
DB_PASSWORD=
Migration for the custom table
We will create the migration file for a new table named 'training_courses' and use $table->softDeletes();
in it.
public function up()
{
Schema::create('training_courses', function (Blueprint $table) {
$table->id();
$table->string('course_title');
$table->string('course_descr');
$table->string('level');
$table->string('duration');
$table->softDeletes(); // for soft delete
$table->timestamps();
});
}
So, our table will have course_title, course_descr, level (basic, Intermediate, etc.) and duration along with the column "deleted_at" for soft delete.
We will now run the migration to create the default Laravel tables and our custom table. Run the below artisan command from the project root to create the tables.
php artisan migrate
This will create all the default Laravel tables and our custom 'training_courses' table. After the migration see the structure of the table.
Now let us update the model for soft delete. Use SoftDelete
Trait here. Below is the model:
app/Models/TrainingCourse.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class TrainingCourse extends Model
{
use HasFactory;
use SoftDeletes;
}
Create Laravel Database Seeder
We will create some dummy data for the courses. Run the below command to create a seeder file:
php artisan make:seeder TrainingCourseSeeder
Update the seeder file as below to insert 3 rows in the training_courses table:
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class TrainingCourseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('training_courses')->insert([
'course_title' => 'Core PHP',
'course_descr' => 'CodeIgniter',
'level' => 'Intermediate',
'duration' => '2 weeks'
]);
DB::table('training_courses')->insert([
'course_title' => 'JavaScript',
'course_descr' => 'Basic JavaScript',
'level' => 'Basic',
'duration' => '1 week'
]);
DB::table('training_courses')->insert([
'course_title' => 'Core PHP',
'course_descr' => 'Training for Core PHP',
'level' => 'Basic',
'duration' => '1 week'
]);
}
}
Update run()
method of the database seeder (database/seeders/DatabaseSeeder.php) as below to call our seeder file:
public function run()
{
$this->call(TrainingCourseSeeder::class);
}
Now run the below command to create the rows:
php artisan db:seed
After running this command, you will see that three rows are inserted in the database table.
Controller code
The controller will have a few methods for each of the actions performed on the course.
index()
method to display all the courses.moveToTrash()
to move data to trash, i.e., soft delete.showTrash()
method to display all trashed data. These data can be restored or deleted permanently.restore()
method to restore the deleted row.forceDelete()
method to delete data from trash permanently.
Let us see the code for the controller
app/Http/Controllers/TrainingCourseController.php
namespace App\Http\Controllers;
use App\Models\TrainingCourse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class TrainingCourseController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$courses = TrainingCourse::all();
return view('view_courses', compact('courses'));
}
public function moveToTrash(Request $request)
{
$id = $request->delete;
TrainingCourse::findOrFail($id)->delete();
Session::flash('message', 'Course Moved to Trash');
return redirect()->route('course.index');
}
public function showTrash()
{
$trash_course = TrainingCourse::onlyTrashed()->get();
return view('trashed_courses', compact('trash_course'));
}
public function restore(Request $request)
{
$id = $request->restore;
$data = TrainingCourse::withTrashed()->find($id);
if (!is_null($data)) {
$data->restore();
Session::flash('message', 'Course restored');
}
return redirect()->route('show.trashed');
}
public function forceDelete(request $request)
{
$id = $request->delete;
$data = TrainingCourse::withTrashed()->find($id);
if (!is_null($data)){
$data->forceDelete();
Session::flash('message', 'Course deleted');
}
return redirect()->route('show.trashed');
}
}
Laravel blade views
We will create two views, one for listing all courses and the other for viewing all deleted (trashed) data. Along with them, we will have the layouts and a couple of Bootstrap Modals for user confirmation before deleting.
We have the below files in the resources/views folder:
header, footer and master are for the layouts. view_courses.blade.php
is for listing all courses and trashed_courses.blade.php
is for the list of all trashed (soft deleted) courses.
resources/views/layouts/header.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Laravel Soft Delete</title>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<meta name="_token" content="mMnVrUOUnNcqYg3oqtARevIdRWTRokhlTC8wAkd6">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" media="screen" href="https://codehow2.com/assets/css/style.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
resources/views/layouts/footer.blade.php
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.min.js"></script>
resources/views/layouts/master.blade.php
@include('layouts.header')
<body>
@yield('main-content')
@include('layouts.footer')
@stack('js')
</body>
</html>
resources/views/view_courses.blade.php
@extends('layouts.master')
@section('main-content')
<h1>Laravel Soft Delete</h1>
<div class="container">
<div class="text-end">
<a href="{{ route('show.trashed') }}" class="btn btn-primary">View Trashed Data</a>
</div>
<h4>List of Courses</h4>
<div class="table-responsive">
<table class="table table-striped table-bordered mt-5">
@if (session('message'))
<div class="alert alert-success">
{{ session('message') }}
</div>
@endif
<thead>
<tr>
<th>#</th>
<th>Course Name</th>
<th>Description</th>
<th>Level</th>
<th>Duration</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@forelse($courses as $index => $row)
<tr>
<td>{{ $index + 1 }}</td>
<td>{{ $row->course_title }}</td>
<td>{{ $row->course_descr }}</td>
<td>{{ $row->level }}</td>
<td>{{ $row->duration }}</td>
<td>
<button class="fa fa-trash" onClick="trashConfirm('{{ $row->id }}')" data-toggle="modal"
data-target="#deleteConfirm" title="Move to Trash"></button>
</td>
</tr>
@empty
<tr>
<td colspan="6">No Courses Found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@include ('modals.modal_trash')
@endsection
@push('js')
<script>
function trashConfirm(id) {
document.getElementById('delete_id').value = id;
$("#modalTrash").modal('show');
}
</script>
@endpush
In the above code, we have a simple list of all courses with a delete button (an icon for trash) against each course. A modal for confirmation will open when it is clicked. Also, a JavaScript function is called to write the id of course in a form in the modal for confirmation. We will see it in the modal.
resources/views/modals/modal_trash.blade.php
<!-- Modal -->
<div class="modal fade" id="modalTrash" tabindex="-1" role="dialog" aria-labelledby="modalTrashTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<form action="{{ route('soft.delete') }}" method="post">
{{ csrf_field() }}
<input type="hidden" name="delete" id="delete_id">
<div>
<center>
<h1>!</h1>
</center>
</div>
<div class="modal-body">
<center>
<h1>Are You Sure?</h1>
<h6>You want to Move Course to Trash!</h6>
</center>
</div>
<div class="row" style="margin-bottom: 50px; text-align: center;">
<div class="col-sm-3"></div>
<div class="col-sm-3">
<button type="button" class="btn btn-danger btn-cancel" data-bs-dismiss="modal">Cancel</button>
</div>
<div class="col-sm-3">
<button type="submit" class="btn btn-success btn-submit">Move to Trash</button>
</div>
<div class="col-sm-3"></div>
</div>
</form>
</div>
</div>
</div>
Below is the view for trashed data.
resources/views/trashed_courses.blade.php
@extends('layouts.master')
@section('main-content')
<h1>Laravel Soft Delete</h1>
<div class="container">
<div class="text-end">
<a href="{{ route('course.index') }}" class="btn btn-primary">Back to Courses</a>
</div>
@if (session('message'))
<div class="alert alert-success">
{{ session('message') }}
</div>
@endif
<h4>Courses Trashed Data</h4>
<div class="table-responsive">
<table class="table table-striped table-bordered mt-5">
<thead>
<tr>
<th>#</th>
<th>Course Name</th>
<th>Description</th>
<th>Level</th>
<th>Duration</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@forelse($trash_course as $index => $row)
<tr>
<td>{{ $index + 1 }}</td>
<td>{{ $row->course_title }}</td>
<td>{{ $row->course_descr }}</td>
<td>{{ $row->level }}</td>
<td>{{ $row->duration }}</td>
<td>
<button class="btn-restore" onClick="restoreFunction('{{ $row->id }}')"
data-toggle="modal" data-target="#restoreModal">Restore</button>
<button class="btn-delete" onClick="delConfirm('{{ $row->id }}')"
data-toggle="modal" data-target="#deleteModal">Permanent Delete</i></button>
</td>
</tr>
@empty
<tr>
<td colspan="6">No data Found in Trash</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@include('modals.modal_restore_delete')
@endsection
@push('js')
<script>
function delConfirm(id) {
document.getElementById('delete_id').value = id;
$("#deleteModal").modal('show');
}
function restoreFunction(id) {
document.getElementById('restore_id').value = id;
$("#restoreModal").modal('show');
}
</script>
@endpush
This will list all deleted or trashed data. Against each row, there are two buttons; one for restoring and the other for permanent deletion. There will be a confirmation modal for each of the actions.
resources/views/modals/modal_restore_delete.blade.php
<!-- Modal for Permanent Delete Confirmation-->
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<form action="{{ route('permanent.delete') }}" method="post">
{{ csrf_field() }}
<input type="hidden" name="delete" id="delete_id">
<div>
<center>
<h1>!</h1>
</center>
</div>
<div class="modal-body">
<center>
<h1>Are You Sure?</h1>
<h6>You want to Permanently delete the Course? It can not be Restored!!</h6>
</center>
</div>
<div class="row" style="margin-bottom: 50px; text-align: center;">
<div class="col-sm-3"></div>
<div class="col-sm-3">
<button type="button" class="btn btn-danger btn-cancel" data-bs-dismiss="modal">Cancel</button>
</div>
<div class="col-sm-3">
<button type="submit" class="btn btn-success btn-submit">Delete</button>
</div>
<div class="col-sm-3"></div>
</div>
</form>
</div>
</div>
</div>
<!-- Restore Modal -->
<div class="modal fade" id="restoreModal" tabindex="-1" role="dialog" aria-labelledby="restoreModalTitle"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<form action="{{ route('course.restore') }}" method="post">
{{ csrf_field() }}
<input type="hidden" name="restore" id="restore_id">
<div class="swal-icon swal-icon--warning">
<center>
<h1>!</h1>
</center>
</div>
<div class="modal-body">
<center>
<h1>Are You Sure</h1>
<h6>Are You Sure You Want to Restore Selected Course?</h6>
</center>
</div>
<div class="row" style="margin-bottom: 50px; text-align: center;">
<div class="col-sm-3"></div>
<div class="col-sm-3">
<button type="button" class="btn btn-danger btn-cancel" data-bs-dismiss="modal">Cancel</button>
</div>
<div class="col-sm-3">
<button type="submit" class="btn btn-success btn-submit">Yes Restore It</button>
</div>
<div class="col-sm-3"></div>
</div>
</form>
</div>
</div>
</div>
Routes
Route::get('/', [TrainingCourseController::class, 'index'])->name('course.index');
Route::group(['prefix' => 'course'], function () {
Route::post('move-trash', [TrainingCourseController::class, 'moveToTrash'])->name('soft.delete');
Route::get('trashed', [TrainingCourseController::class, 'showTrash'])->name('show.trashed');
Route::post('restore', [TrainingCourseController::class, 'restore'])->name('course.restore');
Route::post('delete-trash', [TrainingCourseController::class, 'forceDelete'])->name('permanent.delete');
});
Below are the styles:
public/css/style.css
* {box-sizing: border-box;
}
body {
margin: 0;
font-family: Helvetica, sans-serif;
font-size:14px;
}
h1,h4{
text-align: center;
margin-bottom: 20px;
margin-top: 10px;
}
.container{
min-height: 600px;;
}
.modal-content{
color:#fff;
background: #32243c;
}
table{
width: 70%!important;
margin: auto;
}
table>thead{
background-color:#2b5171;
color:#fff;
}
button{
border: none;
}
.btn-cancel{
padding:10px;
margin-left: -10px;
width:120px;
}
.btn-submit{
padding:10px;
margin-left: -10px;
width:140px;
}
.btn-restore{
background: #1586e9;
color: #fff;
border-radius: 6px;
padding: 6px;
font-size: 14px;
}
.btn-delete {
background: #de0808;
color: #fff;
border-radius: 6px;
padding: 6px;
font-size: 14px;
}
.fa-trash{
color:#ef3b3b;
}
.alert-success{
text-align: center;
}
Test the application
From the project root, start the php development server:
php artisan serve
From the browser run localhost:8000
. Check the Laravel soft delete, restore and permanent delete functions against the courses.
Post a Comment