外观
PHP文件与目录操作
约 2849 字大约 10 分钟
PHPWeb后端入门
2026-03-04
今天我们将帮助学生会的张华同学解决一个实际棘手问题:调查问卷的数据持久化。表单收集到的数据如果不存下来,一旦页面刷新就会永远消失。在 Web 开发的世界里,文件系统(File System) 是程序最基础的物理记忆库。
🎯 学习目标
通过本课学习,你将掌握对服务器硬盘中目录和文件的绝对控制权,能够轻松实现数据的保存、读取和统计统计功能。这套后端技术将让你的数据随叫随到!
1. 目录的基本操作
创建与销毁的艺术
在写代码时,操作目录就像是在硬盘上“盖房子”和“拆房子”。PHP内置了非常直观的函数来完成这些工作。通过调用 mkdir() 函数,我们可以在指定的路径上开辟一块新的存储空间。
当需要删除目录时,可以使用 rmdir() 函数。但须谨记:直接删除非空目录会引发致命错误! 这就要求在拆除前必须先清空其中的文件。我们可以利用 is_dir() 和 file_exists() 提前进行状态检查,从而编写出健壮的代码,避免程序抛出突兀的警告异常。
💡 进阶技巧:级联创建目录
如果你需要一次性创建多级嵌套目录(例如:test/2026/03),普通模式将会失败。此时只需将 mkdir() 的第三个参数 $recursive 设为 true,系统就会开启递归创建模式,自动沿着路径将缺少的目录全部补齐。
<?php
$dirPath = "./survey_data/2026";
// 检查目录是否存在,不存在则开启递归创建
if (!file_exists($dirPath)) {
mkdir($dirPath, 0777, true);
echo "太棒了,连环文件夹创建成功!";
}
// 删除空目录
// rmdir("./survey_data/2026");
?>2. 目录与路径解析
迷宫导航仪
在处理复杂的文件路径时(例如:D:/wwwroot/php/survey/result.txt),如何优雅地提取文件名、扩展名或它所在的父级目录?PHP 提供了强大的路径解析三剑客:basename()、dirname() 和 pathinfo()。
特别是 pathinfo() 函数,它会直接返回一个包含了完整解析信息的关联数组,极大地方便了开发者按需取用。
D:/wwwroot/php/survey/result.txt
📌 魔术常量 (Magic Constants)
在现代主流 PHP 框架(如 Laravel、ThinkPHP)中,强烈推荐使用绝对路径以避免环境差异带来的路径错误。借助于内置的魔术常量 __DIR__(当前脚本所在目录)与 __FILE__(当前脚本完整路径),能够保证你的路径定位始终绝对精准。
<?php
$path = __FILE__; // 获取当前脚本的绝对路径
$info = pathinfo($path);
echo "当前目录是:\n";
print_r($info['dirname']);
echo "\n文件扩展名是:\n";
print_r($info['extension']);
?>3. 目录句柄与遍历
查户口式的排查
要获取某个目录下的所有文件列表,需要借助目录遍历操作。传统的底层方式是使用句柄 (Handle) 机制:首先用 opendir() 开启目录通道,接着循环调用 readdir() 逐行读取条目,最后通过 closedir() 安全释放内存。
值得注意的是,无论何种操作系统环境,历遍结果中永远会夹杂 . (表示当前目录) 与 .. (表示上级目录) 两个系统保留字。开发时务必通过条件判断将其过滤,否则极易引发死循环或逻辑错误。
survey_data/
💡 现代 PHP 的遍历替代方案
相较于稍显繁琐的底层句柄操作,现代开发更倾向于使用高层封装方案:
scandir():一步到位,将目录内容以排好序的数组直接返回。DirectoryIterator类:面向对象的优雅迭代器,专为处理大型复杂目录树定制。
4. 文件的基本操作与属性获取
文件的户口本
操作文件本身是家常便饭,copy() 用于克隆分身,rename() 不仅能改名还能玩“瞬间移动”(移动文件路径),而 unlink() 则是毁灭性的文件删除魔法。
要了解一个文件的底细,我们可以通过 filesize() 获取它的大小,filetype() 探查它的真身。这些数据在前端展示时往往需要进行单位换算,因为计算机底层认的是字节。
📏 字节单位的极客转换
计算机底层以字节(Byte)为标准计数单位,但这并不符合常规用户的阅读习惯。在实际输出给前端时,通常需要封装一个单位转换方法进行降级格式化: MB=1024×1024Bytes 这不仅体现了对用户体验的关注,也是 Web 开发者必备的细节素养。
<?php
$file = "dummy.txt";
file_put_contents($file, "Hello PHP!"); // 快速造个文件
if(is_file($file)){
$size = filesize($file);
$kb_size = round($size / 1024, 2); // 字节转KB
echo "文件大小为: " . $kb_size . " KB";
unlink($file); // 用完就删,环保好习惯
}
?>5. 文件的读写操作
灵魂的注入与读取
在 PHP 中,传统的流式读写依赖 fopen() 划定读写模式(只读 r、追加 a、截断覆盖 w),配合 fread() / fwrite() 执行流式 I/O,并利用 fclose() 闭合通道。这套繁复的流程虽掌控力极强,却略显臃肿。
为了大幅提升开发效率,PHP 原生提供了一对强效的语法糖实现:file_get_contents() 与 file_put_contents()。它们自动封包了所有的句柄及生命周期控制,被广泛应用于配置文件读取、API 聚合或简单的数据持久化中。
⚠️ 并发灾难与 LOCK_EX 文件锁机制
当处于高并发高流量的生产环境中,若有多个请求线程在同一毫秒对同一目标文件进行写入,会引发严重的数据交错损坏 (Data Corruption)。针对此问题,可以向 file_put_contents() 传入第三参标识常量 LOCK_EX 获取操作系统级别的独占排它锁,强制让后续请求者顾序等待。
👨💻
张三
👩💻
李四
votes.txt
<?php
$dataFile = "votes.txt";
$data = "张三投了1票\n";
// FILE_APPEND 表示追加写入,不覆盖老数据
// LOCK_EX 表示加独占锁,防止并发冲突
file_put_contents($dataFile, $data, FILE_APPEND | LOCK_EX);
// 爽快地一次性读取全部内容
$content = file_get_contents($dataFile);
echo "当前投票记录:\n" . $content;
?>6. 文件的上传与安全防范
构建安全的上传关卡
要想解析客户端上传的二进制多媒体流(诸如头像、PDF 附件),HTML 表单域必须强制附带属性:enctype="multipart/form-data"。服务器端收到 Post payload 后,所有暂存的临时文件都会投射进超全局常量 $_FILES。
切记:这只是一处临时的隔离审讯区,你必须在脚本生命周期结束前,指令 move_uploaded_file() 函数把合法的文件物理“押送”转移至持久化存储目录中,否则就会被系统自动清理掉。
[!CAUTION] 🚨 文件上传漏洞 (File Upload Vulnerability) 毫无防护的上传接口无异于开门揖盗。攻击者只需上传精心构造的 WebShell
.php后门木马,一旦触发即可接管整台服务器权限。 企业级防御三板斧:
- 白名单验证:仅放行严格合规且预定义的 MIME 或扩展名。
- 不可逆重命名:利用微秒时间戳或哈希算法(如 md5)强制覆盖原有文件名,杜绝猜测。
- 目录权限降级:上传目标文件夹绝对禁止任何脚本执行权限。
<?php
// 假设这是 upload.php 处理逻辑
if(isset($_FILES['resume'])){
$tmp_name = $_FILES['resume']['tmp_name'];
$error = $_FILES['resume']['error'];
if($error === UPLOAD_ERR_OK){
// 安全起见:随机重命名,防止覆盖和猜测
$newName = md5(time() . rand(1,1000)) . ".doc";
$dest = "./uploads/" . $newName;
if(move_uploaded_file($tmp_name, $dest)){
echo "简历上传成功,你的新代号是:{$newName}";
}
}
}
?>7. 综合实战:问卷统计工具开发
数据的归宿
现在,我们要帮张华同学把前面的知识融会贯通,开发一个真正的问卷统计工具。我们将表单提交的数据,通过 JSON序列化 转换为字符串,然后安全地写入到文本文件中。当需要展示时,再将文件读取出来并反序列化回数组,进行渲染。
这个过程完美地体现了数据的生命周期:从前端表单采集,经由PHP逻辑校验,最终落入操作系统的持久化存储之中。
<?php
// pro07.php:问卷统计核心逻辑
$vote_file = "survey_result.json";
// 1. 读取历史数据 (文件读取与解析)
$stats = ['男' => 0, '女' => 0, '文学' => 0, '科技' => 0];
if (file_exists($vote_file)) {
$content = file_get_contents($vote_file);
if (!empty($content)) {
// 将JSON格式的文本还原为数组
$stats = json_decode($content, true);
}
}
// 2. 接收新投票并处理 (表单数据处理)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$gender = $_POST['gender'] ?? '';
$type = $_POST['type'] ?? '';
// 简单校验与数据累加
if(isset($stats[$gender])) $stats[$gender]++;
if(isset($stats[$type])) $stats[$type]++;
// 3. 将更新后的数据存回文件 (文件写入操作)
// JSON_UNESCAPED_UNICODE 保证中文字符不被转码
$json_str = json_encode($stats, JSON_UNESCAPED_UNICODE);
file_put_contents($vote_file, $json_str, LOCK_EX);
echo "投票成功!感谢您的参与。<hr>";
}
// 4. 实时展示统计结果
echo "<h3>当前战况播报:</h3>";
foreach($stats as $item => $count){
echo "选项 [{$item}] 获得了 {$count} 票。<br>";
}
?>8. 课程知识点总结
| 模块分类 | 核心函数/指令 | 核心功能与应用场景描述 |
|---|---|---|
| 目录基础 | mkdir, rmdir, is_dir | 负责目录的构建与清理,适用于初始化系统存储结构。 |
| 路径解析 | pathinfo, basename | 精准拆解文件路径,适用于获取文件扩展名或隔离真实文件名。 |
| 句柄与遍历 | opendir, readdir, scandir | 读取目录内的详细清单,适用于制作文件管理器或批量处理文件。 |
| 文件属性 | filesize, filetype, unlink | 获取文件基本信息与执行销毁操作,适用于页面展示文件体积和环境清理。 |
| 文件读写 | file_get_contents, file_put_contents | 极速读取与写入文本内容,支持文件锁,适用于轻量级日志记录与数据持久化。 |
| 文件上传 | $_FILES, move_uploaded_file | 将客户端文件安全转移至服务器指定位置,适用于简历投递、头像设置等业务。 |
9. 拓展与深思
- 架构瓶颈前瞻:尝试使用
.json文件完成了小型的问卷统计系统,体会到了持久化原理。但试想一下,若此刻并发量突增至万级峰值,单体文件锁 (LOCK_EX) 会遭遇怎样的 I/O 阻塞灾难?这其实正预示着我们需要引入更为专业的结构化数据库(如 MySQL 或 Redis)来进行宏大的状态管理。 - 零信任安全验证:前文提到的利用
$_FILES['upload']['type']真的牢不可破吗?实际上伪造扩展名甚至篡改请求头对黑客易如反掌。你可以查阅 PHP 的 图片真实性检测机制(如exif_imagetype或getimagesize),来思考如何搭建更坚固的后端护城河。
