Preventing Duplicate Local Names with Zip Archive

When creating a zip archive with PHP’s ZipArchive utility, you have the option of specifying a local name for each file so that when the zip is downloaded and extracted, the local name will be used in the extracted folder’s contents.

However, if you happen to specify the same local name on more than one file, one file will override the other and your zip will contain less files than it should. The solution to this problem is simple and involves checking that there are no duplicate local names while the zip is being created.

The PHP code below is a wrapper for ZipArchive and will let you easily create a zip file with local names, validating the zip and making sure that everything was successful.

Code: Creating a Zip with PHP

/**
 * Creates an archive of the files at the specified path.
 * @param arrFiles 	An array of (key, value) pairs where key is the path to the file 
 *					and the value is the local name that will be saved.
 * @param strDestinationPath The path to where the zip will be saved.
 * @return True on success, false on failure.
 */
function createArchive( $arrFiles, $strDestinationPath ) {
	// Create the zip archive.
	$objZip = new ZipArchive();
	$objZip->open( $strDestinationPath, ZipArchive::CREATE );
	
	// We keep track of the local names used so far to prevent duplicates.
	$arrLocalNames = array();
	
	// Add the files.
	foreach( $arrFiles as $strFilePath => $strLocalName ) {
		// A local name is optional.
		if( !empty( $strLocalName ) ) {
			// We have to make sure that there aren't two of the same local names.
			// Otherwise, one file will override the other and we will be missing the file.
			while( $arrLocalNames[ $strLocalName ] > 0 ) {
				$arrPathInfo = pathinfo( $strLocalName );
				$intNext = $arrLocalNames[ $strLocalName ]++;
				$strLocalName = "$arrPathInfo[filename] ($intNext).$arrPathInfo[extension]";
			}
			// Add to the count.
			$arrLocalNames[ $strLocalName ]++;
		}
		// Add the file safely.
		$objZip->addFile( $strFilePath, $strLocalName );
	}

	$blnFilesMatch = ( count( $arrFiles ) == $objZip->numFiles );
	$blnSuccess = $objZip->close();
	$blnZipExists = file_exists( $strDestinationPath );

	// Check to make sure the destination exists.
	// Also, check that each file was zipped up.
	return $blnSuccess && $blnZipExists && $blnFilesMatch;
}

When a name collision occurs, an integer value in brackets is appended to the name and then consequently re-checked to make sure this new name is not a duplicate in itself.

Untitled.jpg will remain Untitled.jpg when added first.
Untitled.jpg will be renamed to Untitled (2).jpg on added thereafter and so forth.

Sample Usage

createArchive( array( 
	"/path/to/file1.jpg" => "file1.jpg",
	"/path/to/file2.jpg" => "file2.jpg",
	"/path/to/file3.jpg" => "file.jpg"
	"/path/to/file4.jpg" => "file.jpg" // Will be renamed to "file (1).jpg"
), "/path/to/myarchive.zip" );

Good luck!

Leave a Reply

Your email address will not be published. Required fields are marked *