← Back to Index

Chapter 6: File Operations

File I/O, Directory Management & Data Processing

1 File I/O Basics

One-Shot Read/Write (Recommended for Small Files)

file_get_contents() and file_put_contents() are the most concise file I/O methods in PHP, completing the operation in a single line of code.

<?php
// 读取整个文件为字符串
$content = file_get_contents('config.txt');

// 从 URL 读取(需 allow_url_fopen=On)
$html = file_get_contents('https://example.com');

// 写入文件(覆盖已有内容)
file_put_contents('output.txt', "Hello, PHP!\n");

// 追加写入
file_put_contents('log.txt', date('[Y-m-d H:i:s]') . " 操作记录\n", FILE_APPEND);

// 写入数组(每个元素作为一行)
$lines = ["第一行\n", "第二行\n", "第三行\n"];
file_put_contents('lines.txt', $lines);

Handle-Based Read/Write (For Large Files / Fine-Grained Control)

<?php
// 打开文件获取句柄
$fp = fopen('data.txt', 'r');
if ($fp === false) {
    die('无法打开文件');
}

// 读取指定字节
$chunk = fread($fp, 1024);  // 读取 1024 字节

// 读完后关闭
fclose($fp);

// 写入示例
$fp = fopen('output.txt', 'w');
fwrite($fp, "写入的内容\n");
fwrite($fp, "第二行内容\n");
fclose($fp);

File Open Modes

Mode Description If File Missing Pointer Position
rRead-onlyErrorBeginning
wWrite-only (truncate)Auto-createBeginning
aAppend writeAuto-createEnd
r+Read/WriteErrorBeginning
w+Read/Write (truncate)Auto-createBeginning
a+Read + AppendAuto-createEnd

🔄 Comparison with Python

PHP's file_get_contents() is equivalent to Python's open(f).read(); fopen() corresponds to Python's open().

// PHP: 一次性读取
$text = file_get_contents('file.txt');

// Python 等价:
// text = open('file.txt').read()
// 或 with open('file.txt') as f: text = f.read()

// PHP: 句柄式
$fp = fopen('file.txt', 'r');
$line = fgets($fp);
fclose($fp);

// Python 等价:
// f = open('file.txt', 'r')
// line = f.readline()
// f.close()

PHP doesn't have Python's with syntax sugar, so you need to call fclose() manually. SplFileObject automatically closes on destruction.

2 Line-by-Line Reading

fgets() — Line-by-Line Reading

Suitable for processing large files, keeping only one line in memory at a time.

<?php
$fp = fopen('access.log', 'r');
$lineNum = 0;

while (($line = fgets($fp)) !== false) {
    $lineNum++;
    // 去除行尾换行符
    $line = rtrim($line, "\r\n");
    if (str_contains($line, 'ERROR')) {
        echo "第 {$lineNum} 行: {$line}\n";
    }
}

fclose($fp);

file() — Read All Lines into an Array at Once

<?php
// 每行作为数组元素,自动去除换行符
$lines = file('config.ini', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

foreach ($lines as $index => $line) {
    echo ($index + 1) . ": {$line}\n";
}

// 统计行数
$totalLines = count(file('data.txt'));

SplFileObject — Object-Oriented Approach

SplFileObject implements the Iterator interface, allowing direct iteration with foreach, and automatically manages resources.

<?php
$file = new SplFileObject('server.log', 'r');
$file->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE);

foreach ($file as $lineNumber => $line) {
    if (str_contains($line, '500')) {
        printf("行 %d: %s\n", $lineNumber + 1, $line);
    }
}

// 读取指定行(跳到第 100 行)
$file->seek(99);
echo $file->current();

💡 Tips for Large File Processing

When processing GB-sized log files, avoid file_get_contents() or file() (which load everything into memory). Use fgets() or SplFileObject to read line by line, keeping memory usage constant.

3 Directory Operations

Basic Directory Operations

<?php
// 创建目录(第三个参数 true 表示递归创建)
mkdir('storage/uploads/images', 0755, true);

// 删除空目录
rmdir('temp');

// 列出目录内容
$items = scandir('storage');
// ['.' , '..' , 'uploads', 'cache', 'logs']

// 过滤掉 . 和 ..
$items = array_diff(scandir('storage'), ['.', '..']);

// 判断类型
foreach ($items as $item) {
    $path = "storage/{$item}";
    if (is_dir($path)) {
        echo "📁 {$item}\n";
    } elseif (is_file($path)) {
        echo "📄 {$item}\n";
    }
}

RecursiveDirectoryIterator — Recursive Traversal

<?php
$dir = new RecursiveDirectoryIterator('src', FilesystemIterator::SKIP_DOTS);
$iterator = new RecursiveIteratorIterator($dir);

foreach ($iterator as $file) {
    /** @var SplFileInfo $file */
    echo $file->getPathname() . ' (' . $file->getSize() . " bytes)\n";
}

// 只遍历 PHP 文件
$phpFiles = new RegexIterator($iterator, '/\.php$/');
foreach ($phpFiles as $phpFile) {
    echo $phpFile->getPathname() . "\n";
}

glob() — Pattern Matching

<?php
// 查找所有 PHP 文件
$phpFiles = glob('src/*.php');

// 递归查找(PHP 本身不支持 **,需组合使用)
$phpFiles = glob('src/**/*.php');  // 仅匹配一层子目录

// 查找多种扩展名
$images = glob('uploads/*.{jpg,png,gif}', GLOB_BRACE);

// 只查找目录
$dirs = glob('modules/*', GLOB_ONLYDIR);

foreach ($phpFiles as $file) {
    echo basename($file) . "\n";
}

4 Path Handling

<?php
$path = '/var/www/html/uploads/photo.jpg';

// pathinfo() — 返回路径各部分
$info = pathinfo($path);
// ['dirname' => '/var/www/html/uploads',
//  'basename' => 'photo.jpg',
//  'extension' => 'jpg',
//  'filename' => 'photo']

echo pathinfo($path, PATHINFO_EXTENSION);  // 'jpg'

// 其他常用函数
echo basename($path);          // 'photo.jpg'
echo basename($path, '.jpg');  // 'photo'
echo dirname($path);           // '/var/www/html/uploads'
echo dirname($path, 2);       // '/var/www/html'

// 解析为绝对路径(解析符号链接和 ..)
echo realpath('../config');    // '/var/www/config'

// 魔术常量
echo __DIR__;   // 当前脚本所在目录的绝对路径
echo __FILE__;  // 当前脚本文件的绝对路径

Path Function Quick Reference

Function / Constant Purpose Example Result
pathinfo($p)Parse path componentsAssociative array
basename($p)Filename (with extension)photo.jpg
dirname($p)Parent directory path/var/www/html/uploads
realpath($p)Resolve to absolute path/var/www/config
__DIR__Current script directory/var/www/html
__FILE__Current script full path/var/www/html/index.php
DIRECTORY_SEPARATORSystem directory separator/ (Linux) or \ (Windows)

💡 Cross-Platform Path Joining

Use DIRECTORY_SEPARATOR or simply / (PHP handles forward slashes correctly on Windows too). Recommended: __DIR__ . '/config/app.php'.

5 CSV Processing

Reading CSV Files

<?php
$fp = fopen('users.csv', 'r');

// 读取表头
$headers = fgetcsv($fp);
// ['姓名', '邮箱', '年龄']

// 逐行读取数据
$users = [];
while (($row = fgetcsv($fp)) !== false) {
    // 将表头和数据组合为关联数组
    $users[] = array_combine($headers, $row);
}
fclose($fp);

// $users = [
//   ['姓名' => '张三', '邮箱' => 'zhang@example.com', '年龄' => '28'],
//   ['姓名' => '李四', '邮箱' => 'li@example.com',    '年龄' => '32'],
// ]

foreach ($users as $user) {
    echo "{$user['姓名']} ({$user['邮箱']})\n";
}

Writing CSV Files

<?php
$data = [
    ['产品', '价格', '库存'],
    ['PHP 入门', 59.9, 120],
    ['Laravel 实战', 79.9, 85],
    ['MySQL 优化', 69.9, 200],
];

$fp = fopen('products.csv', 'w');

// 写入 UTF-8 BOM(Excel 兼容中文)
fwrite($fp, "\xEF\xBB\xBF");

foreach ($data as $row) {
    fputcsv($fp, $row);
}
fclose($fp);

echo "CSV 写入完成,共 " . (count($data) - 1) . " 条数据\n";

SplFileObject for CSV Reading

<?php
$file = new SplFileObject('users.csv', 'r');
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::READ_AHEAD);

$headers = null;
foreach ($file as $row) {
    if ($headers === null) {
        $headers = $row;
        continue;
    }
    $user = array_combine($headers, $row);
    echo "{$user['姓名']}\n";
}

6 JSON File Operations

Reading JSON Files

<?php
// 读取并解码 JSON 文件
$json = file_get_contents('config.json');
$config = json_decode($json, true);  // true → 返回关联数组

if ($config === null && json_last_error() !== JSON_ERROR_NONE) {
    die('JSON 解析错误: ' . json_last_error_msg());
}

echo $config['database']['host'];   // localhost
echo $config['database']['port'];   // 3306

Writing JSON Files

<?php
$data = [
    'app_name' => '我的应用',
    'version'  => '2.0.0',
    'features' => ['用户管理', '权限控制', '数据统计'],
    'database' => [
        'host' => 'localhost',
        'port' => 3306,
        'name' => 'myapp',
    ],
];

// 写入格式化的 JSON
$jsonStr = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
file_put_contents('config.json', $jsonStr);

// 输出:
// {
//     "app_name": "我的应用",
//     "version": "2.0.0",
//     "features": ["用户管理", "权限控制", "数据统计"],
//     ...
// }

Common JSON Encoding Flags

Flag Purpose
JSON_PRETTY_PRINTFormatted output (indented with newlines)
JSON_UNESCAPED_UNICODEDon't escape Unicode characters (e.g. CJK) to \uXXXX
JSON_UNESCAPED_SLASHESDon't escape slashes / (more readable URLs)
JSON_THROW_ON_ERRORThrow JsonException on parse failure (recommended)
JSON_FORCE_OBJECTForce arrays to encode as JSON objects {}

💡 Recommended PHP 8.x Practice

Use JSON_THROW_ON_ERROR instead of manually checking json_last_error(), combined with try/catch for cleaner code:

try {
    $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo "解析失败: {$e->getMessage()}";
}

7 File Upload Handling

HTML Upload Form

<!-- 必须使用 enctype="multipart/form-data" -->
<form action="upload.php" method="POST" enctype="multipart/form-data">
    <input type="file" name="avatar" accept="image/*">
    <button type="submit">上传头像</button>
</form>

$_FILES Superglobal

<?php
// $_FILES['avatar'] 的结构:
// [
//   'name'     => 'photo.jpg',       // 原始文件名
//   'type'     => 'image/jpeg',      // MIME 类型(不可信)
//   'tmp_name' => '/tmp/phpXXXX',    // 临时文件路径
//   'error'    => UPLOAD_ERR_OK,     // 错误码(0 表示成功)
//   'size'     => 245678,            // 文件大小(字节)
// ]

Complete Upload Handling Example

<?php
function handleUpload(string $fieldName, string $uploadDir = 'uploads/'): string|false
{
    if (!isset($_FILES[$fieldName]) || $_FILES[$fieldName]['error'] !== UPLOAD_ERR_OK) {
        $errors = [
            UPLOAD_ERR_INI_SIZE   => '文件超过 php.ini 中 upload_max_filesize 限制',
            UPLOAD_ERR_FORM_SIZE  => '文件超过表单中 MAX_FILE_SIZE 限制',
            UPLOAD_ERR_PARTIAL    => '文件只上传了一部分',
            UPLOAD_ERR_NO_FILE    => '没有选择文件',
            UPLOAD_ERR_NO_TMP_DIR => '服务器缺少临时目录',
        ];
        $code = $_FILES[$fieldName]['error'] ?? -1;
        echo '上传失败: ' . ($errors[$code] ?? '未知错误') . "\n";
        return false;
    }

    $file = $_FILES[$fieldName];

    // 1. 验证文件大小(限制 5MB)
    $maxSize = 5 * 1024 * 1024;
    if ($file['size'] > $maxSize) {
        echo "文件过大,最大允许 5MB\n";
        return false;
    }

    // 2. 验证文件类型(通过文件内容而非扩展名)
    $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeType = $finfo->file($file['tmp_name']);

    if (!in_array($mimeType, $allowedTypes, true)) {
        echo "不支持的文件类型: {$mimeType}\n";
        return false;
    }

    // 3. 验证扩展名
    $allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, $allowedExts, true)) {
        echo "不允许的扩展名: {$ext}\n";
        return false;
    }

    // 4. 生成安全的文件名(避免覆盖和目录穿越)
    $newName = bin2hex(random_bytes(16)) . '.' . $ext;
    $destPath = rtrim($uploadDir, '/') . '/' . $newName;

    // 5. 确保目标目录存在
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    // 6. 移动文件(move_uploaded_file 会验证是否为合法上传文件)
    if (!move_uploaded_file($file['tmp_name'], $destPath)) {
        echo "移动文件失败\n";
        return false;
    }

    return $destPath;
}

// 使用
$path = handleUpload('avatar', 'uploads/avatars/');
if ($path) {
    echo "上传成功: {$path}\n";
}

🔒 Security Considerations

  • Never trust $_FILES['type']; use finfo to detect the real MIME type
  • Use random filenames to prevent overwriting and directory traversal attacks
  • Disable PHP execution in the upload directory (set php_flag engine off in .htaccess)
  • Configure upload_max_filesize and post_max_size in php.ini
  • Always use move_uploaded_file() instead of copy() or rename()

8 File Info & Permissions

<?php
$file = 'storage/data.json';

// 文件是否存在
if (file_exists($file)) {
    echo "文件大小: " . filesize($file) . " 字节\n";

    // 格式化文件大小
    function formatSize(int $bytes): string {
        $units = ['B', 'KB', 'MB', 'GB'];
        $i = 0;
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        return round($bytes, 2) . ' ' . $units[$i];
    }
    echo "可读大小: " . formatSize(filesize($file)) . "\n";

    // 修改时间
    $mtime = filemtime($file);
    echo "最后修改: " . date('Y-m-d H:i:s', $mtime) . "\n";

    // 权限检查
    echo "可读: " . (is_readable($file) ? '是' : '否') . "\n";
    echo "可写: " . (is_writable($file) ? '是' : '否') . "\n";

    // 修改权限
    chmod($file, 0644);
}

// 复制、重命名、删除
copy('source.txt', 'backup.txt');
rename('old.txt', 'new.txt');        // 也可用于移动文件
unlink('temp.txt');                   // 删除文件

Common File Functions Quick Reference

Function Purpose Return Value
file_exists($p)Whether file or directory existsbool
is_file($p)Whether it's a regular filebool
is_dir($p)Whether it's a directorybool
is_readable($p)Whether it's readablebool
is_writable($p)Whether it's writablebool
filesize($p)File size (bytes)int|false
filemtime($p)Last modified timeint|false (Unix timestamp)
fileatime($p)Last access timeint|false
chmod($p, $m)Change permissionsbool
copy($src, $dst)Copy filebool
rename($old, $new)Rename / Movebool
unlink($p)Delete filebool
tempnam($dir, $pre)Create temporary filestring|false

💡 Note: Stat Cache

PHP caches the results of file_exists(), filesize(), and similar functions. If a file is modified externally during script execution, call clearstatcache() to clear the cache before re-reading.

9 Chapter Summary

📖 File I/O

  • • Use file_get_contents() / file_put_contents() for small files
  • • Use fopen() + fgets() for line-by-line processing of large files
  • SplFileObject provides OOP-style file operations
  • • Use mode 'a' or FILE_APPEND flag for appending

📁 Directories & Paths

  • scandir() to list directories, glob() for pattern matching
  • RecursiveDirectoryIterator for recursive traversal
  • pathinfo() to parse paths, __DIR__ to get current directory
  • mkdir($dir, 0755, true) to create directories recursively

📊 Data Processing

  • • CSV: fgetcsv() / fputcsv()
  • • JSON: json_decode() / json_encode()
  • • Add JSON_UNESCAPED_UNICODE for CJK characters in JSON
  • • PHP 8.x recommends JSON_THROW_ON_ERROR

🔒 Uploads & Security

  • • Use finfo to detect real MIME type; don't trust the client
  • • Random filenames prevent overwriting and directory traversal
  • move_uploaded_file() to move uploaded files
  • • Disable PHP execution in upload directories