跳至內容

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


華夏公益教科書