그러냐

[PHP] 다중 파일을 zip으로 묶어받기 본문

php

[PHP] 다중 파일을 zip으로 묶어받기

관절분리 2017. 12. 11. 11:43
반응형

php로 여러 개의 파일을 묶는 방법은 여러가지가 있다. exec()으로 압축 프로그램을 실행시켜서 묶거나 , ZipArchive로 묶거나, 직접 묶거나. 이 글은 다 묶어질 때까지 기다릴 필요가 없는 직접 묶는 방법으로 코딩한 소스를 공유하고자 작성하였다!

소스와 사용법

PKZip의 구조를 정리해놓은 문서와 PHP ZipArchive 클래스를 참고해서 직접 코딩했다. 이 소스는 zip 관리용이 아니라 묶어받기용이다.

<?php
class DirectZip
{
const BUFFER_SIZE = 4194304; // 4MiB
private $currentOffset;
private $entries;
public function open($filename)
{
set_time_limit(0);
ini_set('zlib.output_compression', 'Off');
header('Pragma: no-cache');
header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
header('Cache-Control: no-store');
header('Content-Type: application/zip');
header('Content-Disposition: attachment; ' .
'filename="' . $filename . '"; ' .
'filename*=UTF-8\'\'' . rawurlencode($filename));
$this->currentOffset = 0;
$this->entries = array();
}
public function addEmptyDir($dirname)
{
if ($this->addFile('php://temp', $dirname.'/') === false)
{
return false;
}
}
public function addFromString($localname, $contents)
{
$tmp = tempnam(sys_get_temp_dir(), __CLASS__);
$pointer = @fopen($tmp, 'wb');
if ($pointer === false)
{
@unlink($tmp);
return false;
}
fwrite($pointer, $contents);
$result = $this->addFile($tmp, $localname);
fclose($pointer);
@unlink($tmp);
if ($result === false)
{
return false;
}
}
public function addFile($filename, $localname = null)
{
$entry = new DirectZipEntry(empty($localname) ? basename($filename) : $localname, $this->currentOffset);
if ($entry->open($filename) === false)
{
return false;
}
$this->entries[] = $entry;
ob_start();
self::write(0x504B0304, 'N'); // sig entry
self::writeEntryStat($entry);
echo $entry->name;
$this->currentOffset += strlen(ob_get_flush());
while (!feof($entry->pointer))
{
$buffer = @fread($entry->pointer, self::BUFFER_SIZE);
echo $buffer;
flush();
$this->currentOffset += strlen($buffer);
}
$entry->close();
}
public function close()
{
ob_start();
foreach ($this->entries as $entry)
{
self::write(0x504B0102, 'N'); // sig index
self::write(0); // os: fat
self::writeEntryStat($entry);
self::write(0); // comment len
self::write(0); // disk # start
self::write(0); // internal attr
self::write(0, 'V'); // external attr
self::write($entry->offset, 'V');
echo $entry->name;
}
$length = strlen(ob_get_flush());
self::write(0x504B0506, 'N'); // sig end
self::write(0); // disk number
self::write(0); // disk # index start
self::write(count($this->entries)); // disk entries
self::write(count($this->entries)); // total entries
self::write($length, 'V');
self::write($this->currentOffset, 'V');
self::write(0); // comment len
flush();
}
private static function writeEntryStat($entry)
{
self::write(substr($entry->name, -1) == '/' ? 20 : 10);
self::write(2048); // flags: unicode filename
self::write(0); // compression: store
self::write($entry->mtime, 'V');
self::write($entry->crc, 'V');
self::write($entry->size, 'V'); // compressed size
self::write($entry->size, 'V'); // uncompressed size
self::write(strlen($entry->name));
self::write(0); // extra field len
}
private static function write($binary, $format = 'v')
{
echo pack($format, $binary);
}
}
class DirectZipEntry
{
public $offset;
public $pointer;
public $name;
public $crc;
public $size;
public $mtime;
public function __construct($name, $offset)
{
$this->offset = $offset;
$this->name = $name;
}
public function open($filename)
{
$this->pointer = @fopen($filename, 'rb');
if ($this->pointer === false)
{
return false;
}
list(, $this->crc) = unpack('N', hash_file('crc32b', $filename, true));
$fstat = fstat($this->pointer);
$this->size = $fstat['size'];
$mtime = $filename == 'php://temp' ? time() : $fstat['mtime'];
$this->mtime = (date('Y', $mtime) - 1980) << 25 | date('m', $mtime) << 21 | date('d', $mtime) << 16 |
date('H', $mtime) << 11 | date('i', $mtime) << 5 | date('s', $mtime) >> 1;
}
public function close()
{
fclose($this->pointer);
}
}
?>
view rawcla_directzip.php hosted with ❤ by GitHub
<?php
require 'cla_directzip.php';
$zip = new DirectZip();
$zip->open('브라우저로 보낼 압축파일 이름.zip');
$zip->addFile('/tmp/추가할 파일.jpg', '압축파일 내 파일 이름.jpg');
$zip->addEmptyDir('압축파일 내 폴더 이름'); // 안 해도 상관없음.
$zip->addFile('/tmp/추가할 파일2.png', '압축파일 내 폴더 이름/압축파일 내 파일 이름.png');
$zip->addFile('/tmp/추가할 파일3.jpg'); // 압축파일에 파일을 '추가할 파일3.jpg'로 추가
$zip->addFromString('바로 글쓰기.txt', '파일 내용'); // 압축파일에 '파일 내용'을 '바로 글쓰기.txt'로 추가
$zip->close();
/*
브라우저로 보낼 압축파일 이름.zip
│ 바로 글쓰기.txt
│ 압축파일 내 파일 이름.jpg
│ 추가할 파일3.jpg
└─압축파일 내 폴더 이름
압축파일 내 파일 이름.png
*/
?>
view rawget_zip.php hosted with ❤ by GitHub

php 5.3 이상의 환경에서 위와 같이 코딩하면 한 주소에서 파일 여러 개를 묶어서 내려받을 수 있다.

단점

다 묶일 때까지 기다리거나 임시파일 만들 필요가 없는 이 소스에도 단점이 있다. 파일 이어받기가 안 되고 언제 다 받을지 모른다는 것인데, 다 받아질 때까지 느긋하게 기다리라고 클라이언트에 알려두면 좋을 것 같다.

최대한 간단하게 코딩해서 그런지 파일의 만든 날짜, 엑세스한 날짜 등이 지워진다는 것을 단점이라고 해야 되나 모르겠다. 난 좋은데.

  1. 질문
    4장의 jpg 파일을 zip으로 묶어서 다운받는 기능을 구현중입니다.
    인터넷 검색하다가 이 글을 발견하고 올레를 외쳤는데...php5.4 이상의 환경이라니...
    저는 php5.3 이거든요 T.T
    위 소스로 zip파일 생성도 되고, 다운도 되고, 다운 받은 걸 압축해제도 됩니다.
    근데, 압축 해제 시에 '압축파일이 손상되었습니다.(CRC에러)' 메세지가 떠요. 
    혹시 관련해서 해결 방법을 아시면 조언 좀 부탁드립니다 T.T
  2. 질문
    우와우와-정말정말 감사합니다!!!!
    복 받으실 거예요 T.T
  3. 질문
    죄송한데요...질문 하나만 더 드릴게요...
    전 반디집을 사용해서 zip파일 해제에 문제가 없었는데요. 
    윈도우의 기본 zip파일 해제도 문제 없습니다.
    그런데, 알집을 사용하는 컴에서는 zip파일 해제 시에 '압축파일의 헤더가 손상되었습니다'란 오류메세지가 떠요. 
    이 부분 관련해서는 해결 방법이 없을까요? 
    조언 부탁드리겠습니다...
    • 2014.04.12 20:49 신고 [EDIT/DEL]
      압축파일을 메모장으로 열어보고 php 에러 메세지가 있는지 확인하고 알아서 해결해주세요.
      기본설정으로 했을 때 timezone관련 에러가 떴던 걸로...

      해결하기 귀찮으면 error_reporting(0);을 파일 제일 처음에 넣는 방법도 있습니다.
  4. 좋은 코드 남겨주셔서 감사합니다.
    PHP 5.3.3에서 테스트 했을경우 153라인($this->crc = unpack('N', hash_file('crc32b', $filename, true))[1];)에서 오류가 발생하였는데, 댓글에 남겨주신 코드로 변경하였더니 잘 됩니다.
    파일 다운로드까지 남은시간 및 총 파일 용량은 아래의 코드추가로 해결하였습니다.
    07라인 public function open($filename) -> public function open($filename, $filesize)
    16라인에 추가 header("Content-Length: $filesize");
    그리고 괜찮으시다면 출처 남기고 블로그에 퍼가도 될까요?
    • 2014.10.05 23:04 신고 [EDIT/DEL]
      글쎄요.. $filesize는 Zip Entry의 헤더 크기까지 포함한 건가요? (self::write 하는 곳...)
      아니라면 브라우저가 받는 도중에 끊을 것 같은데, 잘 된다 하시니 걱정은 안 해도 되겠네요.
      퍼가는 건 마음대로~
    • 2015.10.07 15:12 신고 [EDIT/DEL]
      어떤 압축은 잘 되고, 어떤 압축은 손상된 파일이라고 나오네요..
      사용 안하는게 좋을 것 같습니다... ㅠㅠ
      답변 감사합니다.
    • 2015.10.08 09:30 신고 [EDIT/DEL]
      그렇죠.. 파일을 만들어놓고 보내는 게 아니라서 Content-Length 헤더는 보내지 않아야 합니다.
      동적으로 생성할 파일의 최종 크기를 생성 전에 알 수 없기 때문이죠..
  5. 테러보이
    좋은 소스 감사합니다.
    실례가 되지 않는다면 http://blog.terrorboy.net 으로 스크랩 해가도 될까요?
    문론, 내용 하단에 원본 출처를 남긴다는 조건으로요^^
  6. 비틀즈
    좋은소스로 제대로 작업했어요 감사합니다.
    너무 좋은 소스 담아갈께요~~~~
  7. OMG
    감사합니다
    잘 만드셨네요^^
  8. 꽁냥
    올려놓으신 소스 참고해서 처리했습니다..
    좋은내용 감사합니다^^
  9. 질문
    윈도우 7에스는 작동 되나요.?
  10. 압축 다운이 되고 압축 해제해봤습니다
    문제는 압축하일에서 addFile 함수로 보낸 파일이 압축파일내로 안들어가집니다
    $zip->open('아리아워크룸(신경욱).zip');
    $zip->addEmptyDir('아리아워크룸(신경욱)');
    $zip->addFile('/attach/20160215/1455506575.jpg', '아리아워크룸(신경욱)/1.jpg');
    $zip->addFromString('아리아워크룸(신경욱)/바로 글쓰기.txt', '파일 내용');
    이렇게 넣었는데요
    텍스트파일은 압축해제하니 저대로 뜹니다
    폴더도 생성되고요
    이미지파일만 생성이 안되는데 이유가 뭘까요?
    1455506575.jpg 파일은 실제 웹 서버에 올라가 있는 파일입니다
  11. 압축 다운이 되고 압축 해제해봤습니다
    문제는 압축하일에서 addFile 함수로 보낸 파일이 압축파일내로 안들어가집니다

    텍스트파일은 압축해제하니 제대로 뜹니다
    폴더도 생성되고요
    이미지파일만 생성이 안되는데 이유가 뭘까요?
    파일은 실제 웹 서버에 올라가 있는 파일로 경로 설정도 root를 / 로 시작했습니다
    • 2016.03.25 20:06 신고 [EDIT/DEL]
      대상 파일의 경로를 잘 확인해야 합니다.
      www
      -.php
      -attach
      --20160215
      ---1455506575.jpg
      폴더 구성이 이렇게 돼있다면 '/attach'에서 '/'를 빼야 합니다.
      추가로, ftp에서 볼 때 root와 웹서버의 root는 같지 않을 수 있으므로 조심하세요.
  12. 질문
    질문이있습니다 위에서 170번째줄 변경하라고 하셨는데 어디를 변경하면 되는건지 도통..모르겠습니다
    저도 알집과 윈집에서 파일열기 및 압축해제에서 손상된 파일이라는 오류메세지가 나타납니다.
  13. 질문
    소스를 받아 잘 사용하고 있습니다. 이번에 서버를 변경하며 5.5.9 버전이 되었습니다.
    다운로드는 되는것 같은데 알집에서 헤더가 손상되었다고 나옵니다. 파일은 잘들어 오는것 같습니다,.
  14. KJM
    좋은 소스 감사합니다.
    보내주신 소스로 만들어보니 파일 압축과 다운은 잘되는데 다운받은 압축파일을 열어보면 압축되어진 이미지 파일들의 크기가 0으로 나오고 있습니다. 이미지는 당연히 비어있구요ㅠㅠ 내용이 왜 안들어가지는지 알수 있을까요?
  15. KJM
    서버 환경입니다.
    EUC-KR (PHP5.2, MySQL5.x)
    • 2017.10.30 08:37 신고 [EDIT/DEL]
      일단 PHP 버전이 권장버전보다 낮네요..
      소스코드에 골뱅이(@)를 다 지워보시고 출력되는 에러 로그를 첨부해주실레요?
      제일 의심가는 게 BUFFER_SIZE const 선언부이긴 한데, BUFFER_SIZE 값을 75번째 줄에 바로 입력하는 거도 고려해주세요.


출처  : http://blog.bloodcat.com/277

반응형