Facade Cache menyimpan hasil operasi yang mahal, misalnya query database atau perhitungan laporan, agar permintaan berikutnya membacanya kembali tanpa mengulang proses. Topik ini membahas konsep store dan driver bawaan Laravel 13, operasi dasar facade Cache, pola read-through dengan remember, pengelompokan kunci dengan tag cache, serta rate limiting berbasis cache. Setiap contoh dijalankan pada aplikasi catatan sehingga keluarannya dapat diverifikasi ulang.
Store cache dan driver database#
Store adalah konfigurasi satu media penyimpanan cache, sedangkan driver adalah kode yang menangani komunikasi dengan media penyimpanannya. Daftar store didefinisikan pada config/cache.php, dan store yang digunakan secara default dipilih melalui variabel CACHE_STORE pada berkas .env. Laravel 13 menjadikan database sebagai store bawaan, sehingga nilai cache tersimpan pada tabel di database yang sama dengan data aplikasi. Skemanya sudah disiapkan oleh migration bawaan melalui tabel cache dan cache_locks, sehingga cache dapat digunakan tanpa layanan tambahan seperti Redis.
Perilaku store bawaan dapat diamati langsung. Contoh berikut membaca nama driver, menyimpan satu nilai, membacanya, lalu menghapusnya kembali.
# operasi dasar facade Cache pada store bawaan
cd ~/aplikasi-catatan
php artisan tinker --execute='
use Illuminate\Support\Facades\Cache;
echo "Driver: " . Cache::getDefaultDriver() . PHP_EOL;
Cache::forget("kunci-demo");
Cache::put("kunci-demo", "Isi nilai tersimpan.", 60);
echo "Get: " . Cache::get("kunci-demo") . PHP_EOL;
echo "Has: " . var_export(Cache::has("kunci-demo"), true) . PHP_EOL;
Cache::forget("kunci-demo");
echo "Setelah forget: " . var_export(Cache::get("kunci-demo", "nilai default"), true) . PHP_EOL;
'Driver: database
Get: Isi nilai tersimpan.
Has: true
Setelah forget: 'nilai default'Metode put() menerima kunci, nilai, dan masa kedaluwarsa dalam detik, sedangkan get() mengembalikan nilai dari argumen kedua apabila kunci tidak ditemukan. Pemanggilan forget() menghapus kuncinya sehingga get() berikutnya mengembalikan nilai default tersebut. Karena drivernya database, setiap nilai cache benar-benar menjadi baris pada tabel cache, dan baris itu dapat diperiksa langsung.
# menyimpan ulang satu nilai lalu memeriksa barisnya pada tabel cache
cd ~/aplikasi-catatan
php artisan tinker --execute='
use Illuminate\Support\Facades\Cache;
Cache::forget("laporan-jumlah-kategori");
Cache::put("laporan-jumlah-kategori", 3, 60);
'
sqlite3 database/database.sqlite "select key from cache where key like '%laporan-jumlah-kategori%';"
php artisan tinker --execute='
use Illuminate\Support\Facades\Cache;
Cache::forget("laporan-jumlah-kategori");
'laravel-cache-laporan-jumlah-kategoriKunci tersimpan dengan awalan laravel-cache- sesuai konfigurasi prefix, sedangkan kolom expiration pada baris yang sama memuat waktu kedaluwarsa dalam bentuk bilangan epoch. Pembacaan langsung seperti ini hanya untuk pembuktian karena kode aplikasi tetap mengakses nilai melalui facade.
Menyimpan hasil perhitungan dengan remember#
Pola pemakaian cache yang paling umum adalah read-through, yakni membaca nilai dari cache terlebih dahulu dan menghitungnya hanya apabila belum ada. Metode remember() mengerjakan pola tersebut dalam satu pemanggilan dengan kunci, masa kedaluwarsa, dan closure penghitungnya sebagai argumen. Apabila kunci tersedia dan belum kedaluwarsa, closure tidak dijalankan sama sekali. Diagram berikut merangkum kedua jalurnya.
firstOrCreate agar keluarannya sama pada setiap eksekusi.
# hasil perhitungan dihitung sekali lalu dibaca dari cache
cd ~/aplikasi-catatan
php artisan tinker --execute='
use App\Models\Category;
use Illuminate\Support\Facades\Cache;
foreach (["Kerja", "Belajar", "Pribadi"] as $nama) {
Category::firstOrCreate(["nama" => $nama]);
}
Cache::forget("laporan-jumlah-kategori");
$hasil = Cache::remember("laporan-jumlah-kategori", 60, function () {
return Category::whereIn("nama", ["Kerja", "Belajar", "Pribadi"])->count();
});
echo "Pertama: " . $hasil . PHP_EOL;
$hasil = Cache::remember("laporan-jumlah-kategori", 60, function () {
return Category::whereIn("nama", ["Kerja", "Belajar", "Pribadi"])->count();
});
echo "Kedua: " . $hasil . PHP_EOL;
Cache::forget("laporan-jumlah-kategori");
echo "Setelah forget: " . var_export(Cache::has("laporan-jumlah-kategori"), true) . PHP_EOL;
'Pertama: 3
Kedua: 3
Setelah forget: falsePemanggilan pertama menjalankan closure karena cache masih kosong, sedangkan pemanggilan kedua cukup membaca nilai tersimpan sehingga query tidak diulang. Angka 3 pada kedua baris berasal dari dua jalur yang berbeda. Pemanggilan forget() di akhir mengembalikan keadaan awal agar contoh dapat diulang, sedangkan perintah berikut mengosongkan seluruh store sekaligus.
# mengosongkan seluruh isi store cache bawaan
cd ~/aplikasi-catatan
php artisan cache:clear INFO Application cache cleared successfully.Tag cache#
Apabila banyak kunci milik satu kelompok data, menghapusnya satu per satu rawan melewatkan salah satunya. Tag cache mengatasi masalah tersebut dengan menandai sekumpulan kunci, lalu mengosongkan seluruh kunci ber-tag tertentu dalam satu pemanggilan flush(). Aksesnya dilakukan melalui Cache::tags(["laporan"]), baik untuk put, get, maupun flush.
Tidak semua driver mendukung tag. Dokumentasi mencatat bahwa driver file, dynamodb, database, dan storage tidak mendukungnya, sedangkan driver array, redis, dan memcached mendukungnya. Pada store database bawaan, pemanggilan tags() langsung gagal.
# menguji tag cache pada driver database bawaan
cd ~/aplikasi-catatan
php artisan tinker --execute='
use Illuminate\Support\Facades\Cache;
Cache::tags(["laporan"])->put("laporan-ringkas", "Ringkasan laporan.", 60);
' BadMethodCallException This cache store does not support tagging.Error tersebut muncul saat pemanggilan berlangsung, bukan saat konfigurasi dibaca. Agar mekanismenya tetap dapat diamati, contoh berikut mengulang skenarionya pada store array yang mendukung tag.
# tag pada store array yang mendukung pengelompokan kunci
cd ~/aplikasi-catatan
php artisan tinker --execute='
use Illuminate\Support\Facades\Cache;
$cache = Cache::store("array");
$cache->tags(["laporan"])->put("laporan-ringkas", "Ringkasan laporan 3 kategori.", 60);
$cache->put("pengunjung-hari-ini", 12, 60);
echo "Tag: " . $cache->tags(["laporan"])->get("laporan-ringkas") . PHP_EOL;
$cache->tags(["laporan"])->flush();
echo "Laporan hilang: " . var_export($cache->tags(["laporan"])->missing("laporan-ringkas"), true) . PHP_EOL;
echo "Pengunjung selamat: " . $cache->get("pengunjung-hari-ini") . PHP_EOL;
'Tag: Ringkasan laporan 3 kategori.
Laporan hilang: true
Pengunjung selamat: 12Nilai ber-tag laporan hilang setelah flush() dipanggil, sedangkan kunci yang tidak diberi tag tetap ada. Perlu dicatat bahwa store array menyimpan nilai dalam memori proses dan mengosongkannya saat proses berakhir, sehingga store ini cocok untuk demonstrasi dan pengujian, bukan untuk produksi. Pada produksi, tag digunakan bersama store redis atau memcached.
Rate limiting berbasis cache#
Rate limiting membatasi jumlah aksi dalam satu jendela waktu, misalnya tiga kali pengiriman surat per menit. Facade RateLimiter menghitung percobaan pada store cache yang sama dengan facade Cache, sehingga perilakunya dapat diamati dengan cara yang sama. Metode attempt() menerima kunci, batas percobaan, aksi yang dijalankan, dan masa jendela dalam detik. Metode ini mengembalikan nilai kembalian aksinya apabila batas belum tercapai dan false apabila batas sudah tercapai.
# membatasi tiga percobaan pengiriman per jendela 60 detik
cd ~/aplikasi-catatan
php artisan tinker --execute='
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::clear("kirim-pengingat");
for ($i = 1; $i <= 4; $i++) {
$hasil = RateLimiter::attempt("kirim-pengingat", 3, function () {
return "Pengingat terkirim.";
}, 60);
echo "Percobaan {$i}: " . var_export($hasil, true) . PHP_EOL;
}
echo "Sisa percobaan: " . RateLimiter::retriesLeft("kirim-pengingat", 3) . PHP_EOL;
RateLimiter::clear("kirim-pengingat");
echo "Setelah clear: " . RateLimiter::retriesLeft("kirim-pengingat", 3) . PHP_EOL;
'Percobaan 1: 'Pengingat terkirim.'
Percobaan 2: 'Pengingat terkirim.'
Percobaan 3: 'Pengingat terkirim.'
Percobaan 4: false
Sisa percobaan: 0
Setelah clear: 3Percobaan keempat ditolak karena penghitung untuk kunci kirim-pengingat sudah mencapai batas tiga, meskipun kodenya siap dijalankan. Metode tooManyAttempts(), retriesLeft(), dan availableIn() melaporkan keadaan penghitung, sedangkan clear() mengosongkannya kembali seperti pada baris terakhir. Facade yang sama digunakan framework untuk membatasi lalu lintas HTTP. Definisi rate limiter bernama diletakkan pada metode boot() service provider, dan rute yang menggunakan middleware throttle dengan nama tersebut dibatasi menurut definisi itu.
// tidak-dijalankan
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});Kesalahan umum#
Kesalahan pertama adalah memperlakukan cache sebagai penyimpanan. Nilai cache dapat hilang kapan saja akibat kedaluwarsa, forget(), atau cache:clear yang dijalankan pihak lain, sehingga kode aplikasi wajib tetap benar apabila cache kosong. Selalu sediakan jalur penghitungan ulang, dan jangan menempatkan satu-satunya salinan data penting di dalam cache.
Kesalahan kedua adalah membiarkan nilai cache tidak pernah dibersihkan ketika data sumbernya berubah. Laporan jumlah kategori akan terus menampilkan angka lama sampai kuncinya kedaluwarsa, dan selama itu pembacanya melihat data lama yang belum mencerminkan perubahan terbaru. Panggil forget() pada titik ketika data diperbarui, atau pendekkan masa kedaluwarsanya agar selisih datanya dapat diterima.
Kesalahan ketiga adalah menulis Cache::tags() tanpa memeriksa dukungan driver. Pada store database bawaan, pemanggilan itu memunculkan BadMethodCallException saat runtime seperti yang dibuktikan di atas. Gunakan store yang mendukung tag, atau kelola kelompok kunci dengan awalan nama yang seragam lalu hapus daftarnya satu per satu.
Ringkasan#
- Store bawaan Laravel 13 adalah
database, sehingga nilai cache tersimpan pada tabelcacheyang sudah disiapkan migration bawaan. put(),get(),forget(), danremember()menutupi operasi dasar, denganremember()menghitung nilai hanya saat kunci belum ada atau sudah kedaluwarsa.- Tag cache mengosongkan sekumpulan kunci dalam satu
flush(), tetapi hanya pada driver tertentu sepertiarray,redis, danmemcached, sedangkan driverdatabasemenolaknya denganBadMethodCallException. - RateLimiter menghitung percobaan pada store cache yang sama, dengan
attempt()menolak aksi yang melampaui batas danclear()mengosongkan penghitungnya.
Referensi#
- Cache — Laravel — Dokumentasi resmi store, operasi facade Cache, tag cache, dan masa kedaluwarsa.
- Rate Limiting — Laravel
— Dokumentasi
RateLimiter::attempt,availableIn, dan konfigurasi cache untuk rate limiter. - Routing — Laravel
— Bagian rate limiting yang memuat definisi
RateLimiter::fordan middlewarethrottle.