PHP文件系统基本操作类

  <?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
  *
  */
  ?>