Setelah pengguna mengirim form, aplikasi biasanya ingin menampilkan konfirmasi: “Produk berhasil ditambahkan” atau “Password salah”. Masalahnya, pola Post/Redirect/Get mengharuskan handler POST mengirim redirect, bukan halaman — sehingga pesan yang di-echo langsung akan ik terbuang bersama response yang diabaikan browser. Flash messages menyelesaikan masalah ini dengan meminjam daya ingat session: pesan disimpan di server saat POST, lalu dibaca dan dihapus otomatis pada request GET berikutnya. Chapter ini membahas pola dasarnya, pembuatan helper class yang rapi, hingga konvensi penampilannya di halaman.
Masalah Pesan yang Hilang Setelah Redirect #
Mari kita ingat alur Post/Redirect/Get (PRG) dari chapter Post/Redirect/Get
: request POST diproses, lalu server merespons 302 Redirect agar browser mengambil halaman hasil lewat GET. Konsekuensinya, apa pun yang dicetak pada response POST tidak akan pernah dilihat pengguna, karena browser langsung menindih response tersebut dengan request GET baru. Variabel biasa juga tidak bisa menyimpan pesan antar-request karena setiap request PHP berjalan dari nol.
Session menjadi tempat penyimpanan yang tepat karena isinya bertahan antar-request dan hanya bisa ditulis dari sisi server. Alurnya terdiri dari tiga langkah: handler POST menyimpan pesan ke $_SESSION, redirect dikirim, lalu halaman GET membaca pesan tersebut dan sekaligus menghapusnya. Karena pesan dihapus pada pembacaan pertama, pesan itu muncul tepat satu kali — itulah asal nama flash, seperti kilatan cahaya yang muncul sekali lalu lenyap.
Pola Dasar Simpan, Tampilkan, Lalu Hapus #
Bentuk paling sederhana flash message hanya memakai satu kunci session. Simpan string pesan pada request pertama, lalu pada request berikutnya baca nilainya dan panggil unset() pada kunci yang sama. Contoh berikut meniru dua request tersebut dalam satu proses — persis seperti pola yang kita pakai di chapter Sessions
:
<?php
$dir = sys_get_temp_dir() . '/php-kursus-sesi';
if (!is_dir($dir)) {
mkdir($dir);
}
session_save_path($dir);
session_id('demo-flash-dasar');
@unlink($dir . '/sess_demo-flash-dasar');
// "Request POST": handler menyimpan pesan sebelum redirect
session_start();
$_SESSION['flash'] = 'Produk berhasil ditambahkan ke keranjang';
session_write_close();
// "Request GET" setelah redirect: baca lalu hapus
session_start();
$pesan = $_SESSION['flash'] ?? null;
unset($_SESSION['flash']);
echo "Request setelah redirect: $pesan\n";
echo 'Dibaca ulang: ' . var_export($_SESSION['flash'] ?? null, true) . "\n";Output yang Dihasilkan:
Request setelah redirect: Produk berhasil ditambahkan ke keranjang
Dibaca ulang: NULLMari kita telaah baris-baris kode di atas:
- Baris 9-12 (request POST):
session_start()membuka session, pesan disimpan ke$_SESSION['flash'], dansession_write_close()menuliskannya ke penyimpanan server — pada aplikasi nyata, di titik inilahheader('Location: ...')dikirim. - Baris 15-18 (request GET): session dimuat kembali, pesan dibaca dengan operator
??agar tidak memicu warning saat kunci tidak ada, laluunset()menghapus kunci tersebut. - Baris 20: pembacaan kedua menghasilkan
NULL, membuktikan pesan benar-benar sudah terhapus dan tidak akan muncul lagi pada request berikutnya.
Hapus di baris 17 adalah jantung pola ini. Jika kunci tidak dihapus, pesan akan terus muncul di setiap halaman yang dikunjungi pengguna selanjutnya — bug klasik yang membuat notifikasi “berhantu”.
Helper Class Flash untuk Penggunaan Berulang #
Menulis pola simpan-baca-hapus secara manual di banyak halaman cepat menjadi repetitif dan rawan lupa unset(). Solusinya adalah membungkus pola tersebut menjadi helper class dengan tiga method: set() untuk menyimpan, get() untuk mengambil pesan satu jenis, dan all() untuk mengambil semuanya sekaligus. Setiap method pembacaan sekaligus membersihkan data yang dibacanya.
<?php
$dir = sys_get_temp_dir() . '/php-kursus-sesi';
if (!is_dir($dir)) {
mkdir($dir);
}
session_save_path($dir);
session_id('demo-flash-class');
@unlink($dir . '/sess_demo-flash-class');
class Flash
{
public static function set(string $type, string $message): void
{
$_SESSION['flash'][$type][] = $message;
}
public static function get(string $type): array
{
$messages = $_SESSION['flash'][$type] ?? [];
unset($_SESSION['flash'][$type]);
return $messages;
}
public static function all(): array
{
$messages = $_SESSION['flash'] ?? [];
unset($_SESSION['flash']);
return $messages;
}
}
$log = [];
// "Request POST": handler form menyimpan beberapa pesan sekaligus
session_start();
Flash::set('success', 'Produk berhasil ditambahkan ke keranjang');
Flash::set('error', 'Stok produk tidak mencukupi');
Flash::set('info', 'Promo gratis ongkir berlaku hari ini');
session_write_close();
// "Request GET" setelah redirect: tampilkan semua pesan
session_start();
foreach (Flash::all() as $type => $messages) {
foreach ($messages as $message) {
$log[] = "[$type] $message";
}
}
$log[] = 'Pesan tersisa setelah ditampilkan: ' . count($_SESSION['flash'] ?? []);
session_write_close();
// "Request GET" berikutnya: pesan sudah menghapus dirinya sendiri
session_start();
$log[] = 'Request berikutnya: ' . count(Flash::all()) . ' pesan';
session_write_close();
echo implode("\n", $log), "\n";Output yang Dihasilkan:
[success] Produk berhasil ditambahkan ke keranjang
[error] Stok produk tidak mencukupi
[info] Promo gratis ongkir berlaku hari ini
Pesan tersisa setelah ditampilkan: 0
Request berikutnya: 0 pesanStruktur datanya penting diperhatikan: $_SESSION['flash'] adalah array dua tingkat — tingkat pertama adalah jenis pesan (success, error, info), tingkat kedua adalah daftar pesannya. Mari kita bedah class di atas:
Flash::set(): menambahkan pesan ke akhir array jenisnya dengan operator[], sehingga beberapa pesan sejenis dapat menumpuk dalam satu request.Flash::get(string $type): mengambil salinan daftar pesan untuk satu jenis, menghapus jenis tersebut dari session, lalu mengembalikan salinan itu ke pemanggil.Flash::all(): sama sepertiget(), tetapi mengambil seluruh jenis sekaligus dan mengosongkan seluruh keranjangflash— cocok dipanggil satu kali di layout halaman.- Baris 36-39: satu request POST menyimpan tiga pesan berbeda jenis; tampilan
[$type]di depan tiap baris hanya untuk keperluan demo CLI.
Hasil eksekusi membuktikan dua properti penting: semua pesan muncul pada request pertama setelah redirect, dan Pesan tersisa: 0 menunjukkan keranjang bersih setelah all() dipanggil. Request ketiga memperoleh 0 pesan karena session memang sudah kosong.
Jenis Pesan dan Konvensi Penampilan #
Jenis pesan memetakan maksud pesan ke gaya tampilan. Tiga jenis yang paling umum dipakai beserta konvensinya:
| Jenis | Makna | Contoh | Konvensi kelas CSS |
|---|---|---|---|
success | operasi berhasil | “Produk berhasil ditambahkan” | alert alert-success (hijau) |
error | operasi gagal atau validasi tidak lolos | “Stok tidak mencukupi” | alert alert-danger (merah) |
info | informasi netral | “Promo berlaku hari ini” | alert alert-info (biru) |
Konvensi nama alert alert-* mengikuti kelas yang lazim dipakai framework CSS seperti Bootstrap, sehingga desainer dan framework saling berbicara dalam bahasa yang sama. Fungsi berikut mengubah struktur $_SESSION['flash'] menjadi HTML yang siap dicetak di layout:
<?php
function renderFlash(array $semuaPesan): string
{
$html = '';
foreach ($semuaPesan as $type => $messages) {
$class = match ($type) {
'success' => 'alert alert-success',
'error' => 'alert alert-danger',
default => 'alert alert-info',
};
$items = '';
foreach ($messages as $message) {
$items .= '<li>' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</li>';
}
$html .= "<ul class=\"$class\">$items</ul>\n";
}
return $html;
}
echo renderFlash([
'success' => ['Produk berhasil ditambahkan ke keranjang'],
'error' => ['Stok produk tidak mencukupi', 'Kode promo kedaluwarsa'],
]);Output yang Dihasilkan:
<ul class="alert alert-success"><li>Produk berhasil ditambahkan ke keranjang</li></ul>
<ul class="alert alert-danger"><li>Stok produk tidak mencukupi</li><li>Kode promo kedaluwarsa</li></ul>Ekspresi match memetakan jenis pesan ke kelas CSS, dengan cabang default yang menampung jenis tak dikenal sebagai info. Fungsi mengembalikan string alih-alih langsung echo agar mudah diuji dan agar pemanggil yang memutuskan di bagian halaman pesan dicetak. Perhatikan pemanggilan htmlspecialchars() pada isi pesan: pesan flash sering berisi input pengguna (misalnya nama produk), dan tanpa escaping pesan tersebut menjadi pintu masuk serangan XSS yang akan kita bahas tuntas di section Web Security.
Flash Messages dalam Alur Post/Redirect/Get #
Untuk melihat posisi flash message di alur nyata, berikut kerangka handler POST lengkap dengan redirect-nya. Blok ini membutuhkan web server sungguhan untuk diamati (header Location dan cookie tidak bekerja di CLI), jadi hasilnya kita pahami lewat alurnya, bukan eksekusi langsung:
<?php
session_start();
// ... validasi input dan simpan produk ke database ...
// Simpan pesan sekali-pakai SEBELUM redirect
Flash::set('success', 'Produk berhasil ditambahkan ke keranjang');
header('Location: /keranjang');
exit;Urutannya krusial: Flash::set() harus dipanggil sebelum header(), karena penulisan ke $_SESSION dan pengiriman header sama-sama menuntut belum ada output apa pun. Kemudian di sisi halaman /keranjang, layout memanggil Flash::all() dan renderFlash() di dekat bagian atas halaman — pesan tampil, session dibersihkan, dan bila pengguna menekan F5 pesan tidak muncul lagi karena sudah lenyap. Perilaku inilah yang membuat flash messages begitu cocok dengan PRG: notifikasi tampil tepat satu kali tanpa risiko duplikasi submit.
Ringkasan #
Flash message adalah pesan sekali-pakai yang disimpan ke $_SESSION sebelum redirect dan dihapus pada pembacaan pertamanya — pola simpan, tampilkan, lalu hapus. Helper class Flash dengan method set(), get(), dan all() merapikan pola tersebut agar aman dipakai berulang, sementara pemetaan jenis success/error/info ke kelas CSS memberi konvensi tampilan yang konsisten. Dengan bekal ini, kita siap membahas pola ingatan jangka panjang lintas penutupan browser pada chapter Remember-Me Patterns.