如何在PHP中压缩和解压文件

184 阅读6分钟

* { box-sizing: border-box; } body {margin: 0;}

在互联网上传输文件时,压缩文件有很多好处。在大多数情况下,压缩格式的所有文件的总大小会下降一个很好的幅度。这意味着你将节省一些带宽,而用户也将获得更快的下载速度。一旦用户下载了一个文件,他们可以随时解压。简而言之,压缩可以使在互联网上提供文件对你和你的访问者来说都容易得多。

有一个因素会使你不愿意压缩文件,或者使这个过程非常令人厌烦,那就是你可能要手动进行压缩。幸运的是,PHP有很多专门处理文件压缩和提取的扩展。你可以使用这些扩展中的功能,在PHP中自动压缩文件。

本教程将教你如何在PHP中压缩和解压(压缩和提取)文件,并从一个压缩包中提取文件。还将学习如何删除或重命名压缩包中的文件,而不必先提取它们。

在PHP中压缩单个文件

PHP ZipArchive类有很多属性和方法,可以帮助你压缩和解压你的所有文件。

你可以一次添加文件到你的压缩档案中,或者一次添加整个目录。无论哪种情况,第一步是创建一个新的ZipArchive 实例,然后调用open($filename, [$flags]) 方法。这个方法将打开一个新的ZIP压缩包,用于读取、写入或其他修改。可选的$flag 参数有五个有效值,决定如何处理不同的情况。

  • **ZipArchive::OVERWRITE**-如果指定的归档文件已经存在,这个标志将覆盖它的内容。
  • ZipArchive::CREATE-如果不存在,这个标志将创建一个新的存档。
  • ZipArchive::EXCL-如果存档已经存在,该标志将导致错误。
  • ZipArchive::CHECKCONS-这个标志将告诉 PHP 对归档文件进行额外的一致性检查,如果检查失败将给出一个错误。
  • **ZipArchive::RDONLY**-这个标志在 PHP 7.4.3 中可用,允许以只读模式打开一个归档文件。

你可以查看这个方法的文档,了解在打开文件失败的情况下返回的不同错误代码。如果压缩文件被成功打开或创建,该方法将返回true

你必须在不同的情况下使用不同的标志组合来避免任何意外的结果。例如,如果你不关心现有压缩文件的内容,就使用ZipArchive::OVERWRITE 标志。如果没有以这个名字存在的档案,它就不会创建一个新的档案。

你可以使用ZipArchive::OVERWRITEZipArchive::CREATE 的组合,以便覆盖现有的存档,并在没有特定名称的情况下创建一个新的存档。下面是一个例子。

$zip->open('my_compressed_files.zip', ZipArchive::OVERWRITE|ZipArchive::CREATE);

当使用ZipArchive::CREATE 标志时,你会注意到它开始修改一个已经存在的存档。这可能是你真正想做的,但它也可能产生一些意想不到的后果。你可以使用ZipArchive::CREATEZipArchive::EXCL 的组合来确保你只处理以前不存在的存档。

$zip->open('new_compressed_files.zip', ZipArchive::EXCL|ZipArchive::CREATE);

一旦你成功地打开了存档,你可以使用addFile($filename, $localname, $start, $length) 方法将指定路径下的任何文件添加到存档中。$filename 参数是你要添加到存档的文件的路径。$localname 参数用于为文件分配一个名称,以便将其存储在存档中。你可以在每次要添加新的文件到存档中时调用addFile()

在将所有必要的文件添加到归档中后,你可以简单地调用close() 方法来关闭它并保存更改。

假设你有一个网站,允许用户下载不同字体的文件,以及使用这些字体的许可信息。像这样的文件将是使用PHP自动归档的完美例子。下面的代码告诉你如何做到这一点。

<?php

$zip = new ZipArchive();
$zip->open('compressed/font_files.zip', ZipArchive::CREATE);
 
$zip->addFile('fonts/Monoton/Monoton-Regular.ttf', 'Monoton-Regular.ttf');
$zip->addFile('fonts/Monoton/OFL.txt', 'license.txt');
 
$zip->close();

?>

我们首先创建一个ZipArchive 实例,然后使用open() 方法来创建我们的存档。addFile() 方法将我们实际的**.ttf字体文件和.txt**许可证文件添加到存档中。

你应该注意到,原来的文件是在fonts/Monoton目录下的。然而,PHP代码直接把它放在了我们存档的根目录下。你可以改变目录结构以及存档中的文件名称。

压缩一个目录中的多个文件

将单个文件添加到你的归档文件中,一段时间后会变得很累。例如,你可能想创建一个目录中所有**.pdf.png**文件的存档。在这种情况下,addGlob($pattern, $flags, $options) 方法将被证明非常有用。这种方法的唯一缺点是,你失去了对存档中单个文件位置的控制。然而,你仍然可以使用$options 参数来影响存档内的目录结构。选项是以关联数组的形式传递的。

  • add_path-分配给add_path 的值以存档中文件的本地路径为前缀。
  • remove_path-分配给remove_path 的值用于从添加到归档文件的不同文件的路径中移除匹配的前缀。
  • remove_all_path-将remove_all_path 的值设为true ,将从文件的路径中删除除名称外的所有内容。在这种情况下,文件被添加到归档文件的根部。

重要的是要记住,在移除一个路径之前,要对add_path 中指定的值进行前缀处理。

下面的代码片断将使addGlob() 和所有这些选项的使用更加清晰。

$zip = new ZipArchive();
$zip->open('compressed/user_archive.zip', ZipArchive::CREATE);
 
$options = array('add_path' => 'light_wallpapers/', 'remove_all_path' => TRUE);
$zip->addGlob('lights/*.jpg', 0, $options);

$options = array('add_path' => 'font_files/', 'remove_all_path' => TRUE);
$zip->addGlob('documents/*.ttf', 0, $options);

$options = array('add_path' => 'pdf_books/', 'remove_all_path' => TRUE);
$zip->addGlob('documents/*.pdf', 0, $options);

$options = array('add_path' => 'images/', 'remove_all_path' => TRUE);
$zip->addGlob('documents/*.{jpg, png}', GLOB_BRACE, $options);
 
$zip->close();

像往常一样,我们首先创建一个ZipArchive 实例,然后使用open() 方法来创建我们的存档。在调用addGlob() 方法之前,我们还为$options 数组中的add_path 键每次指定不同的值。这样,我们可以一次处理一组特定的文件,并提供相应的归档选项。

在第一种情况下,我们遍历lights目录下的所有**.jpg文件,并把它们放在归档的light_wallpapers目录下。同样地,我们遍历文档目录中所有的.ttf文件,然后把它们放在存档中一个叫做font_files的文件夹里。最后,我们一次遍历文档中所有的.jpg.png文件,并将它们全部放在images**目录中。

正如你所看到的,$options 参数中的值对于组织存档中的内容很有用。

修改或覆盖存档

你可以结合不同的标志和选项来实现存档内特定的文件和目录结构。正如我前面提到的,我们可以使用open() 方法来打开一个新的或现有的存档进行读写。

在本节中,我们将学习由于传递给open() 方法的标志和传递给$options 方法的addGlob() 方法的不同组合而可能产生的差异。假设没有名为compressed_files.zip的档案,我们运行以下代码。

<?php

$zip = new ZipArchive();

$zip->open('compressed_files.zip', ZipArchive::CREATE);

$options = array('add_path' => 'wallpapers/');
$zip->addGlob('files/images/*.*', 0, $options);

$zip->close();

?>

它将从目录files/images中获取所有图像文件,并将它们放在存档根目录wallpapers中。存档中的图像文件的路径将是wallpapers/files/images

你可以通过使用以下一组选项来摆脱所有图像的初始路径。

$options = array('add_path' => 'wallpapers/', 'remove_all_path' => TRUE);

现在图像文件将直接存储在wallpapers里面,原始图像路径将被丢弃。

正如我前面提到的,ZipArchive::CREATE 将创建一个新的存档,或者开始修改现有的存档,如果它已经存在。你不会得到任何关于现有存档被修改的警告。这意味着,如果你执行本节的代码来创建存档,然后在用新的选项替换了原来的选项后再执行,你会得到一些意外的结果。

归档文件将包含一个带有子目录files/images壁纸目录,以及直接在壁纸目录中的图像。刚开始的时候可能会有点混乱,让你不明白为什么你设置的$options ,却没有得到尊重。

如果你想覆盖归档文件中已经存在的所有内容,但又不想让代码在不存在的情况下出错,那么一定要确保使用ZipArchive::CREATE|ZipArchive::OVERWRITE

还要记住,自 libzip 1.6.0 起,一个空文件不再被认为是一个有效的存档。

从一个存档中提取内容

ZipArchive 类有一个叫做extractTo($destination, $entries) 的方法来提取一个归档文件的内容。你可以用它来提取存档中的所有内容,或者只提取一些特定的文件。$entries 参数可以用来指定要提取的单个文件名,也可以用它来传递一个文件阵列。

需要记住的一点是,你需要指定存档内文件的正确路径,以便提取它。例如,我们在上一节中存档了一个名为AlegreyaSans-Light.ttf的字体文件。该文件被储存在存档中一个名为font_files的目录中。这意味着你需要在$entries 参数中指定的路径将是font_files/AlegreyaSans-Light.ttf,而不是简单的AlegreyaSans-Light.ttf

在提取过程中,目录和文件结构将被保留,文件将被提取到它们各自的目录中。

<?php

$zip = new ZipArchive();
$zip->open('compressed/user_archive.zip', ZipArchive::CREATE);
$zip->extractTo('uncompressed/', 'font_files/AlegreyaSans-Light.ttf');
$zip->close();
 
?>

如果你省略第二个参数,该方法将提取档案中的所有文件。

获得对存档的更多控制

ZipArchive 类也有很多其他的方法和属性,以帮助你在提取所有内容之前获得更多关于存档的信息。

你可以使用count() 方法来计算存档中的文件数量。另一个选择是使用numFiles 属性。它们可以用来遍历存档中的所有文件,只提取你需要的文件--或者你可以对它们做一些其他的事情,比如从存档中删除它们。

在下面的例子中,我们要删除存档中所有包含斜体字的文件。类似的代码也可以用来删除所有不包含特定单词的文件。你也可以遍历这些文件,用其他东西替换某个特定的词。

<?php

$zip = new ZipArchive();
$zip->open('compressed/user_archive.zip', ZipArchive::CREATE);

$file_count = $zip->count();

for($i = 0; $i < $file_count; $i++) {
    $file_name = $zip->getNameIndex($i);

    if(stripos($file_name, 'Italic') !== false) {
        $zip->deleteName($file_name);
    }
}
 
$zip->close();

?>

在上面的代码中,我们使用deleteName() 来删除一个单独的文件。然而,你也可以用它来删除整个目录。

一个类似的函数renameName($oldname, $newname) ,可以用来改变存档中任何文件的名称。如果一个名为$newname 的文件已经存在,你会得到一个错误。

最后的思考

我们已经介绍了ZipArchive 类的一些非常有用的方法,这些方法将使PHP中自动压缩和提取文件变得轻而易举。现在,你应该能够根据你自己的标准,一次性地压缩单个文件或一组文件。同样,你应该能够从档案中提取任何特定的文件而不影响其他内容。

count()numFiles 的帮助下,你将获得对单个文件的更多控制,重命名或删除它们将变得超级容易。你应该至少翻阅一次文档,了解更多此类功能。