The ZipArchive class
Index value of last added entry (file or directory). Available as of PHP 8.0.0 and PECL zip 1.18.0.
Status of the Zip Archive. Available for closed archive, as of PHP 8.0.0 and PECL zip 1.18.0.
System status of the Zip Archive. Available for closed archive, as of PHP 8.0.0 and PECL zip 1.18.0.
Number of files in archive
File name in the file system
Table of Contents
- ZipArchive::addEmptyDir — Add a new directory
- ZipArchive::addFile — Adds a file to a ZIP archive from the given path
- ZipArchive::addFromString — Add a file to a ZIP archive using its contents
- ZipArchive::addGlob — Add files from a directory by glob pattern
- ZipArchive::addPattern — Add files from a directory by PCRE pattern
- ZipArchive::clearError — Clear the status error message, system and/or zip messages
- ZipArchive::close — Close the active archive (opened or newly created)
- ZipArchive::count — Counts the number of files in the archive
- ZipArchive::deleteIndex — Delete an entry in the archive using its index
- ZipArchive::deleteName — Delete an entry in the archive using its name
- ZipArchive::extractTo — Extract the archive contents
- ZipArchive::getArchiveComment — Returns the Zip archive comment
- ZipArchive::getArchiveFlag — Returns the value of a Zip archive global flag
- ZipArchive::getCommentIndex — Returns the comment of an entry using the entry index
- ZipArchive::getCommentName — Returns the comment of an entry using the entry name
- ZipArchive::getExternalAttributesIndex — Retrieve the external attributes of an entry defined by its index
- ZipArchive::getExternalAttributesName — Retrieve the external attributes of an entry defined by its name
- ZipArchive::getFromIndex — Returns the entry contents using its index
- ZipArchive::getFromName — Returns the entry contents using its name
- ZipArchive::getNameIndex — Returns the name of an entry using its index
- ZipArchive::getStatusString — Returns the status error message, system and/or zip messages
- ZipArchive::getStream — Get a file handler to the entry defined by its name (read only)
- ZipArchive::getStreamIndex — Get a file handler to the entry defined by its index (read only)
- ZipArchive::getStreamName — Get a file handler to the entry defined by its name (read only)
- ZipArchive::isCompressionMethodSupported — Check if a compression method is supported by libzip
- ZipArchive::isEncryptionMethodSupported — Check if a encryption method is supported by libzip
- ZipArchive::locateName — Returns the index of the entry in the archive
- ZipArchive::open — Open a ZIP file archive
- ZipArchive::registerCancelCallback — Register a callback to allow cancellation during archive close.
- ZipArchive::registerProgressCallback — Register a callback to provide updates during archive close.
- ZipArchive::renameIndex — Renames an entry defined by its index
- ZipArchive::renameName — Renames an entry defined by its name
- ZipArchive::replaceFile — Replace file in ZIP archive with a given path
- ZipArchive::setArchiveComment — Set the comment of a ZIP archive
- ZipArchive::setArchiveFlag — Set a global flag of a ZIP archive
- ZipArchive::setCommentIndex — Set the comment of an entry defined by its index
- ZipArchive::setCommentName — Set the comment of an entry defined by its name
- ZipArchive::setCompressionIndex — Set the compression method of an entry defined by its index
- ZipArchive::setCompressionName — Set the compression method of an entry defined by its name
- ZipArchive::setEncryptionIndex — Set the encryption method of an entry defined by its index
- ZipArchive::setEncryptionName — Set the encryption method of an entry defined by its name
- ZipArchive::setExternalAttributesIndex — Set the external attributes of an entry defined by its index
- ZipArchive::setExternalAttributesName — Set the external attributes of an entry defined by its name
- ZipArchive::setMtimeIndex — Set the modification time of an entry defined by its index
- ZipArchive::setMtimeName — Set the modification time of an entry defined by its name
- ZipArchive::setPassword — Set the password for the active archive
- ZipArchive::statIndex — Get the details of an entry defined by its index
- ZipArchive::statName — Get the details of an entry defined by its name
- ZipArchive::unchangeAll — Undo all changes done in the archive
- ZipArchive::unchangeArchive — Revert all global changes done in the archive
- ZipArchive::unchangeIndex — Revert all changes done to an entry at the given index
- ZipArchive::unchangeName — Revert all changes done to an entry with the given name
User Contributed Notes 16 notes
Zip a folder (include itself).
Usage:
HZip::zipDir(‘/path/to/sourceDir’, ‘/path/to/out.zip’);
class HZip
<
/**
* Add files and sub-directories in a folder to zip file.
* @param string $folder
* @param ZipArchive $zipFile
* @param int $exclusiveLength Number of text to be exclusived from the file path.
*/
private static function folderToZip ( $folder , & $zipFile , $exclusiveLength ) <
$handle = opendir ( $folder );
while ( false !== $f = readdir ( $handle )) <
if ( $f != ‘.’ && $f != ‘..’ ) <
$filePath = » $folder / $f » ;
// Remove prefix from file path before add to zip.
$localPath = substr ( $filePath , $exclusiveLength );
if ( is_file ( $filePath )) <
$zipFile -> addFile ( $filePath , $localPath );
> elseif ( is_dir ( $filePath )) <
// Add sub-directory.
$zipFile -> addEmptyDir ( $localPath );
self :: folderToZip ( $filePath , $zipFile , $exclusiveLength );
>
>
>
closedir ( $handle );
>
/**
* Zip a folder (include itself).
* Usage:
* HZip::zipDir(‘/path/to/sourceDir’, ‘/path/to/out.zip’);
*
* @param string $sourcePath Path of directory to be zip.
* @param string $outZipPath Path of output zip file.
*/
public static function zipDir ( $sourcePath , $outZipPath )
<
$pathInfo = pathInfo ( $sourcePath );
$parentPath = $pathInfo [ ‘dirname’ ];
$dirName = $pathInfo [ ‘basename’ ];
$z = new ZipArchive ();
$z -> open ( $outZipPath , ZIPARCHIVE :: CREATE );
$z -> addEmptyDir ( $dirName );
self :: folderToZip ( $sourcePath , $z , strlen ( » $parentPath /» ));
$z -> close ();
>
>
?>
With PHP 5.6+, you may come up with theses errors.
Warning: Unknown: Cannot destroy the zip context in Unknown on line 0
Warning: ZipArchive::close(): Can’t remove file: No such file or directory in xxxx.php on line xx
Warning: Unknown: Cannot destroy the zip context in Unknown on line 0
$za = new ZipArchive ;
$za -> open ( ’51-n.com.zip’ , ZipArchive :: CREATE | ZipArchive :: OVERWRITE );
?>
Warning: ZipArchive::close(): Can’t remove file: No such file or directory in xxxx.php on line xx
$za = new ZipArchive ;
$za -> open ( ’51-n.com.zip’ , ZipArchive :: CREATE | ZipArchive :: OVERWRITE );
$za -> close ();
?>
It happens when the zip archive is empty.
Your zip archive will not be saved on disk unless it has at least one file. What’s more, when ZipArchive::OVERWRITE is applied, if there exists a file with the same name, it will be removed after ZipArchive::open() is called.
So, don’t forget to put at least one file to your zip archive.
$za = new ZipArchive ;
$za -> open ( ’51-n.com.zip’ , ZipArchive :: CREATE | ZipArchive :: OVERWRITE );
$za -> addFromString ( ‘wuxiancheng.cn.txt’ , ‘yes’ );
$za -> close ();
?>
There is a usefull function to get the ZipArchive status as a human readable string :
function ZipStatusString ( $status )
switch( (int) $status )
case ZipArchive :: ER_OK : return ‘N No error’ ;
case ZipArchive :: ER_MULTIDISK : return ‘N Multi-disk zip archives not supported’ ;
case ZipArchive :: ER_RENAME : return ‘S Renaming temporary file failed’ ;
case ZipArchive :: ER_CLOSE : return ‘S Closing zip archive failed’ ;
case ZipArchive :: ER_SEEK : return ‘S Seek error’ ;
case ZipArchive :: ER_READ : return ‘S Read error’ ;
case ZipArchive :: ER_WRITE : return ‘S Write error’ ;
case ZipArchive :: ER_CRC : return ‘N CRC error’ ;
case ZipArchive :: ER_ZIPCLOSED : return ‘N Containing zip archive was closed’ ;
case ZipArchive :: ER_NOENT : return ‘N No such file’ ;
case ZipArchive :: ER_EXISTS : return ‘N File already exists’ ;
case ZipArchive :: ER_OPEN : return ‘S Can\’t open file’ ;
case ZipArchive :: ER_TMPOPEN : return ‘S Failure to create temporary file’ ;
case ZipArchive :: ER_ZLIB : return ‘Z Zlib error’ ;
case ZipArchive :: ER_MEMORY : return ‘N Malloc failure’ ;
case ZipArchive :: ER_CHANGED : return ‘N Entry has been changed’ ;
case ZipArchive :: ER_COMPNOTSUPP : return ‘N Compression method not supported’ ;
case ZipArchive :: ER_EOF : return ‘N Premature EOF’ ;
case ZipArchive :: ER_INVAL : return ‘N Invalid argument’ ;
case ZipArchive :: ER_NOZIP : return ‘N Not a zip archive’ ;
case ZipArchive :: ER_INTERNAL : return ‘N Internal error’ ;
case ZipArchive :: ER_INCONS : return ‘N Zip archive inconsistent’ ;
case ZipArchive :: ER_REMOVE : return ‘S Can\’t remove file’ ;
case ZipArchive :: ER_DELETED : return ‘N Entry has been deleted’ ;
default: return sprintf ( ‘Unknown status %s’ , $status );
>
>
A way of zipping files and downloading them thereafter:
$files = array( ‘image.jpeg’ , ‘text.txt’ , ‘music.wav’ );
$zipname = ‘enter_any_name_for_the_zipped_file.zip’ ;
$zip = new ZipArchive ;
$zip -> open ( $zipname , ZipArchive :: CREATE );
foreach ( $files as $file ) $zip -> addFile ( $file );
>
$zip -> close ();
///Then download the zipped file.
header ( ‘Content-Type: application/zip’ );
header ( ‘Content-disposition: attachment; filename=’ . $zipname );
header ( ‘Content-Length: ‘ . filesize ( $zipname ));
readfile ( $zipname );
Simple class xZip to zip big folders into multiple parts and unzip multi zip files at once.
class xZip public function __construct () <>
private function _rglobRead ( $source , & $array = array()) if (! $source || trim ( $source ) == «» ) $source = «.» ;
>
foreach ((array) glob ( $source . «/*/» ) as $key => $value ) $this -> _rglobRead ( str_replace ( «//» , «/» , $value ), $array );
>
foreach ((array) glob ( $source . «*.*» ) as $key => $value ) $array [] = str_replace ( «//» , «/» , $value );
>
>
private function _zip ( $array , $part , $destination ) $zip = new ZipArchive ;
@ mkdir ( $destination , 0777 , true );
if ( $zip -> open ( str_replace ( «//» , «/» , » < $destination >/partz < $part >.zip» ), ZipArchive :: CREATE )) foreach ((array) $array as $key => $value ) $zip -> addFile ( $value , str_replace (array( «../» , «./» ), NULL , $value ));
>
$zip -> close ();
>
>
public function zip ( $limit = 500 , $source = NULL , $destination = «./» ) if (! $destination || trim ( $destination ) == «» ) $destination = «./» ;
>
$this -> _rglobRead ( $source , $input );
$maxinput = count ( $input );
$splitinto = (( $maxinput / $limit ) > round ( $maxinput / $limit , 0 )) ? round ( $maxinput / $limit , 0 ) + 1 : round ( $maxinput / $limit , 0 );
for( $i = 0 ; $i < $splitinto ; $i ++) $this -> _zip ( array_slice ( $input , ( $i * $limit ), $limit , true ), $i , $destination );
>
unset( $input );
return;
>
public function unzip ( $source , $destination ) @ mkdir ( $destination , 0777 , true );
foreach ((array) glob ( $source . «/*.zip» ) as $key => $value ) $zip = new ZipArchive ;
if ( $zip -> open ( str_replace ( «//» , «/» , $value )) === true ) $zip -> extractTo ( $destination );
$zip -> close ();
>
>
>
public function __destruct () <>
>
//$zip = new xZip;
//$zip->zip(500, «images/», «images_zip/»);
//$zip->unzip(«images_zip/», «images/»);
?>
The following code can be used to get a list of all the file names in a zip file.
for( $i = 0 ; $i < $za ->numFiles ; $i ++ ) <
$stat = $za -> statIndex ( $i );
print_r ( basename ( $stat [ ‘name’ ] ) . PHP_EOL );
>
?>
//use bzip2 + ZipArchive to reduce file size of your zip archives.
$zip = new ZipArchive ;
$zip -> open ( ‘i.zip’ , ZIPARCHIVE :: CREATE | ZIPARCHIVE :: OVERWRITE );
$file = ‘wuxiancheng.cn.sql’ ;
$bzFilename = $file . ‘.bz2’ ;
$sql = file_get_contents ( $file );
$sql = bzcompress ( $sql , 9 );
$zip -> addFromString ( $bzFilename , $sql );
$zip -> setArchiveComment ( ‘zipped on ‘ . date ( ‘Y-M-d’ ));
?>?php
Read a file from an archive to a variable.
A warning is printed automatically in case of a CRC32 mismatch, which we capture, so we can print our own error message.
$zip = new ZipArchive ();
if ( $zip -> open ( ‘archive.zip’ )) $fp = $zip -> getStream ( ‘myfile.txt’ ); //file inside archive
if(! $fp )
die( «Error: can’t get stream to zipped file» );
$stat = $zip -> statName ( ‘myfile.txt’ );
$buf = «» ; //file buffer
ob_start (); //to capture CRC error message
while (! feof ( $fp )) $buf .= fread ( $fp , 2048 ); //reading more than 2156 bytes seems to disable internal CRC32 verification (bug?)
>
$s = ob_get_contents ();
ob_end_clean ();
if( stripos ( $s , «CRC error» ) != FALSE ) echo ‘CRC32 mismatch, current ‘ ;
printf ( «%08X» , crc32 ( $buf )); //current CRC
echo ‘, expected ‘ ;
printf ( «%08X» , $stat [ ‘crc’ ]); //expected CRC
>
fclose ( $fp );
$zip -> close ();
//Done, unpacked file is stored in $buf
>
?>
To create a corrupt file, change a byte in a zip file using a hex editor.
There is a limit withing PHP 5.3.3 (which seems to have been addressed in later versions; 5.3.29 seems ok on a different server).
If you try to open a zip file with more than 65,535 files in it (in my case it had 237,942 files) then you cannot access the later files. The numFiles property only reports the first 65k files.
Hi there.
I just wrote a little function to zip a whole folder while maintaining the dir-structure. I hope it might help someone.
function folderToZip ( $folder , & $zipFile , $subfolder = null ) if ( $zipFile == null ) // no resource given, exit
return false ;
>
// we check if $folder has a slash at its end, if not, we append one
$folder .= end ( str_split ( $folder )) == «/» ? «» : «/» ;
$subfolder .= end ( str_split ( $subfolder )) == «/» ? «» : «/» ;
// we start by going through all files in $folder
$handle = opendir ( $folder );
while ( $f = readdir ( $handle )) if ( $f != «.» && $f != «..» ) if ( is_file ( $folder . $f )) // if we find a file, store it
// if we have a subfolder, store it there
if ( $subfolder != null )
$zipFile -> addFile ( $folder . $f , $subfolder . $f );
else
$zipFile -> addFile ( $folder . $f );
> elseif ( is_dir ( $folder . $f )) // if we find a folder, create a folder in the zip
$zipFile -> addEmptyDir ( $f );
// and call the function again
folderToZip ( $folder . $f , $zipFile , $f );
>
>
>
>
?>
Use it like this:
$z = new ZipArchive ();
$z -> open ( «test.zip» , ZIPARCHIVE :: CREATE );
folderToZip ( «storeThisFolder» , $z );
$z -> close ();
?>
Have a good day!
Here is a simple function which zips folders with all sub folders or only a simple file. the $data var can be a string or an array.
public function un_zip ( $data , $arcpf , $mode = ‘zip’ , $obj = » ) <
$absoluterpfad = ‘YOUR_BASE_PATH’ ;
$arcpf = $absoluterpfad . DS . $arcpf ;
if( is_object ( $obj )== false ) <
$archiv = new ZipArchive ();
$archiv -> open ( $arcpf , ZipArchive :: CREATE );
>else < $archiv =& $obj ;>
if( $mode == ‘zip’ ) <
if( is_array ( $data )== true ) <
foreach( $data as $dtmp ) <
$archiv =& un_zip ( $dtmp , $arcpf , ‘zip’ ,& $archiv );
>
>else <
if( is_dir ( $data )== true ) <
$archiv -> addEmptyDir ( str_replace ( $absoluterpfad . DS , » , $data ));
$files = scandir ( $data );
$bad = array( ‘.’ , ‘..’ );
$files = array_diff ( $files , $bad );
foreach( $files as $ftmp ) <
if( is_dir ( $data . DS . $ftmp )== true ) <
$archiv -> addEmptyDir ( str_replace ( $absoluterpfad . DS , » , $data . ‘/’ . $ftmp ));
$archiv =& un_zip ( $data . DS . $ftmp , $arcpf , ‘zip’ ,& $archiv );
>elseif( is_file ( $data . DS . $ftmp )== true ) <
$archiv -> addFile ( $data . DS . $ftmp , str_replace ( $absoluterpfad . DS , » , $data . ‘/’ . $ftmp ));
>
>
>elseif( is_file ( $data )== true ) < $archiv ->addFile ( $data , str_replace ( $absoluterpfad . DS , » , $data ));>
>
>
if( is_object ( $obj )== false ) < $archiv ->close ();>
else
if( $mode == ‘unzip’ ) < $archiv ->extractTo ( $data );>
>
?>
A modern way to zip a folder recursivly is using the DirectoryIterator. I use this little class:
class MakeZip
private ZipArchive $zipArchive ;
private int $startPathLength ; // chars to remove from the start for the stored entity
public function __construct (
string $zipArchivename ,
public readonly string $startPath ,
public readonly mixed $convert_function ,
)
$this -> zipArchive = new \ ZipArchive ;
$this -> zipArchive -> open ( $zipArchivename , ZipArchive :: CREATE );
$this -> startPathLength = strlen ( $this -> startPath );
$this -> zipDir ( $startPath );
>
public function __destruct ()
$this -> zipArchive -> close ();
>
/**
* Add files and sub-directories in a folder to zip file.
* @param string $folder
* @param ZipArchive $zipFile
* @param int $exclusiveLength Number of text to be exclusived from the file path.
*/
private function zipDir ( $folder )
echo $folder . ‘
‘ . PHP_EOL ;
foreach (new \ DirectoryIterator ( $folder ) as $f ) if ( $f -> isDot ())
continue; //skip . ..
if ( $f -> isDir ()) if( $f -> getExtension () === ‘git’ ) continue; // skip .git folder
$this -> zipArchive -> addEmptyDir ( $f -> getPathname ());
$this -> zipDir ( $f -> getPathname ());
continue;
>
if ( $f -> isFile ()) if ( $f -> getBasename () === basename ( __FILE__ )) continue; // skip self
if ( $f -> getExtension () === ‘zip’ ) continue; // skip ZIP files
$this -> zipArchive -> addFile ( substr ( $f -> getPathname (), $this -> startPathLength ) ); // remove ‘./’
>
>
>
?>
Which can be used thusly:
$host = str_replace ( ‘.’ , ‘_’ , $_SERVER [ ‘HTTP_HOST’ ]);
$date = date ( ‘Ymd-His’ );
$zip = new \ MakeZip ( «./archiv- $host — $date .zip» , ‘./’ , $convert_function );
unset( $zip );
?>
Add encryption or other features in the constructor.
Important: Due to the natural file size limit of 4GB (~3,6GB to be correct) of zip files, this class will generate corrupt files of the result is larger than 4 GB. Using tar.gz is a proper alternative.
You can check general purpose flag to test if the zip file is encrypted. Example function below.
/**
* Check if the file is encrypted
*
* Notice: if file doesn’t exists or cannot be opened, function
* also return false.
*
* @param string $pathToArchive
* @return boolean return true if file is encrypted
*/
function isEncryptedZip ( $pathToArchive ) $fp = @ fopen ( $pathToArchive , ‘r’ );
$encrypted = false ;
if ( $fp && fseek ( $fp , 6 ) == 0 ) $string = fread ( $fp , 2 );
if ( false !== $string ) $data = unpack ( «vgeneral» , $string );
$encrypted = $data [ ‘general’ ] & 0x01 ? true : false ;
>
fclose ( $fp );
>
return $encrypted ;
>
Be wary that there are several algorithms to generate a zip file. I found that Office OpenXML files created with ZipArchive are not recognized by Excel 2007, for example.
You have to use a different class to zip in this case, such as PclZip.
How to detect corrupt files with CRC mismatch:
Creating a corrupt archive for testing is simple — zip some files and change a byte with a hex editor in the resulting ZIP file. Now you can test the file with a ZIP application to learn which file inside the archive is corrupt.
ZipArchive seems unable to detect broken files. ZipArchive::CHECKCONS doesn’t help, only if it’s not a ZIP file at all. It happily decompressed corrupt files in my tests and the user downloading the data is not informed.
You can simply verify on the server for smaller files:
$maxsize = 1024 * 1024 ;
$z = new ZipArchive ;
$r = $z -> open ( «foo.zip» , ZipArchive :: CHECKCONS );
if( $r !== TRUE )
die( ‘ZIP error when trying to open «foo.zip»: ‘ . $r );
$stat = $z -> statName ( «mybrokenfile.txt» );
if( $stat [ ‘size’ ] > $maxsize )
die( ‘File too large, decompression denied’ );
$s = $z -> getStream ( $file );
$data = stream_get_contents ( $s , $maxsize );
fclose ( $s );
if( $stat [ ‘crc’ ] != crc32 ( $data ))
die( ‘File is corrupt!’ );
//echo ‘File is valid’;
//you may send the file to the client now if you didn’t output anything before
header ( ‘Content-Description: File Transfer’ );
header ( ‘Content-Type: application/octet-stream’ );
header ( ‘Content-Disposition: attachment; filename=»mybrokenfile.txt»‘ );
header ( ‘Content-Transfer-Encoding: binary’ );
header ( ‘Content-Length: ‘ . $stat [ ‘size’ ]);
ob_clean ();
echo $data ;
$z -> close ();
?>
If the file shall not be fully decompressed on the server but decompressed while streaming to the client due to it’s size, the file transfer already startet and printing an error message later doesn’t work. Maybe the best way would be to interrupt the connection before closing the file transfer. The client should be able to detect this as corrupt download.
On the server side a function is needed, that can calculate the CRC32 on streamed data stepwise.