phpphp-ziparchive

Warning on ZipArchive Close


I am trying to fix an issue in an auto-zip script for some images, which I wrote a while ago and it worked until now. Everything seems fine until $zip->close(); which gives the following:

 <b>Warning</b>:  ZipArchive::close(): Read error: No such file or directory in <b></b> on line <b>287</b><br />

I read the docs and some forums and found out that this could happen in one of the following scenarios:

  1. If no actual files are added to the zip, since PHP 5.6—this could be a likely explanation since I recently upgraded to PHP 5.6. However:
    • I check that each file exists before adding it
    • I tried to add a dummy non-empty text file to the zip. Adding it to the zip returns true, as does file_exists() on the file
    • When I echo $zip->numFiles and it gives a number of at least 1 (when the zip has no files except the dummy)
  2. If the directory to where the zip needs to be written doesn't exist or doesn't have the right permissions: This didn't appear to be the case, but just to be sure, I wrote a text file to the same folder in the same script and there was no problem
  3. If there is a problem writing to the temp directory. It's a bit harder to check this but I have an upload script in the same system that works, and I made sure that there are no disk space problems etc.

Here is the relevant code. Some variables are defined beforehand. Note that I write every problem to my log, and this script doesn't generate any entries!

$zip_file = 'Project'.$project_id.'.zip';
$zip = new ZipArchive;
if ($zip_result = $zip->open($zip_path.'/'.$zip_file, ZIPARCHIVE::CREATE) !== true) {
    echo 'Error creating zip for project: '.$project_id.'. Error code: '.$zip_result;
    help::debugLog('Error creating zip for project: '.$project_id.'. Error code: '.$zip_result);
    return false;
}
$file_list = array();

foreach ($item_thumbs as $item)
{
    $full_thumb_path = $thumb_dir.'/'.$item['thumb'];
    if (file_exists($full_thumb_path) and $item['thumb'])
    {
        $file_added = $zip->addFile($full_thumb_path, basename($item['thumb']));
        if (!$file_added)
            help::debugLog('Failed to add item thumb to project zip. Project: '.$project_id.', file name: '.$item['thumb']);
        else
            $file_list[] = $item['thumb'];
    }
    elseif ($item['thumb']) /* If thumb indicated in DB doesn't exist in file system */
        help::debugLog('Item thumb file '.$item['thumb'].' from item: '.$item['id'].' is missing from its indended location: '.$full_thumb_path);
}

/* Added 2016-05-18 -- creates dummy file for the zip listing its contents, important in case zip is empty */
$file_list_path = $zip_path.'/file_list.txt';
if (!($file_list_file = fopen($file_list_path, 'w+')))
    help::debugLog('Failed to create list file (intended for zip) for project: '.$project_id);
fwrite($file_list_file, "File list:\n");
fwrite($file_list_file, implode("\n", $file_list));
if (file_exists($file_list_path))
{
    fclose($file_list_file);
    if (!$zip->addFile($file_list_path))
        help::debugLog('Failed to add list file to project zip for project: '.$project_id);
    unlink($file_list_path);
}
else
    help::debugLog('Failed to create list file (intended for zip) for project: '.$project_id);

$zip->close(); // line 287

Solution

  • Turns out the solution was very simple, and it's actually referred to in the docs (php.net), in one of the comments by Jared at kippage dot com:

    It may seem a little obvious to some but it was an oversight on my behalf.

    If you are adding files to the zip file that you want to be deleted make sure you delete AFTER you call the close() function.

    If the files added to the object aren't available at save time the zip file will not be created.

    (Source: https://www.php.net/manual/en/ziparchive.close.php#93322)

    So from my code above, the "dummy" text file is deleted before the zip is closed, necessarily making it that the file doesn't exist at the time the zip is created.

    I had good reason to believe that the zip was created in a temporary location and only moved to the final location on close(). Turns out this is not the case.