среда, 18 марта 2009 г.

ZIP средствами PHP. Распаковка

PHP & zip©
Итак мы подошли к первой реальной распаковке на PHP нашего архива. Нас интересует возможность закачки на сайт всего архива с ее распаковкой и размещением в дирректории на сервере.
Для того чтоб скачать архив с компьютера или считать его с другого сайта, нужно выполнить некоторые стандартные операции, которые широко освещаны в инете. Поэтому я сейчас не буду касаться этой темы, а все внимание заострю на моменте конкретной распаковки некого архива, который, на этот момент средствами штатной транспортировки файлов, доставлен на наш сервер. Также я не коснусь проблем конкретного размещения распакованого архива, и буду считать , что фолдер куда мне необходимо разместить его мне известен исходно.

Для чтения заголовков, которые записаны в виде двоичных данных, в PHP есть две замчательные функция, для работы с двоичными данными, разной длины и размещения. Это функции unpack(); pack(); . Для распаковки нам нужна конкретно unpack, которая не только изымает данные в необходимом нам формате, но и размещает их в предопределенном для нас именованом формате. Чтоб описать все форматы запишем следующий PHP-код
define("HDZ_FILE","Vid/vver/vflg/vcmp/vmtm/vmdt/
Vcrc/Vcsz/Vfsz/vnfln/vexln");
define("LEN_FILE",30);
define("SGN_FILE",0x04034b50);

Первая крнстанта HDZ_FILE задает формат и поля заголовка. Как видно из конкретики формат данных для функции unpack, следующий. Каждый ключ будущих распакованых двоичных данных в массив определяется длиной+типом и именем , разделенное иежду собой прямым слэшем (/). Первая буква каждого ключа определяет тип и формат поля. Не вдаваясь в подробности форматов для этой функции (в принципе это заслуживает отдельной статьи) скажу, что здесь используется только два формата V и v, которые соответственно определяют , что двоичные данные хранятся в виде четырех и двух байтовых слов, причем последовательность их определяется от младшего байта к старшему.
Вторая константа LEN_FILE определяет длину заголовка в байтах, а третья SGN_FILE конкрктно его сигнатуру.

Чтобы прочитать одну запись из архивированого файла была определена функция:
function _zip_item(&$zip){

Эта функция читает из файла определенного ссылкой на дескриптор $zip
  if($dat=@fread($zip,LEN_FILE)){

Читаем из файла $zip заголовок ввиде цепочки байт длиной определенной константой LEN_FILE
    $itm = unpack(HDZ_FILE,$dat);

Если чтение успешно, то функцией unpack по формату распаковываем его в масив
    if($itm['id']!=SGN_FILE) return -1;

Проверяем заголовок на наличии необходимой сигнатуры
если нет, то это не наши данные , и возвращаем -1.
    if($itm['nfln'])
$itm['name'] = @fread($zip,$itm['nfln']);

Если поле nfln не равно нулю, то читаем из файла $zip - имя нашего файла
    if($itm['exln'])
$itm['exta'] = @fread($zip,$itm['exln']);

Также читаем экстра-данные, если они есть.
Все впринципе необходимая нам информация для распаковки имеется
Далее при наличии их - читаем из файла
    if($itm['csz']){
$d = @fread($zip,$itm['csz']);

и при не нулевом значения в поле cmp выполняем распаковку данных
      $itm['dat'] = $itm['cmp']? @gzinflate($d):$d;
}

Вот в принципе и все.
Осталось за малым, это определить тип записи, а конкретно это файл или директория.Однозначный алгоритм здесь может быть разным, но для простоты я считаю, что перед нами директория если в конце имени присутствует прямой слэш, шаблон которого задается через нерегулярное выражение END_SLASH, как "/$".
    $itm['type'] = 0;
if(ereg(END_SLAH,$itm['name'])){
$itm['name'] = ereg_replace(
END_SLAH,"",$itm['name']);
$itm['type'] = 1;
}
return $itm;

результат работы выдается через возврат функции
  }
return 0;

В противном случае возвращается 0 - как ошибка.
}

Теперь приведем основную функцию, которая использует функцию чтения и распаковки единицы данных выполняет полноправное действа со всем файлом , включая чтение распаковку и размещение результатов ее вместе с генерацией всех необходимых дирректориев.
function unzip($src,$to=0){

Вот эта функция, которая и будет выполнять конкретную распаковку. Сразу оговоримся, что функция может быть и другой. Параметры функции определяются так. $src - это полный путь до нашего исходного архива, а $to - это полный путь до места куда наш архив будет распаковываться. Этот параметр инмеет значение по умолчанию равный 0, что означает условие отсутствия перенесения распакованых данных на диск, оставляя их в памяти, передавая через параметр возвращения функции. Такой способ управления назначением, позволяет оперативно управлять полученой информацией.
  $dst = array();

Усьанавливаем начальный массив для информации в нуль
  if($fnm = fopen($src,"rb")){

Открываем файл источник, получив его файловый дескриптор
    while(is_array($d=_zip_item($fnm))){

читаем по порядку все элементы ZIP-архива
      if($to){

и при наличии необходимости размещения
        $pth = "$to/{$d['name']}";

формируем полный путь, куда будут размещены текущие распакованые данные
        if($d['type']) @mkdir($pth);

Если запись определена как дирректория - то создаем ее
        elseif($out=@fopen($pth,"wb")){

в противном случае - создаем файл для записи с именем полученым из архива
          if(@fwrite($out,$d['dat'],$d['fsz']))

и сохраняем данные в конкретный файл
            unset($d['dat']);

и при успешной записи в файл обнуляем содержимое памяти, уничтожив элемент массива
          @fclose($out);
}
$d['dat'] = "to OK!";

при этом сделаем отметку - что данные дошли до места назначения
      }
$dst[] = $d;

пополняем пул выходных данных результатом работы
    }
@fclose($fnm);

Окончательно закрываем источник, в конце работы
  }
if($d<0) return $dst;

если последняя сигнатура была не наша, то это означает конец работы
  return 0;
}

Вот в принципе и все....

Пример вызова функции


include("$_libr/unzip.php");
unzip("/tmp/11.zip","/tmp/zip");

И можно попить кофейка...

Файлы по теме статьи здесь