第4章 4.2 Cookie 与 Session
「上节课小明的购物车数据为什么刷新就没了?」
——因为购物车信息存在「内存」里,页面一关就丢了。这节课我们给它安个「永久仓库」。
🎯 开场 3 分钟:为什么要学这个?
想象你去超市寄存柜存包:
- 柜子(Cookie):给你一把钥匙(随机字符串),包存在固定柜子里。你拿着钥匙,下次来直接开柜子取包。钥匙丢了/过期了,柜子就打不开了。
- 超市管理员(Session):你报会员卡号,管理员翻记录本找你之前存的东西。记录本在管理员手里(服务器端),比柜子更安全。
痛点问题:
1. 用户登录后刷新页面,怎么知道「这个浏览器是张三而不是李四」?
2. 购物车里的东西,凭什么关掉浏览器还在?
学完这章,你能写出「记住我登录」「购物车持久化」这种真实功能。
🧱 基础 25 分钟:核心概念
什么是 Cookie?——超市柜子的钥匙
Cookie 是服务器发给浏览器的一小段文本,浏览器会自动存起来,下次请求时再带回去。
生\n\n
\n\n
\n\n活类比:你去游泳馆,柜子钥匙上写着「3号柜」,这把钥匙就是 Cookie。你下次来出示钥匙,工作人员就知道「3号柜是你的」。
为什么要用 Cookie?
- 解决 HTTP 无状态问题(服务器记不住你是谁)
- 存储用户偏好、登录状态等少量数据
PHP 中操作 Cookie
<?php
// 设置 Cookie:key 是 "username",值是 "xiaoming",1小时后过期
setcookie("username", "xiaoming", time() + 3600);
// 读取 Cookie
if (isset($_COOKIE["username"])) {
echo "欢迎回来," . $_COOKIE["username"];
} else {
echo "你是新用户";
}
setcookie("键", "值", 过期时间戳):像往柜子放东西$_COOKIE["键"]:像拿钥匙开柜子取东西
什么是 Session?——游泳馆的会员记录本
Session 是服务器上存储的用户数据,依赖 Cookie 保存一个 session_id 来识别用户。
生活类比:Cookie 是钥匙,Session 是柜子里详细记录你物品的登记本。钥匙只是入口,真正的东西在柜子里(服务器端)。
为什么要用 Session?
- Cookie 存不了敏感信息(用户能看到)
- Session 数据在服务器端,更安全
- 能存更多数据
PHP 中操作 Session
<?php
// 启动 Session(必须在任何输出之前!)
session_start();
// 存储数据
$_SESSION["username"] = "xiaoming";
$_SESSION["role"] = "admin";
// 读取数据
if (isset($_SESSION["username"])) {
echo "当前用户:" . $_SESSION["username"];
echo ",角色:" . $_SESSION["role"];
}
// 删除单个数据
unset($_SESSION["role"]);
// 删除所有数据(登出)
session_destroy();
注意!
session_start()必须放在 PHP 文件第一行(或在任何echo、
Cookie vs Session 对比
| Cookie | Session | |
|---|---|---|
| 存放位置 | 浏览器(客户端) | 服务器 |
| 安全性 | 低(用户能看到) | 高(用户看不到) |
| 存储量 | 小(≤4KB) | 大(取决于服务器配置) |
| 生命周期 | 可设置过期时间 | 浏览器关闭即失效(默认) |
一个完整的小例子:用户登录流程
<?php
session_start();
// 模拟登录验证
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = $_POST["username"];
$password = $_POST["password"];
// 假设正确的账号密码
if ($username === "admin" && $password === "123456") {
$_SESSION["username"] = $username;
$_SESSION["login_time"] = date("Y-m-d H:i:s");
echo "登录成功!<br>";
echo '<a href="profile.php">查看个人资料</a>';
exit;
} else {
echo "账号或密码错误";
}
}
?>
<form method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<button type="submit">登录</button>
</form>
刷新页面后访问 $_SESSION["username"],数据还在——这就是 Session 的威力。
🔥 实战 35 分钟:3 个递进的小项目
项目 1:访问次数计数器(5 分钟)
记录用户访问网页的次数,刷新页面次数会增加。
<?php
session_start();
// 初始化计数器
if (!isset($_SESSION["visit_count"])) {
$_SESSION["visit_count"] = 0;
}
// 每次访问 +1
$_SESSION["visit_count"]++;
echo "你是第 " . $_SESSION["visit_count"] . " 次访问这个页面";
?>
预期输出:
你是第 1 次访问这个页面
(刷新后)你是第 2 次访问这个页面
(再刷新)你是第 3 次访问这个页面
解释:用 $_SESSION["visit_count"] 记录次数,每次刷新 +1。
项目 2:记住用户偏好的主题切换(15 分钟)
用户选择「深色/浅色」主题,刷新后记住选择。
<?php
session_start();
// 处理主题切换请求
if (isset($_GET["theme"])) {
$_SESSION["theme"] = $_GET["theme"];
}
// 获取当前主题,默认浅色
$current_theme = $_SESSION["theme"] ?? "light";
// 定义主题样式
$bg_color = ($current_theme === "dark") ? "#333" : "#fff";
$text_color = ($current_theme === "dark") ? "#fff" : "#333";
?>
<!DOCTYPE html>
<html>
<head>
<title>主题切换</title>
</head>
<body style="background:<?php echo $bg_color; ?>; color:<?php echo $text_color; ?>;">
<h1>当前主题:<?php echo $current_theme; ?></h1>
<p>选择主题:</p>
<a href="?theme=light">浅色</a> |
<a href="?theme=dark">深色</a>
<p>刷新页面,主题会保持不变!</p>
<p>Session ID: <?php echo session_id(); ?></p>
</body>
</html>
预期输出:
- 点击「深色」后,页面背景变深色、文字变白色
- 刷新页面,主题不变
- Session ID 每次刷新都一样(说明是同一个会话)
解释:把用户选择存进 $_SESSION["theme"],读取时优先取 session,没有再用默认值。
项目 3:简单的待办清单(持久化版)(15 分钟)
添加待办事项,刷新页面后数据不丢失。
<?php
session_start();
// 初始化待办列表
if (!isset($_SESSION["todos"])) {
$_SESSION["todos"] = [];
}
// 处理添加
if (isset($_POST["todo"]) && !empty($_POST["todo"])) {
$_SESSION["todos"][] = [
"content" => $_POST["todo"],
"time" => date("H:i:s")
];
}
// 处理删除
if (isset($_GET["delete"])) {
$index = (int)$_GET["delete"];
if (isset($_SESSION["todos"][$index])) {
array_splice($_SESSION["todos"], $index, 1);
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>我的待办</title>
<style>
body { font-family: Arial; max-width: 500px; margin: 50px auto; }
.todo-item { padding: 10px; border-bottom: 1px solid #ccc; }
.todo-item span { color: #888; font-size: 12px; }
.delete { color: red; text-decoration: none; }
</style>
</head>
<body>
<h1>📝 我的待办清单</h1>
<form method="post">
<input type="text" name="todo" placeholder="输入待办事项" style="width: 300px;">
<button type="submit">添加</button>
</form>
<h3>待办列表(共 <?php echo count($_SESSION["todos"]); ?> 项):</h3>
<?php if (empty($_SESSION["todos"])): ?>
<p>暂无待办,添加一个吧!</p>
<?php else: ?>
<?php foreach ($_SESSION["todos"] as $index => $todo): ?>
<div class="todo-item">
<?php echo ($index + 1) . ". " . htmlspecialchars($todo["content"]); ?>
<span>(添加于 <?php echo $todo["time"]; ?>)</span>
<a href="?delete=<?php echo $index; ?>" class="delete"
onclick="return confirm('确定删除?')">删除</a>
</div>
<?php endforeach; ?>
<?php endif; ?>
</body>
</html>
预期输出:
- 添加「买牛奶」,列表显示「1. 买牛奶」
- 刷新页面,列表还有「买牛奶」
- 关闭浏览器重开,数据还在
- 点「删除」后该项消失
解释:用 $_SESSION["todos"] 数组存储所有待办,htmlspecialchars() 防止 XSS 攻击。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:session_start() 位置错误
<?php
// ❌ 错误:在 echo 之后才启动 session
echo "先输出一句话";
session_start(); // 会报错:headers already sent
?>
<?php
// ✅ 正确:session_start() 必须放第一行
session_start();
echo "现在可以输出了";
?>
坑 2:Cookie 过期时间设置错误
<?php
// ❌ 错误:过期时间戳已经过了(过去的时间)
setcookie("token", "abc", time() - 3600); // 删除 cookie 应该这样写
?>
<?php
// ✅ 正确:设置1小时后过期
setcookie("token", "abc", time() + 3600);
// ✅ 正确:明确删除 Cookie(设置过期时间为过去)
setcookie("token", "", time() - 3600);
?>
坑 3:Session 乱码或数据丢失
<?php
// ❌ 错误:文件开头有 BOM 头或空格
session_start(); // 文件开头有隐藏字符
...
?>
<?php
// ✅ 正确:确保文件是纯 UTF-8 无 BOM
// 检查方法:用十六进制编辑器看文件开头是不是 EF BB BF
session_start();
?>
坑 4:Session 存储路径配置错误
<?php
// ❌ 错误:session_save_path() 也要在 session_start() 之前
session_start();
session_save_path("/some/path"); // 太晚了
?>
<?php
// ✅ 正确:在 session_start() 之前配置
session_save_path("/tmp/sessions");
session_start();
?>
坑 5:生产环境 Session 泄露
<?php
// ❌ 错误:生产环境用默认的 Files 存储
// 多个 PHP-FPM 进程可能互相覆盖
session_start();
$_SESSION["user"] = "sensitive_data";
?>
<?php
// ✅ 正确:生产环境用 Redis 等外部存储
// 或配置 session.cookie_httponly 防止 JS 读取
session_set_cookie_params([
'lifetime' => 3600,
'httponly' => true, // 禁止 JS 读取 Cookie
'secure' => true // 只在 HTTPS 传输
]);
session_start();
?>
性能小贴士:Session 懒加载
如果页面不需要 Session 数据,不要每次都启动:
<?php
// 只在需要时才启动 Session
if (need_session()) {
session_start();
// ... 用 session 的代码
}
?>
调试技巧:用 print_r 看 Session
<?php
session_start();
// 任何时候都可以打印看 Session 状态
echo "<pre>";
print_r($_SESSION);
echo "</pre>";
?>
输出类似:
Array
(
[username] => xiaoming
[login_time] => 2024-01-15 10:30:00
[visit_count] => 5
)
✏️ 练习题
练习 1(2 分钟):修改过期时间
- 输入:把项目 1 的计数器改成「只保留 10 秒」
- 预期输出:10 秒后再刷新,计数重置为 1
- 提示:
time() + 10秒
练习 2(2 分钟):添加登录检查
- 输入:在项目 2 主题切换里加一个
if判断,如果没登录就不显示切换链接 - 预期输出:未设置
$_SESSION["username"]时不显示主题切换 - 提示:先
$_SESSION["username"] = "test"模拟登录
练习 3(3 分钟):统计在线人数
- 输入:用 Session 统计当前有多少人访问过页面(用文件存储访问记录)
- 预期输出:显示「当前在线人数:X」
- 提示:用
file_put_contents把 session_id 写到文件
练习 4(3 分钟):合并待办和主题
- 输入:把项目 2 和项目 3 的功能合并到一个页面
- 预期输出:既能切换主题,待办清单也能持久化
- 提示:两个项目的
session_start()可以共用
练习 5(5 分钟):分析报错
- 输入:用户报「Warning: session_start(): Cannot send session cache limiter」
- 预期输出:说出原因并修复
- 提示:检查 session_start() 前面有没有输出
作业:做一个「访问日志记录器」
需求描述:记录每个访客的访问时间、访问次数、最后访问时间,把数据持久化存储。
功能点:
1. 首次访问显示「欢迎新用户」,记录访问时间
2. 再次访问显示「欢迎回来,你来过 X 次,上次是 Y」
3. 刷新后访问次数 +1
加分项:
1. 把日志写入文件(JSON 格式),重启 PHP 也不丢数据
2. 显示「历史上共有 Z 个访客」
验收标准:
- 能跑起来
- 刷新页面次数递增
- 关闭浏览器重开,次数继续累加(不清空)
提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
本文学到的 3 个核心点:
1. Cookie 是钥匙(存客户端),Session 是柜子(存服务器端)
2. setcookie() 设置 Cookie,$_COOKIE 读取;session_start() 启动 Session,$_SESSION 读写
3. Session 依赖 Cookie 传递 session_id,真正数据存在服务器
延伸学习资源:
- PHP 官方文档:Session
- 《PHP 核心技术与最佳实践》:第 5 章 Session 深入理解
互动钩子:
「你在登录注册时遇到过『记住我』功能失效的问题吗?是 Cookie 过期还是 Session 没启动?评论区聊聊,老粉优先回复!」
下章剧透:
「用户填完表单、记住了登录状态,下一步要上传头像怎么办?下一节教你用 PHP 处理文件上传——让用户把图片『快递』到服务器上!」

评论(0)