Ở các bài CRUD trước, khi gọi:
$user->delete();
record sẽ bị xóa khỏi database.
Ví dụ:
users
────────────────────────
id | name
1 | Nguyễn Văn A
2 | Trần Văn B
3 | Lê Văn C
Xóa User id = 2:
$user->delete();
thì record có thể biến mất hoàn toàn:
users
────────────────────────
id | name
1 | Nguyễn Văn A
3 | Lê Văn C
Trong hệ thống thực tế, cách này khá nguy hiểm.
Ví dụ Admin vô tình xóa một bài viết quan trọng. Chúng ta sẽ muốn:
🗑️ Xóa
↓
📦 Đưa vào Thùng rác
↓
♻️ Có thể khôi phục
Đây chính là Soft Delete.
MỤC LỤC
- Soft Delete là gì?
- Khi nào nên dùng Soft Delete?
- Thêm cột
deleted_at - Soft Delete cho Posts
- Thêm
SoftDeletesvào Model delete()lúc này hoạt động thế nào?- Vì sao User đã xóa không xuất hiện?
withTrashed()onlyTrashed()- Restore
- Route Restore
- Controller Restore
- Nút Restore trong Trash
forceDelete()- Phân biệt
delete()vàforceDelete() - Tạo trang Trash
- Hiển thị Trash
- Xóa vĩnh viễn
- Controller
forceDelete() - Xóa toàn bộ Trash
- Restore nhiều User
- Nút Trash trong trang Users
- Luồng hoạt động hoàn chỉnh
- Soft Delete với Posts
- Soft Delete và Relationship
- Kiểm tra một record có bị xóa không
- Soft Delete không phải Backup
- Khi nào không nên dùng Soft Delete?
- Best Practice
- Tổng kết Bài 30
1. Soft Delete là gì?
Soft Delete là cơ chế đánh dấu một record đã bị xóa thay vì xóa thật khỏi database.
Laravel sử dụng cột:
deleted_at
Ví dụ:
posts
────────────────────────────────────
id | title | deleted_at
1 | Bài viết 1 | NULL
2 | Bài viết 2 | 2026-08-18 10:30:00
3 | Bài viết 3 | NULL
Trong đó:
deleted_at = NULL
→ record đang hoạt động.
deleted_at = thời gian
→ record đã bị Soft Delete.
2. Khi nào nên dùng Soft Delete?
Soft Delete đặc biệt hữu ích với:
Users
Posts
Categories
Products
Orders
Customers
Documents
Comments
Đặc biệt với Admin CMS:
Xóa
↓
Trash
↓
Restore
thường an toàn hơn xóa vĩnh viễn.
3. Thêm cột deleted_at
Tạo migration:
php artisan make:migration add_deleted_at_to_users_table
Mở migration:
Schema::table('users', function (Blueprint $table) {
$table->softDeletes();
});
Laravel sẽ tạo:
deleted_at
với kiểu dữ liệu phù hợp.
Sau đó:
php artisan migrate
4. Soft Delete cho Posts
Nếu muốn áp dụng cho Posts:
php artisan make:migration add_deleted_at_to_posts_table
Migration:
Schema::table('posts', function (Blueprint $table) {
$table->softDeletes();
});
Sau đó:
php artisan migrate
5. Thêm SoftDeletes vào Model
Đây là bước quan trọng.
Mở:
app/Models/User.php
Thêm:
use Illuminate\Database\Eloquent\SoftDeletes;
Sau đó:
class User extends Authenticatable
{
use SoftDeletes;
}
Model hoàn chỉnh có thể:
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasFactory;
use SoftDeletes;
}
6. delete() lúc này hoạt động thế nào?
Khi:
$user->delete();
Laravel không xóa record khỏi database.
Thay vào đó:
deleted_at = thời gian hiện tại
Ví dụ:
id | name | deleted_at
1 | Nguyễn Văn A | NULL
2 | Trần Văn B | 2026-08-18 11:00:00
User id = 2 vẫn tồn tại trong database.
7. Vì sao User đã xóa không xuất hiện?
Laravel tự động loại các record Soft Delete khỏi những truy vấn Eloquent thông thường.
Ví dụ:
$users = User::all();
sẽ chỉ lấy:
deleted_at IS NULL
Do đó User đã đưa vào Trash sẽ không xuất hiện trong danh sách bình thường.
Đây là một trong những ưu điểm lớn của Soft Delete.
8. withTrashed()
Nếu muốn lấy cả record đang hoạt động và đã xóa:
$users = User::withTrashed()->get();
//hoặc
$users = User::query()
// SOFT DELETE
->withTrashed()
Kết quả:
User bình thường
+
User đã Soft Delete
9. onlyTrashed()
Nếu chỉ muốn lấy những record đã xóa:
$users = User::onlyTrashed()->get();
Đây chính là dữ liệu cho trang:
🗑️ Trash
Ví dụ:
public function trash()
{
$users = User::onlyTrashed()->latest('deleted_at')->paginate(10);
return view('users.trash', compact('users'));
}
10. Restore
Muốn khôi phục User:
$user->restore();
Laravel sẽ đưa:
deleted_at
về:
NULL
Ví dụ:
Trước:
id | name | deleted_at
2 | ABC | 2026-08-18
Sau restore:
id | name | deleted_at
2 | ABC | NULL
User xuất hiện trở lại trong danh sách bình thường.
11. Route Restore
Trong:
routes/web.php
thêm:
Route::patch(
'users/{user}/restore',
[UserController::class, 'restore']
)->name('users.restore');
12. Controller Restore
Trong UserController:
public function restore($id)
{
$user = User::withTrashed()->findOrFail($id);
$user->restore();
return redirect()
->route('admin.users.index')
->with('success', 'Khôi phục User thành công.');
}
Chú ý:
withTrashed()
rất quan trọng.
Nếu không có nó, User đã Soft Delete sẽ không được tìm thấy bởi truy vấn Eloquent thông thường.
13. Nút Restore trong Trash
Blade:
{{-- KHÔI PHỤC --}}
@if ($user->trashed())
<form
action="{{ route('admin.users.restore', $user->id) }}"
method="POST"
class="inline">
@csrf
@method('PATCH')
<button
type="submit"
class="px-3 py-1 bg-green-600 text-white rounded hover:bg-green-700">
♻️ Khôi phục
</button>
</form>
@endif
14. forceDelete()
Soft Delete không xóa thật.
Nếu muốn xóa vĩnh viễn:
$user->forceDelete();
Record sẽ thực sự bị xóa khỏi database.
Ví dụ:
Soft Delete:
id | name | deleted_at
2 | ABC | 2026-08-18
Sau:
$user->forceDelete();
record id = 2 không còn tồn tại.
15. Phân biệt delete() và forceDelete()
| Method | Kết quả |
|---|---|
delete() | Soft Delete |
restore() | Khôi phục |
forceDelete() | Xóa vĩnh viễn |
withTrashed() | Lấy cả record đã xóa |
onlyTrashed() | Chỉ lấy record đã xóa |
Đây là 5 method cần nhớ.
Chú ý: khi softdelete() thì posts của user đó vẫn còn -> cần sửa post model
Muốn lấy được cả User đã bị xóa:
public function user()
{
return $this->belongsTo(User::class)
->withTrashed();
}
16. Tạo trang Trash
Route (phải đặt trước restore và resource):
Route::get(
'users/trash',
[UserController::class, 'trash']
)->name('users.trash');
Controller:
public function trash()
{
$users = User::onlyTrashed()
->latest('deleted_at')
->paginate(10);
return view('admin.users.trash', compact('users'));
}
17. Hiển thị Trash
Ví dụ:
<table class="w-full">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Deleted At</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@foreach ($users as $user)
<tr>
<td>{{ $user->id }}</td>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>
{{ $user->deleted_at }}
</td>
<td>
<form
method="POST"
action="{{ route('admin.users.restore', $user->id) }}"
>
@csrf
@method('PATCH')
<button type="submit">
♻️ Restore
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
{{ $users->links() }}
18. Xóa vĩnh viễn
Trong Trash:
<form
method="POST"
action="{{ route('admin.users.forceDelete', $user->id) }}"
>
@csrf
@method('DELETE')
<button type="submit">
🗑️ Xóa vĩnh viễn
</button>
</form>
Route:
Route::delete(
'users/{user}/force-delete',
[UserController::class, 'forceDelete']
)->name('users.forceDelete');
19. Controller forceDelete()
public function forceDelete($id)
{
$user = User::onlyTrashed()->findOrFail($id);
$user->forceDelete();
return redirect()
->route('admin.users.trash')
->with('success', 'Đã xóa vĩnh viễn.');
}
Ở đây sử dụng:
onlyTrashed()
để đảm bảo chỉ xóa vĩnh viễn những record đang nằm trong Trash.
20. Xóa toàn bộ Trash
Có thể xóa nhiều record:
User::onlyTrashed()->forceDelete();
Hoặc:
User::onlyTrashed()->each(function ($user) {
$user->forceDelete();
});
Tuy nhiên với dữ liệu lớn, cần cân nhắc cách xử lý để tránh tải quá nhiều model vào memory.
UserController:
public function forceDeleteAll()
{
User::onlyTrashed()->forceDelete();
return redirect()
->route('admin.users.trash')
->with('success', 'Đã xóa vĩnh viễn tất cả thùng rác.');
}
Route:
Route::delete(
'users/force-delete-all',
[UserController::class, 'forceDeleteAll']
)->name('users.forceDeleteAll');
Phần trash.blade.php xem phần 21
21. Restore nhiều User
Có thể khôi phục nhiều record:
User::onlyTrashed()
->whereIn('id', $ids)
->restore();
Ví dụ:
ids = [5, 8, 12]
sẽ khôi phục ba User.
UserController
public function restoreMany(Request $request)
{
$ids = $request->input('ids', []);
User::onlyTrashed()
->whereIn('id', $ids)
->restore();
return redirect()
->route('admin.users.trash')
->with('success', 'Khôi phục User thành công.');
}
Route
Route::patch(
'users/restore-many',
[UserController::class, 'restoreMany']
)->name('users.restoreMany');
<x-app-layout>
<div class="max-w-7xl mx-auto py-8">
<x-ui.card
title="Thùng rác Users"
description="Quản lý các User đã bị Soft Delete">
{{-- ACTION --}}
<div class="flex justify-between items-center mb-4">
{{-- RESTORE MANY --}}
<form
method="POST"
action="{{ route('admin.users.restoreMany') }}"
id="restoreForm">
@csrf
@method('PATCH')
<button
type="submit"
onclick="return confirm('Bạn có chắc muốn khôi phục các User đã chọn?')"
class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700">
♻️ Khôi phục đã chọn
</button>
</form>
{{-- DELETE ALL --}}
<form
method="POST"
action="{{ route('admin.users.forceDeleteAll') }}"
onsubmit="return confirm('⚠️ Bạn có chắc muốn XÓA VĨNH VIỄN toàn bộ User trong thùng rác? Hành động này không thể hoàn tác!')">
@csrf
@method('DELETE')
<button
type="submit"
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700">
🗑️ Xóa vĩnh viễn tất cả
</button>
</form>
</div>
{{-- TABLE --}}
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b">
{{-- SELECT ALL --}}
<th class="p-3 text-left">
<input
type="checkbox"
id="selectAll"
class="w-4 h-4">
</th>
<th class="p-3 text-left">
ID
</th>
<th class="p-3 text-left">
Name
</th>
<th class="p-3 text-left">
Email
</th>
<th class="p-3 text-left">
Deleted At
</th>
<th class="p-3 text-left">
Action
</th>
</tr>
</thead>
<tbody>
@forelse ($users as $user)
<tr class="border-b">
{{-- CHECKBOX --}}
<td class="p-3">
<input
type="checkbox"
name="ids[]"
value="{{ $user->id }}"
form="restoreForm"
class="user-checkbox w-4 h-4">
</td>
{{-- ID --}}
<td class="p-3">
{{ $user->id }}
</td>
{{-- NAME --}}
<td class="p-3">
{{ $user->name }}
</td>
{{-- EMAIL --}}
<td class="p-3">
{{ $user->email }}
</td>
{{-- DELETED AT --}}
<td class="p-3">
{{ $user->deleted_at }}
</td>
{{-- ACTION --}}
<td class="p-3">
<div class="flex gap-2">
{{-- RESTORE --}}
<form
method="POST"
action="{{ route('admin.users.restore', $user->id) }}">
@csrf
@method('PATCH')
<button
type="submit"
class="px-3 py-1 bg-green-600 text-white rounded hover:bg-green-700">
♻️ Restore
</button>
</form>
{{-- FORCE DELETE --}}
<form
method="POST"
action="{{ route('admin.users.forceDelete', $user->id) }}"
onsubmit="return confirm('⚠️ Xóa vĩnh viễn User này? Không thể hoàn tác!')">
@csrf
@method('DELETE')
<button
type="submit"
class="px-3 py-1 bg-red-600 text-white rounded hover:bg-red-700">
🗑️ Xóa vĩnh viễn
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td
colspan="6"
class="p-6 text-center text-gray-500">
🗑️ Thùng rác đang trống.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- PAGINATION --}}
<div class="mt-4">
{{ $users->links() }}
</div>
</x.ui.card>
</div>
{{-- SELECT ALL --}}
<script>
const selectAll = document.getElementById('selectAll');
const checkboxes = document.querySelectorAll('.user-checkbox');
selectAll?.addEventListener('change', function () {
checkboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
});
</script>
</x-app-layout>
22. Nút Trash trong trang Users
Trang Users có thể thêm:
<a href="{{ route('admin.users.trash') }}">
🗑️ Thùng rác
</a>
Giao diện:
Users
[+ Thêm User] [🗑️ Thùng rác]
-------------------------------------
| ID | Name | Email | Role | Action |
-------------------------------------
23. Luồng hoạt động hoàn chỉnh
USERS
│
│ delete()
↓
Soft Delete
│
↓
deleted_at
│
↓
TRASH
↙ ↘
restore() forceDelete()
↓ ↓
Users Xóa thật
24. Soft Delete với Posts
Posts cũng tương tự.
Bước 1: thêm withTrash() trong PostController (xem lại mục 8)
Bước 2: thêm route store (xem lại mục 11)
Bước 3: thêm hàm restore trong controller (mục 12)
Bước 4: thêm nút restore trong view (xem lại phần 13)
Bước 5: xem lại 16->22 để tạo thêm trang Trash hoàn chỉnh cho posts
Model:
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
}
Migration:
$table->softDeletes();
Xóa:
$post->delete();
Trash:
Post::onlyTrashed()->get();
Restore:
$post->restore();
Xóa vĩnh viễn:
$post->forceDelete();
25. Soft Delete và Relationship
Ví dụ User có Posts:
$user->posts
Các Post đã Soft Delete thông thường sẽ không xuất hiện.
Nếu muốn lấy cả Posts đã xóa:
$user->posts()->withTrashed()->get();
Chỉ lấy Posts đã xóa:
$user->posts()->onlyTrashed()->get();
Điều này rất quan trọng khi xây dựng hệ thống CMS có nhiều relationship.
26. Kiểm tra một record có bị xóa không
Có thể sử dụng:
$user->trashed();
Nếu:
true
→ User đang bị Soft Delete.
Nếu:
false
→ User đang hoạt động.
Ví dụ:
if ($user->trashed()) {
// User đang nằm trong Trash
}
27. Soft Delete không phải Backup
Đây là điểm rất quan trọng.
Soft Delete:
Database
↓
deleted_at
không phải:
Backup Database
Nếu database bị:
mất dữ liệu
hỏng ổ đĩa
DROP TABLE
server lỗi
thì Soft Delete không thể bảo vệ dữ liệu.
Vì vậy dự án thực tế vẫn cần:
Database Backup
+
Soft Delete
28. Khi nào không nên dùng Soft Delete?
Không phải bảng nào cũng cần.
Ví dụ một số dữ liệu tạm thời hoặc dữ liệu không cần khôi phục có thể xóa trực tiếp.
Soft Delete cũng làm database giữ lại record đã xóa, vì vậy bảng có lượng dữ liệu rất lớn cần có chiến lược dọn dẹp phù hợp.
29. Best Practice
Đối với Blog CMS trong khóa học này, có thể áp dụng:
Users
↓
Soft Delete
Posts
↓
Soft Delete
Categories
↓
Soft Delete
Giao diện Admin:
Users
├── Active
└── Trash
Posts
├── Published
├── Draft
└── Trash
Categories
├── Active
└── Trash
30. Tổng kết Bài 30
Sau bài này, chúng ta đã biết:
Soft Delete
↓
deleted_at
Các method quan trọng:
delete()
→ đưa vào Trash.
restore()
→ khôi phục.
forceDelete()
→ xóa vĩnh viễn.
withTrashed()
→ lấy cả record đã xóa.
onlyTrashed()
→ chỉ lấy record đã xóa.
trashed()
→ kiểm tra record có đang bị Soft Delete hay không.
Mô hình hoàn chỉnh:
CRUD
│
↓
Users
│
┌────┴────┐
↓ ↓
Active Trash
│
┌────┴────┐
↓ ↓
Restore Force Delete
│
↓
Active
🎯 Bài tập thực hành
Thêm Soft Delete cho
users.Thêm Soft Delete cho
posts.Thêm nút 🗑️ Thùng rác.
Hiển thị danh sách record đã xóa.
Thêm ♻️ Restore.
Thêm 🗑️ Xóa vĩnh viễn.
Thêm xác nhận trước khi
forceDelete().Thêm Pagination cho Trash.
Thử kết hợp Trash với DataTables ở Bài 29.
Bài tiếp theo — Bài 31: Accessor & Mutator sẽ tìm hiểu cách Laravel biến đổi dữ liệu khi đọc/ghi Model, ví dụ định dạng tên, tạo thuộc tính ảo và tự động xử lý dữ liệu trước khi lưu vào database.
x1
quay về MỤC LỤC




Không có nhận xét nào:
Đăng nhận xét