Cómo tratar archivos comprimidos con PHP

Recientemente me tocó realizar una modificación a un sistema que había desarrollado para recibir un único archivo comprimido, en lugar de un conjunto de archivos en forma individual.

Dejando de lado los ajustes hechos en el front-end (No fue gran cosa realmente, se trató de cambiar un formulario con 5 inputs por uno solo y, como lo había hecho usando el framework Symfony esa parte fue simple, ni tuve que tocar HTML), la parte interesante fue cómo procesar el archivo comprimido.

Para empezar, algo que tuve que acordar con el usuario era el formato de compresión que íbamos a utilizar. Obviamente no es lo mismo descomprimir un archivo .rar que un .zip (Diferentes formatos, diferentes algoritmos de compresión, etc…).

En mi caso no tuve problema porque tenía la posibilidad de definir cuál era el que más me convenía (a veces no tenemos esa suerte y debemos ajustarnos al contexto que nos toca), con lo cual, elegí el formato zip.

Tomé esta determinación, principalmente, porque sabía que en PHP tenía buen soporte para usarlo (En general, no soy muy fanático de re-inventar la rueda… especialmente cuando hay deadlines involucrados :)).

La pieza clave de todo este asunto es la clase ZipArchive.

Se trata de una clase que muy probablemente tengas instalada (Si usás una distribución estándar de php) y, si no, tampoco es tan difícil instalarla.

Varias cosas se pueden hacer con ella, pero las principales son: comprimir y descomprimir archivos.

Descomprimir archivos zip

Empiezo por la segunda porque es la que más probablemente te encuentres.

Veamos un poco de código:

$zip = new \ZipArchive();
if ( $zip->open( $file ) ) {
    $tmp_dir = sys_get_temp_dir();
    $zip->extractTo($tmp_dir);
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $originalName = $zip->getNameIndex($i);
        $movedFileName = $reports_dir.basename($originalName);
        rename($tmp_dir . DIRECTORY_SEPARATOR . $originalName, $movedFileName);
        $files[$originalName] = $movedFileName;
    }
    $zip->close();
}

Esta es la parte de mi sistema que trata con el archivo subido por el usuario:

Primero creo una instancia de ZipArchive ($zip = new \ZipArchive();).

Después abro el archivo (que se supone está comprimido debidamente):

$zip->open( $file )

Es importante hacer el chequeo con el if ya que, como cualquier otra operación de E/S, puede fallar (por ejemplo por falta de permisos, por formato incorrecto, etc…).

Luego descomprimo (¡Lo que efectivamente quería hacer!):

$zip->extractTo($tmp_dir);

En mi caso, estoy extrayendo todo a un directorio temporal (Usando la función sys_get_temp_dir) para luego procesar los archivos contenidos en el zip uno por uno.

Uso la propiedad numFiles ($zip->numFiles) para saber cuántos archivos estaban dentro del comprimido y voy tomándolos de a uno.

Con $zip->getNameIndex($i) puedo obtener el nombre que tenía un determinado archivo antes de incorporarlo al comprimido.

Por último estoy moviendo el archivo a otro directorio (donde almaceno todos los archivos que luego procesaré, pero eso ya es particular de mi aplicación):

rename($tmp_dir . DIRECTORY_SEPARATOR . $originalName, $movedFileName);

Por último, como con cualquier otro archivo, libero los recursos:

$zip->close();

En el caso de un archivo zip, esto es muy importante ya que los recursos ocupados pueden ser significativos.

Comprimir archivos hacia un zip

También me ha tocado en alguna ocasión realizar la operación inversa (Generar un archivo comprimido para enviar a un cliente a través de un WebService SOAP).

Veamos algo de código:

$zip = new ZipArchive();

if ( $zip->open('codigo.zip',  ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE ) ) {
        $zip->addFile( 'table2.php' );
        $zip->addFile( 'table3.php' );
        $zip->addFile( 'table4.php' );
        $zip->close();
}

En este ejemplo, la clave está en el segundo parámetro que se pasa al método open: ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE aquí lo que se está haciendo es generar un número entero mediante una combinación binaria de otros dos (ZIPARCHIVE::CREATEZIPARCHIVE::OVERWRITE son dos constantes). Si bien esta no es una operación muy común en PHP, es perfectamente válida.

En definitiva, se está indicando al método open que cree el archivo si no existe y que lo sobre-escriba en caso contrario.

Y con eso está todo listo para agregar archivos al zip (¡que todavía no se generó!).

Una vez ingresados todos los archivos requeridos, al ejecutar $zip->close() se escribe el resultado al disco.

Agregar contraseña a un archivo zip

Otra funcionalidad interesante que provee la clase ZipArchive es la de establecer una contraseña de descompresión, de modo que sólo quien la conozca sea capaz de obtener los contenidos del archivo.

Esto se logra usando una combinación de métodos:

  1. setPassword para establecer la contraseña
  2. setEncryptionName (o setEncryptionIndex) para codificar los archivos una vez agregados.

Ejemplo:

$zip = new ZipArchive();
if ($zip->open('test.zip', ZipArchive::CREATE) === TRUE) {
    $zip->setPassword('secret');
    $zip->addFile('text.txt');
    $zip->setEncryptionName('text.txt', ZipArchive::EM_AES_256);
    $zip->close();
    echo "Ok\n";
} else {
    echo "KO\n";
}

De esta forma, para descomprimir el archivo se requiere conocer la contraseña (Y, si usamos un algoritmo como AES256, alguna utilidad moderna como 7z o WinZip):

Este es un ejemplo de una clase que viene incorporada dentro de PHP (Existen unas cuantas), pero la riqueza de la Programación Orientada a Objetos con PHP no se agota ahí (¡Es sólo el comienzo de hecho!). En el curso Desarrollo de Grandes Aplicaciones Web con PHP aprenderás cómo sacarle provecho a toda la potencia que esta característica del lenguaje pone a tu disposición.

mchojrin

Por mchojrin

Ayudo a desarrolladores PHP a acceder mercados y clientes más sofisticados y exigentes

¿Te quedó alguna duda? Publica aca tu pregunta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.