Comprimir carpeta i descarregar
Aquest exemple combina dues tasques essencials en el desenvolupament web: comprimir un directori sencer de manera recursiva i servir l'arxiu ZIP resultant al navegador per a la descàrrega, i finalment, eliminar-lo del servidor.
Exemple recursiu
La funció createZip utilitza una logica robusta i estandarditzada basada en iteradors recursius (SPL), i integrat tot el procés en una funció principal.
<?php
// Incloure les classes d'Iteradors (SPL)
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use ZipArchive;
// ----------------------------------------------------------------------
// Configuració (Ajusta la ruta de la carpeta i el nom del fitxer de sortida)
// ----------------------------------------------------------------------
$directori_origen = './includes/';
$nom_zip_descarrega = 'paquet_documents_' . date('Ymd') . '.zip';
$ruta_servidor_zip = sys_get_temp_dir() . '/' . uniqid() . '.zip'; // Guardar-lo al directori temporal
// ----------------------------------------------------------------------
// FUNCIÓ: Compressió Recursiva (la lògica robusta de l'SPL)
// ----------------------------------------------------------------------
/**
* Comprimeix un directori sencer de forma recursiva utilitzant SPL.
* @param string $source_path Ruta del directori a comprimir.
* @param string $zip_path Ruta on es crearà l'arxiu ZIP.
* @return bool Retorna TRUE si la compressió és reeixida.
*/
function compress_directory_recursive(string $source_path, string $zip_path): bool
{
if (!is_dir($source_path)) return false;
$zip = new ZipArchive();
$res = $zip->open($zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE);
if ($res !== TRUE) return false;
// Aconseguir la ruta absoluta per calcular les rutes relatives internes
$source_path = realpath($source_path);
// Iterador Recursiu de la SPL
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($source_path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $name => $file) {
$file_path = $file->getRealPath();
// Calcular la ruta interna (p. ex.: sub_dir/fitxer.txt)
$internal_name = substr($file_path, strlen($source_path) + 1);
$zip->addFile($file_path, $internal_name);
}
return $zip->close();
}
// ----------------------------------------------------------------------
// FLUX D'EXECUCIÓ (Creació i Descàrrega)
// ----------------------------------------------------------------------
// 1. CREACIÓ DEL ZIP
if (!compress_directory_recursive($directori_origen, $ruta_servidor_zip)) {
exit("Error: No s'ha pogut crear l'arxiu ZIP.");
}
// 2. DESCARREGA DEL ZIP
if (file_exists($ruta_servidor_zip)) {
// Neteja del buffer de sortida (IMPORTANT! No hi pot haver cap sortida HTML abans)
if (ob_get_level()) {
ob_end_clean();
}
// Capçaleres de Descàrrega
header('Content-Description: File Transfer');
header('Content-Type: application/zip');
// Nom del fitxer tal com apareixerà a la descàrrega de l'usuari
header('Content-Disposition: attachment; filename="' . basename($nom_zip_descarrega) . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($ruta_servidor_zip));
// Descarregar
readfile($ruta_servidor_zip);
// 3. NETEJA (Eliminació del fitxer temporal)
unlink($ruta_servidor_zip);
// Sortir del script
exit;
} else {
echo "Error: No s'ha trobat l'arxiu ZIP creat.";
}
?>
-
sys_get_temp_dir()iuniqid(): El fitxer ZIP es crea en un directori temporal del sistema amb un nom únic ($ruta_servidor_zip). Això garanteix que no hi hagi conflictes si dos usuaris intenten descarregar el mateix arxiu alhora, ni tampoc quedi cap arxiu ZIP al directori de l'aplicació. -
SPL Iterators (
RecursiveIteratorIterator): Assegura que tots els fitxers i subdirectoris de la ruta$directori_origens'afegeixen, mantenint l'estructura interna del directori correctament.
Descàrrega
La secció de descàrrega és fonamental i requereix capçaleres HTTP precises:
| Capçalera | Valor | Propòsit |
Content-Type |
application/zip |
Indica al navegador que el contingut és un arxiu ZIP. |
Content-Disposition |
attachment; filename="..." |
Força el navegador a obrir un diàleg de descàrrega en lloc d'intentar mostrar el contingut. |
Content-Length |
filesize($ruta_servidor_zip) |
Especifica la mida exacta del fitxer. Crucial per a descàrregues fiables i per mostrar el progrés correcte. |
Cache-Control, Pragma, Expires |
Diversos | Prevé que el navegador o els proxies emmagatzemin el fitxer a la memòria cau, obligant-lo a descarregar la versió més actualitzada. |
ob_get_level() i ob_end_clean()
Aquestes dues funcions formen part del sistema de Control de Sortida (Output Control) de PHP. El Control de Sortida permet als desenvolupadors gestionar i manipular les dades que s'envien del script PHP al servidor web i finalment al navegador (HTML, text, JSON, etc.) abans que s'enviïn realment.
És fonamental quan es treballa amb capçaleres HTTP (com les de descàrrega de fitxers) o quan es volen realitzar operacions de depuració o templating avançades.
ob_get_level()
La funció ob_get_level() s'utilitza per obtenir el nivell d'anidament actual del buffer de sortida.
Què és un Buffer?
Un buffer és una àrea temporal de memòria on s'emmagatzema la sortida generada per PHP (missatges echo, contingut HTML, etc.) abans de ser enviada.
-
Quan s'activa el Control de Sortida (
ob_start()), s'inicia un nou buffer. -
Els buffers es poden aniuar (es poden iniciar diversos buffers l'un dins de l'altre).
| Paràmetre | Retorn | Propòsit |
| Cap | Integer (Nombre enter) | Retorna el nombre de buffers de sortida actius actualment. |
Cas d'Ús: S'utilitza principalment per a la comprovació de l'estat o per assegurar-se que tots els buffers oberts es tanquen abans de realitzar una acció crítica (com enviar capçaleres HTTP o acabar la connexió).
Exemple: Si
ob_get_level()retorna2, vol dir que actualment hi ha dos buffers de sortida aniuats i oberts.
ob_end_clean()
La funció ob_end_clean() té dues funcions en una: Finalitzar (End) i Netejar (Clean).
| Component | Funció |
| End | Tanca el buffer de sortida actiu més recent. |
| Clean | Descarta tot el contingut que s'ha acumulat en aquell buffer des que es va iniciar. |
-
Sense Retorn de Dades: Aquesta funció descarta les dades. Si volguessis obtenir el contingut del buffer abans de tancar-lo, utilitzaries
ob_get_clean()(que combinaob_get_contents()iob_end_clean()). -
Gestió d'Errors: Retorna
TRUEen cas d'èxit iFALSEen cas d'error (per exemple, si no hi ha cap buffer actiu per tancar).
El principal cas d'ús de ob_end_clean() és garantir que no s'ha enviat cap sortida al navegador abans que s'hagin d'enviar les capçaleres HTTP. Les capçaleres HTTP (com les de redirecció o les de descàrrega de fitxers) només es poden enviar abans que qualsevol caràcter de text (fins i tot un espai en blanc) hagi estat enviat al client.
En el codi de descàrrega d'un ZIP, s'utilitza la comprovació:
if (ob_get_level()) {
ob_end_clean();
}
Això vol dir: "Si hi ha algun buffer de sortida obert, neteja'l i tanca'l immediatament." Això elimina qualsevol sortida no desitjada que pugui haver estat generada pel script (per exemple, errors, espais en blanc, o missatges de depuració), evitant l'error comú de PHP: "Warning: Cannot modify header information – headers already sent..." (No es pot modificar la informació de la capçalera – capçaleres ja enviades...).
Neteja (unlink i exit)
-
readfile($ruta_servidor_zip): Envia el contingut del fitxer creat al flux HTTP. -
unlink($ruta_servidor_zip): Un cop l'arxiu s'ha enviat (o almenys el servidor ha començat a enviar-lo), esborrem el fitxer temporal del servidor. Això és vital per mantenir l'ordre al servidor. -
exit: La crida aexitassegura que cap codi PHP addicional ni sortida HTML es processa després de la descàrrega, cosa que podria corrompre el fitxer ZIP binari.