Zipping the contents of a folders in PHP

爱⌒轻易说出口 提交于 2019-12-10 21:52:01

问题


Before marking this post as a duplicate, please note that I have already searched for answer on SO and the once I've found so far (listed below) haven't been exactly what I've been looking for.

  • How to [recursively] Zip a directory in PHP?
  • using zipArchive addFile() will not add image to zip
  • ZipArchive - addFile won't work

Those are just some of the ones I've looked at.

My problem is this: I can't use addFromString, I have to use addFile, it's a requirement of the task.

I've already tried a couple of ways, here's my current iteration:

public function getZippedFiles($path)
{
    $real_path = WEBROOT_PATH.$path;

    $files = new RecursiveIteratorIterator (new RecursiveDirectoryIterator($real_path), RecursiveIteratorIterator::LEAVES_ONLY);

    //# create a temp file & open it
    $tmp_file = tempnam($real_path,'');
    $zip_file = preg_replace('"\.tmp$"', '.zip', $tmp_file);

    $zip = new ZipArchive();
    $zip->open($zip_file, ZipArchive::CREATE);

    foreach ($files as $name=>$file)
    {
        error_log(print_r($name, true));
        error_log(print_r($file, true));
        if ( ($file == ".") || ($file == "..") )
        {
            continue;
        }

        $file_path = $file->getRealPath();
        $zip->addFile($file_path);
    }

    $zip->close();
}

When I try to open the resulting file, I get told that "Windows cannot open the folder. The Compressed(zipped) Folder '' is invalid."

I've managed to succesfully complete the task using addFromString, like so:

$file_path = WEBROOT_PATH.$path;
    $files = array();
    if (is_dir($file_path) == true)
    {
        if ($handle = opendir($file_path))
        {
            while (($file = readdir($handle)) !== false)
            {
                if (is_dir($file_path.$file) == false)
                {
                    $files[] = $file_path."\\".$file;
                }
            }

            //# create new zip opbject
            $zip = new ZipArchive();

            //# create a temp file & open it
            $tmp_file = tempnam($file_path,'');
            $zip_file = preg_replace('"\.tmp$"', '.zip', $tmp_file);

            $zip->open($zip_file, ZipArchive::CREATE);

            //# loop through each file
            foreach($files as $file){

                //# download file
                $download_file = file_get_contents($file);

                //#add it to the zip
                $zip->addFromString(basename($file),$download_file);

            }

            //# close zip
            $zip->close();
        }
    }
}

The above is mostly just copied straight from some example code I saw somewhere. If anyone can point me in a good direction I'd be very grateful!

***** UPDATE ***** I added an if around the close like this:

if (!$zip->close()) {
    echo "failed writing zip to archive";
}

The message gets echoed out, so obviously the problem is there. I've also checked to make sure the $zip->open() works, and I've confirmed that it is opening it without a problem.


回答1:


Finally managed to get something working using addFile.

I created a helper class that contains 3 functions: one to list all the files in a directory, 1 to zip all those files, and 1 to download the zipped files:

<?php
require_once($_SERVER["DOCUMENT_ROOT"]."/config.php");

class FileHelper extends ZipArchive
{
    /**
     * Lists files in dir
     * 
     * This function expects an absolute path to a folder intended as the target.
     * The function will attempt to create an array containing the full paths to 
     * all files in the target directory, except for . and ..
     * 
     * @param dir [string]    : absolute path to the target directory
     * 
     * @return result [array] : array of absolute paths pointing to files in the target directory
     */
    public static function listDirectory($dir)
    {
        $result = array();
        $root = scandir($dir);
        foreach($root as $value) {
            if($value === '.' || $value === '..') {
                continue;
            }
            if(is_file("$dir$value")) {
                $result[] = "$dir$value";
                continue;
            }
            if(is_dir("$dir$value")) {
                $result[] = "$dir$value/";
            }
            foreach(self::listDirectory("$dir$value/") as $value)
            {
                $result[] = $value;
            }
        }
        return $result;
    }

    /**
     * Zips and downloads files
     * 
     * This function expects a directory location as target for a temp file, and a list(array)
     * of absolute file names that will be compressed and added to a zip file. After compression,
     * the temporary zipped file will be downloaded and deleted.
     * 
     * @param location [string] : absolute path to the directory that will contain the temp file
     * @param file_list [array] : array of absolute paths pointing to files that need to be compressed
     * 
     * @return void
     */
    public function downloadZip($file)
    {
        $modules = apache_get_modules();
        if (in_array('mod_xsendfile', $modules)) // Note, it is not possible to detect if X-SendFile is turned on or not, we can only check if the module is installed. If X-SendFile is installed but turned off, file downloads will not work
        {
            header("Content-Type: application/octet-stream");
            header('Content-Disposition: attachment; filename="'.basename($file).'"');
            header("X-Sendfile: ".realpath(dirname(__FILE__)).$file);

            // Apache will take care of the rest, so terminate the script
            exit;
        }

        header("Content-Type: application/octet-stream");
        header("Content-Length: " .(string)(filesize($file)) );
        header('Content-Disposition: attachment; filename="'.basename($file).'"');
        header("Content-Transfer-Encoding: binary");
        header("Expires: 0");
        header("Cache-Control: no-cache, must-revalidate");
        header("Cache-Control: private");
        header("Pragma: public");

        ob_end_clean(); // Without this, the file will be read into the output buffer which destroys memory on large files
        readfile($file);
    }

    /**
     * Zips files
     * 
     * This function expects a directory location as target for a temp file, and a list(array)
     * of absolute file names that will be compressed and added to a zip file. 
     * 
     * @param location [string]  : absolute path to the directory that will contain the temp file
     * @param file_list [array]  : array of absolute paths pointing to files that need to be compressed
     * 
     * @return zip_file [string] : absolute file path of the freshly zipped file
     */
    public function zipFile($location, $file_list)
    {
        $tmp_file = tempnam($location,'');
        $zip_file = preg_replace('"\.tmp$"', '.zip', $tmp_file);

        $zip = new ZipArchive();
        if ($zip->open($zip_file, ZIPARCHIVE::CREATE) === true)
        {
            foreach ($file_list as $file)
            {
                if ($file !== $zip_file)
                {
                    $zip->addFile($file, substr($file, strlen($location)));
                }
            }
            $zip->close();
        }

        // delete the temporary files
        unlink($tmp_file);

        return $zip_file;
    }
}
?>

Here's how I called this class's functions:

$location = "d:/some/path/to/file/";
$file_list = $file_helper::listDirectory($location);
$zip_file = $file_helper->zipFile($location, $file_list);
$file_helper->downloadZip($zip_file);



回答2:


Check out this solution, much clearer:

function Zip($source, $destination)
{
    if (!extension_loaded('zip') || !file_exists($source)) {
        return false;
    }

    $zip = new ZipArchive();
    if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
        return false;
    }

    $source = str_replace('\\', '/', realpath($source));

    if (is_dir($source) === true)
    {
        $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);

        foreach ($files as $file)
        {
            $file = str_replace('\\', '/', $file);

            // Ignore "." and ".." folders
            if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
                continue;

            $file = realpath($file);

            if (is_dir($file) === true)
            {
                $zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
            }
            else if (is_file($file) === true)
            {
                $zip->addFile($file, str_replace($source . '/', '', $file));
            }
        }
    }
    else if (is_file($source) === true)
    {
        $zip->addFile($file, str_replace($source . '/', '', $file));
    }

    return $zip->close();
}

And use it like:

Zip('/folder/to/compress/', './compressed.zip');

Check the original post with addFromString



来源:https://stackoverflow.com/questions/29102612/zipping-the-contents-of-a-folders-in-php

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!