Chào anh em cộng đồng Viblo!
Là một Backend Developer làm việc với các hệ thống dữ liệu lớn, chắc hẳn anh em đã từng nghe câu "thần chú" kinh điển của các DBA (Database Administrator): "Dữ liệu là tài sản, tuyệt đối không bao giờ được xóa vật lý (Hard Delete)!"
Hãy tưởng tượng hệ thống E-commerce của bạn có tính năng cho phép khách hàng xóa tài khoản, hoặc nhân viên quản trị xóa một sản phẩm. Nếu bạn chạy lệnh DELETE thẳng tay xuống Database, chuyện gì sẽ xảy ra?
Toàn bộ lịch sử mua hàng, đối soát doanh thu, hay các transaction liên quan đến sản phẩm đó sẽ bị đứt gãy quan hệ (Foreign Key) hoặc mồ côi (Orphan Data). Lúc kế toán cần check lại biến động dòng tiền, data đã bốc hơi mất rồi!
Để giải quyết bài toán này một cách thanh lịch, Laravel cung cấp một vũ khí cực kỳ lợi hại: Trait SoftDeletes (Xóa mềm). Bài viết hôm nay chúng ta sẽ mổ xẻ nó từ cách dùng cơ bản đến những "bãi mìn" thực chiến mà anh em rất hay đạp phải. Lên xe!
1. Soft Delete là gì? (Xóa mà như không xóa)
Về mặt bản chất, Soft Delete (Xóa mềm) không hề xóa bất kỳ dòng dữ liệu nào khỏi ổ cứng của Database.
Thay vì phát ra câu lệnh DELETE FROM products WHERE id = 1, nó sẽ biến thành một câu lệnh Update: UPDATE products SET deleted_at = '2026-05-14 21:00:00' WHERE id = 1.
Cột deleted_at sẽ lưu lại thời điểm record đó bị "đánh dấu" là đã xóa. Nếu cột này có giá trị NULL, nghĩa là dữ liệu vẫn đang tồn tại bình thường.
2. Cách tích hợp "Ăn liền" vào Laravel
Việc cài đặt SoftDeletes trong Laravel dễ đến mức bạn có thể làm xong trong 30 giây.
Bước 1: Bổ sung cột deleted_at vào Migration
Bạn chỉ cần gọi hàm $table->softDeletes();
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('price');
$table->timestamps();
$table->softDeletes(); // Laravel sẽ tự tạo cột deleted_at (TIMESTAMP, NULLABLE)
});
}
Bước 2: Khai báo Trait trong Model
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; // Nhớ import nhé
class Product extends Model
{
use SoftDeletes; // Khai báo sử dụng Trait
protected $fillable = ['name', 'price'];
}
Xong! Kể từ giây phút này, mỗi khi bạn gọi hàm $product->delete();, Laravel sẽ tự động cập nhật giờ hiện tại vào cột deleted_at thay vì ném record đó vào sọt rác.
3. Phép màu bên dưới (Under the hood): Global Scopes
Nhiều anh em fresher thường thắc mắc: "Ủa, dữ liệu vẫn còn trong DB, thế sao lúc gọi Product::all() nó không hiện ra mấy cái đã bị xóa?"
Bí mật nằm ở Global Scopes (Phạm vi truy vấn toàn cục). Khi bạn use SoftDeletes trong Model, Laravel đã âm thầm nhét thêm một lớp SoftDeletingScope.
Mọi câu query Eloquent của bạn (như get(), find(), where()) đều bị can thiệp và tự động nối thêm đoạn WHERE deleted_at IS NULL vào cuối câu SQL trước khi gửi xuống Database. Bằng cách này, dữ liệu đã xóa sẽ hoàn toàn "tàng hình" trước mắt bạn.
4. Các thao tác "Thực chiến" với Soft Deleted Data
Khi hệ thống đã có SoftDeletes, bạn sẽ được trang bị thêm một bộ API cực kỳ mạnh mẽ để thao tác với "thế giới ngầm" này:
// 1. Lấy tất cả, BAO GỒM cả những thằng đã bị xóa mềm (thường dùng cho Admin/Report)
$products = Product::withTrashed()->get();
// 2. CHỈ lấy những thằng ĐÃ bị xóa mềm (dùng làm tính năng "Thùng rác - Trash")
$deletedProducts = Product::onlyTrashed()->get();
// 3. Khôi phục lại dữ liệu đã xóa (Hồi sinh)
$product = Product::withTrashed()->find(1);
$product->restore(); // Cột deleted_at sẽ biến lại thành NULL
// 4. Xóa vĩnh viễn (Hard Delete thật sự - Dùng khi cần dọn rác dứt điểm)
$product->forceDelete();
5. Những "Bãi mìn" Hạng Nặng (Advanced Tips)
Dùng cơ bản thì dễ, nhưng khi đưa vào các dự án lớn, SoftDeletes sẽ sinh ra vài cái bẫy chết người nếu bạn không nắm vững kiến trúc. Đây là kinh nghiệm xương máu:
Bãi mìn 1: Xung đột với Unique Constraint (Lỗi trùng lặp dữ liệu)
Giả sử bạn có bảng users với cột email được đánh index UNIQUE (duy nhất).
- Khách hàng
a@gmail.comđăng ký tài khoản. - Khách hàng xóa tài khoản (Soft delete ->
deleted_atđược set). - Vài tháng sau, khách hàng này quay lại và đăng ký mới cũng bằng email
a@gmail.com.
Bùm! Lỗi Database 500! Hệ quản trị CSDL (MySQL/PostgreSQL) báo lỗi Duplicate entry 'a@gmail.com' for key 'users_email_unique' vì record cũ thực chất vẫn còn nằm trong ổ cứng.
Giải pháp chuẩn:
Trong Laravel Validation Rule, thay vì dùng unique:users, bạn phải kết hợp thêm method whereNull:
use Illuminate\Validation\Rule;
$request->validate([
// Chỉ báo lỗi trùng nếu email tồn tại VÀ chưa bị soft delete
'email' => [
'required',
'email',
Rule::unique('users')->whereNull('deleted_at')
],
]);
Lưu ý: Bạn cũng cần thiết kế lại cấu trúc Unique Index dưới DB thành Composite Key: UNIQUE(email, deleted_at) để DB thực sự cho phép lưu).
Bãi mìn 2: Xóa cha không xóa con (Cascading Soft Deletes)
Nếu bạn thiết lập Foreign Key ON DELETE CASCADE ở Database, nó chỉ hoạt động với Hard Delete. Khi bạn Soft Delete một Category (Danh mục), các Product (Sản phẩm) thuộc danh mục đó sẽ không hề bị xóa theo.
Nếu logic code không chặt chẽ, sản phẩm của danh mục đã xóa vẫn sẽ chình ình ngoài trang chủ!
Giải pháp: Sử dụng Model Events để tự động kích hoạt xóa con khi cha bị xóa:
class Category extends Model
{
use SoftDeletes;
public function products() {
return $this->hasMany(Product::class);
}
protected static function booted()
{
// Lắng nghe sự kiện khi Category bị xóa
static::deleting(function ($category) {
// Duyệt qua và xóa mềm tất cả các con của nó
$category->products()->delete();
});
// Tương tự, nhớ làm sự kiện restored() để khôi phục con khi khôi phục cha nhé!
static::restoring(function ($category) {
$category->products()->withTrashed()->restore();
});
}
}
Bãi mìn 3: SoftDeletes trong Route Binding
Nếu bạn dùng Implicit Route Binding (như Route::get('/products/{product}')), Laravel mặc định sẽ trả về 404 nếu product đó đã bị Soft Delete.
Nếu bạn đang viết API cho trang quản trị (Admin) và muốn xem chi tiết cả sản phẩm đã xóa, bạn phải chèn thêm withTrashed() vào Route:
// Laravel 8+ hỗ trợ chaining method này trực tiếp trên Route
Route::get('/products/{product}', [ProductController::class, 'show'])->withTrashed();
Lời kết
SoftDeletes là minh chứng rõ ràng nhất cho thấy Laravel hiểu rất rõ các "nỗi đau" của việc quản lý dữ liệu trong các hệ thống phần mềm lớn. Nó không chỉ giữ an toàn cho dữ liệu lịch sử, mà còn đóng vai trò quan trọng trong việc audit (truy vết) và làm báo cáo.
Tuy nhiên, đừng vì có SoftDeletes mà lạm dụng nó ở mọi bảng. Với những bảng log, bảng lưu session hay các bảng trung gian (Pivot tables) sinh ra dữ liệu rác liên tục hàng triệu dòng, Hard Delete hoặc Partitioning (phân mảnh DB) mới là chân ái để tiết kiệm dung lượng ổ đĩa.
Anh em ở công ty hiện tại đang quản lý các dữ liệu đã xóa như thế nào? Đã bao giờ bị dính bug liên quan đến Unique Index kết hợp với Soft Delete chưa?
Chúc anh em code chắc tay và bảo vệ data an toàn!