Skip to main content

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
<?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.";
}
?>
  1. sys_get_temp_dir() i uniqid(): 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ó.

  2. SPL Iterators (RecursiveIteratorIterator): Assegura que tots els fitxers i subdirectoris de la ruta $directori_origen s'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() retorna 2, 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 combina ob_get_contents() i ob_end_clean()).

  • Gestió d'Errors: Retorna TRUE en cas d'èxit i FALSE en 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ó:

PHP
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...).

  1. readfile($ruta_servidor_zip): Envia el contingut del fitxer creat al flux HTTP.

  2. 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.

  3. exit: La crida a exit assegura que cap codi PHP addicional ni sortida HTML es processa després de la descàrrega, cosa que podria corrompre el fitxer ZIP binari.