Você conhece a SPL(Standard PHP Library) ? É uma biblioteca de componentes inteiramente escritos em PHP5 dentro do paradigma de Orientação a Objetos para lidar com problemas cotidianos na vida dos programadores que trabalham com PHP.

O ambiente onde este artigo foi desenvolvido/testado:

  • PHP 5.2.1 SAPI CLI (Command Line Interface)
  • Ubuntu Feisty Fawn

O que você precisa saber para aprender melhor o conteúdo ?


Um dos problemas que encontramos sempre é a questão de trabalharmos com iteração de diretórios, por exemplo um script para backup, localizar algum determinado tipo de arquivo dentro de um diretório com muitos sub-diretórios, efetuar mudança de nomes de forma recursiva dentre outros. Precisávamos de um solução elegante e legível para efetuarmos manutenção seja corretiva e ou implementativa, isso se torna mais importante quando você trabalha em equipe ou quando você deixa um código para que outro programador seja responsável pela manutenção. Outro ponto forte para a criação e utilização desta biblioteca é a questão de termos um ponto central de soluções(amplamente suportados) para problemas cotidianos assim agilizando o processo de desenvolvimento de software, ou seja, ao invés de termos que criar sempre uma solução para um problema que já tem solução(não seria re-inventar a roda ?!) ou fazer reutilização de software através de CTRL + C e CTRL V ( como era feito e que ainda é muito utilizado ).

Minha contribuição para que possamos melhorar o desenvolvimento de software onde tem como tecnologia de programação PHP5/6 é iniciar uma série de artigos explanando a utilização da SPL, tendo como início uma classe chamada DirectoryIterator.

Para entendermos melhor o DirectoryIterator precisamos dá uma olhadinha neste diagrama de colaboração, através da figura 1 abaixo:

Figura 1.

Como podemos interpretar esse diagrama de colaboração(Figura 1) o DirectoryIterator é uma classe que implementa a interface Iterator e extende a classe SplFileInfo.
O design da interface Iterator é segundo seu diagrama de colaboração o seguinte:

Figura 2

Basicamente a interface Iterator diz que a classe DirectoryIterator deve obrigatoriamente implementar os métodos current(), key(), next(), rewind(), valid(). Portanto isto quer dizer que um objeto DirectoryIterator pode ser atravessado/iterado/percorrido e que deve ter os controles básico para uma boa iteração de fila como ir para frente( next() ), voltar( rewind() ), atual ( current() ) e controle condicional ( valid() ).

Como ele extende a classe SplFileInfo, isso quer dizer que podemos acessar os métodos públicos desta classe, métodos como isFile(), isDir() , isExecutable()  dentre outros.

Com esse raciocínio básico podemos ir para o código, afinal “Talks is cheap, show me the code”.


$dirName = “/tmp”;
$dirObj = new DirectoryIterator($dirName);

$i = 0;
for(; $dirObj->valid() ; $dirObj->next() )
{
if( !$dirObj->isDot() )
{
print “$i -> {$dirObj->getPathname()} “.” ->; {$dirObj->getType()}\n”;
$i++;
}
}

O código acima mostra os arquivos e diretórios de um determinado diretório informado no construtor do objeto DirectoryIterator.

Basicamente na linha 2 é informado o diretório para ser visualizado e percorrido, na linha 3 é instanciado um objeto DirectoryIterator e o percorremos através de um for conforme visto na linha 5 e nos condicionais deste for podemos ver que estamos utilizando os métodos implementados na classe DirectoryIterator conforme a interface Iterator manda e isso nos dá os condicionais básicos e iteradores para percorrer/iterar este objeto.

Gostou ?! vamos dá um upgrade neste exemplo… vamos criar 2 funções recursivas, uma para guardar o enderecamento dos diretórios, sub-diretórios e seus arquivos em um Array e outra função para imprimir isso de forma recursiva:


/**
* Função para guardar os diretórios/sub-diretórios e seus arquivos
* respectivamente em um vetor(Array) de forma recursiva
*
* @params string $dirName
* @return array $arr
*/
function throughDir($dirName)
{
$dirObj = new DirectoryIterator($dirName);
for(; $dirObj->valid() ; $dirObj->next() )
{
if( $dirObj->isDir() and !$dirObj->isDot() )
{
$arr[$dirObj->getPathname()] = throughDir($dirObj->getPathname());
}
}
return $arr;
}

/**
* Função para imprimir os diretórios/sub-diretórios e seus arquivos
* respectivamente de um vetor(Array) de forma recursiva
*
* @params Array $dirName
* @return void
*/
function printDir($arrDirs)
{
if(is_array($arrDirs))
{
foreach($arrDirs as $dir => $subDir)
{
echo $dir.”\n”;
printDir($subDir);
}
}else{
echo $arrDirs.”\n”;
}
}

$arrDirs = throughDir(“/home/daniel/Desktop/”);
printDir($arrDirs);

Gostaram ?! é muito legal, tudo muito bom e claro perfeito senão tivessemos problemas para serem tratados.

Um dos problemas que podem ocorrer durante a execução de seu script é a falta de permissão para ler e ou abrir diretórios e arquivos, e se isso acontecer irá comprometer a execução de seu programa, ou seja, será finalizado.

No exemplo do código acima você vai conseguir percorrer os diretórios e sub-diretórios que você possuir permissão de leitura mas ao tentar percorrer um sub-diretório onde você não possui permissão para ler, o seu script finalizará a execução.

Um exemplo é você alterar o script anterior para percorrer um diretório que você não tenha permissão, você pode trocar a string “/home/daniel/Desktop” por “/tmp”, geralmente em “/tmp” sempre tem algo que o proprietário é o root e assim não permitindo que você possa ler o conteúdo. No meu caso olha no que deu:

daniel@blackforce:~/Desktop$ php teste2.php

Fatal error: Uncaught exception ‘RuntimeException’ with message ‘DirectoryIterator::__construct(/tmp/gconfd-root): failed to open dir: Permission denied’ in /home/daniel/Desktop/teste2.php:13
Stack trace:
#0 /home/daniel/Desktop/teste2.php(13): DirectoryIterator->__construct(‘/tmp/gconfd-roo…’)
#1 /home/daniel/Desktop/teste2.php(18): throughDir(‘/tmp/gconfd-roo…’)
#2 /home/daniel/Desktop/teste2.php(48): throughDir(‘/tmp’)
#3 {main}
thrown in /home/daniel/Desktop/teste2.php on line 13

Na maioria das vezes é possível fazer com que seu programa saiba com que tipo de erro está lidando e assim fazê-lo executar outros procedimentos para tentar ignorar o problema que o está impedindo de terminar com sucesso a execução.

Para tanto é necessário que você saiba como trabalhar com Exceptions, um exemplo de como capturar exceções no exemplo do código anterior é acrescentando um bloco de try / catch na funcção throughDir() para capturar o erro como no exemplo abaixo:
/**
* Função para guardar os diretórios/sub-diretórios e seus arquivos
* respectivamente em um vetor(Array) de forma recursiva
*
* @params string $dirName
* @return array $arr
*/
function throughDir($dirName)
{
try{
$dirObj = new DirectoryIterator($dirName);
for(; $dirObj->valid() ; $dirObj->next() )
{
if( $dirObj->isDir() and !$dirObj->isDot() )
{
$arr[$dirObj->getPathname()] = throughDir($dirObj->getPathname());
}
}
}catch(RuntimeException $erro){
print $erro;
}
return $arr;
}

Apenas com este tratamento de imprimir o erro olha o resultado da execução:

daniel@blackforce:~/Desktop$ php teste2.php
exception ‘RuntimeException’ with message ‘DirectoryIterator::__construct(/tmp/gconfd-root): failed to open dir: Permission denied’ in /home/daniel/Desktop/teste2.php:13
Stack trace:
#0 /home/daniel/Desktop/teste2.php(13): DirectoryIterator->__construct(‘/tmp/gconfd-roo…’)
#1 /home/daniel/Desktop/teste2.php(18): throughDir(‘/tmp/gconfd-roo…’)
#2 /home/daniel/Desktop/teste2.php(48): throughDir(‘/tmp’)
#3 {main}exception ‘RuntimeException’ with message ‘DirectoryIterator::__construct(/tmp/orbit-root): failed to open dir: Permission denied’ in /home/daniel/Desktop/teste2.php:13
Stack trace:
#0 /home/daniel/Desktop/teste2.php(13): DirectoryIterator->__construct(‘/tmp/orbit-root’)
#1 /home/daniel/Desktop/teste2.php(18): throughDir(‘/tmp/orbit-root’)
#2 /home/daniel/Desktop/teste2.php(48): throughDir(‘/tmp’)
#3 {main}/tmp/.tmp
/tmp/.tmp/tcx
/tmp/hsperfdata_daniel
/tmp/.ICE-unix
/tmp/.X11-unix
/tmp/.esd-1000
/tmp/kde-daniel
/tmp/gconfd-root
/tmp/orbit-daniel
/tmp/hsperfdata_root
/tmp/ksocket-daniel
/tmp/orbit-root
/tmp/ssh-KcBsL23144
/tmp/gconfd-daniel
/tmp/gconfd-daniel/lock
/tmp/virtual-daniel.XCv1fR
/tmp/epiphany-daniel-OTEfiF
/tmp/keyring-vrbWn3
/tmp/.exchange-daniel
/tmp/svbd9.tmp
/tmp/plugtmp
daniel@blackforce:~/Desktop$

Como podemos ver, o erro foi impresso mas não gerou nenhum Fatal Error na execução do programa, o script continuou a execução até o fim e se por acaso tivesse ocorrido outro erro por falta de permissão, novamente ele iria imprimir o erro e seguir em diante, tudo isso porque o Fatal Error é gerado porque você não está tratando devidamente as ocilações de comportamento de seu programa de uma forma adequada.

A única exceção que vimos foi a RuntimeException, mas este post já está muito grande e não quero me estender mais para não ficar muito cansativo de ler, além do que este assunto sobre Exceptions geradas pela SPL merece um artigo somente tratando de explanar sua lógica, forma de captura e tratamento.

Com certeza isso é só o início da uma apresentação sobre a SPL. Espero que com essa introdução à classe DirectoryIterator tenha ajudado de alguma forma a tirar dúvidas sobre esta classe.

Qualquer contribuição/sugestão/correção/reclamação será bem vinda.

Comente! ajude a melhorar a qualidade do artigo.. compartilhe conhecimento.

Fontes:

Updates:
Atualizado endereços das imagens para a nova url