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