一、 引語
本人在做一個企業雲盤項目當中,遇到一個文件在線解壓縮的需求,查了網上很多資料,但都是隻支持單一格式或部分格式,固創建了本工具類,對市面上主流的壓縮格式進行集成支持,並且簡單易用。
二、 功能
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解壓出來的文件名進行轉碼。