Laravel 13 thêm Cache::touch() để gia hạn thời gian sống của một cache item mà không cần đọc rồi ghi lại value. Thao tác nghe nhỏ, nhưng giải quyết đúng trường hợp dữ liệu vẫn còn hợp lệ và chỉ cần kéo dài TTL sau một hoạt động của người dùng.

Bài kiểm tra dùng Laravel 13.25.0 và file cache. Mục tiêu là xác nhận ba việc: value có bị đổi không, key đang tồn tại trả gì và key không tồn tại có bị tạo ngoài ý muốn hay không.

touch() khác put() ở đâu?

put() ghi value và TTL mới. Nếu chỉ muốn gia hạn, ứng dụng thường phải get() rồi put() lại, vừa dài vừa có cửa ghi đè dữ liệu vừa được tiến trình khác cập nhật. Tài liệu Laravel 13 mô tả touch() như một thao tác trực tiếp trên TTL của item hiện có; API này cũng được liệt kê trong release notes Laravel 13.

use Illuminate\Support\Facades\Cache;

$extended = Cache::touch('cart:42', 3600);

if (! $extended) {
    // Key không tồn tại hoặc store không gia hạn được.
}

Tham số thứ hai nhận số giây, DateTimeInterface, DateInterval hoặc một instance Carbon. Giá trị trả về là boolean, vì vậy không nên bỏ qua nếu logic phía sau phụ thuộc vào việc TTL đã được đổi thật. Có thể đối chiếu cách facade chuyển lời gọi xuống store trong mã nguồn Illuminate Cache.

Sơ đồ chọn touch hoặc put theo nhu cầu thay đổi cache
touch() chỉ gia hạn item có sẵn; put() phù hợp khi value cũng cần thay đổi.

Kết quả chạy trên Laravel 13.25

Fixture tạo key catchup:touch với value {"version": 1} và TTL 60 giây. Sau đó touch() đổi TTL thành 3.600 giây. Kết quả trả true, còn value trước và sau giống nhau. Khi gọi cùng thao tác cho key không tồn tại, kết quả là false và Laravel không tự tạo item rỗng.

Kết quả Cache touch trên Laravel 13.25 với key có và không tồn tại
File cache giữ nguyên value; key hiện có trả true, key thiếu trả false.

Điểm này quan trọng với session phụ, giỏ hàng tạm, trạng thái import hoặc token nội bộ: gia hạn được không đồng nghĩa item chắc chắn tồn tại. Nếu key có thể hết hạn giữa hai thao tác, code phải xử lý nhánh false thay vì giả định mọi thứ ổn.

Một ví dụ gia hạn giỏ hàng đúng chỗ

public function keepCartAlive(int $userId): void
{
    $key = "cart:{$userId}";

    if (! Cache::touch($key, now()->addMinutes(30))) {
        report("Không thể gia hạn {$key}");
    }
}

Không nên gọi đoạn này ở mọi request. Chỉ gia hạn khi có tín hiệu hoạt động liên quan, chẳng hạn thêm sản phẩm hoặc mở lại trang checkout. Nếu không, một bot hay tab bị bỏ quên có thể giữ cache lâu hơn chính sách sản phẩm mong muốn.

Những điểm cần kiểm tra trước khi dùng

Kiểm traLý do
Cache store đang dùngĐảm bảo driver hỗ trợ thao tác và hành vi TTL mong đợi
Nhánh trả về falseKey có thể đã hết hạn hoặc không tồn tại
Đơn vị TTLSố nguyên là giây; ngày giờ giúp ý định rõ hơn
Giới hạn thời gian tối đaTránh gia hạn vô hạn dữ liệu đáng lẽ phải hết hạn

Nếu dự án vẫn ở Laravel 12, xem trước đường nâng cấp Laravel 13 rồi mới dùng API mới. Với công cụ Composer chạy một lần, bài CPX 2.0 cũng có ví dụ cách cô lập thử nghiệm khỏi project chính.

Race condition vẫn cần được tính tới

touch() bỏ được vòng đọc rồi ghi lại value, nhờ đó giảm một kiểu race condition phổ biến. Tuy nhiên key vẫn có thể hết hạn ngay trước lúc store xử lý lệnh. Boolean false chính là tín hiệu để ứng dụng chọn tạo lại dữ liệu, trả người dùng về bước trước hoặc ghi log tùy mức độ quan trọng.

Không nên biến nhánh thất bại thành một lệnh put() tự động nếu không có value đáng tin cậy. Ví dụ cache giỏ hàng chứa nhiều dòng sản phẩm mà request hiện tại chỉ biết user ID; tạo một mảng rỗng để “sửa” lỗi có thể xóa dấu vết cần phục hồi. Với dữ liệu có nguồn gốc rõ ràng, có thể dùng remember() để dựng lại; với dữ liệu phiên tạm, nên báo hết hạn.

Nên test những store nào?

Fixture của bài dùng file cache để chứng minh contract cơ bản, nhưng production có thể chạy Redis, Memcached hoặc database. Test tích hợp nên chạy đúng driver đang deploy và bao phủ ít nhất bốn trường hợp: key tồn tại, key thiếu, TTL dạng số giây và thời điểm Carbon. Nếu hệ thống có nhiều worker, thêm một test hai tiến trình cùng cập nhật value và TTL để chắc rằng logic nghiệp vụ không phụ thuộc vào thứ tự mong manh.

Cũng cần quan sát TTL trực tiếp bằng công cụ của store. Với Redis, xem TTL key trước và sau; với database cache, kiểm tra cột expiration. Assertion chỉ đọc lại value chưa đủ để chứng minh thời gian sống đã thay đổi, vì value giống nhau là điều touch() cố tình giữ.

Kết luận

Cache::touch() hợp khi value còn đúng và chỉ TTL cần thay đổi. API ngắn hơn vòng get()/put(), nhưng boolean trả về vẫn phải được xử lý. Fixture Laravel và toàn bộ dependency tải để test đã được xóa sau khi hoàn tất.