Setiap kali Budi membuka halaman keranjang belanja, HTTP memperlakukan request tersebut sebagai tamu yang belum pernah dikenal. Protokol HTTP bersifat stateless: setiap request berdiri sendiri, tanpa ingatan apa pun tentang request sebelumnya. Padahal aplikasi kita jelas butuh mengingat — siapa yang sedang login, isi keranjang, tema gelap yang dipilih pengguna. Session adalah mekanisme yang memberi HTTP “memori”: data disimpan di server, sementara browser hanya memegang sebuah penunjuk berupa cookie. Pada chapter ini kita bedah anatomi session Laravel sampai ke baris tabelnya di database.
HTTP yang Stateless #
Sebuah request HTTP terdiri dari method, URL, dan header. Setelah server mengirim response, koneksi selesai dan tidak ada jejak percakapan yang tersisa. Jika Budi memuat halaman A lalu halaman B, server tidak punya cara bawaan untuk mengetahui bahwa kedua request tersebut datang dari orang yang sama. Variabel PHP biasa pun tidak bisa menolong — setiap request dijalankan dalam proses yang baru, sehingga isi variabel dari request sebelumnya sudah lenyap.
Solusinya adalah memberi browser sebuah “kartu identitas” yang dikirim ulang pada setiap request. Itulah tugas cookie laravel-session: berisi session ID yang nilainya acak sepanjang 40 karakter. Server cukup mencocokkan ID tersebut dengan data yang tersimpan di storage-nya.
Penunjuk di Browser, Data di Server #
Arsitektur session membagi tanggung jawab menjadi dua:
| Komponen | Lokasi | Isi |
|---|---|---|
Cookie laravel-session | Browser pengguna | Session ID (40 karakter acak) |
| Data session | Server (tabel sessions) | nama, _token, _flash, dsb. |
Alur lengkapnya dari request pertama hingga request kedua dapat kita lihat berikut:
Request pertama datang tanpa cookie, jadi Laravel membuat session ID baru, menyimpan data ke server, lalu memasang cookie pada response. Request kedua membawa cookie tersebut, dan Laravel tinggal membaca data yang tersimpan. Browser tidak pernah menyimpan data session miliknya — hanya penunjuknya.
Migrasi Tabel sessions #
Karena sandbox kita memakai SESSION_DRIVER=database, setiap session tinggal di tabel sessions. Laravel sudah menyediakan migrasinya secara bawaan di database/migrations/0001_01_01_000000_create_users_table.php:
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});Mari kita bedah kolom per kolom:
id— session ID yang sama dengan isi cookie. Primary key, bertipe string 40 karakter.user_id— kosong (nullable) selama pengguna belum login; terisi ID user begitu login.ip_addressdanuser_agent— catatan alamat IP dan browser pemilik session, bahan mentah fitur “perangkat aktif”.payload— seluruh data session dalam format JSON yang di-base64-encode.last_activity— timestamp aktivitas terakhir, dipakai untuk mendeteksi session yang kedaluwarsa.
Membuktikan Isi Payload #
Teori di atas bisa kita buktikan langsung. Route berikut menyimpan satu nilai ke session, lalu feature test memeriksa baris tabel yang dihasilkannya:
Route::get('/sesi/simpan', function (Request $request) {
$request->session()->put('nama', 'Budi');
return response('tersimpan');
});Feature test-nya membaca kembali tabel sessions setelah request pertama, lalu mensimulasikan request kedua dari browser dengan mengirim cookie session:
use Illuminate\Session\Middleware\StartSession;
public function test_session_tersimpan_di_server_dan_dibaca_lintas_request(): void
{
config(['session.driver' => 'database']);
$this->get('/sesi/simpan')->assertSee('tersimpan');
$row = DB::table('sessions')->first();
fwrite(STDERR, 'sessions_table: kolom=' . implode(',', array_keys((array) $row))
. ' jumlah_baris=' . DB::table('sessions')->count() . "\n");
fwrite(STDERR, 'cookie_name: ' . config('session.cookie') . "\n");
fwrite(STDERR, 'user_id: ' . var_export($row->user_id, true) . ' (belum login)' . "\n");
$payload = json_decode((string) base64_decode($row->payload), true);
unset($payload['_token']); // token CSRF bernilai acak, disingkirkan agar output deterministik
fwrite(STDERR, 'payload_decoded: ' . json_encode($payload) . "\n");
$this->mulaiRequestBaru($row->id);
$this->get('/sesi/baca')->assertSee('nama=Budi');
fwrite(STDERR, 'request_kedua: baris_tetap=' . DB::table('sessions')->count()
. ' (data benar-benar dibaca dari tabel sessions)' . "\n");
}Method mulaiRequestBaru() membuang instance session dari container sehingga request berikutnya memuat ulang data dari storage — persis seperti proses PHP baru yang melayani request browser sungguhan:
private function mulaiRequestBaru(string $idSesi): void
{
$this->app->forgetInstance('session');
$this->app->forgetInstance('session.store');
$this->app->forgetInstance(StartSession::class);
$this->withCookie('laravel-session', $idSesi);
}Output yang Dihasilkan:
sessions_table: kolom=id,user_id,ip_address,user_agent,payload,last_activity jumlah_baris=1
cookie_name: laravel-session
user_id: NULL (belum login)
payload_decoded: {"nama":"Budi","_previous":{"url":"http:\/\/localhost:8000\/sesi\/simpan","route":null},"_flash":{"old":[],"new":[]}}
request_kedua: baris_tetap=1 (data benar-benar dibaca dari tabel sessions)Output tersebut memperlihatkan isi session secara nyata. Nilai nama yang kita simpan hidup di dalam payload bersama tiga kunci internal: _token milik proteksi CSRF (disembunyikan di output karena nilainya acak), _previous yang mencatat URL terakhir, dan _flash yang menjadi rumah data sekali pakai. Request kedua lalu membuktikan rantai lengkapnya: cookie dikirim, Laravel mencocokkan ID-nya dengan tabel, dan nama=Budi ditemukan tanpa membuat baris baru.
Flash Data vs Data Reguler #
Struktur _flash di payload menarik perhatian karena di situlah Laravel membedakan dua jenis data session. Data reguler seperti nama atau keranjang bertahan selama session hidup — bagus untuk identitas dan preferensi. Flash data hanya bertahan satu request berikutnya — bagus untuk pesan konfirmasi setelah redirect, yang akan kita bahas tuntas di chapter Flash Messages
.
Kedua jenis data tersebut tersimpan di tempat yang sama, di dalam satu payload. Yang membedakan hanyalah pencatatan namanya di array _flash.new dan _flash.old. Mesin penuangannya — yang menghapus flash data tepat setelah satu request — akan kita saksikan bekerja di chapter tersebut.
Dengan konsep ini, pertanyaan berikutnya menjadi praktis: di mana sebaiknya data itu disimpan — file, database, Redis? Pilihan tersebut adalah tugas session driver, yang kita bandingkan satu per satu di chapter Session Drivers . Selengkapnya juga tersedia di dokumentasi Session .