Про работу в php с архивами 1
Материал из DOM
[править] Работа в php с архивами gzip и zip 1
Переезд дело муторное, хоть в мире реальном, хоть в мире виртуальном. Прошло уже почти три недели с того, как я сменил хостинг и перенес сайт, но до сих пор я нахожу ошибки. Причем там где, казалось бы, ошибки в принципе не могли возникнуть. Найдя очередной “баг” и исправив его, решил написать маленькую заметку (по теме) про php и zip-архивы.
Есть у меня на сайте такой сервис: “архив исходных текстов + подсветка ”. Я выкладываю не только статьи с небольшими примерами исходного кода, но и довольно большие фрагменты (десяток файлов разбитых на каталоги). Очевидно, что помещать их в, собственно, текст страницы (статьи) глупо. Можно было бы поместить их в архив zip|rar, снабдить парой комментариев и благополучно “забыть”. Но, как известно у настоящего программиста должны быть три благодетели:
Лень, гордыня, нетерпеливость
Поэтому я решил поработать над собой и написать небольшой плагин для mediawiki, который бы умел показывать файловое дерево со списком файлов и подкаталогов. Я создал каталог sources, внутрь которого поместил подпапки для всех проектов с исходниками. Теперь для того чтобы в текст страницы был вставлено подобное файловое дерево, я пишу в тексте страницы нечто вроде (параметр base на картинке указывает относительный от корня хранилища путь к папке с исходниками):
Жму сохранить и вижу содержимое каталога php/wget.
А по нажатию на файл открывалась бы страница с исходным текстом этого файла (если это текстовой документ). Исходный код был бы подсвечен, например, с помощью geshi. Если жмут на файл картинки, то открывается сама картинка.
Ага, и тут я решил изобрести велосипед!
Точно, файловых менеджеров вагон и маленькая тележка, взять хотя бы phpXplorer или … Стоп, но это отдельный продукт (как его встроить в mediawiki ни малейшего представления), и, скажем прямо, его функциональность избыточна, а если что-то избыточно, то и вредно. Одним словом, плагин я написал. Полюбовавшись на алеповатый интерфейс и походив по каталогам вверх-вниз, я догадался, что получилось то неплохо, но неудобно. Нужна возможность загрузить весь архив со связанными ресурсами за один клик. Давайте разберемся, как в php реализована работа с архивами.
Есть три возможных формата при работе с архивами: gz, zip, bz2. Вообще то можно добавить поддержку любого архиватора (даже без знания c|c++ и умения писать расширения для php) – это вызвать из php команду shell, передав архиватору нужные для его работы параметры. Вам поможет одна из следующих функций:
$XXX_CMD = ‘конструируем строку, запускающую архиватор с некоторым набором параметров командной строки’; // “настоящие” программисты должны данные, из которых конструируется строка для // исполнения shell предварительно проверить и экранировать опасные символы с помощью // escapeshellcmd или escapeshellarg system($XXX_CMD);// можно и с помощью других функций exec($XXX_CMD);// использование внешнего приложения может быть наилучшим способом // выйти из проблемы, если данные подлежащие архивации достаточно велики shell_exec($XXX_CMD);
Найдется еще пара способов запуска внешнего приложения, но разговор все же будет именно о встроенной поддержке архивации в php. Давным-давно (во времена php версии 4.3) в состав php была встроена поддержка архивов gzip. Для работы с архивом нужно открыть его (можно открыть как для чтения так и для записи), при открытии следует указать в качестве параметра режим (чтение или запись), а также степень сжатия. В следующем примере (аккуратно выдранном из официальной справки) открывается для записи файл “somefile.gz”. Режим архивируемой информации равен 9-и, максимальный. Затем в файл пишется немного текста, и файл закрывается.
<?php // сохраняем в файл некоторую строку текста $gz = gzopen('somefile.gz','w9'); gzputs ($gz, 'I was added to somefile.gz'); gzclose($gz); ?>
Для того, чтобы прочитать содержимое архива вы выполняете почти те же самые действия: открыть, прочитать и закрыть:
<?php // получаем содержимое gz-файла в виде строки длиной до 10000 символов $filename = "/usr/local/something.txt.gz"; $zd = gzopen($filename, "r"); $contents = gzread($zd, 10000); gzclose($zd); ?>
Вторым параметром функции gzread следует указать то, сколько байт следует прочитать из файла (если файл кончится раньше, то ничего страшного). Фактически если посмотреть на перечень доступных функций для работы с архивами gzip, то возникнет ощущение дежа-вю. Когда вы работали с обычными файлами, то использовали функции открытия, закрытия, чтения и записи содержимого в файл и их имена были…
| Описание | Обычные файлы | Архивы gzip |
| Открыть файл для чтения или записи | Fopen | gzopen |
| Закрыть файл после завершения работы | Fclose | Gzclose |
| Прочитать произвольное количество байт из файла | Fread | Gzread |
| Проверить, что в файле остались еще не прочитанные данные | Feof | Gzeof |
| Записать в файл информацию | Fwrite | Gzwrite |
| И многие и многие другие |
И для того чтобы окончательно вас запутать разработчики php ввели понятие wrapper-ов или handler-ов, которые могут играть роль посредников при работе с файлами. Например если вы откроете файл так:
$h = fopen (‘boo.txt’, ‘r’);
То файл будет прочитан без каких-либо модификаций (архив это или не архив, да хоть картинка) вы получите те самые биты и байты, из которых файл и состоит. А вот если добавить перед именем файла указание протокола, то правила игры меняются.
$h = fopen (‘http://abracadabra.ru/file.html’, ‘r’);
Теперь мы будем читать содержимое файла размещенного на другом сервере http. А так мы будем читать файл с сервера ftp
$h = fopen (‘ftp://vasyano:secret@comp.server.ru’, ‘r’);
А вот мы прочитали содержимое файла gzip да еще и расположенного на ftp-сервере.
$h = file_get_contents("compress.zlib://ftp://vasyano:secret@center/fex/backup/black-zorro-com_2007-12-08_00-57.sql.gz", "r"); // функция file_get_contents так же как и fopen умеет работать с wrapper-ами
Одним словом, из wrapper-ов можно строить цепочки и результат работы одного из них подавать в качестве исходных данных для другого.
Можно создать собственный wrapper (если это вам интересно, то “копайте” в направлении функции stream_wrapper_register), так чтобы научиться прозрачно читать, скажем, архивы rar (правда, не понятно зачем, но это дело другое).
Супер, скажите вы. Значит я могу легко добавить к своему менеджеру исходников функцию архивации файлов и загрузки их клиентом. Точно и выглядит это примерно так:
// предполагается, что в переменной $fullname находится имя файла, // которое нужно заархивировать и отправить клиенту header("Content-type: application/gzip"); header('Content-Disposition: attachment; filename="'. basename($fullname) . '.zip' . '"'); header("Expires: 0"); header("Cache-Control: must-revalidate, post-check=0,pre-check=0"); header("Pragma: public"); die (gzencode (file_get_contents($fullname), 7));
Содержимое файла было прочитано в память с помощью функции file_get_contents, затем оно поступило на вход функции gzencode которая и выполнила сжатие. Цифра 7 указывает на степень сжатия, ее возможные значения от 0 до 9. 0 – сжатие не выполняется, а 9-ка, соответственно, задает максимальное сжатие. На практике разница между получаемыми после сжатия файлами в режимах 7,8,9 не велика, а вот процессорное время следует экономить.
Итак я научился архивировать файл, но … только один файл. Увы, особенность работы gz функций в том, что сжатию может быть подвергнут один и только один файл, а я ведь хочу иметь возможность поместить в архив содержимое каталога со всеми вложенными в него подкаталогами и файлами. Так что мне пришлось продолжить свой “квест”. Работа с множеством вложенных файлов доступна, например, в старом добром формате zip. Давайте посмотрим, что есть в php и нам может помочь.
На старом хостинге (jino-net.ru) версия php была 5.2.0, в ее состав входит поддержка работы с архивами zip с помощью класса ZipArchive. Далее идет пример кода из справки php, показывающий как можно создать архива и поместить в него несколько файлов.
<?php // создаем объект Архив (внутри его находится множество функций для чтения и записи zip-файлов) $zip = new ZipArchive(); $filename = "./test112.zip"; // открываем (точнее создаем новый файл архива) // указывая имя файла и режим его открытия (CREATE) if ($zip->open($filename, ZIPARCHIVE::CREATE)!==TRUE) { exit("cannot open <$filename>\n"); // открыть файл не удалось, так что завершим работу скрипта аварийно } // теперь начинаем наполнять архив содержимым $zip->addFromString("testfilephp.txt" . time(), "#1 This is a test string added as testfilephp.txt.\n"); $zip->addFromString("testfilephp2.txt" . time(), "#2 This is a test string added as testfilephp2.txt.\n"); $zip->addFile($thisdir . "/too.php","/testfromfile.php"); echo "numfiles: " . $zip->numFiles . "\n"; echo "status:" . $zip->status . "\n"; $zip->close(); ?>
Когда мы добавляем в архив файлы, то можем воспользоваться следующими двумя приемами: добавление существующего файла размещенного на диске, или же в архив помещается новый файл, но его содержимое следует указать явно в виде строки при вызове функции добавления. В первом случае используется функция addFile, а во втором – addFromString.
Теперь я смог написать код формирующий архив с содержимым некоторого каталога. Предполагается, что в переменной $fullpath хранится путь к каталогу, который нужно заархивировать.
$zip = new ZipArchive(); // генерируем случайное имя файла размещенного в каталоге temp – именно внутрь этого файла будет помещен архив $tmpfname = tempnam(null, 'alldirshhhc'); if ($zip->open($tmpfname, ZIPARCHIVE::OVERWRITE)!==TRUE) { return false; } $zip->addFromString("readme_zip.txt", "This file is autogenerated, and contains directory content"); rec_scan_dir_to_zip ( $fullpath, $zip, ‘’ ); $zip->close(); header("Content-type: application/zip"); header('Content-Disposition: attachment; filename="'. (basename($fullpath)) . '.zip' . '"'); header("Expires: 0"); header("Cache-Control: must-revalidate, post-check=0,pre-check=0"); header("Pragma: public"); $h = fopen ($tmpfname, 'rb'); $fc = fread ($h , filesize ($tmpfname)); fclose($h); die ($fc);
Непосредственно процесс формирования архива реализован внутри функции rec_scan_dir_to_zip. Она будет рекурсивно сканировать каталог (первый параметр функции) и помещать все найденные файлы в объект-архив (задан вторым параметром). Значение третьего параметра (он равен пустой строке) вы лучше поймете, когда проанализируете следующий фрагмент кода:
// функция, выполняющая архивирование данного каталога и всех его подкаталогов в архив zip function rec_scan_dir_to_zip($dir, $zip, $fromname){ $dir = realpath ($dir . '/' ) . '/'; $h = opendir ($dir); if (! $h) { /*print 'cannot open:' . $dir ;*/ return false; } while (($file = readdir ($h))!==false){ $fullname = $dir . $file; if ($file == '..' || $file == '.') continue; if (! is_readable ($fullname) ) continue; if (is_dir ($fullname)){ rec_scan_dir_to_zip ($fullname , $zip, $fromname . $file . ‘/’); } else{ $locfilename = $fromname . $file; $zip->addFile($fullname, $locfilename); }// if file or dir }// while closedir ($h); }// if open dir
Обратите внимание только на строку, где я непосредственно добавляю в архив очередной файл. Я должен указать при вызове функции addFile два параметра – имя файла в файловой системе сервера (именно содержимое этого файла будет прочитано, заархивировано и помещено внутрь архива zip), а также “короткое имя” – имя под которым файл будет помещен внутрь архива. Очевидно, что короткое имя должно отсчитываться от корня самого первого каталога, который нужно заархивировать.
|
|
Subscribe Now! |
|


