Percorrer Diretórios e Arquivos com PHP

Artigo que explica como percorrer diretórios e arquivos através de funções e classes do PHP.

Busca em Diretório

Percorrer diretórios e arquivos em PHP é muito simples. Vamos ver 5 formas para se fazer isso, cada uma com suas características e limitações:


Usando as funções opendir, readdir e closedir

Esta forma consiste em abrir um diretório como se fosse um handle de arquivo. Com a função opendir é criada uma variável do tipo resource, que representa um ponteiro para os itens do diretório escolhido (ou é devolvido "false" em caso de falha). Com a função readdir é obtido um item do diretório (na forma de string) e o ponteiro avança uma posição. Com a função closedir o resource é destruído, não sendo mais permitida nenhuma operação sobre o mesmo. Opcionalmente, existe a função rewinddir que serve para retornar o ponteiro de um resource para a primeira posição (só funciona se o resource não foi fechado).

Exemplo:

$dir = opendir('/home');
if ($dir) {
    while (($item = readdir($dir)) !== false) {
        echo $item.'<br />';
    }
    closedir($dir);
}

Observação: foi utilizada a comparação "($item = readdir($dir)) !== false" pois é possível existir um diretório chamado "0" (zero). Como "0" é considerado falso em uma estrutura condicional, é preciso utilizar o operador "não idêntico". Este operador garante que uma string avaliada como false seja considerada diferente de um booleano false. Caso contrário, é ocasionado um loop infinito, já que "." avaliaria o mesmo diretório.

Atenção: a função readdir também retorna os pseudo-diretórios "." e "..". isso significa que se você não deseja exibi-los ou esteja implementando este recurso em uma função recursiva, é necessário checar se o item obtido é diferente de "." e de "..". Uma forma conveniente é usar o comando "continue". Atenção: nem sempre estes pseudo-diretórios são devolvidos na primeira e segunda iteração (isso depende do sistema operacional).

    ...
    if ($item == '.' || $item == '..') {
        continue;
    }
    ...

Ao utilizar esta forma, é útil conhecer as principais funções para verificar se o item é um arquivo, link ou diretório (is_file, is_link e is_dir); se o item existe (file_exists); quais as permissões do item (is_readable, is_writable e is_executable); nome completo do item (realpath); nome base do item (basename); e nome do diretório onde está o item (dirname).


Usando a função dir

Uma forma de utilizar opendir, readdir e closedir de forma que simula programação orientada a objetos é utilizando a função dir. Ela devolve um objeto da classe Directory, que possui dois atributos ($path e $handle) e três métodos (read, rewind e close). Na prática, este recurso só encapsula as três operações básicas e os dois atributos necessários.

Exemplo:

$dir = dir('/home');
if ($dir) {
    while (($item = $dir->read()) !== false) {
        echo $item.'<br />';
    }
    $dir->close();
}

Considero esta utilização depreciada depois que surgiu a classe DirectoryIterator, que será mostrada mais a diante.


Usando a função glob

A função glob permite varrer os itens de um diretório que casam com uma expressão, assim como se faz no terminal de comandos do Linux ou MS-Dos para listar o conteúdo de um diretório:

Exemplo de comando para listar arquivos com extensão ".txt" no Linux:

$ ls -lh *.txt

Exemplo de comando para listar arquivos com extensão ".txt" no MS-Dos:

C:\> dir *.txt

Diferente de opendir, a função glob devolve um array de strings ou false em caso de falha. Outra diferença é que ela não devolve "." e "..". Um outro benefício é o segundo parâmetro, que é uma flag binária de opções. Um exemplo de flag é GLOB_ONLYDIR que faz a função devolver apenas diretórios. A lista de flags disponíveis estão na documentação.

Exemplo:

$itens = glob('./fotos/*.jpg');
if ($itens !== false) {
    foreach ($itens as $item) {
        echo $item.'<br />';
    }
}

A vantagem óbvia desta função é que ela já obtém apenas uma lista de itens que casam com uma expressão, não sendo necessário fazer esta checagem manualmente (durante as iterações). Por retornar um array, também tem as vantagens de se poder manipular o array da forma desejada. Além disso, ela é capaz de devolver os itens de forma ordenada. A desvantagem é que devolvendo um array, podem muitos itens que casam com o padrão e estes dados vão ficar na memória. Se estiver em uma função recursiva, isso pode ser problemático se estiver trabalhando com um volume muito grande de itens percorridos sem liberá-los da memória.

Para realizar operação semelhante com opendir ou com dir seria necessário utilizar algum mecanismo de expressões regulares, como a função preg_match. Com isso, a utilização de memória minimizaria. Outra dica útil, neste caso, é: (i) primeiro percorrer os itens do diretório, separando os sub-diretórios de arquivos; (ii) fechar o resource do diretório corrente; e (iii) percorrer os sub-diretórios recursivamente. Isso faz com que só exista um resource aberto em um dado instante.

Algo útil de se conhecer é a flag GLOB_BRACE. Com ela é possível selecionar arquivos de diferentes extensões, delimitando-as com chaves e separando por vírgulas. Veja o exemplo:

$imagens = glob('/home/rubens/imagens/{*.jpg,*.bmp,*.png}', GLOB_BRACE);

Usando a função scandir

A função scandir, assim como glob, devolve um array de itens. Entretanto, diferente de glob, ela não recebe uma expressão, e sim um diretório como acontece em opendir e dir. A função devolve os itens "." e "..". As vantagens e desvantagens são bastante semelhantes às vistas na função glob.

Exemplo:

$itens = scandir('/home');
if ($itens !== false) { 
    foreach ($itens as $item) { 
        echo $item.'<br />';
    }
}

Usando as classes DirectoryIterator, FilesystemIterator e GlobIterator

Esta classe faz parte da SPL, que esteve disponível a partir da versão 5.0.0 do PHP. Na versão 5.3.0, ela se tornou padrão e não pode mais ser desabilitada. Trata-se, portanto, de uma versão mais elaborada da classe Directory, com mais métodos disponíveis. Para ver a lista de métodos disponíveis, consulte a documentação.

Exemplo:

try {
    $dir = new DirectoryIterator('/home');
    foreach ($dir as $item) {
        if ($item->isDot()) {
            continue;
        }
        echo $item->getFilename().'<br />';
    }

} catch (UnexpectedValueException $e) {
    echo 'Erro: '.$e->getMessage();
}

Esta classe estende a classe SplFileInfo, portanto oferece os métodos públicos deste classe. Alguns exemplos:

  • Obter nomes:
    • getFilename - Obtém o caminho completo do item
    • getPathname - Obtém o caminho completo do diretório onde está o item
    • getBasename - Obtém o nome do item (sem o diretório)
    • getRealPath - Obtém o caminho completo real até o item (resolvendo links simbólicos)
  • Obter informações:
    • getCTime, getATime e getMTime - Obtém a data de criação, último acesso e última modificação
    • getOwner e getGroup - Obtém o ID do dono e do grupo do item
    • getSize - Obtém o tamanho do arquivo
    • getType - Obtém o tipo do item (diretório, arquivo ou link)
    • isDir, isFile e isLink - Checa se o item é um diretório, arquivo ou link
    • getPerms - Obtém a flag binária (valor inteiro) representando as permissões do item
    • isReadable, isWritable e isExecutable - Checa se o item pode ser lido, modificado ou executado

Observação: como a classe DirectoryIterator implementa a interface Iterator, ela possui os métodos para manipular o "ponteiro" para o item percorrido. Por exemplo, possui o método rewind para voltar para a primeira posição. Além disso, como implementa a interface SeekableIterator, possui o método seek, que move o ponteiro para uma posição desejada.

Existe também a classe FilesystemIterator que estende a classe DirectoryIterator, e oferece recursos adicionais. Por exemplo, informar flags binárias para obter alguns comportamentos, tais como: (i) ignorar o "." e "..", (ii) seguir links simbólicos, (iii) especificar o tipo de retorno do método current (usado em iterações com foreach), etc.

Por fim, existe a classe GlobIterator que estende a classe FilesystemIterator e oferece o recurso adicional de percorrer itens a partir de uma expressão, assim como mostrado com a função glob. Porém, por algum motivo desconhecido, o iterador não possui um comportamento semelhante ao proposto pela opção GLOB_BRACE. Particularmente, acho que usar apenas "*" como curinga não seja tão útil. Prefiro usar DirectoryIterator e usar preg_match para testar uma expressão regular mais elaborada.

O uso destas classes é recomendado, pois elas oferecem os mesmos recursos mostrados anteriormente (e alguns novos), e está alinhado com o modelo Orientado a Objetos, para onde os recursos do PHP tem caminhado. A única desvantagem é a incompatibilidade com versões antigas do PHP (inferiores à versão 5).

7 comentários

Anônimo disse...

Então podemos dizer que DirectoryIterator é o melhor? Já que ele agrupa as melhores qualidades das outras funções apresentadas?

rubS (autor do blog) disse...

Pelo que eu considero "melhor", sim, podemos dizer que DirectoryIterator é melhor que scandir ou opendir/readdir/closedir.

Downloads livres disse...

Primeiramente Boa tarde. Me chamo Anderson e achei muito útil o seu Blog. Parabéns pelas explicações.

Tenho uma função que lista arquivos de pastas mais queria que essa função restringisse determinadas extensões e não sei como fazer. Você poderia dar uma força?

public static function GetFiles($dir)
{
GLOBAL $cfg;

$pasta = opendir($dir);
$i=0;
while (false !==($file = readdir($pasta)))
{
if (($file != '.') && ($file != '..'))
{
if(!is_dir($dir.$file))
{
$saida[$i][0] = $file; // Nome da pasta/arquivo
$saida[$i][1] = Mascara::ArquivoTamanho(filesize($dir.$file)); // Tamanho do arquivo
$saida[$i][2] = date ("d/m/Y H:i:s.", filemtime($dir.$file)); // Data de modificação
$saida[$i][3] = end(explode(".",$file)); $saida[$i][4] = $saida[$i][4][0]; // Extensão
if(file_exists($cfg['path_fisico'].'theme/ubuntu/icons/48x48/_'.$saida[$i][3].'.gif')==true) // Ícone
{$saida[$i][4] = '_'.$saida[$i][3];}else{$saida[$i][4] = '_';}
$i++;
}
}
}
return $saida;
}

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Downloads livres

Se você quer percorrer apenas os arquivos de determinada extensão, use a função "glob" mostrada no artigo. Outra opção é usar a função "pathinfo" para obter a extensão do arquivo, conforme mostrado no artigo:
http://rubsphp.blogspot.com.br/2011/02/caminhos-de-arquivos-e-diretorios.html

Anônimo disse...

"{*.jpg,*.bmp,*.png}', GLOB_BRACE);" esse trecho fez a minha vida mais feliz. Muito obrigado!!! :D

Anônimo disse...

Neste trecho:

if ($item == '.' || $item == '..') {
continue;
}

Não deveria ser assim:

if ($item !== '.' && $item !== '..') {
continue;
}

Se for diferente de '.' e diferente de '..' ai sim ele continua

Rubens Takiguti Ribeiro (autor do blog) disse...

Anônimo, o "continue" passa para a próxima iteração do loop, ou seja, queremos pular o item quando ele for "." ou "..". Portanto, o código está correto.