PHP 8中循环生成Zip后无法关闭第二个Zip的问题
问题分析与修复方案
核心错误原因
mfzip类的cz()方法缺少空值校验:当$this->zip未被正确初始化(比如newzip()失败)时,直接调用$this->zip->close()会触发Invalid or uninitialized Zip object致命错误。- 主脚本创建新Zip后未设置密码且未处理失败场景:创建第二个Zip后,没有调用
passzip()设置密码,且未检查newzip()的结果是否成功,后续循环仍会尝试向无效的Zip对象添加文件,最终关闭时出错。 - Zip大小计算逻辑不准确:你通过累加文件在Zip中的大小判断是否拆分,但实际Zip的磁盘大小是经过压缩的,这个逻辑可能导致拆分时机不符合预期(非致命,但需要注意)。
修复后的代码
1. 修正mfzip类
主要给所有方法添加Zip对象有效性校验,避免无效调用:
/** * marcfunk zipping class **/ class mfzip { ## vars public $fstats; public $result; public $zip; /** * create zip archive * @param: string, filename of zipfile **/ public function newzip($filename = "") { $this->result = 900; $this->zip = null; if($filename !== "") { $newzip = new ZipArchive(); if($newzip->open($filename, ZipArchive::CREATE) === TRUE) { $this->result = 200; $this->zip = $newzip; } } } /** * set zip password * @param: string, password for zip **/ public function passzip($hash) { $this->result = 900; if($this->zip instanceof ZipArchive) { if($this->zip->setPassword($hash)) { $this->result = 200; } } } /** * add file to archive * @param: string, path to local file * @param: string, path in zip file **/ public function add($local = "", $remote = "") { $this->result = 900; if($this->zip instanceof ZipArchive && $local !== "" && file_exists($local) && $remote !== "") { if($this->zip->addFile($local, $remote)) { $this->result = 200; } } } /** * crypt file * @param: string, path in zip file **/ public function cf($remote = "") { $this->result = 900; if($this->zip instanceof ZipArchive && $remote !== "") { if($this->zip->setEncryptionName($remote, ZipArchive::EM_AES_256)) { $this->result = 200; } } } /** * stats of file * @param: string, path in zip file **/ public function stats($remote = "") { $this->result = 900; $this->fstats = null; if($this->zip instanceof ZipArchive && $remote !== "") { if($stats = $this->zip->statName($remote)) { $this->fstats = $stats; $this->result = 200; } } } /** * close zip **/ public function cz() { $this->result = 900; if($this->zip instanceof ZipArchive) { if($this->zip->close() === TRUE) { $this->result = 200; // 关闭后清空zip对象,避免重复调用 $this->zip = null; } } } }
2. 修正主脚本
主要修复创建新Zip后的密码设置和失败处理逻辑:
/** * check and process file list **/ ## log $log->logging($lf, "Check backuppathes list."); ## check file list if(!empty($fullfilelist)) { ## log $log->logging($lf, "Setting zip parameter."); ## zip counter $zc = 0; ## zip size $zs = 0; ## zip path $zp = __DIR__ . DIRECTORY_SEPARATOR . "zip" . DIRECTORY_SEPARATOR; // 确保目录存在 if(!is_dir($zp)) { mkdir($zp, 0755, true); $log->logging($lf, "Created zip directory: " . $zp); } ## zipfile $zipfile = $zp . date("Y-m-d") . "-" . str_pad($zc, 3, "0", STR_PAD_LEFT) . ".zip"; ## log $log->logging($lf, "Create zip file: " . $zipfile); ## start zip file $myzip[$zc] = new mfzip; $myzip[$zc]->newzip($zipfile); ## check new zip file if($myzip[$zc]->result == 200) { ## log $log->logging($lf, "Zip " . $zipfile . " starts! Set password..."); ## set zip password $myzip[$zc]->passzip($CFG['zippass']); ## check setting if($myzip[$zc]->result == 200) { ## log $log->logging($lf, "Password set! Process pathes..."); ## process file list foreach($fullfilelist as $ffl) { ## local file $l = $ffl; ## log $log->logging($lf, "Add local file: " . $l); ## file in zip $r = substr($ffl, 3); // 去掉多余的长度参数,默认到末尾即可 ## checking file reading if(is_readable($l)) { ## add file $myzip[$zc]->add($l, $r); ## check adding if($myzip[$zc]->result == 200) { ## log $log->logging($lf, "Adding successful! Crypt file..."); ## crypt file $myzip[$zc]->cf($r); ## check crypt if($myzip[$zc]->result == 200) { ## log $log->logging($lf, "Crypting successful! Get stats of file..."); ## get file stats $myzip[$zc]->stats($r); ## check stats if($myzip[$zc]->result == 200 && is_array($myzip[$zc]->fstats)) { ## log $log->logging($lf, "Get stats successful! Checking zip size..."); ## filesize $rfs = $myzip[$zc]->fstats['size']; ## new zip size $zs = $zs + $rfs; ## log $log->logging($lf, "File in zip: " . $rfs . " bytes | current zip size: " . $zs . " bytes"); ## check zip size with configured size if($zs > $CFG['zipsize']) { ## log $log->logging($lf, "Close zip and start new file (" . $zc . ")!"); ## close zip $myzip[$zc]->cz(); ## check closing if($myzip[$zc]->result == 200) { ## log $log->logging($lf, "Closing successfully: " . $zipfile); ## counting up $zc++; ## zipfile $zipfile = $zp . date("Y-m-d") . "-" . str_pad($zc, 3, "0", STR_PAD_LEFT) . ".zip"; ## start new zip file $myzip[$zc] = new mfzip; $myzip[$zc]->newzip($zipfile); ## check new file if($myzip[$zc]->result == 200) { ## log $log->logging($lf, "Create new zip file: " . $zipfile); ## set password for new zip $myzip[$zc]->passzip($CFG['zippass']); if($myzip[$zc]->result != 200) { $log->logging($lf, "ERROR: Can't set password for new zip file: " . $zipfile); break; } } else { $log->logging($lf, "ERROR: Creating new zip file: " . $zipfile); break; } ## set zip size to 0 $zs = 0; } else { ## log $log->logging($lf, "ERROR: Closing error: " . $zipfile); break; } } } else { ## log $log->logging($lf, "ERROR: Can't get stats of file " . $r); } } else { ## log $log->logging($lf, "ERROR: Can't crypt file: " . $r); } } else { ## log $log->logging($lf, "ERROR: Can't adding file: " . $r); } } else { ## log $log->logging($lf, "ERROR: Can't read file: " . $l); } } } else { ## log $log->logging($lf, "ERROR: Can't set password for zip file: " . $zipfile); } ## log $log->logging($lf, "Last closing file: " . $zipfile); ## 检查当前Zip对象是否有效再关闭 if(isset($myzip[$zc]) && $myzip[$zc]->zip instanceof ZipArchive) { $myzip[$zc]->cz(); ## check closing if($myzip[$zc]->result == 200) { ## log $log->logging($lf, "Closing successfully!"); } else { ## log $log->logging($lf, "ERROR: Closing error: " . $zipfile); } } else { $log->logging($lf, "ERROR: No valid zip object to close for file: " . $zipfile); } } else { ## log $log->logging($lf, "ERROR: Creating zip file: " . $zipfile); } } else { ## log $log->logging($lf, "ERROR: No backup pathes!"); }
额外优化点
- 使用
DIRECTORY_SEPARATOR替代硬编码的\\,让代码跨平台兼容 - 创建Zip目录前先检查是否存在,避免目录不存在导致的Zip创建失败
- 去掉
substr()中多余的长度参数,默认截取到字符串末尾即可 - 关闭Zip后清空
$this->zip对象,避免重复调用close()导致错误
内容的提问来源于stack exchange,提问作者marcfunk
相关产品推荐
相关产品推荐

