Introdução

Existem situações em sistemas de informação Web onde é necessário o envio de um arquivo muito grande ou então vários arquivos de uma vez para o cliente. Talvez uma imagem, talvez um pacote de relatórios, ou simplesmente conjuntos de arquivos. Este artigo mostra como utilizar a extensão Zip do PHP para criar um pacote ZIP (ou ler um pacote existente) e realizar operações sobre ele, tais como incluir arquivos/diretórios para serem compactados, extrair o conteúdo, enviar o pacote para o cliente, etc.


Extensão ZIP

Em PHP, existe uma extensão própria para geração de arquivos ZIP, e ela se chama justamente "[Somente usuários registrados podem vem os links. ]". Ela trabalha com o paradigma OO para manipular arquivos ZIP, tanto para escrita quanto para leitura. A classe principal da extensão é a ZipArchive, que representa um arquivo ZIP.

Mas antes de apresentar o funcionamento da classe, é preciso estar ciente de algumas características de um pacote Zip:

O pacote pode armazenar, internamente, arquivos e diretórios como se fosse um sistema de arquivos de um HD;
O conteúdo do pacote fica armazenado de forma compactada;
Cada elemento do pacote (arquivo ou diretório) possui um nome e um índice único, que podem ser usados para referenciá-los para leitura ou edição.
Cada elemento do pacote é independente do outro, portanto, se existe um elemento diretório "teste/" e um elemento arquivo "teste/texto.txt", é possível apagar o elemento diretório, mas o elemento arquivo continuar existindo. Ou seja, a lógica de funcionamento é diferente de um sistema de arquivos que, ao apagar um diretório, o conteúdo interno é apagado.
Cada elemento do pacote pode conter comentários próprios, armazenados no próprio pacote.
Sobre o funcionamento da classe, qualquer operação se inicia criando um objeto (através do construtor que, aparentemente, não recebe parâmetros) e chamando o método open, que serve tanto para especificar o nome do arquivo ZIP a ser criado quanto para especifiar um nome de um arquivo ZIP já existente. Após realizar as operações desejadas, chama-se o método close para salvar o arquivo de acordo com o que foi feito no objeto, ou seja, corresponde a uma persistência de dados. Caso este método não seja chamado pelo programador, o PHP o invocará automaticamente no final da execução do script. Portanto, caso tenha sido feita alguma alteração que não se pretende salvar no arquivo, deve-se chamar um método para desfazer as operações (unchangeAll). Observação: ao fechar o arquivo, os elementos são reindexados (começando a partir do zero), portanto, ao ser aberto novamente, o índice de um elemento pode ter mudado.

O objeto do tipo ZipArchive pode ser usado várias vezes. Ou seja, não é necessário criar um objeto para cada arquivo a ser aberto, a não ser que se deseja manipular dois arquivos paralelamente. Caso contrário, basta chamar open e close sucessivamente para abrir e fechar pacotes diferente.

Ao abrir o arquivo com open, pode-se especificar a operação desejada através de uma constante binária para o segundo parâmetro. A constante pode ser a combinação dos seguintes valores:

  • ZipArchive::OVERWRITE - limpa o arquivo aberto, caso ele exista e seja incluído pelo menos um elemento.
  • ZipArchive::CREATE - cria o arquivo com o nome especificado, caso ele não exista, ou carrega o conteúdo do arquivo, caso ele exista (exceto se for executado conjuntamente com a constante EXCL, mostrada abaixo).
  • ZipArchive::EXCL - usada em combinação com CREATE para indicar criação exclusiva, ou seja, só criar o arquivo se ele não existir. Caso o arquivo exista, o método retorna o código do erro e não salva o arquivo.
  • ZipArchive::CHECKCONS - usada para checar a consistência do diretório central com o cabeçalho do arquivo ZIP, onde ficam informações sobre o conteúdo (como se fosse um índice).
  • Não passar nenhuma constante abre o arquivo para leitura/escrita sem apagar o conteúdo carregado inicialmente.



Criando e Manipulando um arquivo ZIP dinamicamente

Para criar um arquivo ZIP dinamicamente, basta chamar o método open com a flag ZipArchive::CREATE. Depois, basta usar os métodos para manipular o objeto:

  • addEmptyDir - Adicionar um elemento diretório a um ponto do pacote.
  • addFromString - Adiciona um elemento arquivo a partir do conteúdo de uma String.
  • addFile - Adiciona um elemento arquivo a partir do conteúdo de um arquivo do HD, ou seja, copia um arquivo do HD para dentro do pacote (podendo mudar o nome durante a cópia).
  • deleteName - Remove um elemento do pacote pelo seu nome.
  • deleteIndex - Remove um elemento do pacote pelo seu índice.


Para manipular os comentários do arquivo ou dos elementos, use os métodos:

  • getArchiveComment - Obtém o comentário do arquivo.
  • getCommentIndex - Obtém o comentário de um elemento pelo seu índice.
  • getCommentName - Obtém o comentário de um elemento pelo seu nome.
  • setArchiveComment - Define o comentário do arquivo.
  • setCommentIndex - Define o comentário de um elemento pelo seu índice.
  • setCommentName - Define o comentário de um elemento pelo seu nome.


Veja um exemplo:
Código PHP:
// Criando o objeto
$z = new ZipArchive();

// Criando o pacote chamado "teste.zip"
$criou $z->open('teste.zip'ZipArchive::CREATE);
if (
$criou === true) {

    
// Criando um diretorio chamado "teste" dentro do pacote
    
$z->addEmptyDir('teste');

    
// Criando um TXT dentro do diretorio "teste" a partir do valor de uma string
    
$z->addFromString('teste/texto.txt''Conteúdo do arquivo de Texto');

    
// Criando outro TXT dentro do diretorio "teste"
    
$z->addFromString('teste/outro.txt''Outro arquivo');

    
// Copiando um arquivo do HD para o diretorio "teste" do pacote
    
$z->addFile('/home/rubens/teste.php''teste/teste.php');

    
// Apagando o segundo TXT
    
$z->deleteName('teste/outro.txt');

    
// Salvando o arquivo
    
$z->close();
} else {
    echo 
'Erro: '.$criou;

Lendo e Manipulando um arquivo ZIP dinamicamente

Para realizar a leitura ou manipulação de um arquivo ZIP existente, basta invocar o método open sem nenhuma constante, em seguida usar o método getFromIndex ou getFromName, para obter o conteúdo de um arquivo interno através do seu índice ou do seu nome respectivamente.

Veja um exemplo:
Código PHP:
// Criando o objeto
$zip = new ZipArchive();

// Abrindo o arquivo para leitura/escrita
$abriu $zip->open('teste.zip');
if (
$abriu === true) {

    
// Obtendo o conteudo de um arquivo pelo nome
    
$conteudo_txt $zip->getFromName('teste/texto.txt');

    
// Obtendo o conteudo de um arquivo pelo indice
    
$conteudo_php $zip->getFromIndex(2);

    
// Salvando o arquivo
    
$z->close();

} else {
    echo 
'Erro: '.$abriu;

Porém, nem sempre sabemos os nomes dos elementos de um pacote. Para avaliá-los dinamicamente, podemos usar o atributo interno numFiles, que guarda a quantidade de elementos. Sabendo quantos elementos existem no pacote, podemos percorrer do índice zero até a "quantidade menos um" (último elemento), inclusive obter o nome do elemento com o método getNameIndex, ou informações sobre o arquivo com o método statIndex, conforme o exemplo:
Código PHP:
// Criando o objeto
$zip = new ZipArchive();

// Abrindo o arquivo para leitura/escrita
$abriu $zip->open('teste.zip');
if (
$abriu === true) {

    
// Listando os nomes dos elementos
    
for ($i 0$i $z->numFiles$i++) {

        
// Obtendo informacoes do indice $i
        
$stat $z->statIndex($i);

        
// Obtendo apenas o nome do indice $i
        
$nome $z->getNameIndex($i);

        
// Exibindo informacoes do elemento
        
echo $stat['name'].PHP_EOL;        // Nome do elemento
        
echo $stat['index'].PHP_EOL;       // Indice do elemento
        
echo $stat['crc'].PHP_EOL;         // CRC
        
echo $stat['size'].PHP_EOL;        // Tamanho original (em bytes)
        
echo $stat['mtime'].PHP_EOL;       // Data de modificacao
        
echo $stat['comp_size'].PHP_EOL;   // Tamanho compactado (em bytes)
        
echo $stat['comp_method'].PHP_EOL// Metodo de compressao
    
}

    
// Fechando o arquivo
    
$z->close();

} else {
    echo 
'Erro: '.$abriu;

Observação: para se obter o índice de um elemento a partir do seu nome, basta usar o método locateName informando o nome.


Extraíndo o conteúdo de um arquivo ZIP

Para extrair (decompactar/descomprimir) o conteúdo de um pacote ZIP para um diretório, basta chamar o método extractTo. O objeto pode ser tanto um arquivo novo (recém criado) quanto um arquivo já existente e que foi aberto para leitura. O método recebe por parâmetro o diretório onde o conteúdo deve ser extraído e, opcionalmente, um elemento ou vetor de elementos a serem extraídos (pelo nome). Exemplo:
Código PHP:
// Criando o objeto
$zip = new ZipArchive();

// Abrindo o arquivo para leitura/escrita
$abriu $zip->open('teste.zip');
if (
$abriu === true) {

    
// Extraindo todo conteudo no diretorio "/home/rubens/"
    
$z->extractTo('/home/rubens/');

    
// Extraindo apenas um arquivo no diretório "/tmp/"
    
$z->extractTo('/tmp/', array('teste/texto.txt'));

    
// Fechando o arquivo
    
$z->close();

} else {
    echo 
'Erro: '.$abriu;


Enviando um pacote ZIP ao cliente

Para enviar um pacote ZIP ao cliente, basta criá-lo com as instruções acima, depois utilizar a função header e readfile para enviar ao cliente, desta forma:
Código PHP:
// Criando o arquivo zip com nome "teste.zip"
$z = new ZipArchive();
$z->open('teste.zip');
...
$z->close();

// Enviando para o cliente fazer download
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="teste.zip"');
readfile('teste.zip');
exit(
0); 

Observações Importantes

Lembre-se que assim como para qualquer outro tipo de arquivo, o usuário usado pelo servidor Web (por exemplo, o usuário "apache") precisa ter permissão de escrita em um diretório para conseguir criar um arquivo ZIP lá. Da mesma forma, o usuário precisa de permissão de leitura e/ou escrita sobre um arquivo ZIP para realizar as respectivas operações.

Vale relembrar que os elementos do pacote são independentes. Portanto, para apagar um diretório e todo o seu conteúdo, é preciso percorrer os elementos e verificar se o elemento percorrido "pertence" ao diretório. Para isso, basta verificar se o nome do elemento percorrido contém o nome do diretório a ser apagado no seu início (ou seja, se ele tem o diretório como prefixo). Além disso, é preciso garantir que seja usada uma única barra para indicar delimitador de diretórios.

Retirado de :[Somente usuários registrados podem vem os links. ]