PHP支持多格式的解压缩工具类
一、 引语
本人在做一个企业云盘项目当中,遇到一个文件在线解压缩的需求,查了网上很多资料,但都是只支持单一格式或部分格式,固创建了本工具类,对市面上主流的压缩格式进行集成支持,并且简单易用。
二、 功能
1. 支持zip、rar、phar、tar、gz、bz2、7z格式的解压
2. 支持对单文件、多文件、文件夹进行压缩成zip文件格式数据库连接池
三、 前置条件
1. 安装php_zip插件:用于解压缩zip格式文件
2. 安装php_rar插件:用于解压缩rar格式文件
3. 安装php_phar插件:用于解压缩phar、tar、gz、bz2格式文件
4. 安装p7zip p7zip-full软件:用于解压缩7z格式文件
四、 实现
class ZipUtil { /** * 解压 * @param string $zipFilePath 压缩文件路径 * @param string $toDirPath 解压目录路径 * @return string * @throws \Exception */ public static function extract(string $zipFilePath, string $toDirPath) { $toDirPath = rtrim($toDirPath, '/'); self::deleteDir($toDirPath, false); if (!is_file($zipFilePath)) throw new \Exception('文件不存在。'); if (!is_dir($toDirPath)) { mkdir($toDirPath, 0777, true); } $zipFilePathInfo = pathinfo($zipFilePath); $zipExt = pathinfo($zipFilePath, PATHINFO_EXTENSION); switch ($zipExt) { case 'zip': if (!class_exists('\ZipArchive')) throw new \Exception('未安装Zip插件。'); $zipArch = new \ZipArchive(); if ($zipArch->open($zipFilePath) !== true) throw new \Exception('解压失败。'); //$zipArch->extractTo($toDirPath); //这个中文会乱码 //解决中文会乱码 $fileNum = $zipArch->numFiles; for ($i = 0; $i < $fileNum; ++$i) { $statInfo = $zipArch->statIndex($i, \ZipArchive::FL_ENC_RAW); $statInfo['name'] = self::convertToUtf8($statInfo['name']); //print_r($statInfo); if ($statInfo['crc'] === 0 && $statInfo['name'][strlen($statInfo['name']) - 1] === '/') { $dirPath = $toDirPath . '/' . $statInfo['name']; if (!is_dir($dirPath)) { mkdir($dirPath, 0777, true); } } else { copy('zip://' . $zipFilePath . '#' . $zipArch->getNameIndex($i), $toDirPath . '/' . $statInfo['name']); } } $zipArch->close(); break; case 'rar': if (!class_exists('\RarArchive')) throw new \Exception('未安装Rar插件。'); $rarArch = \RarArchive::open($zipFilePath); if ($rarArch === false) throw new \Exception('解压失败。'); $entries = $rarArch->getEntries(); if ($entries === false) throw new \Exception('解压失败。'); foreach ($entries as $entry) { $entry->extract($toDirPath); } $rarArch->close(); break; case 'phar': if (!class_exists('\Phar')) throw new \Exception('未安装Phar插件。'); $phar = new \Phar($zipFilePath, null, null); $extract = $phar->extractTo($toDirPath, null, true); if (!isset($zipFilePathInfo['extension'])) { unlink($zipFilePath); } if ($extract === false) throw new \Exception('解压失败。'); break; case 'tar': case 'gz': case 'bz2': if (!class_exists('\PharData')) throw new \Exception('未安装Phar插件。'); $formats = [ 'tar' => \Phar::TAR, 'gz' => \Phar::GZ, 'bz2' => \Phar::BZ2, ]; $format = $formats[$zipExt]; $phar = new \PharData($zipFilePath, null, null, $format); $extract = $phar->extractTo($toDirPath, null, true); if (!isset($zipFilePathInfo['extension'])) { unlink($zipFilePath); } if ($extract === false) throw new \Exception('解压失败。'); break; case '7z': if(shell_exec('type 7z') === null) throw new \Exception('未安装p7zip软件。'); $cmd = '7z x ' . $zipFilePath . ' -r -o' . $toDirPath; $result = shell_exec($cmd); break; default: throw new \Exception('不支持的解压格式。'); } return $toDirPath; } /** * 压缩多个文件 * @param array $files 文件列表,格式:[['file_type'=>'file|folder', 'file_path'=>'/a/b/test.txt', 'local_name'=>'b/test.txt']] * @param string $toFilePath 压缩文件路径 * @return string * @throws \Exception */ public static function package(array $files, string $toFilePath) { $toFilePathInfo = pathinfo($toFilePath); if (!is_dir($toFilePathInfo['dirname'])) { mkdir($toFilePathInfo['dirname'], 0777, true); } $zipArch = new \ZipArchive(); if ($zipArch->open($toFilePath, \ZipArchive::CREATE) !== true) throw new \Exception('压缩失败。'); foreach ($files as $file) { if ($file['file_type'] === 'folder') { $zipArch->addEmptyDir(ltrim($file['local_name'], '/')); } else if ($file['file_type'] === 'file') { if (is_file($file['file_path'])) { $zipArch->addFile($file['file_path'], $file['local_name']); } } } $zipArch->close(); return $toFilePath; } /** * 压缩文件夹 * @param string $dirPath 要压缩的文件夹路径 * @param string $toFilePath 压缩文件路径 * @param bool $includeSelf 是否包含自身 * @return string * @throws \Exception */ public static function packageDir(string $dirPath, string $toFilePath, bool $includeSelf = true) { if (!is_dir($dirPath)) throw new \Exception('文件夹不存在。'); $toFilePathInfo = pathinfo($toFilePath); if (!is_dir($toFilePathInfo['dirname'])) { mkdir($toFilePathInfo['dirname'], 0777, true); } $dirPathInfo = pathinfo($dirPath); //print_r($dirPathInfo); $zipArch = new \ZipArchive(); if ($zipArch->open($toFilePath, \ZipArchive::CREATE) !== true) throw new \Exception('压缩失败。'); $dirPath = rtrim($dirPath, '/') . '/'; $filePaths = self::scanDir($dirPath); if ($includeSelf) { array_unshift($filePaths, $dirPath); } //print_r($filePaths); foreach ($filePaths as $filePath) { $localName = mb_substr($filePath, mb_strlen($dirPath) - ($includeSelf ? mb_strlen($dirPathInfo['basename']) + 1 : 0)); //echo $localName . PHP_EOL; if (is_dir($filePath)) { $zipArch->addEmptyDir($localName); } else if (is_file($filePath)) { $zipArch->addFile($filePath, $localName); } } $zipArch->close(); return $toFilePath; } /** * 压缩单个文件 * @param string $filePath 要压缩的文件路径 * @param string $toFilePath 压缩文件路径 * @return string * @throws \Exception */ public static function packageFile(string $filePath, string $toFilePath) { if (!is_file($filePath)) throw new \Exception('文件不存在。'); $toFilePathInfo = pathinfo($toFilePath); if (!is_dir($toFilePathInfo['dirname'])) { mkdir($toFilePathInfo['dirname'], 0777, true); } $filePathInfo = pathinfo($filePath); $zipArch = new \ZipArchive(); if ($zipArch->open($toFilePath, \ZipArchive::CREATE) !== true) throw new \Exception('压缩失败。'); $zipArch->addFile($filePath, $filePathInfo['basename']); $zipArch->close(); return $toFilePath; } /** * 字符串转为UTF8字符集 * @param string $str * @return false|string */ private static function convertToUtf8(string $str) { $charset = mb_detect_encoding($str, ['UTF-8', 'GBK', 'BIG5', 'CP936']); if ($charset !== 'UTF-8') { $str = iconv($charset, 'UTF-8', $str); } return $str; } /** * 删除目录以及子目录等所有文件 * * - 请注意不要删除重要目录! * * @param string $path 需要删除目录路径 * @param bool $delSelf 是否删除自身 */ private static function deleteDir(string $path, bool $delSelf = true) { if (!is_dir($path)) { return; } $dir = opendir($path); while (false !== ($file = readdir($dir))) { if (($file != '.') && ($file != '..')) { $full = $path . '/' . $file; if (is_dir($full)) { self::deleteDir($full, true); } else { unlink($full); } } } closedir($dir); if ($delSelf) { rmdir($path); } } /** * 遍历文件夹,返回文件路径列表 * @param string $path * @param string $fileType all|file|folder * @param bool $traversalChildren 是否遍历下级目录 * @return array */ private static function scanDir(string $path, string $fileType = 'all', bool $traversalChildren = true) { if (!is_dir($path) || !in_array($fileType, ['all', 'file', 'folder'])) { return []; } $path = rtrim($path, '/'); $list = []; $files = scandir($path); foreach ($files as $file) { if ($file != '.' && $file != '..') { $p = $path . '/' . $file; $isDir = is_dir($p); if ($isDir) { $p .= '/'; } if ($fileType === 'all' || ($fileType === 'file' && !$isDir) || ($fileType === 'folder' && $isDir)) { $list[] = $p; } if ($traversalChildren && $isDir) { $list = array_merge($list, self::scanDir($p, $fileType, $traversalChildren)); } } } return $list; } }
调用:
//示例1:解压zip文件到/test/test1/目录下 ZipUtil::extract('/test/test1.zip', '/test/test1/'); //示例2:解压rar文件到/test/test2/目录下 ZipUtil::extract('/test/test2.rar', '/test/test2/'); //示例3:解压phar文件到/test/test3/目录下 ZipUtil::extract('/test/test3.phar', '/test/test3/'); //示例4:解压tar文件到/test/test4/目录下 ZipUtil::extract('/test/test4.tar', '/test/test4/'); //示例5:解压gz文件到/test/test5/目录下 ZipUtil::extract('/test/test5.tar.gz', '/test/test5/'); //示例6:解压bz2文件到/test/test6/目录下 ZipUtil::extract('/test/test6.tar.bz2', '/test/test6/'); //示例7:解压7z文件到/test/test7/目录下 ZipUtil::extract('/test/test7.7z', '/test/test7/'); //示例8:压缩单个文件 ZipUtil::packageFile('/test/test8/1.txt', '/test/test8.zip'); //示例9:压缩多个文件 ZipUtil::package([ ['file_type'=>'file', 'file_path'=>'/test/test9/1.txt', 'local_name'=>'1.txt'], ['file_type'=>'file', 'file_path'=>'/test/test9/2.txt', 'local_name'=>'2.txt'], ['file_type'=>'folder', 'local_name'=>'1/'], ['file_type'=>'folder', 'local_name'=>'2/'], ], '/test/test9.zip'); //示例10:压缩文件夹 ZipUtil::packageDir('/test/test10/', '/test/test10.zip');
五、 结语
在刚开始使用这个工具类的过程中,发现在了个坑,就是在window压缩的zip文件放到linux进行解压会发生文件名乱码的情况,所以不能直接使用extractTo方法进行解压,需要对zip解压出来的文件名进行转码。