NTM Solutions

Thứ Tư, 19 tháng 8, 2026

📘 Laravel 12 (2026) — BÀI 30 — SOFT DELETE

Ở 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

  1. Soft Delete là gì?
  2. Khi nào nên dùng Soft Delete?
  3. Thêm cột deleted_at
  4. Soft Delete cho Posts
  5. Thêm SoftDeletes vào Model
  6. delete() lúc này hoạt động thế nào?
  7. Vì sao User đã xóa không xuất hiện?
  8. withTrashed()
  9. onlyTrashed()
  10. Restore
  11. Route Restore
  12. Controller Restore
  13. Nút Restore trong Trash
  14. forceDelete()
  15. Phân biệt delete()forceDelete()
  16. Tạo trang Trash
  17. Hiển thị Trash
  18. Xóa vĩnh viễn
  19. Controller forceDelete()
  20. Xóa toàn bộ Trash
  21. Restore nhiều User
  22. Nút Trash trong trang Users
  23. Luồng hoạt động hoàn chỉnh
  24. Soft Delete với Posts
  25. Soft Delete và Relationship
  26. Kiểm tra một record có bị xóa không
  27. Soft Delete không phải Backup
  28. Khi nào không nên dùng Soft Delete?
  29. Best Practice
  30. 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();

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('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:

<form
    method="POST"
    action="{{ route('users.restore', $user->id) }}"
>
    @csrf
    @method('PATCH')

    <button type="submit">
        ♻️ Khôi phục
    </button>
</form>

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()forceDelete()

MethodKế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ớ.


16. Tạo trang Trash

Route:

Route::get(
    'users/trash',
    [UserController::class, 'trash']
)->name('users.trash');

Controller:

public function trash()
{
    $users = User::onlyTrashed()
        ->latest('deleted_at')
        ->paginate(10);

    return view('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('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('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('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.


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.


22. Nút Trash trong trang Users

Trang Users có thể thêm:

<a href="{{ route('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ự.

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

  1. Thêm Soft Delete cho users.

  2. Thêm Soft Delete cho posts.

  3. Thêm nút 🗑️ Thùng rác.

  4. Hiển thị danh sách record đã xóa.

  5. Thêm ♻️ Restore.

  6. Thêm 🗑️ Xóa vĩnh viễn.

  7. Thêm xác nhận trước khi forceDelete().

  8. Thêm Pagination cho Trash.

  9. 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.

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

Đăng nhận xét

Facebook Youtube RSS