PHP可以动态地处理ZIP文件。

50
什么是最简单的方法来压缩服务器上文件夹中的2个文件,并强制下载?而不需要将“zip”保存到服务器上。
$zip = new ZipArchive();
// the string "file1" is the name we're assigning the file in the archive
$zip->addFile(file_get_contents($filepath1), 'file1'); //file 1 that you want compressed
$zip->addFile(file_get_contents($filepath2), 'file2'); //file 2 that you want compressed
$zip->addFile(file_get_contents($filepath3), 'file3'); //file 3 that you want compressed
echo $zip->file(); //this sends the compressed archive to the output buffer instead of writing it to a file.

有人可以确认一下吗:

我有一个文件夹,里面有test1.doc、test2.doc和test3.doc。

根据上面的例子,file1(file2和file3)可能只是test1.doc等。

我需要对"$filepath1"做些什么吗?那是包含这三个文档的文件夹目录吗?

对不起,我的问题很基础。


2
根据文档,ZipArchive类没有file()函数,这将导致错误。为了实现这个功能,需要使用不同的库,比如这个。我把它放在这里,以便其他人不必花费数小时来尝试使用下面的建议来解决问题。 - Neograph734
根据文档所述,ZipArchive的addFile()方法接受要添加的文件的路径名,而不是该文件的内容。必须删除file_get_contents()调用。该方法在出现错误时返回FALSE,请在处理此类代码时使用它进行错误处理,例如不存在的文件或在错误中使用文件内容。 - hakre
9个回答

83
很不幸,在CentOS 5.x上使用PHP 5.3.4-dev和Zend Engine v2.3.0时,我无法让上面的代码工作。我只能得到一个错误信息:
“无效或未初始化的Zip对象”
所以,为了使其正常工作,我不得不使用文件名作为ZipArchive::open()的第一个参数,并将ZipArchive::OVERWRITE标志作为第二个参数来初始化ZipArchive $zip。
我成功地使用了以下片段(取自Jonathan Baltazar在PHP.net ZipArchive::open手册页面上的示例(2008年7月),为了简洁起见,没有进行错误处理):
// Prepare file
$file = tempnam('/path/to/my/tmp/directory', 'zip');
register_shutdown_function('unlink', $file);
$zip = new ZipArchive();
$zip->open($file, ZipArchive::OVERWRITE);

// Stuff with content
$zip->addFromString('file_name_within_archive.ext', "your string\n");
$zip->addFile('file_on_server.ext', 'second_file_name_within_archive.ext');

// Close and send to users
$zip->close();

header('Content-Type: application/zip');
header('Content-Length: ' . filesize($file));
header('Content-Disposition: attachment; filename="file.zip"');
readfile($file);

我知道这与仅使用内存不同 - 除非你的tmp在内存中 - 但也许这可以帮助其他人,他们像我一样为上述解决方案而苦苦挣扎,并且对于没有RAM磁盘用于临时文件的这种小性能损失根本不是问题。

今天这个对我很有帮助,谢谢。当处理一个生成PDF的管理/内容管理系统时,性能次于将所有文件压缩在一起,这样我就不必用15个下载对话框淹没管理员了 :) - totallyNotLizards
有人知道在我们读取输出缓冲区中的文件后立即删除它是否会出现任何问题吗?或者这种方法是绝对可靠的吗? - Erik Čerpnjak

9

您的代码非常接近。您需要使用文件名而不是文件内容。

$zip->addFile(file_get_contents($filepath1), 'file1');

应该是

$zip->addFile($filepath1, 'file1');

http://us3.php.net/manual/zh/function.ziparchive-addfile.php

如果您需要添加变量中的文件而不是文件本身,可以使用addFromString函数。

$zip->addFromString( 'file1', $data );

我同意这个观点,但你需要使用像Vinko所说的强制头部的方式来让浏览器下载。 - m4rc

7

这对我来说可行(没有写入磁盘)

$tmp_file = tmpfile(); //temp file in memory
$tmp_location = stream_get_meta_data($tmp_file)['uri']; //"location" of temp file

$zip = new ZipArchive;
$res = $zip->open($tmp_location, ZipArchive::CREATE);
$zip->addFile($filepath1, 'file1');
$zip->close();

header('Content-type: application/zip');
header('Content-Disposition: attachment; filename="download.zip"');
echo(file_get_contents($tmp_location));

tmpfile() 可以在内存中创建一个虚拟文件。它就像一个普通的文件,但没有实际位置,并且会在脚本结束时被删除。 stream_get_meta_data($tmp_file)['uri'] 提供了一个路径给其他服务使用虚拟文件。 有了这个路径,你可以像处理真实文件一样处理它。 - user2909086
3
你的评论应该被编辑到你的答案中。(然后,当然,删除你的评论。) - mickmackusa
你应该使用readfile()而不是echo(file_get_contents()) - Clément Moulin - SimpleRezo
3
tmpfile 不在内存中。据我所读,它取决于您设置临时文件的位置。 默认情况下,它位于 /tmp/ 路径下。请参见:https://dev59.com/Opffa4cB1Zd3GeqP-qAa#37574617 - cottton
非常感谢这个。它有效...没有任何东西被永久写入/tmp文件夹。 - Steven Yip

6
如果您有访问zip命令行实用程序的权限,可以尝试使用以下命令:
<?php
$zipped_data = `zip -q - files`;
header('Content-type: application/zip');
header('Content-Disposition: attachment; filename="download.zip"');
echo $zipped_data;
?>

这里的files是你想要压缩成zip文件的内容,而zip是zip可执行文件所在的位置。

当然,这假设你使用的是Linux或类似的操作系统。在Windows中,可能需要使用其他的压缩工具来实现相似的功能。

此外,还有一个zip扩展,其用法可以参考这里


编辑你的问题并附上错误信息。首先怀疑你没有安装zip扩展。 - Vinko Vrsalovic

5

我尝试了maraspin的方法。奇怪的是,当我删除引用文件大小的标题行时,它就可以工作了:

header('Content-Length: ' . filesize($file));

有了上述的更改,代码可以轻松运行!如果没有这个更改,我以前会收到以下错误信息:

未找到中央目录签名。这个文件不是zip文件,或者它构成多部分存档的一个磁盘。在后一种情况下,中央目录和zip文件注释将在此存档的最后一个磁盘上找到。

测试环境:

操作系统:Ubuntu 10.10 浏览器:Firefox 默认的LAMP堆栈。


1
头部是“Content-Length”,文件大小而不是长度被设置为它。也许这就是原因? - Parziphal

4

1

如果你想插入 'Content-Length',可以这样做:

$length = filesize($file);
header('Content-Length: ' . $length);

我不知道为什么,但如果你把它放在同一行,它就会崩溃。


0

在Unix系统上(可能还有其他系统),

您可以通过删除文件的文件条目(将其从其i节点中“取消链接”)并使用先前打开的句柄发送其数据来应用@maraspin答案的简单技巧。这基本上是tmpfile()所做的事情; 通过这种方式,您可以使任何文件“临时化”。

代码与 maraspin 的代码相同,直到最后几行:

// Prepare File
$file = tempnam("tmp", "zip");
$zip = new ZipArchive();
$zip->open($file, ZipArchive::OVERWRITE);

// Stuff with content
$zip->addFromString('file_name_within_archive.ext', $your_string_data);
$zip->addFile('file_on_server.ext', 'second_file_name_within_archive.ext');

// Close and send to users
$zip->close();

header('Content-Type: application/zip');
header('Content-Length: ' . filesize($file));
header('Content-Disposition: attachment; filename="file.zip"');

// open a handle to the zip file.
$fp = fopen($file, 'rb');
// unlink the file. The handle will stay valid, and the disk space will remain occupied, until the script ends or all file readers have terminated and closed.
unlink($file);
// Pass the file descriptor to the Web server.
fpassthru($fp);

脚本完成、异常终止、应用程序池循环或Apache子进程回收后,文件的“消失”将被正式确认并释放其磁盘空间。


0

这对我有效

$tmp = tempnam(sys_get_temp_dir(), 'data');
$zip = new ZipArchive();
$zip->open($tmp, ZipArchive::CREATE);//OVERWRITE    //CREATE
$zip->addFromString('aaa.txt', 'data');//zip on the fly 
$zip->close();
header('Content-type: application/zip');
header('Content-Disposition: attachment; filename="download.zip"');

ob_clean();
flush();

if (file_exists($tmp))
    readfile($tmp);
else
    echo 'No file';

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接