便捷函数
概述
PHP 提供了一系列便捷的文件操作函数,可以用一行代码完成常见的文件读写任务。file_get_contents() 和 file_put_contents() 是其中最常用的两个函数。对于简单的文件操作,便捷函数比 fopen 系列更简洁高效。
适用场景
- 快速读取配置文件
- 一次性写入小文件
- 读取远程 URL 内容
- INI 配置解析
基础概念
核心便捷函数
| 函数 | 功能 | 返回值 |
|---|---|---|
file_get_contents() | 读取整个文件为字符串 | string|false |
file_put_contents() | 将字符串写入文件 | int|false |
file() | 读取文件为数组(每行一个元素) | array|false |
readfile() | 读取文件并直接输出 | int|false |
parse_ini_file() | 解析 INI 配置文件 | array|false |
parse_ini_string() | 解析 INI 格式字符串 | array|false |
性能说明
file_get_contents() 使用内存映射(mmap)技术,在读取大文件时比 fread() 更高效。但处理超大文件(>内存)时应使用流式读取。
file_put_contents 标志
| 标志 | 说明 |
|---|---|
FILE_USE_INCLUDE_PATH | 在 include_path 中搜索文件 |
FILE_APPEND | 追加写入(等同于 a 模式) |
LOCK_EX | 获取独占锁再写入 |
PHP 8.0+ 变更
file_get_contents() 和 file_put_contents() 在 PHP 8.0 中已正确声明返回类型。file() 的 $flags 参数类型更严格。
语法与代码示例
file_get_contents 读取文件
php
<?php
// 基本读取
$content = file_get_contents('/tmp/config.json');
if ($content === false) {
throw new RuntimeException('文件读取失败');
}
echo $content;
// 指定偏移和长度读取
$partialContent = file_get_contents('/tmp/data.txt', false, null, 0, 1024);
echo $partialContent; // 只读取前 1024 字节
// 从偏移位置开始读取
$fromOffset = file_get_contents('/tmp/data.txt', false, null, 100, 500);
echo $fromOffset; // 从第 100 字节开始读取 500 字节file_put_contents 写入文件
php
<?php
// 基本写入(覆盖模式)
$bytes = file_put_contents('/tmp/output.txt', 'Hello, World!');
echo "写入 {$bytes} 字节\n";
// 追加写入
file_put_contents('/tmp/log.txt', "[INFO] 新日志\n", FILE_APPEND);
// 带锁写入(防止并发冲突)
file_put_contents('/tmp/counter.txt', '100', LOCK_EX);
// 写入数组(自动连接为字符串)
$lines = ['Line 1', 'Line 2', 'Line 3'];
file_put_contents('/tmp/lines.txt', implode(PHP_EOL, $lines));file() 读取文件为数组
php
<?php
// 读取为行数组(包含换行符)
$lines = file('/tmp/data.txt');
if ($lines === false) {
throw new RuntimeException('读取失败');
}
foreach ($lines as $lineNumber => $line) {
echo sprintf("第 %d 行: %s", $lineNumber + 1, $line);
}
// 去除换行符
$lines = file('/tmp/data.txt', FILE_IGNORE_NEW_LINES);
// 跳过空行
$lines = file('/tmp/data.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
// 实用技巧:快速读取文件为行数组并去空白
$lines = array_filter(
array_map('trim', file('/tmp/data.txt', FILE_IGNORE_NEW_LINES))
);readfile 直接输出到缓冲区
php
<?php
// readfile 直接输出到浏览器,返回字节数
$bytes = readfile('/tmp/image.jpg');
// 常用于文件下载
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="document.pdf"');
header('Content-Length: ' . filesize('/tmp/document.pdf'));
readfile('/tmp/document.pdf');
exit;parse_ini_file 解析配置
php
<?php
// config.ini 内容示例:
// [database]
// host = localhost
// port = 3306
// name = myapp
//
// [cache]
// driver = redis
// ttl = 3600
// 基本解析(扁平数组)
$config = parse_ini_file('/tmp/config.ini');
print_r($config);
// 分段解析(按节分组为二维数组)
$config = parse_ini_file('/tmp/config.ini', true);
print_r($config);
/*
输出:
[
'database' => ['host' => 'localhost', 'port' => '3306', 'name' => 'myapp'],
'cache' => ['driver' => 'redis', 'ttl' => '3600'],
]
*/
// PHP 8.0+ 指定 scanner_mode 自动推断类型
$config = parse_ini_file(
'/tmp/config.ini',
true,
INI_SCANNER_TYPED // 数字、布尔值、null 会被自动转换
);
// port 会从字符串 "3306" 变为整数 3306实战示例
配置管理类
php
<?php
declare(strict_types=1);
class ConfigManager
{
private array $config = [];
private string $configFile;
public function __construct(string $configFile)
{
$this->configFile = $configFile;
$this->load();
}
private function load(): void
{
if (!file_exists($this->configFile)) {
throw new RuntimeException("配置文件不存在: {$this->configFile}");
}
$config = parse_ini_file($this->configFile, true, INI_SCANNER_TYPED);
if ($config === false) {
throw new RuntimeException("配置文件解析失败: {$this->configFile}");
}
$this->config = $config;
}
public function get(string $key, mixed $default = null): mixed
{
$keys = explode('.', $key);
$value = $this->config;
foreach ($keys as $segment) {
if (!is_array($value) || !array_key_exists($segment, $value)) {
return $default;
}
$value = $value[$segment];
}
return $value;
}
public function set(string $key, mixed $value): void
{
$keys = explode('.', $key);
$config = &$this->config;
foreach ($keys as $i => $segment) {
if ($i === count($keys) - 1) {
$config[$segment] = $value;
} else {
if (!isset($config[$segment]) || !is_array($config[$segment])) {
$config[$segment] = [];
}
$config = &$config[$segment];
}
}
}
public function save(): void
{
$content = $this->toIniString($this->config);
file_put_contents($this->configFile, $content);
}
private function toIniString(array $config, string $prefix = ''): string
{
$result = '';
foreach ($config as $key => $value) {
if (is_array($value) && !is_int(key($value))) {
$section = $prefix ? "{$prefix}.{$key}" : $key;
$result .= PHP_EOL . "[{$section}]" . PHP_EOL;
$result .= $this->toIniString($value, $section);
} else {
if (is_bool($value)) {
$value = $value ? 'true' : 'false';
} elseif ($value === null) {
$value = 'null';
} elseif (!is_scalar($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
}
$result .= "{$key} = {$value}" . PHP_EOL;
}
}
return $result;
}
}
// 使用示例
$config = new ConfigManager('/tmp/app.ini');
$host = $config->get('database.host', 'localhost');
$config->set('cache.ttl', 7200);
$config->save();JSON 文件工具类
php
<?php
declare(strict_types=1);
class FileHelper
{
public static function read(string $path): string
{
$content = file_get_contents($path);
if ($content === false) {
throw new RuntimeException("读取失败: {$path}");
}
return $content;
}
public static function write(string $path, string $content, int $flags = 0): int
{
$bytes = file_put_contents($path, $content, $flags);
if ($bytes === false) {
throw new RuntimeException("写入失败: {$path}");
}
return $bytes;
}
public static function append(string $path, string $content): int
{
return self::write($path, $content, FILE_APPEND | LOCK_EX);
}
public static function readJson(string $path, bool $assoc = true): mixed
{
$content = self::read($path);
$data = json_decode($content, $assoc);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON 解析失败: ' . json_last_error_msg());
}
return $data;
}
public static function writeJson(string $path, mixed $data, bool $pretty = true): int
{
$flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
if ($pretty) {
$flags |= JSON_PRETTY_PRINT;
}
return self::write($path, json_encode($data, $flags));
}
public static function readLines(string $path, bool $skipEmpty = true): array
{
$flags = FILE_IGNORE_NEW_LINES;
if ($skipEmpty) {
$flags |= FILE_SKIP_EMPTY_LINES;
}
$lines = file($path, $flags);
return $lines ?: [];
}
}
// 使用示例
FileHelper::writeJson('/tmp/test.json', ['name' => 'test', 'value' => 42]);
$data = FileHelper::readJson('/tmp/test.json');
FileHelper::append('/tmp/log.txt', "[INFO] 测试完成\n");注意事项
大文件处理
php
<?php
// file_get_contents 不适合读取超大文件(可能耗尽内存)
// 对于大文件应使用生成器逐行读取
function readLargeFile(string $path): Generator
{
$handle = fopen($path, 'r');
if ($handle === false) {
throw new RuntimeException("无法打开文件: {$path}");
}
try {
while (($line = fgets($handle)) !== false) {
yield $line;
}
} finally {
fclose($handle);
}
}
// 使用
foreach (readLargeFile('/tmp/huge.log') as $line) {
if (str_contains($line, 'ERROR')) {
echo $line;
}
}FILE_APPEND 与 LOCK_EX 同时使用
php
<?php
// PHP 7.2.14+ 可以同时使用 FILE_APPEND | LOCK_EX
file_put_contents('/tmp/log.txt', $message, FILE_APPEND | LOCK_EX);
// 旧版本需要手动 flock
$handle = fopen('/tmp/log.txt', 'a');
if (flock($handle, LOCK_EX)) {
fwrite($handle, $message);
flock($handle, LOCK_UN);
}
fclose($handle);LOCK_EX 阻塞
LOCK_EX 是阻塞锁,在高并发写入场景可能成为性能瓶颈。考虑使用消息队列或 Redis 进行异步写入。
原子写入
php
<?php
// 原子写入:先写临时文件,再 rename
function atomicWrite(string $path, string $content): void
{
$tempPath = $path . '.' . getmypid() . '.tmp';
file_put_contents($tempPath, $content, LOCK_EX);
if (!rename($tempPath, $path)) {
@unlink($tempPath);
throw new RuntimeException("原子写入失败: {$path}");
}
}rename 原子性
rename() 在同一个文件系统上是原子操作,可以保证文件内容的完整性。
最佳实践
1. 便捷函数与 fopen 的选择
php
<?php
// 适合 file_get_contents/file_put_contents 的场景:
// - 小文件(< 1MB)
// - 一次性读写
// - 配置文件、模板文件
$content = file_get_contents('config.json');
// 适合 fopen 系列的场景:
// - 大文件
// - 需要逐行处理
// - 需要文件锁定
// - 需要读写指针操作
$handle = fopen('huge.csv', 'r');2. 安全读取远程内容
php
<?php
// 读取远程 URL 时设置超时
$context = stream_context_create([
'http' => [
'timeout' => 10,
'method' => 'GET',
],
]);
$content = file_get_contents('https://example.com/api/data', false, $context);
if ($content === false) {
throw new RuntimeException('远程文件读取失败');
}3. JSON 配置文件管理
php
<?php
// 推荐 JSON 格式的配置文件(比 INI 支持嵌套和更多数据类型)
$configPath = __DIR__ . '/config.json';
// 读取
$config = json_decode(file_get_contents($configPath), true);
// 修改
$config['debug'] = false;
$config['database']['port'] = 3307;
// 保存(原子写入)
$tempPath = $configPath . '.tmp';
file_put_contents($tempPath, json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
rename($tempPath, $configPath);4. 错误处理模板
php
<?php
declare(strict_types=1);
function loadConfig(string $path): array
{
if (!file_exists($path)) {
throw new InvalidArgumentException("配置文件不存在: {$path}");
}
$content = file_get_contents($path);
if ($content === false) {
throw new RuntimeException("配置文件读取失败: {$path}");
}
$data = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException(
"JSON 解析错误: " . json_last_error_msg() . " (文件: {$path})"
);
}
return $data;
}