前言
這幾天在看easyui,看到樹形結構這個組件的時候突發奇想,能不能把站點以目錄樹的形式展示呢?
然后著手實現了一下,具體的來說是實現了對數據層的獲取,還沒有附加到tree組件上。下面就來談談我對這次文件信息抓取的體會吧。
遍歷文件
在PHP中遍歷文件有很多方式,但是適用的場景不盡相同。所以在合適的場合適用合適的方法顯得至關重要,下面簡要的了解一下。
scandir
如果說想找到一款類似于Python中使用os.walk
獲取文件目錄信息的優雅的方法,在PHP中就不是那么的方便了,唯一能稱得上簡單的就是scandir
函數,但是這個函數并不優雅,其作用就是掃描給定目錄下的文件信息(如果包含子目錄,那就只能顯示到子目錄的層級,再想查看子目錄下的信息,那就不行了,否則會報錯的)。
空口無憑,找個實例來看一下就一目了然了。
給定目錄
<?php
/**
* Created by PhpStorm.
* User: ${郭璞}
* Date: 2017/2/3
* Time: 20:25
* Description: scandir函數測試
*/
$pathinfo = scandir('.');
var_dump($pathinfo);
效果如下
非法使用
所謂非法使用,就是指給出非目錄文件時的場景,比如我們直接給個文件的路徑,就是這樣了。
<?php
/**
* Created by PhpStorm.
* User: ${郭璞}
* Date: 2017/2/3
* Time: 20:25
* Description: scandir函數測試
*/
$pathinfo = scandir('./scandir.php');
var_dump($pathinfo);
所以,使用scandir函數的時候務必明確這一點,傳正確的參數?。。?/p>
dir函數
既然使用scandir函數不行了,那咱們就換個思路唄。下面介紹一個比較常用的方法。
<?php
/**
* Created by PhpStorm.
* User: ${郭璞}
* Date: 2017/2/3
* Time: 20:25
* Description: scandir函數測試
*/
$path = ".";
if(is_dir($path)) {
$dirinfo = dir($path);
while($file = $dirinfo->read()) {
echo "<mark>".$file."</mark><br />";
}
$dirinfo->close();
}else if (is_file($path)) {
echo "<font color='green'>".$path."</font>";
}
如果將$path='.'
換成$path='./scandir.php'
。將出現如下結果。
當然,這兩個方法都沒能實現我們想要的效果。不能突破子目錄的情況,沒辦法遍歷到最底層的文件信息。
遞歸法
既然如此,那就得另尋他法了。我個人覺得遞歸的方法不賴,應該可以靈活地處理這些問題,說做就做。
使用面向過程的PHP編碼方法需要處理外部數組引用問題,顯得代碼不是很容易理解,所以我選擇面向對象的方法,將外部數組封裝到一個類中,專門用于處理這類問題。
<?php
/**
* Created by PhpStorm.
* User: ${郭璞}
* Date: 2017/2/3
* Time: 9:32
* Description: 讀取給定目錄及子目錄下文件路徑信息
*/
class FileWatcher{
public $fileinfo;
/**
* FileWatcher constructor.
* @param $path 給定路徑
*/
function __construct(){
$this->fileinfo = array();
}
/**
* 去除路徑設置信息,析構方法
*/
function __destruct()
{
// TODO: Implement __destruct() method.
$this->fileinfo = null;
}
public function scanDir($path) {
if(is_dir($path)) {
$tmpdir = dir($path);
while($tmpfile = $tmpdir->read()) {
if($tmpfile!='.' && $tmpfile!='..')
$this->scanDir($path."/".$tmpfile);
}
$tmpdir->close();
}
if(is_file($path)) {
array_push($this->fileinfo, $path);
}
return $this->fileinfo;
}
}
下面是測試時使用的代碼。
$fileWatcher = new FileWatcher();
$result = $fileWatcher->scanDir('.');
var_dump($result);
最終實現的效果為:
路徑解析
單單是這樣,不是很好用。我就想著能不能實現類似于Python中os.walk
那樣優雅的獲取相關的信息呢?
數據結構設計
使用過那個方法的應該都了解,獲取到的元組信息非常的詳細,包括路徑啊,目錄級啊什么的非常的詳細。
但是我這邊為了以后使用easyui的tree組件,可能需要處理一下目錄深度的問題,所以我設計了下面的數據結構。比較簡單,但是實用性感覺還是挺強的。
class FileInfo{
// 目錄深度
public $level;
// 文件經過的路徑,以數組形勢依次填充
public $pathstep;
// 文件的完整路徑
public $fullpath;
public function __construct()
{
//pathstep 存儲當前路徑經過的文件夾信息
$this->pathstep = array();
}
public function __destruct()
{
// TODO: Implement __destruct() method.
$this->pathstep = null;
$this->level = null;
$this->fullpath = null;
}
}
原理解析
我個人認為原理還是比較簡單的了,那就是以文件分隔符作為計算標準。當然了,需要處理一大堆的路徑適配問題,尤其是./
和../
這樣的相對路徑。
處理完這些之后就輕松多了,使用explode
函數將字符串進行分割,裝填到數組中即可。
代碼實現
class PathParser{
private $patharray;
private $resultSet;
public function __construct($patharray)
{
// 從外部獲取到處理結果集
$this->patharray = $patharray;
// 初始化結果集數組
$this->resultSet = array();
// bean類對象
$this->fileinfo = new FileInfo();
}
public function __destruct()
{
// TODO: Implement __destruct() method.
$this->resultSet = null;
$this->level = null;
$this->fullpath = null;
}
public function parse() {
for ($index=0; $index<count($this->patharray); $index++) {
// 賦予完整路徑
$fileinfo = new FileInfo();
$fileinfo->fullpath = $this->patharray[$index];
//計算level
$fileinfo->level = $this->parseLavel($fileinfo->fullpath);
// echo $fileinfo->level."<------->";
// 計算經過的路徑并進行存儲
$fileinfo->pathstep = $this->parseStep($fileinfo->fullpath);
// echo $fileinfo->pathstep."<br />";
// var_dump($fileinfo->pathstep);
array_push($this->resultSet, $fileinfo);
}
//返回計算結果,整體作為結果集返回
return $this->resultSet;
}
/**
* 獲取給定路徑所經過的路徑的結果集,將用于分級目錄展示
* @param $fileinfo
* @return int
*/
public function parseStep($fileinfo) {
if(!$fileinfo) {
echo "<mark>".$fileinfo." path error!</mark>";
exit();
}
// 判斷是否為相對路徑是的話去掉第一級目錄。 啊好煩,windows上和linux上差別還這么大,怎么處理好呢。。。
// 還是按照文件在服務器上的位置來進行來處理好了。判斷是不是相對路徑然后再針對“路徑分隔符”計算路徑的level
if($this->isRelativePath($fileinfo) == 1) {
// 相對路徑處理
// 去掉相對路徑符號
$fileinfo = substr($fileinfo,2, strlen($fileinfo));
// 按照目錄分隔符 作為切割標準,結果就是路徑本身包含的路徑信息
return explode("/", $fileinfo);
}else if($this->isRelativePath($fileinfo) == 2){
$fileinfo = substr($fileinfo, 3, strlen($fileinfo));
return explode("/", $fileinfo);
}else if ($this->isAbsolutePath($fileinfo)) {
// 絕對路徑處理
}else{
// 文件路徑非法
echo "<mark>".$fileinfo." 文件路徑非法</mark>";
exit();
}
}
public function parseLavel($fileinfo) {
if(!$fileinfo) {
echo "<mark>".$fileinfo." path error!</mark>";
exit();
}
//按照文件在服務器上的位置來進行來處理好了。判斷是不是相對路徑然后再針對“路徑分隔符”計算路徑的level
if($this->isRelativePath($fileinfo) == 1) {
// 相對路徑處理
// 去掉當前相對路徑符號
$fileinfo = substr($fileinfo,2, strlen($fileinfo));
// 通過計算 路徑分隔符來作為level的判斷標準
// echo "<mark>".count(explode("/", $fileinfo))."</mark>";
return count(explode("/", $fileinfo));
}else if ($this->isRelativePath($fileinfo) == 2 ) {
//去掉父級目錄信息
$fileinfo = substr($fileinfo, 3, strlen($fileinfo));
return count(explode("/", $fileinfo));
}else if ($this->isAbsolutePath($fileinfo)) {
// 絕對路徑處理
// 算了,先不做這塊了,貌似偏離了我這個需求。
}else{
// 文件路徑非法
echo "<mark>".$fileinfo." 文件路徑非法</mark>";
exit();
}
}
/**
* 判斷是否為相對路徑
* @param $path
* @return bool
*
*/
private function isRelativePath($path) {
// 父級目錄擁有更高的優先級
$prefix = substr($path, 0, 3);
if($prefix == "../") {
return 2;
}
// 處理 當前目錄情況
$prefix = substr($path, 0, 2);
if ($prefix == "./"){
return 1;
}else{
return false;
}
}
/**
* 判斷給定路徑是否為絕對路徑
* @param $path
* @return bool
*/
private function isAbsolutePath($path) {
$prefix = substr($path, 0, 1);
if($prefix=="/"){
return true;
}else{
return false;
}
}
}
演示
下面演示一下實現的效果吧。
當前目錄
測試代碼如下
//獲取全部文件以及路徑信息
$fileWatcher = new FileWatcher();
$result = $fileWatcher->scanDir('.');
$pathParser = new PathParser($result);
$resultSet = $pathParser->parse();
echo json_encode($resultSet);
結果圖
父級目錄
對于父級目錄信息獲取,也是非常方便的。之前網上下載了easyui的壓縮包,解壓后扔到了apache服務器上,下面來看看對這個大文件信息集的獲取情況吧。
測試代碼把路徑中的那個.
改成../easyui
即可。
結果還行吧。我看著挺詳細的了。那么到這里就差不多實現預期的效果了。
總結
回顧一下,今天主要是對于文件目錄信息的遍歷。
顯示通過通用的scandir 函數和dir循環讀取方式對目錄進行了讀取,但是效果不佳,于是轉戰遞歸實現。
為了達到一個更加優雅的信息獲取效果,又設計了一個專門針對于文件的類,用于存儲相關數據。
為了處理相對路徑中本級目錄和父級目錄等特殊情況,又使用了substr和explode函數,最后封裝成了一個通用的類,效果還不錯。
缺點嘛,顯而易見。代碼的風格不是很好,命名什么的也是按照我自己的套路來的,不是很正規。
其他的貌似也沒什么了,那就先這樣好了。