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 “Zip“. 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 especificar 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:
// 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:
// Criando o objeto $z = new ZipArchive(); // Abrindo o arquivo para leitura/escrita $abriu = $z->open('teste.zip'); if ($abriu === true) { // Obtendo o conteudo de um arquivo pelo nome $conteudo_txt = $z->getFromName('teste/texto.txt'); // Obtendo o conteudo de um arquivo pelo indice $conteudo_php = $z->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:
// Criando o objeto $z = new ZipArchive(); // Abrindo o arquivo para leitura/escrita $abriu = $z->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.
Extraindo o conteúdo de um arquivo ZIP
Para extrair (descompactar/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 array de elementos a serem extraídos (pelo nome). Exemplo:
// Criando o objeto $z = new ZipArchive(); // Abrindo o arquivo para leitura/escrita $abriu = $z->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:
// 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.
Fonte: https://rubsphp.blogspot.com/2011/03/manipulando-arquivos-zip-pelo-php.html