許多開發者發現:setcookie('name', '') 或 setcookie('name', null) 竟然能刪除 Cookie,而官方文檔建議設定過期時間為過去。這背後是什麼機制?
關鍵檔案:ext/standard/head.c → php_setcookie()
// 簡化版核心邏輯
PHPAPI int php_setcookie(char *name, int name_len, char *value, int value_len, time_t expires, ...) {
// ... 參數解析 ...
if (value && value_len == 0) { // ★ 關鍵判斷:值存在但長度為 0
// 空字串情況:強制產生刪除 Cookie
dt = php_format_date("D, d-M-Y H:i:s T", ..., 1, 0); // 時間戳 1 = 1970-01-01 00:00:01 GMT
snprintf(cookie, len + 100, "Set-Cookie: %s=deleted; expires=%s", name, dt);
efree(dt);
} else {
// 正常設定 Cookie
snprintf(cookie, len + 100, "Set-Cookie: %s=%s", name, value ? encoded_value : "");
if (expires > 0) {
strlcat(cookie, "; expires=", len + 100);
dt = php_format_date(..., expires, ...);
strlcat(cookie, dt, len + 100);
}
}
// ... 發送 header ...
}
| 呼叫方式 | 內部處理 | 生成 Header 範例 | 備註 |
|---|---|---|---|
setcookie('name', '') | value="", value_len=0 | Set-Cookie: name=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT | 最常用、語意清晰 |
setcookie('name', null) | PHP 內部 zend_parse_parameters 將 null 轉為空字串 | 同上 | 等同空字串 |
setcookie('name', 'x', time()-3600) | expires 為過去時間 | Set-Cookie: name=x; expires=Wed, 17-Jan-2024 10:00:00 GMT | 傳統寫法,需計算時間 |
註解原文:MSIE doesn't delete a cookie when you set it to a null value so in order to force cookies to be deleted, even on MSIE, we pick an expiry date in the past
早期 IE 不支援 Set-Cookie: name=(無值)刪除,PHP 為了跨瀏覽器一致性,**主動攔截空值並注入過期時間**,確保行為統一。
<?php
// test_cookie.php
header('Content-Type: text/plain');
// 1. 設定 Cookie
setcookie('test_empty', '', time() + 3600); // 預期:設定一個空值 Cookie
setcookie('test_null', null, time() + 3600); // 預期:同空值
setcookie('test_normal', 'value', time() + 3600);
// 2. 觀察響應 Header(瀏覽器開發者工具 Network 標籤)
// 實際發送:
// Set-Cookie: test_empty=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0
// Set-Cookie: test_null=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0
// Set-Cookie: test_normal=value; expires=Fri, 18-Jan-2025 12:00:00 GMT
// 3. 驗證 $_COOKIE 下次請求
// test_empty、test_null 不會出現在 $_COOKIE 中(已被瀏覽器刪除)
// test_normal 會出現
?>
<?php
function deleteCookie(string $name, string $path = '/', string $domain = '', bool $secure = false, bool $httponly = true): void {
setcookie($name, '', [
'expires' => time() - 3600, // 明確過期
'path' => $path,
'domain' => $domain ?: '',
'secure' => $secure,
'httponly' => $httponly,
'samesite' => 'Lax',
]);
}
// PHP 7.3+ 陣列語法
deleteCookie('session_id');
deleteCookie('remember_token', '/', '.example.com');
<?php
function clearAuthCookies(): void {
$cookies = ['user_id', 'session_token', 'remember_me', 'csrf_token'];
foreach ($cookies as $name) {
deleteCookie($name);
}
// 如果用 session
session_destroy();
}
?>
| 誤區 | 真相 |
|---|---|
setcookie('name', false) 刪除 | false 轉字串為 "",同空值刪除 |
setcookie('name', 0) 刪除 | 0 轉字串為 "0",**不會刪除**,會設定值為 "0" |
只設定名稱 setcookie('name') | 報錯:缺少參數 2 |
刪除後立即 $_COOKIE['name'] 為空 | $_COOKIE 只反映**當前請求**的 Cookie,下一次請求才會消失 |
<?php
// 現代寫法(PHP 7.3+)
setcookie('auth', '', [
'expires' => time() - 3600,
'path' => '/',
'domain' => '.example.com',
'secure' => true, // 僅 HTTPS
'httponly' => true, // 防 XSS
'samesite' => 'Strict', // 防 CSRF
]);
?>
setcookie($name, '') 能刪除 Cookie 是 PHP 內核為了相容舊版 IE 故意設計的「特性」,實際上注入了 expires=1970-01-01 與 value=deleted。雖然好用,但**生產代碼建議顯式設定過期時間與屬性**,確保語意明確、跨瀏覽器行為一致、安全屬性完整傳遞。