Kodokon kodokon.com

文件:读取、写入、上传、JSON

读写文件、记录事件日志、安全地接收上传,并用 JSON 存储你的数据。

10 分钟 · 3 题

在 Kodokon 中打开本课

读取一个文件只需一个函数:file_get_contents() 把全部内容作为一个字符串返回,file() 则把它拆分成一个按行组成的数组。在读取之前,用 is_readable() 检查访问权限:一个错误的路径会返回 false 并伴随一个警告。

PHP
<?php

declare(strict_types=1);

$path = __DIR__ . '/notes.txt';

if (!is_readable($path)) {
    exit("File not found or unreadable.");
}

$content = (string) file_get_contents($path);
echo nl2br(htmlspecialchars($content, ENT_QUOTES));

$lines = file(
    $path,
    FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
);
echo count($lines) . " non-empty line(s)";
先整体读取同一个文件,再逐行读取。

写入时,file_put_contents() 默认会替换全部内容。两个标志会改变这一行为:FILE_APPEND 会追加到末尾而不是覆盖,LOCK_EX 会设置一把排他锁,这样两个同时到来的请求就不会把各自的写入混在一起。这正是一个极简应用日志的实现秘诀。

PHP
<?php

declare(strict_types=1);

$logFile = __DIR__ . '/app.log';

$line = sprintf(
    "[%s] New order from %s\n",
    date('Y-m-d H:i:s'),
    'alice@example.com'
);

file_put_contents(
    $logFile,
    $line,
    FILE_APPEND | LOCK_EX
);

echo "Event logged.";
用寥寥几行代码实现一个可靠的应用日志。

文件上传值得格外小心。表单必须声明 enctype="multipart/form-data",随后 PHP 会在 $_FILES 中描述接收到的文件。要验证一切:错误码、大小,以及由 finfo 检测出的真实类型 - 绝不要相信扩展名或浏览器所声称的类型。最后,只有 move_uploaded_file() 才应该用来移动那个临时文件。

PHP
<?php

declare(strict_types=1);

$maxSize = 2 * 1024 * 1024;
$file = $_FILES['avatar'] ?? null;

if ($file === null || $file['error'] !== UPLOAD_ERR_OK) {
    exit("No valid file received.");
}

if ($file['size'] > $maxSize) {
    exit("File too large (2 MB max).");
}

$info = new finfo(FILEINFO_MIME_TYPE);
$mime = $info->file($file['tmp_name']);
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png'];

if (!isset($allowed[$mime])) {
    exit("Format not allowed.");
}

$name = bin2hex(random_bytes(8));
$target = __DIR__ . '/uploads/' . $name
    . '.' . $allowed[$mime];

move_uploaded_file($file['tmp_name'], $target);
echo "Image saved.";
upload.php:大小、真实类型和文件名都在掌控之中。

JSON 格式是 PHP 与外部世界之间天然的桥梁:json_encode() 会把一个数组序列化,json_decode() 则做相反的事。把 true 作为第二个参数传入可以得到关联数组,而 JSON_THROW_ON_ERROR 标志能把格式错误的 JSON 变成一个清晰的异常,而不是一个悄无声息的 null

PHP
<?php

declare(strict_types=1);

$path = __DIR__ . '/tasks.json';

$json = is_readable($path)
    ? (string) file_get_contents($path)
    : '[]';

$tasks = json_decode(
    $json,
    true,
    512,
    JSON_THROW_ON_ERROR
);

$tasks[] = [
    'title' => 'Review the lesson',
    'done' => false,
];

file_put_contents(
    $path,
    json_encode(
        $tasks,
        JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
    ),
    LOCK_EX
);

echo count($tasks) . " task(s) saved";
一个迷你数据仓库:读取、修改、重写。

知识检测

确认你已牢记本课的重点内容。

  1. 传给 file_put_contents() 的 FILE_APPEND 标志有什么作用?
    • 它会把内容追加到文件末尾,而不是覆盖文件
    • 如果父文件夹不存在,它会创建父文件夹
    • 它会在写入期间锁定文件
  2. 为什么不应该依赖 $_FILES['avatar']['type'] 来检查一次上传?
    • 这个键只在某些服务器上存在
    • 它只对图片有效
    • 这个值是由浏览器发送的,可能被伪造;你必须用 finfo 检测真实类型
  3. 在 json_decode($json, true) 中,第二个参数 true 改变了什么?
    • 解码在出错时会抛出一个异常
    • JSON 对象会变成 PHP 关联数组,而不是 stdClass 对象
    • 键会被转换成大写