PHP cung cấp sẵn lớp ZipArchive để thao tác với định dạng ZIP. Dưới đây là một lớp bao bọc giúp nén toàn bộ thư mục, liệt kê nội dung gói nén và giải nén an toàn, đồng thời hỗ trợ bỏ qua một số tệp/tên thư mục khi cần.
Lưu ý về đường dẫn
Nên truyền đường dẫn tương đối cho thư mục cần nén. Khi đó, tên các mục bên trong tệp ZIP sẽ khớp với cấu trúc thư mục mong muốn. Nếu buộc phải dùng đường dẫn tuyệt đối cho tệp gốc, hãy tự xử lý đối số thứ hai của addFile() để tránh lưu cả đường dẫn hệ thống vào bên trong gói nén.
Cách sử dụng
$worker = new ZipArchiveHelper();
$archivePath = './storage/backup.zip';
$sourcePath = './storage/images/';
$result = $worker->zipFolder($archivePath, $sourcePath);
if ($result) {
echo 'Nén thành công';
} else {
echo 'Nén thất bại';
}
Mã nguồn lớp xử lý ZIP
class ZipArchiveHelper
{
protected $archive;
protected $ignoredFiles;
public function __construct()
{
$this->archive = new ZipArchive();
}
/**
* Nén một thư mục thành tệp ZIP.
*
* @param string $zipPath Đường dẫn tệp ZIP đầu ra
* @param string $folderPath Đường dẫn thư mục cần nén
* @param array $ignore Danh sách tên tệp/thư mục bỏ qua
* @return bool
*/
public function zipFolder(string $zipPath, string $folderPath, array $ignore = []): bool
{
if (!is_dir($folderPath)) {
throw new InvalidArgumentException('Thư mục cần nén không tồn tại.');
}
$this->ignoredFiles = $ignore;
$flags = ZipArchive::CREATE | ZipArchive::OVERWRITE;
if ($this->archive->open($zipPath, $flags) !== true) {
throw new RuntimeException('Không thể tạo tệp ZIP.');
}
$folderPath = rtrim(realpath($folderPath), '\\/');
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$folderPath,
FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS
),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $item) {
if (in_array($item->getFilename(), $this->ignoredFiles, true)) {
continue;
}
$realPath = $item->getRealPath();
$relative = str_replace('\\', '/', substr($realPath, strlen($folderPath) + 1));
if ($item->isDir()) {
$this->archive->addEmptyDir($relative . '/');
} else {
$this->archive->addFile($realPath, $relative);
}
}
return $this->archive->close();
}
/**
* Giải nén tệp ZIP ra thư mục đích.
*
* @param string $zipPath Đường dẫn tệp ZIP
* @param string $destination Đường dẫn thư mục đích
* @return bool
*/
public function unzipFile(string $zipPath, string $destination): bool
{
if (!$this->isZipFile($zipPath)) {
return false;
}
if ($this->archive->open($zipPath) !== true) {
return false;
}
$result = $this->archive->extractTo($destination);
$this->archive->close();
return $result;
}
/**
* Trả về danh sách tệp và thư mục trong gói ZIP.
*
* @param string $zipPath Đường dẫn tệp ZIP
* @return array
*/
public function getContents(string $zipPath): array
{
$files = [];
$dirs = [];
if ($this->archive->open($zipPath) !== true) {
return ['files' => $files, 'dirs' => $dirs];
}
for ($i = 0; $i < $this->archive->numFiles; $i++) {
$name = $this->archive->getNameIndex($i);
if (substr($name, -1) === '/') {
$dirs[] = $name;
} else {
$files[] = $name;
}
}
$this->archive->close();
return ['files' => $files, 'dirs' => $dirs];
}
/**
* Kiểm tra định dạng ZIP qua 4 byte đầu tiên.
*
* @param string $zipPath
* @return bool
*/
protected function isZipFile(string $zipPath): bool
{
$handle = fopen($zipPath, 'rb');
if (!$handle) {
return false;
}
$bytes = fread($handle, 4);
fclose($handle);
return $bytes === "PK\x03\x04";
}
}