PHP操作ZIP之ZipArchive类以及如何避免生成压缩文件带有目录层级的问题

常用的方法

php ZipArchive可以说是php自带的一个函数了,他可对对文件进行压缩与解压缩处理,但是使用此类之前我们必须在php.ini中把extension=php_zip.dll前面的分号有没有去掉,然后再重启Apache这样才能使用这个类库。

ziparchive 可选参数,更多的使用例子,参考PHP - Manual: ZipArchive - 互联网笔记

ZipArchive::addEmptyDir

添加一个新的文件目录

php 复制代码
<?php
$zip = new ZipArchive;
if ($zip->open('test.zip') === TRUE) {
    if($zip->addEmptyDir('newDirectory')) {
        echo 'Created a new root directory';
    } else {
        echo 'Could not create the directory';
    }
    $zip->close();
} else {
    echo 'failed';
}
?>

ZipArchive::addFile

将文件添加到指定zip压缩包中。

ZipArchive::addFromString

添加的文件同时将内容添加进去

ZipArchive::close

关闭ziparchive

ZipArchive::extractTo

将压缩包解压

ZipArchive::open

打开一个zip压缩包

ZipArchive::getStatusString

返回压缩时的状态内容,包括错误信息,压缩信息等等

ZipArchive::deleteIndex

删除压缩包中的某一个文件,如:deleteIndex(0)删除第一个文件

ZipArchive::deleteName

删除压缩包中的某一个文件名称,同时也将文件删除。

ZipArchive::open

php 复制代码
<?php
$zip = new ZipArchive;
$res = $zip->open('test.zip', ZipArchive::CREATE);
if ($res === TRUE) {
    $zip->addFromString('test.txt', 'file content goes here');
    $zip->addFile('data.txt', 'entryname.txt');
    $zip->close();
    echo 'ok';
} else {
    echo 'failed';
}
?>
php 复制代码
<?php
$name = tempnam(sys_get_temp_dir(), "FOO");
$zip = new ZipArchive;
$res = $zip->open($name, ZipArchive::OVERWRITE); /* truncate as empty file is not valid */
if ($res === TRUE) {
    $zip->addFile('data.txt', 'entryname.txt');
    $zip->close();
    echo 'ok';
} else {
    echo 'failed';
}
?>

基本使用例

解压缩zip文件

注意,解压的文件夹中不要有中文!会引起乱码!

php 复制代码
$zip = new ZipArchive;//新建一个ZipArchive的对象
/*
通过ZipArchive的对象处理zip文件
$zip->open这个方法的参数表示处理的zip文件名。
如果对zip文件对象操作成功,$zip->open这个方法会返回TRUE
*/

if ($zip->open('test.zip'))
{
    $zip->extractTo('images');//假设解压缩到在当前路径下images文件夹的子文件夹php
    $zip->close();//关闭处理的zip文件
}

文件追加内容添加到zip文件

php 复制代码
zip = new ZipArchive;
$res = $zip->open('test.zip', ZipArchive::CREATE);
if ($res) {
    $zip->addFromString('test.txt', 'file content goes here');
    $zip->close();
    echo 'ok';
} else {
  echo 'failed';
}

建议:$zip->open 使用try-catch去捕捉

php 复制代码
try {
   $zipResult = $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE);
    //关闭处理的zip文件
    $zip->close();
}  catch (Exception $e) {
    exit( $e->getMessage());
}

将服务器上的文件夹打包成zip文件

php 复制代码
function addFileToZip($path, $zip) {

    $handler = opendir($path); //打开当前文件夹由$path指定。
    
    /*
    循环的读取文件夹下的所有文件和文件夹
    其中$filename = readdir($handler)是每次循环的时候将读取的文件名赋值给$filename,
    为了不陷于死循环,所以还要让$filename !== false。
    一定要用!==,因为如果某个文件名如果叫'0',或者某些被系统认为是代表false,用!=就会停止循环
    */
    
    while (($filename = readdir($handler)) !== false) {
        //文件夹文件名字为'.'和'..',不要对他们进行操作
        if ($filename != "." && $filename != "..") {
            $filePath = "{$path}/{$filename}";
            // 如果读取的某个对象是文件夹,则递归
            if (is_dir($filePath)) {
                addFileToZip($filePath, $zip);
            } else { 
                var_dump($filePath);
                // 将文件加入zip对象 传入第二个参数是避免出现目录层级的问题
                $zip->addFile($filePath, pathinfo($filePath, PATHINFO_BASENAME));
            }
        }
    }
    @closedir($path);
}
$STATICS_PATH = 'd:/xampp/htdocs/xin-card';
$templatePath = '2023/03/0d9cf19188485dc70e21a1aae4ffad8d';
// 要下载文件的最终目录
$finalPath = "{$STATICS_PATH}/templates/{$templatePath}";

if(!file_exists($finalPath)) {
    exit('路径不存在');
}

$zip = new ZipArchive();
$zipName = time() . '.zip';
// d:/xampp/htdocs/xin-card/zip/187823213.zip
$zipPath = "{$STATICS_PATH}/zip/{$zipName}";
try {
    $zipResult = $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE);
    //调用方法,对要打包的根目录进行操作,并将ZipArchive的对象传递给方法
    addFileToZip($finalPath, $zip); 
    //关闭处理的zip文件
    $zip->close();
}  catch (Exception $e) {
    exit( $e->getMessage());
}

解决生成压缩文件带有目录层级的问题

比如上面的addFileToZip方法,我一开始是这么写的:

php 复制代码
if (is_dir($filePath)) {
       addFileToZip($filePath, $zip);
} else { 
       $zip->addFile($filePath);
}

这样写最终导致的后果就是生成的压缩文件,里面带有目录层级!

像这样:D:\xampp\htdocs\xin-card\statics\templates-packages\1679986315\D_\xampp\htdocs\xin-card\statics\templates-packages\2023\03\0d9cf19188485dc70e21a1aae4ffad8d

实际上只有最后的文件夹才是我想要的!

如果你使用php ZipArchive addFile 方法把多个文件压缩在1个目录时会产生一个问题,我们只想要在当前目录把所有文件放在一起,结果他安装每个文件的所在目录在当前目录创建一遍,解决方式如下:

php 复制代码
$allAttachment = [
    '1.png',   
    '2.png',   
    '3.png',   
    '4.png',    
];

// 循环保存文件到Zip中
foreach ($allAttachment as $attachmentItem) {
    $rootpath = 'd:/xampp/htdocs/xin-cards/';
    if ($attachmentItem) {
        $attachmentItem = "$rootpath/$attachmentItem";
        // 添加文件
        $zip->addFile($attachmentItem);
        // 对添加的文件重新命名,避免出现目录问题
        $zip->renameName($attachmentItem, basename($attachmentItem));
    }
}

// 关闭
$zip->close();
如果不能解决您的问题,可以尝试如下方式

// 添加文件
$zip->addFile($attachmentItem, pathinfo($attachmentItem, PATHINFO_BASENAME));

浏览器下载并删除压缩文件

以下是打包服务器上某个文件夹中所有文件,并下载的然后再删除压缩文件的全部代码:

php 复制代码
<?php
$STATICS_PATH = PATH['statics'];
$templatePath = '2023/03/0d9cf19188485dc70e21a1aae4ffad8d';
$finalPath = "{$STATICS_PATH}/templates-packages/{$templatePath}";
if(!file_exists($finalPath)) {
    exit('路径不存在');
}

function addFileToZip($path, $zip) {

    $handler = opendir($path); //打开当前文件夹由$path指定。
    
    /*
    循环的读取文件夹下的所有文件和文件夹
    其中$filename = readdir($handler)是每次循环的时候将读取的文件名赋值给$filename,
    为了不陷于死循环,所以还要让$filename !== false。
    一定要用!==,因为如果某个文件名如果叫'0',或者某些被系统认为是代表false,用!=就会停止循环
    */
    
    while (($filename = readdir($handler)) !== false) {
        //文件夹文件名字为'.'和'..',不要对他们进行操作
        if ($filename != "." && $filename != "..") {
            $filePath = "{$path}/{$filename}";
            // 如果读取的某个对象是文件夹,则递归
            if (is_dir($filePath)) {
                addFileToZip($filePath, $zip);
            } else { 
                // 将文件加入zip对象 传入第二个参数是避免出现目录层级的问题
                $zip->addFile($filePath, pathinfo($filePath, PATHINFO_BASENAME));
            }
        }
    }
    @closedir($path);
}

$zip = new ZipArchive();
$zipName = time() . '.zip';
$zipPath = "{$STATICS_PATH}/templates-packages/{$zipName}";
try {
    $zipResult = $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE);
    //调用方法,对要打包的根目录进行操作,并将ZipArchive的对象传递给方法
    addFileToZip($finalPath, $zip); 
    //关闭处理的zip文件
    $zip->close();

    //下载文件
    $file = fopen($zipPath, "r");
    //返回的文件类型
    Header("Content-type: application/octet-stream");
    //按照字节大小返回
    Header("Accept-Ranges: bytes");
    //返回文件的大小
    Header("Accept-Length: " . filesize($zipPath));
    //这里设置客户端的弹出对话框显示的文件名
    Header("Content-Disposition: attachment; filename=" . $zipName);
    //一次性将数据传输给客户端
    //echo fread($file, filesize($filePath));
    //一次只传输1024个字节的数据给客户端
    //向客户端回送数据
    $buffer = 1024;//
    //判断文件是否读完
    while (!feof($file)) {
        //将文件读入内存
        $file_data = fread($file, $buffer);
        //每次向客户端回送1024个字节的数据
        echo $file_data;
 
    }
    //将生成的zip文件在服务器端删除,只需要客户端下载就行了
    @unlink($zipPath);

}  catch (Exception $e) {
    exit( $e->getMessage());
}

相关资料

解决phpZipArchive生成压缩文件带有目录层级的问题【阿里开发者社区】

解决phpZipArchive生成压缩文件带有目录层级的问题

php开启ziparchivephpZipArchive类使用实例详解

相关推荐
ServBay8 小时前
告别面条代码,PSL 5.0 重构 PHP 性能与安全天花板
后端·php
JaguarJack3 天前
FrankenPHP 原生支持 Windows 了
后端·php·服务端
BingoGo3 天前
FrankenPHP 原生支持 Windows 了
后端·php
JaguarJack4 天前
PHP 的异步编程 该怎么选择
后端·php·服务端
BingoGo4 天前
PHP 的异步编程 该怎么选择
后端·php
JaguarJack4 天前
为什么 PHP 闭包要加 static?
后端·php·服务端
ServBay5 天前
垃圾堆里编码?真的不要怪 PHP 不行
后端·php
用户962377954485 天前
CTF 伪协议
php
BingoGo8 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack8 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端