$_COOKIE — Cookie 变量
概述
$_COOKIE 是 PHP 中用于访问 HTTP Cookie 数据的超全局变量。Cookie 是服务器通过 HTTP 响应头发送到客户端浏览器的一小段数据,浏览器会在后续请求中自动将其包含在请求头中。$_COOKIE 的工作是读取这些来自客户端的 Cookie 数据,而设置 Cookie 则需要通过 setcookie() 或 header() 函数完成。
前置知识
在阅读本节之前,你需要了解:
- HTTP 协议中 Cookie 的工作机制(Set-Cookie 响应头 / Cookie 请求头)
setcookie()函数的基本用法- Cookie 的属性:名称、值、过期时间、路径、域名、安全标志
- 跨站脚本攻击(XSS)和跨站请求伪造(CSRF)的基本概念
基础概念
Cookie 的工作流程
1. 客户端首次访问
浏览器 ─── GET / ───→ 服务器
浏览器 ←── Set-Cookie: theme=dark; expires=...; path=/ ── 服务器
2. 浏览器保存 Cookie
浏览器内部存储: theme=dark
3. 客户端后续请求
浏览器 ─── GET /page ───→ 服务器
Cookie: theme=dark
浏览器 ←── 页面内容 ── 服务器
4. PHP 读取 Cookie
$_COOKIE['theme'] = 'dark'$_COOKIE 与 setcookie() 的关系
$_COOKIE:只读当前请求中客户端发送的 Cookiesetcookie():向客户端发送 Set-Cookie 响应头,在下一次请求才能通过$_COOKIE读取
请求 1: setcookie('name', 'Alice', ...)
→ 响应: Set-Cookie: name=Alice
→ $_COOKIE['name'] 此时还不存在!
请求 2: 浏览器自动携带 Cookie: name=Alice
→ $_COOKIE['name'] = 'Alice' ← 此时才能读到常见误区
setcookie() 设置的 Cookie 不能在当前请求的 $_COOKIE 中立即读取。Cookie 只能在下一次请求中被访问。这是 Cookie 工作机制的本质,而非 PHP 的限制。
Cookie 属性一览
| 属性 | 说明 | 示例 |
|---|---|---|
| name | Cookie 名称 | 'theme' |
| value | Cookie 值 | 'dark' |
| expires | 过期时间戳(Unix 时间戳) | time() + 86400 |
| path | 作用路径 | '/' |
| domain | 作用域名 | '.example.com' |
| secure | 仅 HTTPS 传输 | true |
| httponly | 禁止 JS 访问 | true |
| samesite | 跨站策略(PHP 7.3+) | 'Lax' |
语法与代码
基本 Cookie 操作
php
<?php
declare(strict_types=1);
// 设置 Cookie(8.0+ 使用命名参数,更清晰)
setcookie(
name: 'username',
value: 'alice',
expires_or_options: time() + 86400, // 24 小时后过期
path: '/',
domain: 'example.com',
secure: true, // 仅 HTTPS
httponly: true, // 禁止 JS 访问
samesite: 'Lax'
);
// 读取 Cookie
$username = $_COOKIE['username'] ?? 'Guest';
echo "欢迎回来, {$username}!";
// 删除 Cookie(将过期时间设为过去)
setcookie(
name: 'username',
value: '',
expires_or_options: time() - 3600,
path: '/'
);
unset($_COOKIE['username']);Cookie 安全设置工具类
php
<?php
declare(strict_types=1);
/**
* 安全的 Cookie 管理类
* 统一管理 Cookie 的设置、读取和删除
*/
class CookieManager
{
private readonly int $defaultTtl;
private readonly string $path;
private readonly string $domain;
private readonly bool $secure;
private readonly bool $httponly;
private readonly string $samesite;
public function __construct(
int $defaultTtl = 86400, // 24 小时
string $path = '/',
string $domain = '',
bool $secure = true,
bool $httponly = true,
string $samesite = 'Lax'
) {
$this->defaultTtl = $defaultTtl;
$this->path = $path;
$this->domain = $domain;
$this->secure = $secure;
$this->httponly = $httponly;
$this->samesite = $samesite;
}
/**
* 设置 Cookie
*/
public function set(string $name, string $value, ?int $ttl = null): bool
{
$expires = $ttl !== null
? time() + $ttl
: time() + $this->defaultTtl;
return setcookie(
name: $name,
value: $this->encrypt($value),
expires_or_options: [
'expires' => $expires,
'path' => $this->path,
'domain' => $this->domain,
'secure' => $this->secure,
'httponly' => $this->httponly,
'samesite' => $this->samesite,
]
);
}
/**
* 获取 Cookie
*/
public function get(string $name, string $default = ''): string
{
if (!isset($_COOKIE[$name])) {
return $default;
}
return $this->decrypt($_COOKIE[$name]);
}
/**
* 检查 Cookie 是否存在
*/
public function has(string $name): bool
{
return isset($_COOKIE[$name]);
}
/**
* 删除 Cookie
*/
public function delete(string $name): void
{
if (isset($_COOKIE[$name])) {
unset($_COOKIE[$name]);
}
setcookie(
name: $name,
value: '',
expires_or_options: [
'expires' => time() - 3600,
'path' => $this->path,
'domain' => $this->domain,
'secure' => $this->secure,
'httponly' => $this->httponly,
'samesite' => $this->samesite,
]
);
}
/**
* 简单的值加密(防止用户篡改 Cookie)
*/
private function encrypt(string $value): string
{
// 实际项目中应使用更安全的加密方式
// 这里使用 base64 编码作为示例
return base64_encode($value);
}
private function decrypt(string $value): string
{
$decoded = base64_decode($value, true);
return $decoded !== false ? $decoded : '';
}
}
// 使用示例
$cookie = new CookieManager(
secure: true,
httponly: true,
samesite: 'Strict'
);
// 设置 Cookie
$cookie->set('theme', 'dark', 3600); // 1 小时
$cookie->set('lang', 'zh-CN'); // 24 小时(默认)
// 读取 Cookie
$theme = $cookie->get('theme', 'light');
$lang = $cookie->get('lang', 'en');
echo "主题: {$theme}, 语言: {$lang}";
// 删除 Cookie
$cookie->delete('theme');记住我功能实现
php
<?php
declare(strict_types=1);
/**
* "记住我"功能的 Cookie 管理器
*/
class RememberMe
{
private const COOKIE_NAME = 'remember_token';
private const TOKEN_LENGTH = 32;
public function __construct(
private readonly string $secretKey = 'your-secret-key-here'
) {}
/**
* 生成并设置记住我 Cookie
*/
public function setToken(int $userId): string
{
$token = bin2hex(random_bytes(self::TOKEN_LENGTH));
// 将 token 存储到数据库(实际项目中)
// $this->storeToken($userId, $token);
// 设置 Cookie(30 天有效)
$this->setCookie($token, 30 * 86400);
return $token;
}
/**
* 验证记住我 Cookie
*/
public function verify(): ?int
{
$token = $_COOKIE[self::COOKIE_NAME] ?? null;
if ($token === null || !ctype_xdigit($token)) {
return null;
}
// 从数据库查找 token 对应的用户
// $userId = $this->findUserByToken($token);
$userId = 1; // 模拟
if ($userId !== null) {
// 重新生成 token 防止重放攻击
$this->setToken($userId);
}
return $userId;
}
/**
* 清除记住我 Cookie
*/
public function clear(): void
{
setcookie(
name: self::COOKIE_NAME,
value: '',
expires_or_options: time() - 3600,
path: '/',
secure: true,
httponly: true,
samesite: 'Lax'
);
unset($_COOKIE[self::COOKIE_NAME]);
}
private function setCookie(string $token, int $ttl): void
{
setcookie(
name: self::COOKIE_NAME,
value: $token,
expires_or_options: [
'expires' => time() + $ttl,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]
);
}
}详细说明
Cookie 的作用域
Path 属性
Cookie 的 path 属性决定了哪些 URL 路径下的请求会携带该 Cookie:
Cookie: name=value; path=/app
URL: /app/page1 → 携带 ✓
URL: /app/page2 → 携带 ✓
URL: /app/admin/ → 携带 ✓
URL: /other/page → 不携带 ✗
URL: / → 不携带 ✗php
<?php
declare(strict_types=1);
// 整站可用的 Cookie
setcookie('site_lang', 'zh', path: '/');
// 仅管理后台可用的 Cookie
setcookie('admin_token', 'xyz', path: '/admin');
// 仅特定页面可用的 Cookie
setcookie('page_config', 'dark', path: '/settings');Domain 属性
Cookie 的 domain 属性决定了哪些域名下的请求会携带该 Cookie:
Cookie: name=value; domain=.example.com
example.com → 携带 ✓
www.example.com → 携带 ✓
api.example.com → 携带 ✓
sub.other.example.com → 不携带 ✗
evil.com → 不携带 ✗域名前导点
历史上域名前导点(.example.com)表示包含所有子域名。RFC 6265 已弃用此前导点,现代浏览器不再需要它。但为了兼容旧浏览器,PHP 仍会自动添加。
Cookie 大小限制
- 单个 Cookie 大小限制:约 4096 字节(4KB)
- 单个域名下的 Cookie 数量限制:约 50 个
- 浏览器总 Cookie 数量限制:约 300 个
SameSite 属性(PHP 7.3+)
| 值 | 说明 | 适用场景 |
|---|---|---|
Strict | 完全禁止跨站发送 Cookie | 高安全要求(银行、支付) |
Lax | 跨站链接导航允许发送 Cookie,禁止跨站 POST | 推荐,大多数网站使用 |
None | 允许跨站发送(需配合 Secure) | 需要跨站集成(如嵌入第三方) |
注意事项
1. 不要在 Cookie 中存储敏感信息
php
<?php
declare(strict_types=1);
// 错误:在 Cookie 中存储密码
// setcookie('password', $password);
// 正确:仅存储用户 ID 和 Token
setcookie('user_id', (string)$userId);
setcookie('remember_token', $token);2. setcookie() 必须在输出之前调用
php
<?php
declare(strict_types=1);
// 错误:在输出之后设置 Cookie
// echo 'Hello';
// setcookie('name', 'value'); // 会产生警告
// 正确:在任何输出之前
setcookie('name', 'value');
echo 'Hello';3. Cookie 值的安全处理
php
<?php
declare(strict_types=1);
// Cookie 值可能被用户篡改
$userId = (int)($_COOKIE['user_id'] ?? 0);
if ($userId <= 0) {
// 无效用户 ID
$userId = 0;
}
// 输出 Cookie 值时需转义
echo htmlspecialchars($_COOKIE['username'] ?? '', ENT_QUOTES, 'UTF-8');最佳实践
- 始终设置 httponly:防止 JavaScript 访问 Cookie,抵御 XSS 攻击
- 始终设置 secure:确保 Cookie 仅通过 HTTPS 传输
- 设置 SameSite:使用
Lax或Strict防止 CSRF 攻击 - 不存储敏感数据:Cookie 中不存储密码、Token 明文等
- 设置合理的过期时间:根据业务需求设置合适的 TTL
- 对值进行签名:防止用户篡改 Cookie 值
- 使用命名参数:PHP 8.0+ 使用命名参数提高可读性
- 封装 Cookie 管理:通过类统一管理 Cookie 操作
下一节
下一节将详细介绍 $argc 和 $argv,了解 PHP 命令行模式下的参数获取方式。