PHP и ZIP: Unicode в именах файлов

На одном проекте требовалось сохранять файлы с русскими именами в ZIP архив. Оказалось, что это не так и просто, как кажется с первого взгляда. Формат ZIP был разработан Филом Кацем (Phil Katz) в 1989 году. В те времена никто и не думал, что файловые имена могут быть не латинскими.

В 2001 году, после смерти Каца, вышла версия 4.5 спецификации, позволяющая добавлять в архив файлы, больше 4 Гб. В 2006 году появилась спецификация 6.3.0, которая поддерживает хранение имен файлов в кодировке UTF-8. Эта спецификация нам и интересна.

Как можно на PHP создать архив содержащий имена файлов на русском?

  • Костыли. Windows XP не поддерживает юникод в именах файлов в ZIP, зато имена файлов можно перекодировать в кодировку WINDOWS-866. MacOS и Ubuntu поддерживает UTF-8 в именах файлов. По-идее можно для каждой операционной системы, но этот вариант самый плохой. Я его не советую.
  • Использовать ext-zip в php >=8, начиная с этой версии класс ZipArchive научился работать и с большими файлами и с юникодом в именах файлов.
    <?php
    $zip = new ZipArchive();
    $zip->open(__DIR__.'/1.zip', ZipArchive::CREATE);
    $zip->addFile(__FILE__, 'Привет.php');
    $zip->close();
    ?>
  • Использовать вместо ext-zip библиотеку. Библиотека для своей работы не требует расширение zip. Есть версия как для PHP >=5.5, так и более новая версия с под PHP 7.4. Последний способ мне больше всего, подключил пакет через composer и все.
// создание нового архива
$zipFile = new \PhpZip\ZipFile();
try{
    $zipFile
        ->addFromString('zip/entry/filename', "Is file content") // добавить запись из строки
        ->addFile('/path/to/file', 'data/tofile') // добавить запись из файла
        ->addDir(__DIR__, 'to/path/') // добавить файлы из директории
        ->saveAsFile($outputFilename) // сохранить архив в файл
        ->close(); // закрыть архив
            
    // открытие архива, извлечение файлов, удаление файлов, добавление файлов, установка пароля и вывод архива в браузер.
    $zipFile
        ->openFile($outputFilename) // открыть архив из файла
        ->extractTo($outputDirExtract) // извлечь файлы в заданную директорию
        ->deleteFromRegex('~^\.~') // удалить все скрытые (Unix) файлы
        ->addFromString('dir/file.txt', 'Test file') // добавить новую запись из строки
        ->setPassword('password') // установить пароль на все записи
        ->outputAsAttachment('library.jar'); // вывести в браузер без сохранения в файл
}
catch(\PhpZip\Exception\ZipException $e){
    // обработка исключения
}
finally{
    $zipFile->close();
}

Что стоит почитать: