PHP编程
文件
处理文件是任何编程语言中重要的一部分,PHP 也不例外。无论你处理文件的原因是什么,PHP 都可以通过一系列函数来满足这些需求。在开始之前,确保你已经阅读并理解了本书前五个章节中介绍的概念。
文件夹操作
- 显示当前目录:
dirname()
。 - 改变目录:
chdir()
。 - 创建新目录:
mkdir()
。
fopen() 和 fclose()
fopen()
是文件操作的基础。它以你指定的模式打开文件并返回一个句柄。使用这个句柄,你可以读取或写入文件,然后用 fclose()
函数关闭它。
示例用法:
<?php
$handle = fopen('data.txt', 'r'); // 打开文件以供读取
fclose($handle); // 关闭文件
?>
在上面的例子中,文件是通过指定模式 'r'
打开以供读取的。关于 fopen()
可用的所有模式的完整列表,可以参考 PHP 手册。
打开和关闭文件本身没有问题,但为了执行有用的操作,你需要了解 fread()
和 fwrite()
。
当 PHP 脚本执行完毕时,所有打开的文件会自动关闭。因此,尽管在打开文件后不严格要求必须关闭文件,但作为良好的编程习惯,应该显式关闭文件。
读取文件
文件的读取可以通过多种方式进行。如果你只想获取文件的所有内容,可以使用 file_get_contents()
函数。如果你希望将文件的每一行作为数组元素,你可以使用 file()
函数。要对文件的读取进行完全控制,可以使用 fread()
。
这些函数通常可以互换使用,每个函数都可以完成另一个函数的功能。前两个函数不需要你先用 fopen()
打开文件,也不需要用 fclose()
关闭文件。它们适用于快速、一时性的文件操作。如果你计划对文件进行多次操作,最好将 fopen()
与 fread()
、fwrite()
和 fclose()
结合使用,因为它更高效。
使用 file_get_contents()
的示例:
<?php
$contents = file_get_contents('data.txt');
echo $contents;
?>
输出:
I am the contents of data.txt
这个函数将整个文件读入一个字符串,从此你可以像处理字符串一样处理它。
使用 file()
的示例:
<?php
$lines = file('data.txt');
foreach($lines as $Key => $line) {
$lineNum = $Key + 1;
echo "Line $lineNum: $line";
}
?>
输出:
Line 1: I am the first line of file
Line 2: I am the second line the of the file
Line 3: If I said I was the fourth line of the file, I'd be lying
这个函数将整个文件读入一个数组。数组中的每一项对应文件中的一行。
使用 fread()
的示例:
<?php
$handle = fopen('data.txt', 'r');
$string = fread($handle, 64);
fclose($handle);
echo $string;
?>
输出:
I am the first 64 bytes of data.txt (if it was ASCII encoded). I
这个函数可以从文件中读取指定数量的字节,并将其作为字符串返回。通常情况下,前两个函数更为常用,但在某些情况下,fread()
是必需的。
通过这三个函数,你可以轻松地将文件中的数据读取到适合操作的格式中。接下来的部分将展示如何使用这些函数来实现彼此的功能,但这部分是可选的。如果你不感兴趣,可以跳过并直接进入写入部分。
以下是更复杂的示例:
<?php
$file = 'data.txt';
function detectLineEndings($contents) {
if(false !== strpos($contents, "\r\n")) return "\r\n";
else if(false !== strpos($contents, "\r")) return "\r";
else return "\n";
}
/* 这等同于 file_get_contents($file),但效率较低 */
$handle = fopen($file, 'r');
$contents = fread($handle, filesize($file));
fclose($handle);
/* 这等同于 file($file),但你需要检查行结束符类型。
Windows 系统使用 \r\n,Mac 系统使用 \r,Unix 系统使用 \n。
file($file) 会自动检测行结束符,而 fread/file_get_contents 不会 */
$lineEnding = detectLineEndings($contents);
$contents = file_get_contents($file);
$lines = explode($lineEnding, $contents);
/* 这也等同于 file_get_contents($file) */
$lines = file($file);
$contents = implode("\n", $lines);
/* 这等同于 fread($file, 64),如果文件是 ASCII 编码 */
$contents = file_get_contents($file);
$string = substr($contents, 0, 64);
?>
通过这些函数和方法,你可以更灵活地处理文件内容,无论是简单的读取操作还是更复杂的文件解析需求。
写入文件
使用 fwrite()
函数结合 fopen()
和 fclose()
来向文件写入内容。正如你所看到的,写入文件的选项没有读取文件时那么多。然而,PHP 5 引入了一个简化写入过程的函数 file_put_contents()
。该函数将在 PHP 5 部分讨论,因为它比较直观,不需要在这里深入讨论。
写入的额外选项并不是来自于函数的数量,而是来自于打开文件时可用的模式。如果你希望向文件写入内容,可以传递三种不同的模式给 fopen()
函数。第一种模式 'w'
会清空文件的所有内容,之后写入的内容将完全替换原有的内容。第二种模式 'a'
会将内容追加到文件的末尾。第三种模式 'x'
只会在文件不存在时使用。所有三种写入模式都会尝试创建文件,如果文件不存在,而 'r'
模式则不会。
使用 'w'
模式的示例:
<?php
$handle = fopen('data.txt', 'w'); // 打开文件并清空内容
$data = "I am new content\nspread across\nseveral lines.";
fwrite($handle, $data);
fclose($handle);
echo file_get_contents('data.txt');
?>
输出:
I am new content
spread across
several lines.
使用 'a'
模式的示例:
<?php
$handle = fopen('data.txt', 'a'); // 打开文件以追加内容
$data = "\n\nI am new content.";
fwrite($handle, $data);
fclose($handle);
echo file_get_contents('data.txt');
?>
输出:
I am the original content.
I am new content.
使用 'x'
模式的示例:
<?php
$handle = fopen('newfile.txt', 'x'); // 只在文件不存在时打开文件
$data = "I am this file's first ever content!";
fwrite($handle, $data);
fclose($handle);
echo file_get_contents('newfile.txt');
?>
输出:
I am this file's first ever content!
在上面展示的三种模式中,'w'
和 'a'
是最常用的,但所有模式的写入过程本质上是相同的。
读取与写入
如果你想同时读取和写入一个文件,只需在模式的末尾加上一个 +
。例如,读取文件需要使用 'r'
模式。如果你想同时读取和写入该文件,则需要使用 'r+'
模式。类似地,你也可以使用 'w+'
模式来读取和写入文件,但这种模式会将文件截断为零长度。有关更多详细信息,参考 fopen()
页面,其中有一个很有用的表格列出了所有可用的模式。
错误检查
错误检查对于任何编程任务都非常重要,尤其是在 PHP 中处理文件时。进行错误检查的主要原因来自于文件所在的文件系统。如今,大多数 Web 服务器都是基于 Unix 的,因此,如果你正在使用 PHP 开发 Web 应用程序,你必须考虑文件权限。在某些情况下,PHP 可能没有权限读取文件,这将导致你编写的读取代码出现错误。更常见的情况是,PHP 没有权限写入文件,这同样会导致错误。而且,文件的存在(显然)是很重要的。在尝试读取文件时,必须确保文件存在。另一方面,如果你尝试使用 'x'
模式创建并写入文件,那么必须确保文件不存在。
简而言之,在编写与文件交互的代码时,始终假设最糟的情况。假设文件不存在,并且你没有权限读取或写入它。在大多数情况下,这意味着你需要告诉用户,为了让脚本正常工作,他们需要调整文件权限,以便 PHP 能够创建文件并读取/写入它们。但这也意味着你的脚本可以进行调整并执行替代操作。
错误检查的两种主要方法
第一种方法是使用 @
操作符来抑制在操作文件时发生的任何错误,然后检查结果是否为 false
。第二种方法是使用 file_exists()
、is_readable()
和 is_writeable()
等函数。
使用 @
操作符的示例:
<?php
$handle = @fopen('data.txt', 'r');
if (!$handle) {
echo 'PHP does not have permission to read this file or the file in question doesn\'t exist.';
} else {
$string = fread($handle, 64);
fclose($handle);
}
$handle = @fopen('data.txt', 'w'); // 'a' 同样适用
if (!$handle) {
echo 'PHP either does not have permission to write to this file or it does not have permission to create this file in the current directory.';
} else {
fwrite($handle, 'I can has content?');
fclose($handle);
}
$handle = @fopen('data.txt', 'x');
if (!$handle) {
echo 'Either this file exists or PHP does not have permission to create this file in the current directory.';
} else {
fwrite($handle, 'I can has content?');
fclose($handle);
}
?>
如上所示,@
操作符主要用于与 fopen()
函数一起使用。它也可以用于其他情况,但通常效率较低。
使用特定检查函数的示例:
<?php
$file = 'data.txt';
if (!file_exists($file)) {
// 文件不存在,不需要读取内容
$contents = '';
// 但可能需要创建文件
$handle = @fopen($file, 'x'); // 仍然需要进行错误检查
if (!$handle) {
echo 'PHP does not have permission to create a file in the current directory.';
} else {
fwrite($handle, 'Default data');
fclose($handle);
}
} else {
// 文件存在,可以尝试读取其内容
if (is_readable($file)) {
$contents = file_get_contents($file);
} else {
echo 'PHP does not have permission to read that file.';
}
}
if (file_exists($file) && is_writeable($file)) {
$handle = fopen($file, 'w');
fwrite($handle, 'I can has content?');
fclose($handle);
}
?>
从最后的例子中可以看出,错误检查使得你的代码非常健壮。它可以准备应对大多数情况并作出相应的处理,这是任何程序或脚本的关键特性。
行结束符
在本章“读取”部分的最后一个示例中,简要提到过行结束符,在处理文件时,了解它们非常重要。当从文本文件中读取数据时,了解该文件使用了什么类型的行结束符非常关键。行结束符是特殊字符,用来告诉程序显示一个新行。例如,Notepad 只有在找到 \r\n
(回车换行符)时,才会将文本移到新的一行(如果启用自动换行,也会显示新的行)。
如果某人在 Windows 系统上写入文本文件,那么每一行很可能会以 \r\n
结束。类似地,在经典的 Macintosh(Mac OS 9 及以下)系统上写入的文件,每一行很可能以 \r
结束。最后,在基于 Unix 的系统(如 Mac OS X 和 GNU/Linux)上写入的文件,每一行通常以 \n
结束。
这为什么重要呢?因为当你使用 file_get_contents()
读取文件时,读取到的字符串会包含这些行结束符,并且这些字符会分散在整个字符串中。有时这些行结束符会影响你想要对字符串做的操作,因此可以使用以下代码移除它们:
<?php
$string = str_replace(array("\n", "\r"), '', $string);
?>
有时,你可能需要知道文本中使用了什么类型的行结束符,以便在添加新文本时保持一致性。幸运的是,在 99% 的情况下,行结束符类型不会在文本中变化,因此可以使用自定义函数 detectLineEndings
来快速检测:
<?php
function detectLineEndings($string) {
if(false !== strpos($string, "\r\n")) return "\r\n";
else if(false !== strpos($string, "\r")) return "\r";
else return "\n";
}
?>
不过大多数情况下,只要意识到文本中存在行结束符,就足够了,你可以根据需要调整脚本来妥善处理。
二进制安全
到目前为止,本章中提到的所有文本都假定采用某种形式的纯文本编码(如 UTF-8 或 ASCII)。然而,文件不一定要采用这种格式,事实上,存在大量不同的文件格式(如图片或可执行文件)。如果你想处理这些文件,必须确保你使用的函数是“二进制安全”的。过去,你需要在模式后添加 b
来告诉 PHP 将文件当作二进制文件处理。如果没有这样做,可能会得到意外的结果,通常是“看起来很奇怪”的数据。
自 PHP 4.3 版本以来,这种做法已不再必要,因为 PHP 会自动检测文件是否需要以文本文件或二进制文件的方式打开,因此你仍然可以遵循这里展示的大部分示例。
与纯文本字符串和字符操作不同,处理二进制数据有很多不同的地方,并且涉及的函数更多,超出了本章的讨论范围。然而,了解这些差异仍然很重要。
序列化
序列化是一种用于程序员的技术,它可以将工作数据保存为一种格式,稍后可以恢复到原来的形式。在简单的情况下,这意味着将一个普通变量(如数组)转换为字符串并将其存储在某个地方。然后,这些数据可以被反序列化,程序员就可以再次使用这个数组。
本书有一个完整的章节介绍序列化,因为它是一个非常有用的技术,掌握它能够有效地使用。在这里提到它,是因为序列化的一个主要用途是在没有数据库的情况下,将数据存储到普通文件中。它还可以用来存储脚本的状态或缓存数据,以便稍后更快速地访问,而文件是其中最常用的存储介质。
在 PHP 中,序列化非常简单,只需要使用 serialize()
和 unserialize()
函数。以下是使用序列化与文件操作结合的示例:
将用户详细信息存储到文件中,以便以后可以轻松检索。
<?php
/* 该部分脚本将数据保存到文件中 */
$data = array(
'id' => 114,
'first name' => 'Foo',
'last name' => 'Bartholomew',
'age' => 21,
'country' => 'England'
);
$string = serialize($data);
$handle = fopen('data.dat', 'w');
fwrite($handle, $string);
fclose($handle);
/* 然后,稍后从文件中检索数据并输出 */
$string = file_get_contents('data.dat');
$data = unserialize($string);
$output = '';
foreach ($data as $key => $datum) {
$field = ucwords($key);
$output .= "$field: $datum\n";
}
echo $output;
?>
输出:
Id: 114
First Name: Foo
Last Name: Bartholomew
Age: 21
Country: England
PHP 5
PHP 5 引入了一个特定的文件操作函数,即 file_put_contents()
。它提供了一种替代写入文件的方法,这在 PHP 4 中是没有的。为了理解它的区别,最简单的方式是通过一个例子来看。
PHP 4 与 PHP 5 使用 file_put_contents()
函数写入文件的示例:
<?php
$file = 'data.txt';
$content = 'New content.';
// PHP 4,覆盖整个文件的数据
$handle = fopen($file, 'w');
fwrite($handle, $content);
fclose($handle);
// PHP 5
file_put_contents($file, $content);
// PHP 4,向文件追加内容
$handle = fopen($file, 'a');
fwrite($handle, $content);
fclose($handle);
// PHP 5
file_put_contents($file, $content, FILE_APPEND);
?>
file_put_contents()
还会尝试创建文件,如果文件不存在,它是二进制安全的。file_get_contents()
并没有 x
模式的等价函数。
file_put_contents()
通常比 fopen()
方法更可取,尤其是在对同一文件执行多次操作时。它在写入时比 file_get_contents()
读文件时更具优势,因此,下面提供了一个函数来模拟 PHP 4 中 file_put_contents()
的行为:
<?php
if (!function_exists('file_put_contents')) {
function file_put_contents($file, $data, $append = false) {
if (!$append) $mode = 'w';
else $mode = 'a';
$handle = @fopen($file, $mode);
if (!$handle) return false;
$bytes = fwrite($handle, $data);
fclose($handle);
return $bytes;
}
}
?>