当前位置:Linux教程 - Php - php文件系统基本操作类

php文件系统基本操作类

PHP文件系统基本操作类     snakevil [原作]  
关键字   PHP IO 操作 类
出处    

<?php
/*
*
* ===========Z=================
*   QQ:118824
*   MSN:[email protected]
*   HP:http://www.snakevil.com/
* ===========Z=================
*
*/

    /**
     * @]Class Name[= IO
     * @]Class URI[= System.IO
     * @]Purpose[=
     *     本类用于对文件系统的处理
     * @]Author[= SNakeVil <51JS,BU,PHPx> ([email protected])
     * @]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> ([email protected])
         * @]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> ([email protected])
         * @]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> ([email protected])
         * @]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> ([email protected])
         * @]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> ([email protected])
         * @]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> ([email protected])
         * @]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> ([email protected])
         * @]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> ([email protected])
         * @]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> ([email protected])
         * @]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> ([email protected])
         * @]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;
                       &n