跳至内容

PHP 编程/文件

来自维基教科书,开放世界中的开放书籍

在任何编程语言中,处理文件都是一项重要部分,PHP 也不例外。无论你出于什么原因想要操作文件,PHP 都将通过使用一些函数来满足你的需求。在你开始这里之前,你应该已经阅读并熟悉了本书前五节中介绍的概念。

文件夹

[编辑 | 编辑源代码]

要显示当前目录:dirname()。

要更改目录:chdir()。

要创建一个新目录:mkdir()。

fopen()fclose()

[编辑 | 编辑源代码]

fopen() 是文件操作的基础。它以你指定的某种模式打开一个文件,并返回一个句柄。使用这个句柄,你可以在关闭文件之前读取或写入文件,并使用fclose() 函数将其关闭。

示例用法
<?php
$handle = fopen('data.txt', 'r'); // Open the file for reading
fclose($handle); // Close the file
?>


在上面的示例中,你可以看到文件通过指定'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
此函数可以从文件读取最多指定数量的字节,并将其作为字符串返回。在大多数情况下,前两个函数会更可取,但有时需要使用此函数。

正如你所见,通过这三个函数,你可以轻松地将数据从文件读取到方便操作的形式。下一部分将展示如何使用这些函数来完成其他函数的工作,但这部分是可选的。如果你不感兴趣,可以跳过它,直接进入写入部分。

<?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";
}

/* This is equivalent to file_get_contents($file), but is less efficient */
$handle = fopen($file, 'r');
$contents = fread($handle, filesize($file));
fclose($handle);

/* This is equivalent to file($file), but requires you to check for the line-ending
type. Windows systems use \r\n, Macintosh \r and Unix \n. File($file) will
automatically detect line-endings whereas fread/file_get_contents won't */
$lineEnding = detectLineEndings($contents);
$contents = file_get_contents($file);
$lines = explode($lineEnding, $contents);

/* This is also equivalent to file_get_contents($file) */
$lines = file($file);
$contents = implode("\n", $lines);

/* This is equivalent to fread($file, 64), if the file is ASCII encoded */
$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'); // Open the file and delete its contents
$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'); // Open the file for appending
$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'); // Open the file only, if it doesn't exist
$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'使用最多,但写入过程在本质上对所有模式都相同。

读取写入

[编辑 | 编辑源代码]

如果你想使用fopen() 来打开一个文件,以便同时进行读取写入,你只需要在模式的末尾添加一个'+'。例如,从文件读取需要'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'); // The same applies for '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)) {
	// No point in reading since there is no content
	$contents = '';
	
	// But might want to create the file instead
	$handle = @ fopen($file, 'x'); // Still need to error-check
	if(!$handle) {
		echo 'PHP does not have permission to create a file in the current directory.';
	} else {
		fwrite($handle, 'Default data');
		fclose($handle);
	}
} else {
	// The file does exist so we can try to read its contents
	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);
}
?>


从最后一个示例可以看出,错误检查使您的代码非常健壮。它使代码能够为大多数情况做好准备并做出相应的行为,这是任何程序或脚本必不可少的一部分。

行尾

[edit | edit source]

行尾在本章“读取”部分的最后一个示例中简要提及,在处理文件时需要注意它们。从文本文件读取数据时,重要的是要知道该文件包含哪种类型的行尾。'行尾'是特殊字符,它们试图告诉程序显示新行。例如,记事本只有在找到新行之前的 "\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";
}
?>

不过,大多数情况下,只需要了解它们在文本中的存在,以便您可以调整脚本以正确地处理它们。

二进制安全

[edit | edit source]

到目前为止,本章中看到的所有文本都假定为以某种形式的纯文本编码(如 UTF-8 或 ASCII)编码。但是,文件不必采用这种格式,实际上,存在大量非这种格式的格式(例如图片或可执行文件)。如果您想使用这些文件,则必须确保您使用的函数是'二进制安全的'。以前,您必须在模式的末尾添加 'b' 以告诉 PHP 将文件视为二进制文件。如果这样做,将会导致意外的结果和通常的'怪异'数据。

从 PHP 4.3 开始,这不再是必需的,因为 PHP 会自动检测是否需要将文件打开为文本文件或二进制文件,因此您仍然可以遵循此处显示的大多数示例。

处理二进制数据与处理纯文本字符串和字符有很大不同,它涉及许多超出本章范围的函数。但是,了解这些区别很重要。

序列化

[edit | edit source]

序列化是程序员用来保存其工作数据的技术,这种格式可以稍后恢复到其以前的形式。在简单的情况下,这意味着将一个普通变量(如数组)转换为字符串,然后将其存储在某个地方。然后可以对该数据进行反序列化,程序员将能够再次使用该数组。

本书中有一整章专门介绍 序列化,因为它是一种需要了解如何有效使用它的一种有用的技术。这里提到它是因为序列化的一种主要用途是在数据库不可用时将数据存储在普通文件上。它还用于存储脚本的状态和缓存数据以供稍后更快地访问,文件是这种存储的首选介质之一。

在 PHP 中,序列化通过使用 serialize()unserialize() 函数很容易执行。下面是一个序列化与文件函数结合使用的示例。

将用户详细信息存储在文件中的示例,以便以后可以轻松检索。

代码:

<?php
/* This part of the script saves the data to a file */
$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);

/* Then, later on, we retrieve the data from the file and output it */
$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

[edit | edit source]

在 PHP 5 中引入了一个特定于文件的函数。那就是 file_put_contents() 函数。它提供了写入文件的替代方法,该方法在 PHP 4 中不存在。要了解其区别,最简单的方法是查看一个示例。

显示在 PHP 4 中写入文件以及在 PHP 5 中使用 file_put_contents() 函数进行等效操作的示例
<?php
$file = 'data.txt';
$content = 'New content.';

// PHP 4, overwrite entire file with data
$handle = fopen($file, 'w');
fwrite($handle, $content);
fclose($handle);

// PHP 5
file_put_contents($file, $content);

// PHP 4, append to a file
$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;
	}
}
?>


华夏公益教科书