PHP Zip
Zip archives are files that are compressed using the zip compression algorithm. They are often used to reduce the size of files and make them easier to transfer
A ZIP archive is a file that bundles one or more files together and compresses them using the zip algorithm. Archives are widely used to shrink download sizes, group related files into a single distributable unit, and back up data. A zip archive normally carries the .zip file extension.
This chapter shows how to create, read, and extract ZIP archives in modern PHP using the built-in ZipArchive class, with runnable examples and the gotchas worth knowing.
The ZipArchive class
The legacy procedural zip_* functions were deprecated in PHP 7.4 and removed in PHP 8.0. Modern code should use the object-oriented ZipArchive class, which is part of the bundled zip extension (enable ext-zip if it is not already active — check with extension_loaded('zip')).
The methods you will reach for most often are:
| Method | Purpose |
|---|---|
open($filename, $flags) | Opens an archive for reading or writing. Returns true or an error code. |
addFile($path, $entryName) | Adds a file from disk to the archive. |
addFromString($entryName, $contents) | Adds an entry from an in-memory string. |
addEmptyDir($dirName) | Adds an empty directory entry. |
extractTo($directory, $entries) | Extracts all entries (or a chosen subset) to a directory. |
getFromName($entryName) | Reads one entry into a string without touching disk. |
statIndex($i) / numFiles | Inspects entries and counts them. |
getStatusString() | Returns a human-readable status message for error handling. |
close() | Writes pending changes and closes the handle. |
Important: Changes you make with
addFile()oraddFromString()are only written to disk when you callclose(). Forgettingclose()produces an empty or corrupt archive.
Creating a ZIP archive
Pass the ZipArchive::CREATE flag to create a new archive, and add ZipArchive::OVERWRITE to start fresh if a file with that name already exists:
$zip = new ZipArchive();
$zipName = 'documents.zip';
if ($zip->open($zipName, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
// Add an entry from a string (no temp file needed).
$zip->addFromString('readme.txt', "Hello from PHP!\n");
// Add a file from disk, optionally under a folder inside the archive.
$zip->addFile('report.csv', 'data/report.csv');
// Add an empty directory entry.
$zip->addEmptyDir('logs');
echo "Adding {$zip->numFiles} entries to {$zipName}.\n";
$zip->close(); // Must be called for the archive to be written.
echo "Archive saved.\n";
} else {
echo "Could not create the archive.\n";
}Assuming report.csv exists, this prints:
Adding 3 entries to documents.zip.
Archive saved.Read numFiles before close() — after the handle is closed the object no longer reflects the entry count.
Reading entries without extracting
You can inspect an archive's contents, or pull a single entry into memory, without unpacking everything to disk:
$zip = new ZipArchive();
if ($zip->open('documents.zip') === true) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$entry = $zip->statIndex($i);
echo $entry['name'] . ' (' . $entry['size'] . " bytes)\n";
}
// Read one entry straight into a string.
echo "---\n" . $zip->getFromName('readme.txt');
$zip->close();
}Output:
readme.txt (16 bytes)
data/report.csv (20 bytes)
logs/ (0 bytes)
---
Hello from PHP!Extracting an archive to disk
extractTo() unpacks the archive. Always check the return value and report failures with getStatusString():
$zip = new ZipArchive();
$filename = 'documents.zip';
$extractTo = './extracted_files';
if ($zip->open($filename) === true) {
if (!is_dir($extractTo)) {
mkdir($extractTo, 0755, true);
}
if ($zip->extractTo($extractTo) === true) {
echo "Archive extracted successfully.";
} else {
echo "Extraction failed: " . $zip->getStatusString();
}
$zip->close();
} else {
echo "Failed to open archive.";
}To extract only specific files, pass their names as the second argument:
$zip->extractTo($extractTo, ['readme.txt', 'data/report.csv']);Common gotchas
- Always call
close(). Until you do, additions live only in memory and the file on disk may be empty. open()does not returnfalseon every error. On failure it returns an integer error code (for exampleZipArchive::ER_NOENTwhen the file is missing). Comparing strictly with=== trueis the safe way to detect success.- Zip Slip. When extracting untrusted archives, a malicious entry named like
../../etc/passwdcan escape the target directory. Validate or sanitise entry names before extracting files you did not create. - Memory.
getFromName()loads the whole entry into memory; for large entries preferextractTo()or stream withgetStream().
Related topics
- PHP file_put_contents() — write the data you read from an archive back to disk.
- PHP file_get_contents() — read whole files, e.g. before adding them to a zip.
- PHP file_exists() — confirm a file is present before calling
addFile(). - PHP fread() — read file contents in chunks.
Conclusion
The ZipArchive class is the modern, reliable way to work with ZIP archives in PHP. You can create archives with addFile() and addFromString(), inspect them with numFiles and statIndex(), pull single entries into memory with getFromName(), and unpack them with extractTo() — always remembering to call close() and to guard against untrusted entry names when extracting.