今天我們將開啟一個新的探索旅程,深入到 PHP 文件系統中,系統的學習和掌握 PHP 文件系統的基本使用。
相信大家在日常研發過程中,難免需要和各種文件糾纏不清。比如,打開 .env 文件并從中讀取配置信息、把項目中的錯誤信息寫入到日志文件中或者獲取圖片的創建時間等等。在處理這些功能時,我們都需要使用到 PHP 文件系統接口。
下面是本文所涉主題的提綱:
一 什么是文件系統
二 深入 PHP 文件系統
三 html' target='_blank'>面向對象的目錄遍歷
四 PHP 文件系統思維導圖
本文較長,耗時約 20 分鐘,請做好戰斗準備!
一 什么是文件系統開始之前,我們首先需要厘清我們所研究的問題領域,理解什么是文件系統,還有我們所研究的對象。
在計算機中,文件系統(file system or filesystem)用于管理數據如何存儲和如何被獲取的。 - 維基百科簡單來說,就是我們應該如何管理我們的目錄(文件夾)和文件。通常,我們將具有相似屬性的文件,存儲到同一個目錄中以便后續查找,這個常見的操作就會涉及到目錄和文件。
對于軟件工程師來講,一個非常典型的使用場景,就是在開發 MVC 項目時,將控制器、視圖和模型等模塊的文件,存儲到不同的目錄結構中方便管理。
無論如何,我們依據不同特性劃分文件和目錄都是為了解決文件存儲和查找的問題。
有了這些認知后,應該自然而然的想到我們當前研究的 PHP 文件系統(或者說文件系統)其所研究的對象,簡單概括起來就是:
目錄(文件夾)
文件
也就是說,本文我們所講解的 PHP 文件系統函數處理,基本都是圍繞目錄和文件展開的。
二 深入 PHP 文件系統在 PHP 文件系統中內置提供了超過 80 個可用的 文件系統函數。由于數量繁多功能強大,自然本文無法將對所有的系統函數逐一講解。一來,時間過于倉促;再者,短時間內我們也沒有那么多的精力將它們全部掌握。
盡管如此,大家也不必氣餒,本文會將有限的時間和精力,來研究以下幾個在文件處理時的常見話題:
文件的元數據應該如何獲取
文件的 MIME 類型如何獲取
文件和目錄的操作處理
文件和目錄的權限管理
另外,補充說明一點,PHP 標準函數庫不僅為我們提供了面向過程的文件系統處理函數。同時,還封裝了常用目錄及文件操作的面向對象接口和迭代器接口方便大家使用:
SplFileInfo
finfo
DirectoryIterator
RecursiveDirectoryIterator
2.1 文件系統的元數據2.1.1 什么是元數據元數據(meta data):通俗一點講就是「數據的數據」。拿一個 php 文件來說它的元數據可以是 創建時間、文件名、文件大小 或 文件所有權限 等,這類能夠表明該文件基本特征的數據就是「元數據(meta data)」了。
2.1.2 常用元數據獲取在這一節,我們將學習一些經常需要獲取的文件元數據函數,包括:
獲取文件的最后修改時間
獲取文件的上次訪問時間
獲取文件的路徑信息
獲取文件的絕對路徑
獲取文件類型
獲取文件大小
獲取文件權限
獲取文件所屬用戶及用戶組
話不多說,擼起袖子開干吧!
獲取文件的最后修改時間
要獲取文件的上次被修改時間戳,我們可以使用函數 filemtime($filename) 或 SplFileInfo::getMTime() 方法。
注意 SplFileInfo 類實例化時接收 $filename 文件路徑作為參數,后續沒有特別說明默認我們已經獲取到了 SplFileInfo 實例才能進行 getMTime() 等類似處理。// 文件路徑請求改成你自己的文件路徑$filename = f://filesystem/test.txt // 面向過程: 獲取文件時間$modifyTimestamp = filemtime($filename);// 面向對象$file = new SplFileInfo($filename);$modifyTimestamp = $file- getMTime();
2 獲取文件的上次訪問時間
可以使用函數 fileatile($filename) 或 SplFileInfo::getATime() 方法,來獲取文件的最后被訪問時間戳。
// 文件路徑請求改成你自己的文件路徑$filename = f://filesystem/test.txt // 面向過程: 獲取文件時間$accessTimestamp = fileatime($filename);// 面向對象$file = new SplFileInfo($filename);$accessTimestamp = $file- getATime();
除了 filemtile 和 fileatime 之外,還有 filectime 來獲取文件的 inode 修改時間(可認為是創建時間)。
有關時間的函數常用的就這些,為了方便記住,我們來看看它們是如何命名的:
2.1 面向過程 file 前綴,面向對象 get 前綴
2.2 a: access(訪問);m:modify(修改);c:create(創建)
2.3 time 后綴
2.4 fileatime,SplFileInfo::getATime;filemtime,SplFileInfo::getMTime;filectime,SplFileInfo::getCTime。
是不是很簡單呢!
注意,使用 filectime 時,對于 Windows 系統會獲取創建時間,但對于類 Unix 系統是修改時間,因為在類 Unix 系統中多數文件系統并沒有創建時間的概念。具體說明可以看 PHP: how can I get file creation date?。3 獲取文件的路徑信息
除了時間這些元數據,另一個經常遇到的情況是獲取文件的路徑信息,包括:
3.1 目錄信息
獲取目錄信息我們可以使用 pathinfo($filename, PATHINFO_DIRNAME)](http://php.net/manual/zh/function.pathinfo.php)**、**[dirname($filename) 和 SplFileInfo::getPath()
比如下面給出的文件:
$filename = F:/Program Files/SSH Communications Security/SSH Secure Shell/Output.txt
將會獲取到 F:/Program Files/SSH Communications Security/SSH Secure Shell 這部分目錄信息。
3.2 文件名信息
這里我們所有的文件名指的是不帶擴展名后綴的文件名稱,比如需要獲取 your_path/filename.txt 中的 filename 部分。
需要取得文件名信息,我們可以使用 pathinfo($filename, PATHINFO_FILENAME)](http://php.net/manual/zh/function.pathinfo.php)**、**[basename($filename, $suffix)](http://php.net/manual/zh/function.basename.php)** 和 **[SplFileInfo::getBasename($suffix) 獲取。
這里給出的 $suffix** 指不獲取 **$suffix 擴展名部分(比如不獲取 $suffix = .txt )。
請看下面的示例:
$filename = F:/Program Files/SSH Communications Security/SSH Secure Shell/Output.txt
將會獲取到 Output 這部分文件名信息。
3.3 擴展名信息
擴展名我們可以使用 pathinfo($filename, PATHINFO_EXTENSION) 和 SplFileInfo::getExtension() 方法拿到。
基于前面的了解,我們可以獲取到 txt 這部分擴展信息,這里不再贅述。
3.4 basename(文件名 + 擴展名)信息
basename 指的是 文件名 + 擴展名 內容信息,可以使用 pathinfo($filename, PATHINFO_BASENAME)](http://php.net/manual/zh/function.pathinfo.php)**、 **[basename($filename)、SplFileInfo::getBasename() 和 SplFileInfo::getFilename() 方法拿到。
雖然這里我們列出了很多的函數,但是基本上還是比較容易理解的,需要注意的是:
pathinfo 可以獲取所有文件相關的路徑信息,如果指定第二個參數選項將僅獲取該部分的信息
文件名和 basename 不是特別容易理解,你可以使用完全相同的方法或函數 basename 和 SplFileInfo::getBasename() 獲取他們,區別在于是否摘除指定的 $suffix 后綴。
3.5 示例
?php$filename = F:/Program Files/SSH Communications Security/SSH Secure Shell/Output.txt
echo --- directory begin: --- . PHP_EOL;echo $directory1 . PHP_EOL, $directory2 . PHP_EOL, $directory3 . PHP_EOL;// 文件名$suffix = .txt $filename1 = pathinfo($filename, PATHINFO_FILENAME);$filename2 = basename($filename, $suffix);$filename3 = $file- getBasename($suffix);echo --- filename begin: --- . PHP_EOL;echo $filename1 . PHP_EOL, $filename2 . PHP_EOL, $filename3 . PHP_EOL;// 擴展名$extension1 = pathinfo($filename, PATHINFO_EXTENSION);$extension2 = $file- getExtension();echo --- extension begin: --- . PHP_EOL;echo $extension1 . PHP_EOL, $extension2 . PHP_EOL;// basename = 文件名 + 擴展名$basename1 = pathinfo($filename, PATHINFO_BASENAME);$basename2 = basename($filename);$basename3 = $file- getBasename();$basename4 = $file- getFilename();echo --- basename begin: --- . PHP_EOL;echo $basename1 . PHP_EOL, $basename2 . PHP_EOL, $basename3 . PHP_EOL, $basename4 . PHP_EOL;
它們的運行結果如下:
--- directory begin: ---F:/Program Files/SSH Communications Security/SSH Secure ShellF:/Program Files/SSH Communications Security/SSH Secure ShellF:/Program Files/SSH Communications Security/SSH Secure Shell--- filename begin: ---OutputOutputOutput--- extension begin: ------ basename begin: ---Output.txtOutput.txtOutput.txtOutput.txt
3.6 文件路徑信息關系圖
4 獲取文件的絕對路徑
絕對路徑由 realpath($path) 和 SplFileInfo::getRealpath() 獲取。
5 獲取文件類型
可以使用 filetype($filename) 和 SplFileInfo::getType() 來獲取文件的類型。
返回值范圍:
dir
file
char
fifo
block
link
unknown
可以查看 Linux 文件類型與擴展名 相關文件類型,這里我們重點關注下 dir 目錄和 file 普通文件類型即可。
6 獲取文件大小
可以使用 filesize($filename) 和 SplFileInfo::getSize() 來獲取文件的大小,不再贅述。
7 獲取文件權限
可以使用 fileperms($filename) 和 SplFileInfo::getPerms() 來獲取到文件的所屬權限。
值得注意的是它們的返回值是十進制表示的權限,如果需要獲取類似 0655 八進制權限表示法,我們需要對返回值進行處處理才行:
// @see http://php.net/manual/zh/function.fileperms.php#refsect1-function.fileperms-examples$permissions = substr(sprintf( %o , fileperms($filename)), -4);
你可以通過 PHP: fileperms() values and convert these 了解更多關于 PHP 獲取文件權限轉換的更多細節。
基本上學習完這些文件元數據信息獲取方法,差不多可以應對日常開發過程中的多數應用場景,盡管如此,還是建議仔細去閱讀官方 文件系統函數,那里才是知識的源泉。
掌握文件的元數據,對我們了解文件的特性大有裨益,就好比兩個人談戀愛,懂得彼此才是最好的狀態。
2.2 文件系統操作可以說我們日常在處理文件的過程中,更多的是在操作文件或者目錄(文件夾),本節我們將學習文件系統操作相關知識。
依據文件類型的不同我們可以簡單的將操作分為:
對目錄(dir)的操作
和對普通文件(file)的操作
2.2.1 目錄操作使用場景在處理目錄時我們一般涉及如下處理:
創建目錄
刪除目錄
打開目錄
讀取目錄
關閉目錄句柄
場景一
我們有一套 CMS 管理系統支持文件上傳處理,當目錄不存在時依據文件上傳時間,動態的創建文件存儲目錄,比如,我們依據 年/月/日(2018/01/01) 格式創建目錄。這里就涉及到 目錄創建 的處理。
場景二
當然,文件上傳完成了還不夠,我們還需要讀取各個目錄下的所有文件。這里涉及 打開目錄、讀取目錄 以及讀取完成后 關閉目錄句柄。
有了相關概念和思路后,我們具體看看究竟 PHP 文件系統給我們提供了哪些方便處理目錄的函數呢?
2.2.1.1 創建目錄在 PHP 文件系統擴展中同樣給我們提供了處理 目錄結構的系統函數。
其中創建一個新目錄需要使用 [mkdir($pathname [, $mode = 0777, $recursive = false])](http://php.net/manual/zh/func... 函數。
$pathname 參數為待創建目錄的路徑
$mode 為創建目錄時的訪問權限,0777 意味著獲取最大訪問權限
$recursive 用于標識是否遞歸創建目錄,默認 false 不會遞歸創建
請看一個示例:
$pathname = /path/to/your/upload/file/2018/01/01 $created = mkdir($pathname);
創建目錄是不是特別的簡單呢?
但是等等,我們在類 Unix 系統中滿心歡喜的使用 mkdir 并采用 $mode=0777 權限來創建一個全新的目錄,但為什么當我們進入到目錄中看到的目錄的權限卻是 0755 呢?
umask 掩碼這里涉及到 umask 掩碼的問題!
重點: 原來我們在類 Unix 系統中創建新目錄是給出的權限會默認減去當前系統的 umask 值,才是實際創建目錄時的所屬權限。
什么意思呢?
比如:
// 我們期望創建的文件權限$mode = 0777;// 當前系統中 umask 值$umask = 0022;// 可以由 umask 命令查看當前系統 umask 值,默認是 0022// 實際創建的文件權限 0777- 0022------= 0755
現在我們來對之前的實例稍作修改,看看 PHP 如何創建目錄時得到希望的系統權限吧:
$pathname = /path/to/your/upload/file/2018/01/01 // 將系統 umask 設置為 0,并取得當前 umask 值(比如默認 0022)$umask = umask(0);$created = mkdir($pathname, $mode = 0777);// 將系統 umask 設置回原值umask($umask);
有關 umask 函數說明可以查看官方手冊。另外可以查看 Why can t PHP create a directory with 777 permissions? 這個問答了解更多細節。
2.2.1.2 目錄遍歷面向過程的目錄遍歷提供兩種解決方案:
通過 opendir、readdir 和 closedir 來遍歷目錄;
另一種是直接使用 scandir 遍歷指定路徑中的文件和目錄。
目錄遍歷示例一,出自 官方文檔:
?php$dir = /etc/php5/ // Open a known directory, and proceed to read its contentsif (is_dir($dir)) { if ($dh = opendir($dir)) { while (($file = readdir($dh)) !== false) { echo filename: $file : filetype: . filetype($dir . $file) . /n closedir($dh);// 輸出結構類似于:// filename: . : filetype: dir// filename: .. : filetype: dir// filename: apache : filetype: dir// filename: cgi : filetype: dir// filename: cli : filetype: dir?
目錄遍歷示例二,出自 官方文檔:
?php$dir = /tmp $files1 = scandir($dir);print_r($files1);// 輸出結構類似于:// Array// [0] = .// [1] = ..// [2] = bar.php// [3] = foo.txt// [4] = somedir// )
目錄的操作處理大致就是在處理這兩類問題,相比于普通文件的處理來講簡單很多,下一節我們會學習有關普通文件的處理,請大家做好戰斗準備。
2.2.2 文件操作使用場景可以說我們在處理文件系統時,絕大多數都是在處理一個普通文件,那么我們在操作文件時,我們究竟在做什么呢?
你可能已經想到了,沒錯我們多數時候就是在處理如下文件問題:
創建一個新的空文件
打開一個文件句柄,以供后續讀取或寫入
將文件中的內容覆蓋掉(覆蓋寫入),或者在文件末尾寫入新的內容(追加寫入)
讀取文件的內容
刪除文件
復制文件
關閉文件句柄
文件的讀取和寫入相對會復雜一些,所以這兩部分的內容會在稍后詳細講解。先讓我們看看其它幾個常見文件處理。
2.2.2.1 創建空文件創建空文件有兩種方式:
一是:以寫入(w)模式使用 fopen($filename, $mode = wb ) 打開一個文件,當文件不存在時則會創建一個新文件;
二是:使用 touch 函數創建一個新文件。
這兩個函數同其它文件系統函數使用大致相同,感興趣的朋友可以閱讀手冊,這里不作展開。
2.2.2.2 刪除文件刪除文件由 unlink($filename) 函數完成。
2.2.2.3 復制文件復制文件由 copy($source, $dest) 函數完成,會將 $source 文件拷貝到 $dest 文件中。
如果需要移動文件(重命名)可以使用 rename($oldname, $newname) 完成這個處理。
以上都是相對簡單的文件處理函數就不一一舉例說明了。
接下來學習如何讀取文件中的內容。依據二八原則,可以說我們百分之八十的時間都在處理文件寫入和讀取的處理,所以我們有必要理清如何對文件進行讀取和寫入。
2.2.2.4 讀取文件讀取文件的標準流程是:
打開一個文件句柄;
使用文件讀取函數讀取文件;
判斷是否到文件結尾,到結尾則結束讀取,否則回到操作 2;
讀取完成關閉句柄;
開始之前我們需要準備一個有數據的文件,比如 F:/php_workspace/php-code-kata/read.txt,在看一個簡單的文件讀取示例:
?php// 這里為了貼合讀取文件的標準流程,使用 do{} while 語句,你也可以修改成 while 語句。
// 讀取顯示大致類似:// hello world!
現在,我們來詳細講解一下上述代碼做了什么處理吧:
使用 fopen($filename, $mode) 打開一個文件或 URL 句柄,供后續文件系統函數使用;
使用 fgetc($handle) 函數從文件句柄中讀取一個字符;
使用 feof($handle) 判斷文件句柄是否到文件的結尾處,否則繼續讀取文件;
當讀取完成后使用 fclose($handle) 關閉打開的文件句柄,完成文件讀取的所有操作。
總體來說,在讀取文件時按照以上處理流程,基本上太容易出錯的。不過即便如此,還是有些重點需要我們小心處理:
我們以什么模式打開一個文件句柄,示例中使用 $mode= rb r(read) 只讀模式開個一個文件句柄(只讀模式下不能對文件盡心寫入)。另外還有幾個常用模式可供使用:
r+ 讀寫模式
w(write) 覆蓋寫入
w+ 覆蓋讀寫
a(append) 追加寫入
a+ 追加讀寫
b 重點關注此模式,為增強項目可移植和健壯性,推薦所有模式添加「b」模式強制使用二進制模式
有關所有可用模式的說明可以從 模式 手冊中查找。
在執行文件內容讀取時除了逐字符讀取(fgetc),要支持一下集中讀取形式:
fgets($handle) 每次讀取一行數據
fgetss($handle) 每次讀取一行數據,并過來 HTML 標記
fgetcsv($handle) 讀取 CSV 文件,每次讀取一樣并解析字段
fread($handle, $length) 每次從句柄中最多讀取 $length 個字節。
處理可以從句柄中讀取文件數據,PHP 還提供將整個文件讀取的方法:
file($filename) 把整個文件讀入一個數組中
file_get_contents($filename) 將整個文件讀入一個字符串
注意: 讀取文件操作時我們推薦使用 file_get_contents。到這里我們基本上就涵蓋了文件讀取的所有知識點,相信大家對文件讀取已經有了一個比較系統的認知。
下面我們進入到文件寫入處理中,看看文件寫入的正確姿勢。
2.2.2.5 讀取寫入典型的文件寫入流程基本上和文件讀取流程一致:
打開一個文件句柄;
使用文件讀取函數向文件中寫入內容;
寫入完成關閉句柄。
依據慣例我們來看一個簡單的示例:
?php$filename = F://php_workspace//php-code-kata//read.txt // 1. 打開一個文件句柄;$handle = fopen($filename, $mode = ab // 2. 使用文件讀取函數向文件中寫入內容fwrite($handle, hello filesystem to write!/n // 3. 寫入完成關閉句柄;fclose($handle);注意:這里我們以追加寫入的模式 $mode = ab 寫入文件內容。
文件寫入就如同文件讀取一樣的簡單,相信大家能夠輕松掌握這方面的知識。然而,我們顯示世界可能充滿了荊棘,稍不留神可能就會深陷泥沼。比如:
我在寫入文件時,同時其他人也在對同一個文件進行寫入,怎么辦?我們可以使用 flock($handle, LOCK_EX) 加鎖函數進行獨占寫入。
每次都需要 打開文件、寫入、再關閉 是在麻煩!有沒有更簡單的方式寫文件呢?PHP 同樣為你考慮到了這點,所以提供了 [file_put_contents($filename, $data [, LOCK_EX])](http://php.net/manual/zh/func... 將一個字符串寫入文件,同樣的它也支持獨占寫入。
到這里,我們基本上就學習完 PHP 文件系統中大多數常用的函數了。然而就如我所說的那樣,現實世界總是殘酷的。尤其是在讀寫文件時,經常會遇到各種各樣的錯誤,我們應該如何才能避免呢?
嗯,PHP 一樣為我們內置了檢測文件有效性的函數,規避各種錯誤。
2.2.2.5 如何處理文件權限及檢測有效性文件有效性檢測
檢測文件的有效性能夠讓我們規避常見的開發錯誤,比如:
當相文件中寫入數據時,是不是需要檢測它有可寫的權限,并且它是不是一個文件而非文件夾?
讀取文件內容時,是不是需要查看下我們能不能對其進行讀取?
在安裝項目時,我們是不是需要檢測已經依據實例配置文件創建了實際的配置文件呢?
這些內容都需要使用到文件有效性檢測相關知識。
判斷文件是否可寫我們有:is_writable($filename) 和 SplFileInfo::isWritable()。
路徑目錄判斷:is_dir($filename)](http://php.net/manual/zh/function.is-dir.php) 和 **[SplFileInfo::isDir()](http://php.net/manual/zh/splfileinfo.isdir.php)**;文件判斷:[is_file($filename) 和 **SplFileInfo::isFile()。
檢測文件或目錄是否已經創建過,我們使用 file_exists($filename) 函數完成。
如何修改文件權限
當我們能夠正確的檢測文件是否存在時,我們還需要面對的問題時,如果我們的文件當前用戶 不可寫入,我們應該如何修改權限使其可寫呢?
這里就涉及修改文件權限操作,之前我們在創建目錄是已經接觸過 umask 掩碼相關知識。這里我們將講解那些已經創建的文件權限變更的方法。
通常,我們會使用 chmod($filename, $mode) 去修改一個文件的權限。
另外,還可以關注以下幾個權限相關的處理函數:
chgrp($filename, $group) 改變文件所屬的組
chown($filename, $user) 改變文件的所有者
以及,之前提到過的 umask 修改掩碼函數。
文章進行到這里,其實基本上 PHP 文件系統的所有知識都已經涉及到了。那么,下回見吧?不不不...
為了應對實戰(面試需要),我們可能需要進一步對目錄遍歷做更進一步的研究。還記得我們之前使用過 scandir 來遍歷指定路徑中的文件和目錄夾么?
現在我們將使用面向對象的接口來重新實現一個權限的目錄遍歷處理。
3 面向對象的目錄遍歷使用面向對象的接口來遍歷目錄,是一個非常有意義的教程,這里我們所涉及使用的接口包括:
DirectoryIterator 創建非遞歸的目錄迭代器
RecursiveDirectoryIterator 創建遞歸的目錄迭代器
RecursiveIteratorIterator 創建一個遞歸迭代器的迭代器(用于迭代獲取 RecursiveIteratorIterator 示例)
話不多說,我們看下如何創建一個功能強大的支持遞歸迭代的目錄迭代程序:
/** * 目錄掃描 * @method listContents($path, $recursive = false) 獲取目錄中所有文件及文件夾class DirectoryScanner * 獲取目錄中所有文件及文件夾 * @param $path 目錄 * @param $recursive 遞歸獲取 * @return array public static function listContents($path, $recursive = false) $iter = $recursive ? static::getRecursiveDirectoryIterator($path) : static::getDirectoryIterator($path); $result = []; foreach ($iter as $file) { if (in_array($file- getFilename(), [ . , .. ])) { continue; $result[] = clone $file; return $result; * 獲取目錄迭代器 * @param $path 目錄 * @return DirectoryIterator::class public static function getDirectoryIterator($path) return new DirectoryIterator($path); * 獲取遞歸目錄迭代器 * @param $path 目錄 * @param $mode 遍歷模式: RecursiveIteratorIterator::SELF_FIRST 從當前目錄開始遍歷;RecursiveIteratorIterator::CHILD_FIRST 從子目錄開始遍歷 * @return RecursiveIteratorIterator::class public static function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST ) return new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), $mode$path = F:/php_workspace/php-code-kata/direcotry-iterator/dir var_dump(DirectoryScanner::listContents($path));var_dump(DirectoryScanner::listContents($path, true));4 PHP 文件系統思維導圖
文件系統思維導圖
相關推薦:
php中導出大量數據的實現方法
PHP如何使用某個鍵值對二維數組排序
PHP開發中redis的主從模式以及加密方法
以上就是PHP文件系統的詳細介紹的詳細內容,PHP教程
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。
新聞熱點
疑難解答