« Сжатие css. Небольшая самописная утилитка | Обучающая машина mysql (начальный релиз) » |
Про работу в php с архивами
Переезд дело муторное, хоть в мире реальном, хоть в мире виртуальном. Прошло уже почти три недели с того, как я сменил хостинг и перенес сайт, но до сих пор я нахожу ошибки. Причем там где, казалось бы, ошибки в принципе не могли возникнуть. Найдя очередной “баг” и исправив его, решил написать маленькую заметку (по теме) про 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 версии 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);
?>
Фактически если посмотреть на перечень доступных функций для работы с архивами gzip, то возникнет ощущение дежа-вю. Когда вы работали с обычными файлами, то использовали функции открытия, закрытия, чтения и записи содержимого в файл и их имена были…
Описание | Обычные файлы | Архивы gzip |
Открыть файл для чтения или записи | Fopen | gzopen |
Закрыть файл после завершения работы | Fclose | Gzclose |
Прочитать произвольное количество байт из файла | Fread | Gzread |
Проверить, что в файле остались еще не прочитанные данные | Feof | Gzeof |
Записать в файл информацию | Fwrite | Gzwrite |
И многие и многие другие |
$h = fopen (‘boo.txt’, ‘r’);
$h = fopen (‘http://abracadabra.ru/file.html’, ‘r’);
$h = fopen (‘ftp://vasyano:secret@comp.server.ru’, ‘r’);
$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 (если это вам интересно, то “копайте” в направлении функции 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));
Итак я научился архивировать файл, но … только один файл. Увы, особенность работы 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);
// функция, выполняющая архивирование данного каталога и всех его подкаталогов в архив 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
« Сжатие css. Небольшая самописная утилитка | Обучающая машина mysql (начальный релиз) » |