php

Crear archivos zip con PHP: agregar archivos y directorios, descargar zip y proteger con contraseña

Desde hace tiempo he querido hacer un post completo sobre cómo trabajar con archivos ZIP en PHP.

Como sabemos, los archivos ZIP son unos paquetes que permiten tener dentro múltiples archivos para su posterior transporte.

Tutorial de creación de archivos zip con PHP

PHP tiene soporte nativo para los archivos ZIP en la clase ZipArchive y permite comprimir o empaquetar archivos de una manera fácil.

Hoy vamos a ver cómo:

  1. Crear un archivo zip  y agregarle contenido
  2. Forzar la descarga de un archivo zip, es decir, crear un zip y mostrarlo en el navegador
  3. Agregar archivos a un zip a partir de un patrón glob
  4. Agregar todo el contenido de un directorio de manera recursiva. Es decir, agregar todo el contenido y si hay un directorio agregar el contenido de ese directorio, así recursivamente.
  5. Proteger un archivo ZIP con contraseña

Todavía no vamos a ver cómo descomprimir o desempaquetar, eso es de otro post.

Comprimir archivos con PHP: primer ejemplo

Aviso: si al leer el post no sabes de dónde salen los archivos comprimidos, quieres ver la relación del código o te pierdes, te invito a ver y clonar el repositorio en GitHub 😉

Veamos el primer ejemplo. En el mismo creamos una nueva instancia de ZipArchive. Abrimos el archivo zip con el método open.

Al método open le pasamos la ruta absoluta del zip y el modo de apertura. Lo estamos abriendo de modo que se cree si no existe y que se sobrescriba si ya existe.

Si no lo sobrescribimos y ya existe, se irán agregando más archivos al zip, ya que guardará los anteriores.

<?php
/**
 * Trabajando con archivos ZIP en PHP
 * Ejemplo 1: simple creación de archivo zip
 * con un poco de contenido
 *
 * @author parzibyte
 */$zip = new ZipArchive();
// Ruta absoluta
$nombreArchivoZip = __DIR__ . "/1-simple.zip";

if (!$zip->open($nombreArchivoZip, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
    exit("Error abriendo ZIP en $nombreArchivoZip");
}
// Si no hubo problemas, continuamos
// Agregamos el script.js
// Su ruta absoluta, como D:\documentos\codigo\script.js
$rutaAbsoluta = __DIR__ . "/script.js";
// Su nombre resumido, algo como "script.js"
$nombre = basename($rutaAbsoluta);
$zip->addFile($rutaAbsoluta, $nombre);

// No olvides cerrar el archivo
$resultado = $zip->close();
if ($resultado) {
    echo "Archivo creado";
} else {
    echo "Error creando archivo";
}

La parte importante para agregar un archivo es el método addFile. Recibe dos argumentos: la ruta absoluta de la ubicación del archivo y su nombre. Ese nombre es el que tendrá dentro del zip.

Utilizamos basename para limpiar el nombre, ya que algo como C:\fotos\perro.png se convierte en perro.png

De esta manera en el zip se añade el archivo con perro.png, aquí bien podrías cambiar el nombre del archivo.

Puedes llamar a addFile cuantas veces quieras, solo recuerda que al final debes llamar al método close; el cual devuelve un booleano indicando si la escritura del archivo fue exitosa.

Al cerrar el archivo se habrá creado un zip en el directorio indicado.

Comprimir y descargar ZIP

En ocasiones vamos a necesitar crear un zip y mandarlo de regreso al usuario a través del navegador web.

Para ello utilizamos la función readfile en conjunto de algunos encabezados HTTP para forzar la descarga.

Primero creamos el archivo zip como normalmente se hace, y una vez que tenemos el archivo lo mandamos a través del navegador web.

Por cierto, si quieres eliminar el archivo al final simplemente invoca a unlink.

<?php
/**
 * Trabajando con archivos ZIP en PHP
 * Ejemplo 2: simple creación de archivo zip
 * con un poco de contenido, para mandarlo
 * para su descarga
 *
 * @author parzibyte
 */$zip = new ZipArchive();
// Ruta absoluta
$nombreArchivoZip = __DIR__ . "/2-simple.zip";

if (!$zip->open($nombreArchivoZip, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
    exit("Error abriendo ZIP en $nombreArchivoZip");
}
// Si no hubo problemas, continuamos
// Agregamos el script.js
// Su ruta absoluta, como D:\documentos\codigo\script.js
$rutaAbsoluta = __DIR__ . "/script.js";
// Su nombre resumido, algo como "script.js"
$nombre = basename($rutaAbsoluta);
$zip->addFile($rutaAbsoluta, $nombre);

// No olvides cerrar el archivo
$resultado = $zip->close();
if (!$resultado) {
    exit("Error creando archivo");
}

// Ahora que ya tenemos el archivo lo enviamos como respuesta
// para su descarga

// El nombre con el que se descarga
$nombreAmigable = "simple.zip";
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=$nombreAmigable");
// Leer el contenido binario del zip y enviarlo
readfile($nombreArchivoZip);

// Si quieres puedes eliminarlo después:
unlink($nombreArchivoZip);

3 – Utilizar patrón GLOB para agregar muchos archivos

La clase ZipArchive también permite agregar archivos basados en un patrón glob. Por ejemplo, para agregar todas las imágenes con extensión png podríamos indicarlas con el patrón *.png

De la misma manera todos los archivos de código fuente de Golang podrían agregarse con el patrón *.go

También soporta directorios: ./fotos/*.psd

Para agregar archivos basados en glob usamos el método llamado addGlob:

<?php
/**
 * Trabajando con archivos ZIP en PHP
 * Ejemplo 3: agregar archivos a partir de patrón GLOB
 *
 * @author parzibyte
 */$zip = new ZipArchive();
// Ruta absoluta
$nombreArchivoZip = __DIR__ . "/3-glob.zip";

if (!$zip->open($nombreArchivoZip, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
    exit("Error abriendo ZIP en $nombreArchivoZip");
}
// Si no hubo problemas, continuamos

$zip->addGlob("*.go");
$zip->addGlob("*.log");
$zip->addGlob("*.xlsx");
$zip->addGlob("*.js");
// Otros ejemplos: $zip->addGlob("./imagenes/*.png");
// Otros ejemplos: $zip->addGlob("*.php");
// Otros ejemplos: $zip->addGlob("*.*");

// No olvides cerrar el archivo
$resultado = $zip->close();
if ($resultado) {
    echo "Archivo creado";
} else {
    echo "Error creando archivo";
}

Agregar un directorio y todo su contenido a un archivo zip, de manera recursiva, usando PHP

Veamos algo interesante, se trata de agregar todo el contenido de un directorio recursivamente.

Es decir, si tenemos una carpeta y la misma tiene archivos todos los archivos son agregados.

Si dentro de la carpeta tenemos otra carpeta con contenido también se agrega. Y así infinitamente, por eso se dice que es de manera recursiva.

Para ello no existe un método como addDirRecursivePlease, pero sí podemos recorrer manualmente un directorio de manera recursiva y llamar a addFile en un ciclo.

Podemos recorrer un directorio de distintas maneras, en el ejemplo se utiliza un RecursiveDirectoryIterator con un RecursiveIteratorIterator.

No te preocupes ni compliques, simplemente vamos a iterar por un directorio de manera recursiva. Y dentro del ciclo agregar archivos.

<?php
/**
 * Trabajando con archivos ZIP en PHP
 * Ejemplo 4: agregar directorio y contenido de manera recursiva
 *
 * @author parzibyte
 */$zip = new ZipArchive();
// Ruta absoluta
$nombreArchivoZip = __DIR__ . "/4-directorio.zip";
$rutaDelDirectorio = __DIR__ . "/imágenes";

if (!$zip->open($nombreArchivoZip, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
    exit("Error abriendo ZIP en $nombreArchivoZip");
}
// Si no hubo problemas, continuamos

// Crear un iterador recursivo que tendrá un iterador recursivo del directorio
$archivos = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($rutaDelDirectorio),
    RecursiveIteratorIterator::LEAVES_ONLY
);

foreach ($archivos as $archivo) {
    // No queremos agregar los directorios, pues los nombres
    // de estos se agregarán cuando se agreguen los archivos
    if ($archivo->isDir()) {
        continue;
    }

    $rutaAbsoluta = $archivo->getRealPath();
    // Cortamos para que, suponiendo que la ruta base es: C:\imágenes ...
    // [C:\imágenes\perro.png] se convierta en [perro.png]
    // Y no, no es el basename porque:
    // [C:\imágenes\vacaciones\familia.png] se convierte en [vacaciones\familia.png]
    $nombreArchivo = substr($rutaAbsoluta, strlen($rutaDelDirectorio) + 1);
    $zip->addFile($rutaAbsoluta, $nombreArchivo);
}
// No olvides cerrar el archivo
$resultado = $zip->close();
if ($resultado) {
    echo "Archivo creado";
} else {
    echo "Error creando archivo";
}

En el ejemplo agregamos todo el contenido de imágenes, que a su vez tiene otros directorios. En cada iteración obtenemos la ruta absoluta del archivo.

Utilizamos substr para agregar un nombre bonito o limpio como vimos anteriormente, pero no usamos basename porque queremos respetar la profundidad de directorios.

Proteger archivo zip con contraseña

Para terminar con los ejemplos veamos cómo agregar una contraseña al archivo zip y proteger los archivos con la misma.

Te aviso de una vez que esto de la contraseña y la protección de archivos funciona en PHP 7.2 y adelante.

Hay librerías y trucos para hacerlo funcionar en otras versiones, pero nosotros no vamos a ver eso. Te recomiendo encarecidamente que actualices tu versión de PHP en Windows, Android o Linux.

<?php
/**
 * Trabajando con archivos ZIP en PHP
 * Ejemplo 5: agregar directorio de manera recursiva
 * protegido por contraseña
 * 
 * ====================
 * ->      NOTA      <-
 * La protección por contraseña
 * solo funciona a partir de PHP 7.2
 * Por favor, actualiza ;) siempre es bueno moverse hacia adelante
 * ====================
 *
 * @author parzibyte
 */
define("PALABRA_SECRETA", "hunter2");

$zip = new ZipArchive();
// Ruta absoluta
$nombreArchivoZip = __DIR__ . "/5-protegido.zip";
$rutaDelDirectorio = __DIR__ . "/imágenes";

if (!$zip->open($nombreArchivoZip, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
    exit("Error abriendo ZIP en $nombreArchivoZip");
}
// Si no hubo problemas, continuamos

// Poner la contraseña de todos los archivos
$zip->setPassword(PALABRA_SECRETA);

// Crear un iterador recursivo que tendrá un iterador recursivo del directorio
$archivos = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($rutaDelDirectorio),
    RecursiveIteratorIterator::LEAVES_ONLY
);

foreach ($archivos as $archivo) {
    // No queremos agregar los directorios, pues los nombres
    // de estos se agregarán cuando se agreguen los archivos
    if ($archivo->isDir()) {
        continue;
    }

    $rutaAbsoluta = $archivo->getRealPath();
    // Cortamos para que, suponiendo que la ruta base es: C:\imágenes ...
    // [C:\imágenes\perro.png] se convierta en [perro.png]
    // Y no, no es el basename porque:
    // [C:\imágenes\vacaciones\familia.png] se convierte en [vacaciones\familia.png]
    $nombreArchivo = substr($rutaAbsoluta, strlen($rutaDelDirectorio) + 1);
    $zip->addFile($rutaAbsoluta, $nombreArchivo);
    // Proteger
    $zip->setEncryptionName($nombreArchivo, ZipArchive::EM_AES_256);

}
// No olvides cerrar el archivo
$resultado = $zip->close();
if ($resultado) {
    echo "Archivo creado";
} else {
    echo "Error creando archivo";
}

Lo que hay que notar aquí es el uso de setPassword en el zip después de haberlo abierto, (solo se necesita una vez) así como el método setEncryptionName que recibe el título del archivo (no su ruta absoluta) y el método de cifrado que es AES 256.

Fíjate en que setEncryptionName es llamado después de agregar cada archivo.

Conclusión

Puedes combinar todos los ejemplos y adecuarlos a tus necesidades. Por ejemplo, descargar el archivo zip después de ponerle contraseña, agregar con glob y después agregar recursivamente, etcétera.

Los archivos que prestaron sus servicios para ser comprimidos son de otros posts como el de cron con Go, los fondos de escritorio de lenguajes de programación, la lectura y escritura de archivos Excel con PHP o el conversor de bases numéricas.

Recuerda que el código completo está en mi GitHub.

Referencias

Muchas cosas presentes aquí fueron tomadas, inspiradas e investigadas de los siguientes enlaces (en serio, eran todas las pestañas que tenía mi navegador mientras hacía los ejemplos)

https://www.php.net/manual/es/book.zip.php

https://www.php.net/manual/es/ziparchive.setpassword.php

https://www.php.net/manual/es/ziparchive.addglob.php

https://github.com/parzibyte/marca_de_agua_php_personal/blob/master/marcar.php

https://stackoverflow.com/questions/39833496/zip-a-file-and-protect-with-a-password-in-php

https://php.net/manual/en/ziparchive.setencryptionname.php

 

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.