PHP文件系统基本操作类
2024年02月23日
<?php/*
*
*===========Z=================
*QQ:118824
*MSN:snakevil_ hotmail.com
*HP:http://www.snakevil.com/
*===========Z=================
*
*/
/**
* ]Class Name[=IO
* ]Class URI[=System.IO
* ]Purpose[=
*本类用于对文件系统的处理
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]Version[=1.1.1
* ]Create[=17:13 2004-3-25
* ]Modifications[=
*4:04 2004-3-30
*+修复generate_path()方法中存在的一些BUG
*+重新设计方法no_comment()
*4:32 2004-3-29
*+简化方法list_dir()的返回值
*+增加方法file_info()获取文件或目录信息
*5:35 2004-3-28
*+整理优化算法
*7:31 2004-3-27
*+将错误处理抽象为基类
*+增加方法no_comment()删除文件中C规范注释
* ]See[=
*/
class IO extends SnkClass{
var$result;//操作返回结果,如方法返回值为mixed,则成功操作结果可在此获得
var$exec_cmd;//执行方法,暂时没应用到
var$exist_dir;//创建目录时最后存在的目录,现用于copy()和move()
var$buffer_size;//文件读取缓冲区大小,根据服务应用规模和服务器配置修改,建议默认值
function IO(){
parent::SnkClass();
$this->result=array();
$this->exec_cmd="";
$this->exist_dir="";
$this->buffer_size=8192;
return$this;
}
/**
* ]Method Name[=list_dir()
* ]Purpose[=
*读取指定目录内容,返回内容数组
* ]Parameter[=
*string$dir_path指定目录路径,默认为当前目录
* ]Return[=mixed错误返回FALSE,否则返回
*array(
*array("name","location","type"),
*......
*)
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]See[=
*/
function list_dir($path="."){
if(!is_dir($path))return$this->error_occur(0x000B,__FUNCTION__);
if(!is_readable($path))return$this->error_occur(0x0002,$path);
$dh= opendir($path);
$result=array();
$path=realpath($path);
if($path[strlen($path)-1]!=DIRECTORY_SEPARATOR)$path.=DIRECTORY_SEPARATOR;//保证目录绝对地址后带目录分隔符
while(FALSE!==($fh=readdir($dh))){//使用!==防止处理名称为0或FALSE的文件、目录
if($fh=="."||$fh=="..")continue;//忽略系统特定文件夹
$i=$path.$fh;//获取绝对地址
$t=array(
"name"=>$fh,
"location"=>$i,
"type"=>is_file($i)?1:(is_dir($i)?0:-1)
);
$result[]=$t;
}
closedir($dh);
unset($dh,$fh,$t,$i);
clearstatcache();//清除文件系统缓存
return$this->result=$result;
}
/**
* ]Method Name[=file_info()
* ]Purpose[=
*获取指定文件或目录的属性
* ]Parameter[=
*string$dir_path指定目录路径,默认为当前目录
* ]Return[=mixed错误返回FALSE,否则返回
*array("name","location","type","size","access","change","modify","read","write"),
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]See[=
*/
function file_info($path="."){
$path=realpath($path);
if(!$path)return$this->error_occur(0x000A,__FUNCTION__);
$result=array(
"name"=>substr($path,strrpos($path,DIRECTORY_SEPARATOR)+1),
"location"=>$path,
"type"=>is_file($path)?1:(is_dir($path)?0:-1),
"size"=>filesize($path),
"access"=>fileatime($path),
"modify"=>filemtime($path),
"change"=>filectime($path),
"read"=>is_readable($path),
"write"=>is_writeable($path)
);
clearstatcache();
return$this->result=$result;
}
/**
* ]Method Name[=seek_file()
* ]Purpose[=
*根据正则表达式条件,在相应目录及给定层次的子目录中搜索匹配的文件、目录
* ]Parameter[=
*string$pattern兼容PERL标准的正则表达式指明搜索匹配要求,会添加/^$/,默认为.*
*string$path进行搜索的目录路径,默认为当前路径
*enum$seesk_type有-1 0 1三种可能值,0仅文件夹,1仅文件,-1两者都包括,默认为1
*int$sub_dir搜索的子目录深度,指定目录不算,建议不要超过5,默认为0
*limit$limit搜索结果限制,避免过度浪费系统资源,默认为100
* ]Return[=mixed错误返回FALSE,否则
*array(
*array(
*"name","locate","type"
*),
*......
*)
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]See[=
*/
function seek_file($pattern=".*",$path=".",$seek_type=1,$sub_dir_level=0,$limit=100){
/*检查参数值*/
$is_error=$seek_type!=1&&$seek_type!=0&&$seek_type!=-1;
$is_error=$is_error&&(!is_int($sub_dir_level)||$sub_dir_level<0);
$is_error=$is_error&&(!is_int($limit)||$limit<1);
if($is_error)return$this->error_occur(0x000B,__FUNCTION__);
unset($is_error);
$result=array();
/*array()==FALSE,所以需要使用===*/
if(FALSE===$i=$this->list_dir($path))return FALSE;//如果不能列举目录,返回
for($j=0,$k=count($i);$j<$k;$j++){
if($i[$j]["type"]==-1)continue;//对于非目录非文件项目,跳过
if($i[$j]["type"]==0&&$sub_dir_level){//如果需要搜索下层目录
if(FALSE===$l=$this->seek_file($pattern,$i[$j]["location"],$seek_type,($sub_dir_level-1),$limit))return FALSE;
$result=array_merge($result,$l);//将下层目录搜索结果添加
}
if($seek_type+$i[$j]["type"]==1||!preg_match("/^".$pattern."$/",$i[$j]["name"]))continue;//如果不搜索当前类型,跳过
$result[]=$i[$j];
if(count($result)>=$limit){//截去超过要求的长度,离开列举
array_splice($result,$limit);
break;
}
}
unset($i,$j,$k,$l);
return$this->result=$result;
}
/**
* ]Method Name[=delete()
* ]Purpose[=
*删除指定对象,文件或文件夹——包括内含子目录和文件的非空文件夹
* ]Parameter[=
*string$path指定要删除的内容路径,文件或目录均可
* ]Return[=boolean错误返回FALSE,否则TRUE
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]See[=
*/
function delete($path=""){
$path=realpath($path);
if(!$path)return$this->error_occur(0x000A,__FUNCTION__);
if(!is_dir($path)){
if( unlink($path))return TRUE;//文件删除成功
return$this->error_occur(0x0004,$path);
}else{
if(FALSE===$i=$this->list_dir($path))return FALSE;//不能列举目录
for($j=0,$k=count($i);$j<$k;$j++)
if(!$this->delete($i[$j]["location"]))return FALSE;//删除目录内容出错
unset($i,$j,$k);
return TRUE;
}
}
/**
* ]Method Name[=generate_path()
* ]Purpose[=
*获取现有或不存在文件、目录的绝对地址
* ]Parameter[=
*string$path要获取地址的文件、目录现有相对、绝对地址
* ]Return[=string获得的地址
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]See[=
*/
function generate_path($path=""){
$i="/"==DIRECTORY_SEPARATOR?"\\":"/";//统一目录分割符
$path=str_replace($i,DIRECTORY_SEPARATOR,strval($path));
if($path[strlen($path)-1]!=DIRECTORY_SEPARATOR)$path.=DIRECTORY_SEPARATOR;
$i=strpos($path,DIRECTORY_SEPARATOR);//获得路径中首个目录分割符的位置
$ext=substr($path,$i+1);
$path=substr($path,0,$i+1);
if($i=realpath($path))$path=$i;//得到基本路径
else{
$ext=$path.$ext;
$path=realpath(".");
}
if(strlen($ext)){//对剩余内容处理
$ext=preg_replace("/[\:\*\?\"\<\>\|]/","",explode(DIRECTORY_SEPARATOR,$ext));
array_pop($ext);
$path=explode(DIRECTORY_SEPARATOR,$path);//建立目录层轴
if($path[count($path)-1]=="")array_pop($path);
while(count($ext)){
$i=array_shift($ext);
if($i==".."&&count($path)>1)array_pop($path);
elseif(""!=str_replace(".","",$i))$path[]=$i;
}
$path=implode(DIRECTORY_SEPARATOR,$path);
}
unset($ext,$i);
return$path;
}
/**
* ]Method Name[=make_dir()
* ]Purpose[=
*建立任意文件夹,相对或绝对路径皆可,深层建立亦可
* ]Parameter[=
*string$path要建立的最终目录路径
* ]Return[=boolean错误返回FALSE,否则TRUE
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]See[=
*/
function make_dir($path=""){
$i=explode(DIRECTORY_SEPARATOR,$this->generate_path($path));//生成目录路径
$path=array_shift($i);
for($j=0,$k=count($i);$j<$k;$j++){
$path.=DIRECTORY_SEPARATOR.$i[$j];
if(!is_dir($path)){
if($this->exist_dir=="")$this->exist_dir=$path;//记录最后存在的目录路径
if(! mkdir($path))return$this->error_occur(0x0003,substr($path,0,strrpos($path,DIRECTORY_SEPARATOR)));
}
}
if($this->exist_dir=="")$this->exist_dir=$path;
return TRUE;
}
/**
* ]Method Name[=verify_file()
* ]Purpose[=
*使用MD5算法比较两个文件是否相同
* ]Parameter[=
*string$src源文件路径
*string$dst目标文件路径
*boolean$interal对于超过1MB文件,设置FALSE省去MD5检验步骤,减轻服务器负担
* ]Return[=boolean错误返回FALSE,否则TRUE
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]See[=
*/
function verify_file($src="",$dst="",$interal=TRUE){
if(!is_file($src)||!is_file($dst))return$this->error_occur(0x000B,__FUNCTION__);
if(!is_readable($src))return$this->error_occur(0x0006,$src);
if(!is_readable($dst))return$this->error_occur(0x0006,$dst);
$i=filesize($src);
if(filesize($dst)!=$i){//文件大小不等
unset($i);
return FALSE;
}
if($i>1024*1024*1024&&!$interal){//对于1MB的文件,如果不要求精确检查,跳过
unset($i);
return TRUE;
}
unset($i);
if(md5_file($src)!=md5_file($dst))return FALSE;//文件MD5效验不符合,内容不相同
return TRUE;
}
/**
* ]Method Name[=copy()
* ]Purpose[=
*对任意文件夹、文件进行复制,相对或绝对路径皆可,文件复制完成后会进行效验,检查是否出错数据错误
* ]Parameter[=
*string$src_path指定要复制的源内容路径,文件或目录均可
*string$dst_path指定要复制的目标内容路径,文件或目录均可,性质由$src_path决定,可为$src_path下层目录
* ]Return[=boolean错误返回FALSE,否则TRUE
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]See[=
*/
function copy($src="",$dst="",$sub=FALSE){
if(!$src=realpath($src))return$this->error_occur(0x000B,__FUNCTION__);
$dst=$this->generate_path($dst);
if(is_dir($src)){//处理目录
/*
*关于算法的说明:
*本来打算使用很简单的递归算法,遇神杀神,遇魔斩魔的,后来发现一个问题:如果目标路径
*为源路径的后代路径怎么办?这样算法会不停的探测下去…
*于是添加了$this->exist_dir属性,用来记录这一情况下目标路径中存在的部分。于是新的问
*题出来了:如何保存这一属性?
*将整个功能整合到$this->copy()方法中,那么必然需要在这个函数中记录$this->exist_dir
*的变化,于是乎需要另外的一个有效的方法来阻止每一次操作中对其的更改。
*作为变通,我使用的隐藏参数$sub,这个参数无论如何,只要算法不变,永远在参数表的最
*后一个。因此,方法开始变得不稳定,但这也没有办法,只能希望程序员自己不要故意破坏。
*在外部调用时,因为默认FALSE,所以对$this->exist_dir写。内部递归时,显性TRUE,不
*该属性,保证有效性。
*/
if(!is_readable($src))return$this->error_occur(0x0002,$src);
if($dst[strlen($dst)-1]!=DIRECTORY_SEPARATOR)$dst.=DIRECTORY_SEPARATOR;
if(TRUE===$sub&&$src==$this->exist_dir)return TRUE;//源路径为记录的目标路径
if(TRUE!==$sub)$this->exist_dir="";//记录创建目录前目标目录路径中存在的目录路径
if(!$this->make_dir($dst))return FALSE;//创建目录
if(FALSE===$i=$this->list_dir($src))return FALSE;//读取目录出错
for($j=0,$k=count($i);$j<$k;$j++)if(!$this->copy($i[$j]["location"],$dst.$i[$j]["name"],TRUE))return FALSE;
unset($i,$j,$k);
RETURN TRUE;
}else{
if(!is_readable($src))return$this->error_occur(0x0006,$src);
if($this->verify_file($src,$dst))return TRUE;
if(!copy($src,$dst))return$this->error_occur(0x0007,$dst);
if(!$this->verify_file($src,$dst)){
unlink($dst);//复制文件失败删除新文件
return$this->error_occur(0x0007,$dst);
}
return TRUE;
}
}
/**
* ]Method Name[=move()
* ]Purpose[=
*对任意文件夹、文件进行移动,相对或绝对路径皆可,文件移动完成后会进行效验,检查是否出错数据错误
* ]Parameter[=
*string$src_path指定要移动的源内容路径,文件或目录均可
*string$dst_path指定要移动的目标内容路径,文件或目录均可,性质由$src_path决定,可为$src_path下层目录
* ]Return[=boolean错误返回FALSE,否则TRUE
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]See[=
*/
function move($src="",$dst="",$sub=FALSE){
if(!$src=realpath($src))return$this->error_occur(0x000B,__FUNCTION__);
$dst=$this->generate_path($dst);
if(is_dir($src)){//处理目录
if(!is_readable($src))return$this->error_occur(0x0002,$src);
if($dst[strlen($dst)-1]!=DIRECTORY_SEPARATOR)$dst.=DIRECTORY_SEPARATOR;
if(TRUE===$sub&&$src==$this->exist_dir)return TRUE;
if(TRUE!==$sub)$this->exist_dir="";
if(!$this->make_dir($dst))return FALSE;
if(FALSE===$i=$this->list_dir($src))return FALSE;
for($j=0,$k=count($i);$j<$k;$j++)if(!$this->move($i[$j]["location"],$dst.$i[$j]["name"],TRUE))return FALSE;
unset($i,$j,$k);
if(FALSE===strpos($this->exist_dir,$src))
if(! rmdir($src))return$this->error_occur(0x0004,$src);//对非目标目录的上层目录,删除
return TRUE;
}else{
if(!is_readable($src))return$this->error_occur(0x0006,$src);
if($this->verify_file($src,$dst))return TRUE;
if(!copy($src,$dst))return$this->error_occur(0x0007,$dst);
if(!$this->verify_file($src,$dst)){
unlink($dst);
return$this->error_occur(0x0007,$dst);
}
if(! unlink($src))return$this->error_occur(0x0006,$src);//删除源文件
return TRUE;
}
}
/**
* ]Method Name[=no_comment()
* ]Purpose[=
*清除文件中C规范的注释
* ]Parameter[=
*string$path指定要执行操作的文件
* ]Return[=boolean错误返回FALSE,否则TRUE
* ]Author[=SNakeVil<51JS,BU,PHPx>(snakevil qq.com)
* ]See[=
*/
function no_comment($path=""){
if(!is_file($path))return$this->error_occur(0x000B,__FUNCTION__);
if(!is_readable($path))return$this->error_occur(0x0006,$path);
if(!is_writeable($path))return$this->error_occur(0x0007,$path);
if(!$th=tmpfile())return$this->error_occur(0x000C,$path);//创建临时文件
$fh=fopen($path,"r+b");
if(!flock($fh,LOCK_EX)){//锁定文件
fclose($fh);
unset($fh);
return$this->error_occur(0x0009,$path);
}
$fbuffer=fread($fh,$this->buffer_size*2);//文件读取缓冲区
$tbuffer="";//临时文件缓冲区
$in_dq=$in_sq=$in_lc=$in_bc=FALSE;
while($fblen=strlen($fbuffer)){//处理原始数据
$fstats=feof($fh);
for($i=0;$i<$fblen;$i++){//分析文件内容
if(!$fstats&&$i+5>$fblen)break;//文件未完全读取时临近缓冲区读取完成读取下一块文件内容
$j=substr($fbuffer,$i,2);
$k=$j[0];
if($j=="/*"&&!$in_dq&&!$in_sq&&!$in_lc){//不在字符串和行注释中,块注释开始
$in_bc=TRUE;
$i++;
}elseif($j=="*/"&&$in_bc){//块注释结束
$in_bc=FALSE;
$i+=2;
}elseif($j=="//"&&!$in_dq&&!$in_sq&&!$in_bc){//行注释开始
$in_lc=TRUE;
$i++;
}elseif($in_lc&&($k=="\r"||$k=="\n"))$in_lc=FALSE;//行注释结束
elseif($j=="\\\\"||$j=="\\\""||$j=="\\'"){//转义字符
$tbuffer.=$j;
$i++;
continue;
}elseif($k=="\""&&!$in_sq&&!$in_bc&&!$in_lc)$in_dq=!$in_dq;//双引号字符串开始、结束
elseif($k=="'"&&!$in_dq&&!$in_bc&&!$in_lc)$in_sq=!$in_sq;//单引号字符串开始、结束
if($in_lc||$in_bc)continue;//在注释中,跳过
$tbuffer.=$fbuffer[$i];
}
$fbuffer=substr($fbuffer,$i);//抛弃读取过的部分
unset($i,$j,$k);
if(!$fstats)$fbuffer.=fread($fh,$this->buffer_size);
if($fstats||strlen($tbuffer)>=$this->buffer_size){//写入合法数据到临时文件
if(!fwrite($th,$tbuffer)){//写入失败,空间不足
fclose($th);
flock($fh,LOCK_UN);
fclose($fh);
unset($th,$fh,$in_dq,$in_sq,$in_lc,$in_bc,$i,$j,$k);
return$this->error_occur(0x000D,"");
}
$tbuffer="";
}
}
unset($fbuffer,$tbuffer,$fstats,$in_dq,$in_sq,$in_lc,$in_bc);
rewind($fh);//回移文件指针到文件首
rewind($th);
$i=$j="";
$k=0;
while(!feof($th)){//将临时文件数据写回源文件
$i=fgets($th,$this->buffer_size);
if($j==""){//获得文件系统的换行符
$j=substr($i,-2);
if($j=="\r\n")$k=2;
elseif($j[1]=="\r"||$j[1]=="\n"){
$k=1;
$j=$j[1];
}else$j="";
}
if(substr($i,-$k)==$j){
$i=rtrim(substr($i,0,-$k),"\t");
if(strlen($i))fwrite($fh,$i.$j);//清除右方空格
else continue;
}else fwrite($fh,rtrim($i,"\t"));
}
fflush($fh);//保存、关闭文件
ftruncate($fh,ftell($fh));
fclose($th);
flock($fh,LOCK_UN);
fclose($fh);
unset($i,$j,$k,$fh,$th);
return TRUE;
}
}
/**
* ]Error List[=
*0x0001指定目录不存在
*0x0002指定目录无读取权限
*0x0003指定目录无写入权限
*0x0004指定目录无删除权限
*0x0005指定文件不存在
*0x0006指定文件无读取权限
*0x0007指定文件无写入权限
*0x0008指定文件无删除权限
*0x0009指定文件无法锁定
*0x000A指定对象不存在
*0x000B方法指定参数不正确
*0x000C无法创建临时文件
*0x000D磁盘空间不足
*0x000E
*0x000F
*0x0010
*0x0011
*
*/
?>