Setiap cookie yang terpasang di browser pengguna bermula dari satu baris header Set-Cookie pada respons yang Anda kirim. Artinya, mengonfigurasi cookie adalah pekerjaan di level respons — persis seperti mengatur status code atau Content-Type yang sudah kita bahas dua chapter sebelumnya. Teori lengkap tiap opsi cookie (Secure, HttpOnly, SameSite, dan teman-temannya) sudah kita bedah pada chapter cookie options
di section HTTP state. Fokus chapter ini adalah sisi mesinnya di PHP: cara kerja bentuk array setcookie(), perhitungan waktu kedaluwarsa, pola penghapusan cookie yang benar, pengaruh path dan domain terhadap cakupan cookie, pengiriman banyak cookie sekaligus, dan konfigurasi cookie yang dipasang otomatis oleh session_start().
Opsi Lengkap pada setcookie() #
Sejak PHP 7.3, setcookie() menerima argumen kedua berupa array asosiatif yang jauh lebih mudah dibaca daripada rantai parameter posisi. Setiap kunci array menjadi satu opsi cookie:
<?php
$berhasil = setcookie("tema", "gelap", [
"expires" => 0,
"path" => "/",
"httponly" => true,
"samesite" => "Lax",
]);
var_dump($berhasil);
var_dump(headers_list());Output yang Dihasilkan:
bool(true)
array(0) {
}Mari kita bedah contoh di atas:
- Baris
setcookie(...): fungsi mengembalikanbool(true)karena pemanggilan dilakukan sebelum output apa pun — syarat yang sama denganheader()pada chapter mengirim HTTP headers . "expires" => 0: nilai n berarti cookie hidup selama browser terbuka (session cookie); begitu browser ditutup, cookie lenyap sendiri.var_dump(headers_list()): mengembalikan array kosong di CLI karena SAPI CLI membuang header yang dipasang — sama seperti perilakuheaders_list()pada chapter tersebut. Di SAPI web, headerSet-Cookiebenar-benar terkirim; kita akan membuktikannya dengan server sungguhan di bagian akhir chapter.
Nilai "expires" yang tidak nol menuntut perhitungan waktu. Bagaimana cara menghitungnya dengan benar? Mari kita bahas khusus.
Menghitung Waktu Kedaluwarsa #
Pola yang paling sering Anda temui di kode nyata adalah time() + 3600 untuk “satu jam dari sekarang”. Pola tersebut bekerja karena expires menerima Unix timestamp — jumlah detik sejak 1 Januari 1970. Contoh berikut memakai waktu beku agar hasilnya bisa ditampilkan secara deterministik:
<?php
$sekarang = new DateTimeImmutable("2026-01-15 08:00:00", new DateTimeZone("Asia/Jakarta"));
$jamKeDepan = $sekarang->modify("+1 hour");
printf("sekarang : %s (epoch %d)\n", $sekarang->format("Y-m-d H:i:s T"), $sekarang->getTimestamp());
printf("+1 jam : %s (epoch %d)\n", $jamKeDepan->format("Y-m-d H:i:s T"), $jamKeDepan->getTimestamp());
printf("selisih : %d detik\n", $jamKeDepan->getTimestamp() - $sekarang->getTimestamp());Output yang Dihasilkan:
sekarang : 2026-01-15 08:00:00 WIB (epoch 1768438800)
+1 jam : 2026-01-15 09:00:00 WIB (epoch 1768442400)
selisih : 3600 detikTiga baris output menunjukkan hubungan yang perlu Anda pahami: getTimestamp() selalu menghasilkan epoch UTC (1768438800) walau objek dibuat dengan zona Asia/Jakarta, karena epoch memang tidak mengenal zona waktu. Selisih 3600 detik itulah yang menjadi umur cookie, sedangkan tanggal berformat 2026-01-15 08:00:00 WIB hanyalah tampilan objek. Saat menuliskan tanggal ke dalam header, format yang sah adalah format GMT seperti Thu, 15 Jan 2026 02:00:00 GMT — kita lakukan konversi dengan setTimezone(new DateTimeZone("UTC")) pada contoh berikut.
Membedah Header Set-Cookie #
setcookie() pada akhirnya membangun satu baris header dari seluruh opsi yang Anda berikan. Fungsi murni berikut meniru proses tersebut dan mengembalikan barisnya, sehingga seluruh keputusan konfigurasi bisa diuji tanpa server:
<?php
function headerSetCookie(string $nama, string $nilai, array $opsi = []): string
{
$sekarang = new DateTimeImmutable("2026-01-15 08:00:00", new DateTimeZone("Asia/Jakarta"));
$kedaluwarsa = $sekarang->modify("+1 hour")->setTimezone(new DateTimeZone("UTC"));
$header = rawurlencode($nama) . "=" . rawurlencode($nilai);
$header .= "; expires=" . $kedaluwarsa->format("D, d M Y H:i:s") . " GMT";
$header .= "; Max-Age=3600";
$header .= "; path=" . ($opsi["path"] ?? "/");
if (!empty($opsi["domain"])) {
$header .= "; domain=" . $opsi["domain"];
}
if (!empty($opsi["secure"])) {
$header .= "; Secure";
}
if (!empty($opsi["httponly"])) {
$header .= "; HttpOnly";
}
$header .= "; SameSite=" . ($opsi["samesite"] ?? "Lax");
return $header;
}
echo headerSetCookie("tema", "gelap", ["httponly" => true]) . "\n";
echo headerSetCookie("sesi", "abc123", [
"path" => "/admin",
"domain" => "toko.example.com",
"secure" => true,
"samesite" => "Strict",
]) . "\n";Output yang Dihasilkan:
tema=gelap; expires=Thu, 15 Jan 2026 02:00:00 GMT; Max-Age=3600; path=/; HttpOnly; SameSite=Lax
sesi=abc123; expires=Thu, 15 Jan 2026 02:00:00 GMT; Max-Age=3600; path=/admin; domain=toko.example.com; Secure; SameSite=StrictMari kita bedah fungsinya baris per baris:
- Baris
setTimezone(new DateTimeZone("UTC")): menggeser zona waktu objek ke UTC sebelum diformat, sehingga08:00WIB tercetak sebagai02:00GMT — format tanggal header HTTP memang selalu GMT. rawurlencode($nama) . "=" . rawurlencode($nilai): nama dan nilai cookie di-escape agar karakter seperti spasi atau;tidak merusak struktur header.expiresdanMax-Ageditulis berdampingan:expiresberupa tanggal mutlak (dipahami klien lawas),Max-Age=3600berupa umur relatif dalam detik (dipakai klien modern). Saat keduanya ada,Max-Ageyang menang — dan nilai relatif ini kebal salah zona waktu.- Rantai
ifuntukdomain,Secure, danHttpOnly: atribut hanya ditulis bila opsinya diisi, meniru perilakusetcookie()yang menghilangkan atribut kosong.
Menghapus Cookie dengan Waktu di Masa Lalu #
Cookie tidak bisa “dihapus” dari server secara langsung — server hanya bisa meminta browser menimpanya. Pola yang berlaku: kirim ulang cookie bernama sama dengan nilai kosong dan waktu kedaluwarsa di masa lalu:
<?php
function headerHapusCookie(string $nama, string $path = "/", string $domain = ""): string
{
$header = rawurlencode($nama) . "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0";
$header .= "; path=$path";
if ($domain !== "") {
$header .= "; domain=$domain";
}
return $header;
}
echo headerHapusCookie("tema") . "\n";
echo headerHapusCookie("sesi", "/admin", "toko.example.com") . "\n";Output yang Dihasilkan:
tema=; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; path=/
sesi=; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; path=/admin; domain=toko.example.comTanggal Thu, 01 Jan 1970 00:00:00 GMT adalah epoch nol — titik nol waktu Unix — dan Max-Age=0 memerintahkan kedaluwarsa seketika. Browser yang menerima header ini akan membuang cookie bernama sama, bukan menyimpan yang baru.
Cakupan path dan domain #
Opsi path dan domain menentukan cookie tidak dikirim ke semua permintaan — browser menyaringnya. Aturan pencocokan path mengikuti RFC 6265: cookie terkirim bila path permintaan sama dengan path cookie, atau path cookie adalah awalan yang diikuti karakter /:
<?php
function cookieTerkirimUntukPath(string $pathCookie, string $pathPermintaan): bool
{
if ($pathCookie === "/") {
return true;
}
if (!str_starts_with($pathPermintaan, $pathCookie)) {
return false;
}
$sisa = substr($pathPermintaan, strlen($pathCookie));
return $sisa === "" || $sisa[0] === "/";
}
function domainCookieCocok(string $hostPermintaan, string $domainCookie): bool
{
$hostPermintaan = strtolower($hostPermintaan);
$domainCookie = strtolower($domainCookie);
return $hostPermintaan === $domainCookie
|| str_ends_with($hostPermintaan, "." . $domainCookie);
}
$ujiPath = [
["/keranjang", "/keranjang"],
["/keranjang", "/keranjang/tambah"],
["/keranjang", "/keranjang-belanja"],
["/", "/admin/laporan"],
];
foreach ($ujiPath as [$pathCookie, $pathPermintaan]) {
$status = cookieTerkirimUntukPath($pathCookie, $pathPermintaan) ? "terkirim" : "tidak";
printf("path cookie %-12s permintaan %-18s => %s\n", $pathCookie, $pathPermintaan, $status);
}
$ujiDomain = [
["api.toko.example.com", "toko.example.com"],
["toko.example.com", "toko.example.com"],
["tokolain.example.org", "toko.example.com"],
];
foreach ($ujiDomain as [$host, $domain]) {
$status = domainCookieCocok($host, $domain) ? "cocok" : "tidak";
printf("host %-22s domain cookie %-18s => %s\n", $host, $domain, $status);
}Output yang Dihasilkan:
path cookie /keranjang permintaan /keranjang => terkirim
path cookie /keranjang permintaan /keranjang/tambah => terkirim
path cookie /keranjang permintaan /keranjang-belanja => tidak
path cookie / permintaan /admin/laporan => terkirim
host api.toko.example.com domain cookie toko.example.com => cocok
host toko.example.com domain cookie toko.example.com => cocok
host tokolain.example.org domain cookie toko.example.com => tidakDua baris pertama output membuktikan aturan awalan: /keranjang/tambah cocok karena sisa path (/tambah) diawali garis miring. Baris ketiga adalah jebakan klasik — /keranjang-belanja tidak cocok meski tampak seperti awalan, karena sisa -belanja diawali tanda hubung, bukan /. Sementara itu, pencocokan domain bekerja dua arah: cookie domain toko.example.com terkirim dari host itu sendiri maupun seluruh subdomainnya seperti api.toko.example.com — berguna untuk berbagi sesi login antar subdomain, sekaligus alasan cookie domain tidak boleh dipasang terlalu luas.
Mengirim Banyak Cookie Sekaligus #
Tidak ada header Set-Cookie gabungan — setiap cookie adalah baris headernya sendiri, dan respons boleh membawa baris Set-Cookie sebanyak yang diperlukan. Pola kumpulan berikut membangun seluruh baris dalam satu fungsi:
<?php
function kumpulanSetCookie(array $preferensi, string $path = "/"): array
{
$headers = [];
foreach ($preferensi as $nama => $nilai) {
$headers[] = "Set-Cookie: " . rawurlencode($nama) . "=" . rawurlencode($nilai)
. "; path=$path; HttpOnly; SameSite=Lax";
}
return $headers;
}
foreach (kumpulanSetCookie(["tema" => "gelap", "bahasa" => "id", "tampilan" => "kompak"]) as $header) {
echo $header . "\n";
}Output yang Dihasilkan:
Set-Cookie: tema=gelap; path=/; HttpOnly; SameSite=Lax
Set-Cookie: bahasa=id; path=/; HttpOnly; SameSite=Lax
Set-Cookie: tampilan=kompak; path=/; HttpOnly; SameSite=LaxSaat pengiriman dilakukan lewat header() alih-alih setcookie(), ingat pelajaran chapter mengirim HTTP headers
: parameter kedua harus false agar panggilan kedua menambah baris baru, bukan mengganti baris Set-Cookie sebelumnya. Fungsi setcookie() tidak menghadapi masalah ini karena setiap panggilannya otomatis menjadi baris tersendiri.
Konfigurasi Cookie untuk Session #
session_start() memasang cookie sesi (PHPSESSID) secara otomatis, dan seluruh perilakunya dikendalikan direktif php.ini yang bisa dibaca lewat ini_get():
<?php
foreach ([
"session.cookie_lifetime" => "umur cookie sesi dalam detik",
"session.cookie_path" => "path tempat cookie sesi dikirim",
"session.cookie_domain" => "domain cookie sesi",
"session.cookie_secure" => "kirim hanya lewat HTTPS",
"session.cookie_httponly" => "larang akses dari JavaScript",
"session.cookie_samesite" => "pelindung pengiriman lintas situs",
] as $kunci => $makna) {
printf("%-26s = %-4s ; %s\n", $kunci, var_export(ini_get($kunci), true), $makna);
}Output yang Dihasilkan:
session.cookie_lifetime = '0' ; umur cookie sesi dalam detik
session.cookie_path = '/' ; path tempat cookie sesi dikirim
session.cookie_domain = '' ; domain cookie sesi
session.cookie_secure = '0' ; kirim hanya lewat HTTPS
session.cookie_httponly = '0' ; larang akses dari JavaScript
session.cookie_samesite = '' ; pelindung pengiriman lintas situsNilai bawaan tersebut jelas tidak ideal untuk produksi: session.cookie_httponly masih '0', session.cookie_secure masih '0', dan session.cookie_samesite kosong. Aplikasi nyata sebaiknya menyetel ketiganya lebih ketat — baik lewat php.ini/.htaccess, maupun per-skrip sebelum session_start() memakai session_set_cookie_params():
<?php
session_set_cookie_params([
"lifetime" => 0,
"path" => "/",
"domain" => "toko.example.com",
"secure" => true,
"httponly" => true,
"samesite" => "Lax",
]);
$params = session_get_cookie_params();
foreach (["lifetime", "path", "domain", "secure", "httponly", "samesite"] as $kunci) {
printf("%-9s => %s\n", $kunci, var_export($params[$kunci], true));
}Output yang Dihasilkan:
lifetime => 0
path => '/'
domain => 'toko.example.com'
secure => true
httponly => true
samesite => 'Lax'Bentuk array session_set_cookie_params() (tersedia sejak PHP 7.3, sama seperti setcookie()) menyetel parameter cookie untuk session yang akan dimulai — panggilan harus dilakukan sebelum session_start(). Setelah itu, session_get_cookie_params() mengonfirmasi keenam nilai benar-benar berubah: secure dan httponly kini true, samesite terisi 'Lax'. Daftar lengkap direktif session beserta efeknya dibahas pada chapter session configuration
.
Menguji di Server Web Sungguhan #
Perilaku lengkap setcookie() baru terlihat di SAPI web. Server bawaan PHP cukup untuk membuktikannya:
<?php
// pasang-cookie.php — dari folder berkas ini, jalankan:
// php -S 127.0.0.1:8092
// lalu amati header responsnya:
// curl -i http://127.0.0.1:8092/pasang-cookie.php
setcookie("tema", "gelap", ["expires" => 0, "path" => "/", "httponly" => true, "samesite" => "Lax"]);
setcookie("bahasa", "id", ["expires" => 0, "path" => "/", "samesite" => "Lax"]);
echo "dua cookie terpasang\n";Saat kita jalankan curl -i terhadap skrip tersebut, respons berstatus HTTP/1.1 200 OK membawa tepat dua baris Set-Cookie: tema=gelap; path=/; HttpOnly; SameSite=Lax dan bahasa=id; path=/; SameSite=Lax — atributnya persis mengikuti opsi yang kita berikan, atribut kosong tidak ditulis. Perhatikan pula bahwa tidak ada expires maupun Max-Age pada keduanya karena expires => 0 memang tidak menghasilkan atribut kedaluwarsa. Matikan server setelah selesai dengan Ctrl+C atau pkill -f "php -S".
Ringkasan #
setcookie() bentuk-array menerjemahkan opsi menjadi satu baris header Set-Cookie: expires dihitung sebagai Unix timestamp (dengan Max-Age sebagai padanan relatif yang lebih tahan salah zona waktu), path dan domain mempersempit cookie sesuai aturan pencocokan awalan path dan subdomain, dan penghapusan cookie menuntut nilai kosong plus kedaluwarsa masa lalu dengan path/domain yang persis sama. Cookie sesi yang dipasang session_start() dikendalikan direktif session.cookie_* yang sebaiknya diperketat lewat session_set_cookie_params(). Setelah cookie, pertanyaan berikutnya adalah seberapa lama respons itu sendiri boleh disimpan oleh klien — jawabannya ada di header Cache-Control, topik chapter berikutnya.
Referensi #
- Dokumentasi
setcookie() - Dokumentasi
session_set_cookie_params() - Direktif konfigurasi session
- Session di PHP
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.php
Unduh berkas<?php
// contoh.php — ringkasan konfigurasi cookie dari sisi respons.
// Semua fungsi murni mengembalikan string header sehingga seluruh
// keputusan konfigurasi bisa diperiksa tanpa server web.
function headerSetCookie(string $nama, string $nilai, array $opsi = []): string
{
$sekarang = new DateTimeImmutable("2026-01-15 08:00:00", new DateTimeZone("Asia/Jakarta"));
$kedaluwarsa = $sekarang->modify("+1 hour")->setTimezone(new DateTimeZone("UTC"));
$header = rawurlencode($nama) . "=" . rawurlencode($nilai);
$header .= "; expires=" . $kedaluwarsa->format("D, d M Y H:i:s") . " GMT";
$header .= "; Max-Age=3600";
$header .= "; path=" . ($opsi["path"] ?? "/");
if (!empty($opsi["domain"])) {
$header .= "; domain=" . $opsi["domain"];
}
if (!empty($opsi["secure"])) {
$header .= "; Secure";
}
if (!empty($opsi["httponly"])) {
$header .= "; HttpOnly";
}
$header .= "; SameSite=" . ($opsi["samesite"] ?? "Lax");
return $header;
}
function headerHapusCookie(string $nama, string $path = "/", string $domain = ""): string
{
$header = rawurlencode($nama) . "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0";
$header .= "; path=$path";
if ($domain !== "") {
$header .= "; domain=$domain";
}
return $header;
}
echo "=== Cookie preferensi (HttpOnly, SameSite=Lax) ===\n";
echo headerSetCookie("tema", "gelap", ["httponly" => true]) . "\n";
echo "=== Cookie sesi admin (Secure, SameSite=Strict) ===\n";
echo headerSetCookie("sesi", "abc123", [
"path" => "/admin",
"domain" => "toko.example.com",
"secure" => true,
"samesite" => "Strict",
]) . "\n";
echo "=== Menghapus cookie dengan path/domain yang sama ===\n";
echo headerHapusCookie("sesi", "/admin", "toko.example.com") . "\n";
echo "=== Parameter cookie sesi yang akan dipakai session_start() ===\n";
session_set_cookie_params([
"lifetime" => 0,
"path" => "/",
"domain" => "toko.example.com",
"secure" => true,
"httponly" => true,
"samesite" => "Lax",
]);
$params = session_get_cookie_params();
foreach (["lifetime", "path", "domain", "secure", "httponly", "samesite"] as $kunci) {
printf("%-9s => %s\n", $kunci, var_export($params[$kunci], true));
}