I often need to ZIP up an entire directory on a server. Whether it's sending out documents to a customer, or just taking a quick backup to send up to Rackspace for safe keeping.
When I do this, it's probably no surprise that I use my own PHP Class to manage the process. This is a little more involved than previous posts, but still relatively straightforward.
First, you'll need an Exception file. Make a new file with this as the content:
PHP Code:
<?php
class App_File_Zip_Exception extends Exception {}
Now, the brains of this operation is this class:
PHP Code:
<?php
class App_File_Zip {
    /**
     * Zip a file, or entire directory recursively.
     *
     * @param string $source directory or file name
     * @param string $destinationPathAndFilename full path to output
     * @throws App_File_Zip_Exception
     * @return boolean whether zip was a success
     */
    public static function CreateFromFilesystem($source, $destinationPathAndFilename)
    {
        $base = realpath(dirname($destinationPathAndFilename));
        if (!is_writable($base))
        {
            throw new App_File_Zip_Exception('Destination must be writable directory.');
        }
        if (!is_dir($base))
        {
            throw new App_File_Zip_Exception('Destination must be a writable directory.');
        }
        if (!file_exists($source))
        {
            throw new App_File_Zip_Exception('Source doesnt exist in location: ' . $source);
        }
        $source = realpath($source);
        if (!extension_loaded('zip') || !file_exists($source))
        {
            return false;
        }
        $zip = new ZipArchive();
        $zip->open($destinationPathAndFilename, ZipArchive::CREATE);
        if (is_dir($source) === true)
        {
            $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
            $baseName = realpath($source);
            foreach ($files as $file)
            {
                if( in_array(substr($file, strrpos($file, DIRECTORY_SEPARATOR)+1), array('.', '..')) )
                {
                    continue;
                }
                $relative = substr($file, strlen($baseName));
                if (is_dir($file) === true)
                {
                    // Add directory
                    $added = $zip->addEmptyDir(trim($relative, "\\/"));
                    if (!$added)
                    {
                        throw new App_File_Zip_Exception('Unable to add directory named: ' . trim($relative, "\\/"));
                    }
                }
                else if (is_file($file) === true)
                {
                    // Add file
                    $added = $zip->addFromString(trim($relative, "\\/"), file_get_contents($file));
                    if (!$added)
                    {
                        throw new App_File_Zip_Exception('Unable to add file named: ' . trim($relative, "\\/"));
                    }
                }
            }
        }
        else if (is_file($source) === true)
        {
            // Add file
            $added = $zip->addFromString(trim(basename($source), "\\/"), file_get_contents($source));
            if (!$added)
            {
                throw new App_File_Zip_Exception('Unable to add file named: ' . trim($relative, "\\/"));
            }
        }
        else
        {
            throw new App_File_Zip_Exception('Source must be a directory or a file.');
        }
        $zip->setArchiveComment('Created with App_Framework');
        return $zip->close();
    }
}
Using the class is relatively simple. Here's how I generally use it:
PHP Code:
<?php
try {
    $source = '/my/path';
    $destination = '/my/writable/path/tmp.zip';
    App_File_Zip::CreateFromFilesystem($source, $destination);
} catch (App_File_Zip_Exception $e) {
    // Zip file was not created.
}
Did this help you out? Did you improve it? Let me know in the comments.

Giorgio commented on Dec 27th 2013
Suppose that you are calling your script this way:
App_File_Zip::CreateFromFilesystem("firstFolder/", "backup.zip");
App_File_Zip::CreateFromFilesystem("secondFolder/", "backup.zip");
If "firstFolder" and "secondFolder" contain the same file, the second call will overwrite this file. This is not correct, since when I am invoking the method this way, my intention is clearly to add two different folders to zip file.
So, I would change the snippet keeping a copy of my original folder and use it when I add files and directories. This way:
$path = realpath($source); // after this I will always use $path instead of $source
$added = $zip->addEmptyDir($source.trim($relative, "\/")); // $source is now used to create a base path inside zip
$added = $zip->addFromString($source.trim($relative, "\/"), file_get_contents($file)); // the same as above
This way, I will create "firstFolder" and "secondFolder" inside zip file: this keeps stuff ordered and avoids the issue I've explained before. Maybe it's not the most elegant solution, but I think it may do the trick.. :-)
Let me know!
Thanks!