How to pack directory using 7-zip in PHP on Windows?

This one was little bit tricky. If you want to pack files on your IIS webserver follow my guide.

At first please install 7-zip on your server. It should be in a following path by default:

C:\Program Files (x86)\7-Zip

Or without (x86) if you installed 64 bit version…

PHP part

Command in your script:

$packingCommand = 'c:"\\Program Files (x86)\\7-Zip\\7z.exe" a -tzip -mx0 -mmt4 -wc:\Windows\Temp -r %(name)s %(path)s\*';

Let’s break it down:

  • a – means add to archive
  • tzip – means we’re selecting zip method (t stands for type)
  • mx0 – I’m using zip compression with level set to 0 because I only want to create a zip container (it work’s really fast this way – about 1s for 512MB file on my machine).
  • mmt4 – means that 7zip should use 4 CPU threads.
  • wc – sets working dir (you should use something writable by server process).
  • r – means recursive directory traversing.

If you want to tweak it, you can find full documentation here and here. Please remember to add m if you’re passing something to the specific method!

What’s next?

I use custom function to replace handlers in a command string. It’s called vsprintf_named and looks like this:

/**
 * vsprintf with ability to take named params.
 * Usage: vpsrintf_named( "$(name)s is %(age)02d", [ 'name' => 'John', 'age' => 30 ] );
 * 
 * @param string $format
 * @param array $args
 */
function vsprintf_named($format, $args) {
    $names = preg_match_all('/%\((.*?)\)/', $format, $matches, PREG_SET_ORDER);
 
    $values = array();
    foreach($matches as $match) {
        $values[] = $args[$match[1]];
    }
 
    $format = preg_replace('/%\((.*?)\)/', '%', $format);
    return vsprintf($format, $values);
}

Now, let’s generate full command for Windows:

/** @var $command **/
 $command = vsprintf_named( $packingCommand, [
 'name' => 'c:\your_file.zip',
 'path' => 'c:\dir\to\pack',
 ] );

And finally:

exec( $command, $output, $return );

And voilà! You can debug this using $output and $return vars.

Simply $return should be set to 0 if everything went good. You can view function output simply like that:

echo implode( ', ', $output );

Bonus:

Here is full benchmark code that I use to check if everything works:


File: benchmark.php
-------------------

<?php    
    /**
     * Benchmark
     * 
     * @category debug
     */
    public function benchmark() {
        /** @var integer $timeStart **/
        $timeStart = microtime(true);
        
        echo 'RUNNING BENCHMARK, PLEASE WAIT.</br>'. PHP_EOL;
        
        /** @var string $name **/
        $name = 'test.zip';
        
        set_time_limit( 600 );
        
        /** @var string $packageDir **/
        $packageDir = Config::$tmpDir . DIRECTORY_SEPARATOR . 'test';
        
        if( file_exists( $packageDir ) ) {
            
            /**
            * Select packing method
            */ 
            // internal zipArchive
            if( Config::$packingMethod == 'internal' ) {
                $zipArchive = new ZipArchive();
                $zipArchive->open( Config::$uploadPath . DIRECTORY_SEPARATOR  . $name, \ZipArchive::CREATE);
                $zipArchive->addDirectory(  Config::$tmpDir . DIRECTORY_SEPARATOR . 'test' );
                $zipArchive->close();
            }
            // command line
            elseif( Config::$packingMethod == 'command' ) {
                echo 'USING COMMAND LINE MODE!<br />' . PHP_EOL;
                
                /** @var $command **/
                $command = vsprintf_named( Config::$packingCommand, [
                    'name' => Config::$uploadPath . DIRECTORY_SEPARATOR  . $name,
                    'path' => Config::$tmpDir . DIRECTORY_SEPARATOR . 'test',
                ] );
                
                exec( $command, $output, $return );
                
                echo $command . '<br />' . PHP_EOL;
                
                echo 'RUNNING AS: ' . get_current_user() . '<br />' . PHP_EOL;
                
                echo implode( ', ', $output ) . ' WITH CODE ' . $return . '<br />' . PHP_EOL;
                
                // Return will return non-zero upon an error
                if( $return !== 0) {
                $error = true;
                    echo 'COMMAND LINE ERROR!<br />' . PHP_EOL;
                }
            }
        } else {
            echo "SORRY BUT TEST DIR DOES NOT EXIST. PLEASE CREATE IT.</br>" . PHP_EOL;
        }
      
        /** @var float $time Execution time. **/
        $time = number_format(( microtime(true) - $timeStart), 4);
        echo "FINISHED SUCCESSFULLY IN: $time SECONDS.</br>" . PHP_EOL;
        
    }

Published by

Konrad Fedorczyk

Konrad Fedorczyk

I'm interested in programming and gamedev. I especially luv HTML5 and everything connected to web technologies.

Leave a Reply

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