ZIP-Dateien lassen sich direkt mit PHP-Bordmitteln erstellen.

Im Folgenden ein paar Zeilen für "mein digitales Gedächtnis"...

$zip = new ZipArchive();

// ZIP-Datei erstellen
if (!$zip->open("pfad/zum/neuen/archiv.zip", ZIPARCHIVE::CREATE))
  die("archiv.zip konnte nicht erstellt werden.");

// Über alle Dateien in einem Ordner loopen und sie der ZIP-Datei hinzufügen
$handle  = opendir("ordner/mit/dateien");
while (false !== $file = readdir($handle)) {
  if (preg_match("/^\./", $file)) continue; // Dateien beginnend mit . ausschließen
  $filePath = "ordner/mit/dateien/$file";

  // $file sollte nur aus dem Dateinamen bestehen
  // Sollte auf Grund eines anderen Codes der Dateiname einen führenden Slash /
  // haben, sollte dieser entfernt werden. (Erklärung unter dem Code)
  //$file = preg_replace("/^\//", "", $file);
 
  // Datei dem ZIP-Archiv hinzufügen (falls sie tatsächlich existiert)
  if (is_file($filePath)) {
    $zip->addFile($filePath, $file);
    }
  }
closedir($handle);
$zip->close();

// Original-Dateien löschen (falls gewünscht)
foreach(glob('ordner/mit/dateien/*') as $file) { unlink($file); }

In einem Anwendungsfall bekam ich ab und an die Rückmeldung, dass das ZIP-Archiv leer wäre. Ich selbst konnte keine Probleme feststellen, wenn ich das betreffende ZIP-Archiv auf meinem Mac entpackte.
Nur an einem Windows-PC mit der System-ZIP-Funktion (einfach doppelklicken und der Inhalt wird in einem Explorer-Fenster angezeigt), blieb das Archiv leer.

Wird beim Dateinamen, der an das ZIP-Archiv übergeben wird, ein Slash vorangestellt, kommt es genau zu diesem Problem bei Windows:

// Macht im Windows Explorer Probleme
$zip->addFile("ordner/mit/dateien/datei.jpg", "/datei.jpg");

// Macht keine Probleme
$zip->addFile("ordner/mit/dateien/datei.jpg",  "datei.jpg");

Somit sollte man darauf achten, dass an dieser Stelle auf jeden Fall kein führender Slash übergeben wird.

Download

Hat man die ZIP-Datei erstellt und irgendwo auf dem Webserver oder außerhalb des Webservers abgelegt, möchte man sie vielleicht irgendwann zum Download anbieten.
Optimalerweise nicht direkt zur ZIP-Datei verlinkt, sondern über ein PHP-Skript, welches die ZIP-Datei ausliest und an den Client weitergibt.

header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=zipdatei.zip");
//header('Content-Length: ' . filesize($path));  // Könnte Probleme bereiten
header('Content-Length: ' . winFilesize($path)); // Eigene Funktion weiter unten erklärt
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Expires: -1");
set_time_limit(0);
if ($file = fopen("pfad/zur/zipdatei.zip", 'rb')) {
  while ((!feof($file)) && (connection_status() == 0)) {
    print(fread($file, 1024*8));
    flush();
    }
  fclose($file);
  }
die();

Weil PHPs Integer-Typ vorzeichenbehaftet ist und viele Platformen 32bit Integer verwenden, kann die Funktion

filesize()
, mit der die Dateigröße der Datei zurückgegeben wird, für Dateien größer als 2GB unerwartete Ergebnisse liefern.
Besonders auf Windows-Systemen hatte ich damit zu kämpfen, dass die heruntergeladenen ZIP-Archive defekt waren, falls sie 2GB Dateigröße überschritten hatten.

Hierfür gibt es eine Workaround-Funktion, die auf Windows-Systemen die korrekte Dateigröße einer Datei zurückgibt:

function winFilesize($file) {
  exec('for %I in ("'.$file.'") do @echo %~zI', $output);
  return $output[0];
  }

Viele weitere Ansätze, um in PHP die Größe einer Datei zu ermitteln, gibt es auf der PHP-Seite von filesize().