Questo articolo illustra un usuale compito che si potrebbe avere sperimentato durante lo sviluppo di un’applicazione in PHP: elenchi di file e directory. Si occupa di diverse soluzioni di base e avanzate, ciascuno con i suoi pro e contro. Per primo si presenta tre approcci che utilizzano alcune funzioni di base di PHP e poi si progredisce verso quelli più robusti che fanno uso di iteratori SPL.

Questo articolo descrive i diversi modi per raggiungere lo stesso obiettivo: come recuperare e filtrare i file e le directory in un determinato percorso. Questi sono alcuni punti chiave da ricordare:

  • La funzione glob() è una soluzione one-line e permette il filtraggio, ma non è molto flessibile.
  • La soluzione usando opendir(), readdir(), e closedir() è un po’ prolisso e ha bisogno di un post-filtraggio, ma è più flessibile.
  • La funzione scandir() necessita il post-filtraggio e non ha bisogno di gestire lo handle.
  • Se si desidera utilizzare un approccio OOP, è necessario utilizzare la libreria SPL. Inoltre è possibile estendere le classi in base alle proprie esigenze.
  • Mentre il GlobIterator è in grado di fare pre-filtraggio, gli altri come RegexIterator possono fare lo stesso in modo confortevole.

Ai fini della discussione, supponiamo una struttura di directory simile a quella riportata di seguito:

[code lang=”cpp”] \admin
\user
|—documenti.txt
|—dati.dat
|—stile.css
|—articolo.txt
|—admin.dat
|—script.php
|—test.dat
|—text.txt
[/code]
Le soluzioni di base
La prima serie di approcci dimostrano l’uso delle funzioni glob(), una combinazione delle funzioni opendir(), readdir() e closedir(), e la funzione scandir().
Usando glob()
La prima funzione in discussione è glob(), che ci permette di eseguire una ricerca di percorsi utilizzando i caratteri jolly, comuni nelle shell note. La funzione ha due parametri:

  • $pattern (obbligatorio): il modello di ricerca
  • $flags (opzionale): alcuni flag vengono elencati nella documentazione ufficiale

Vediamo alcuni esempi! Per eseguire una ricerca nella directory su tutti i file e directory che terminano in .txt, è necessario scrivere:

[code lang=”php”] <?php

$filelist = glob("*.txt");

?>
[/code]

Se si visualizza filelist $filelist, l’uscita sarà:
[code lang=”cpp”] array (
0 => ‘articolo.txt’,
1 => ‘text.txt’
)
[/code]
Se si vuole un elenco di file e directory che iniziano con te, il codice da scrivere è il seguente:
[code lang=”php”] <?php

$filelist = glob("te*");

?>
[/code]

L’uscita sarà:
[code lang=”php”] array (
0 => ‘test.dat’
1 => ‘text.txt’
)
[/code]
Per ottenere un elenco di directory che contiene ad, il codice è:
[code lang=”php”] <?php

$filelist = glob("*ad*", GLOB_ONLYDIR);

?>
[/code]

In quest’ultimo esempio, l’uscita è:
[code lang=”cpp”] array (
0 => ‘admin’
)
[/code]
L’ultimo esempio fa uso della costante GLOB_ONLYDIR come secondo parametro opzionale. Come si può vedere, il file chiamato admin.dat è escluso per questo motivo. Anche se la funzione glob() è facile da usare, ma a volte non è così flessibile. Per esempio, non ha un flag per recuperare solo i file (e non le directory) che corrispondono a un dato modello.
Usando opendir() e readdir()
Il secondo approccio serve per leggere i file e le directory e coinvolge le funzioni opendir(), readdir(), and closedir().
opendir() apre la directory e restituisce lo handle della connessione. Una volta che lo handle è recuperato, è possibile utilizzare readdir(). Ad ogni chiamata, questa funzione darà il nome del file successivo o directory all’interno di una directory aperta. Quando tutti i nomi sono stati recuperati, la funzione restituisce il valore false. Per chiudere lo handle della connessione si utilizza closedir().
A differenza glob(), questo approccio è un po’ più complicato dal momento che non si dispone di parametri che consentono di filtrare i file e le directory restituiti. Si deve effettuare un post-filtraggio per ottenere quello che si cerca.
In parallelo con la funzione glob(), l’esempio seguente recupera un elenco di tutti i file e le directory che iniziano con “te”:
[code lang=”php”] <?php

$filelist = array();
if ($handle = opendir(".")) {
while ($entry = readdir($handle)) {
if (strpos($entry, "te") === 0) {
$filelist[] = $entry;
}
}
closedir($handle);
}

?>
[/code]

L’uscita è la stessa dell’esempio precedente.
Ma se si esegue il codice sopra riportato, nell’uscita del valore di $entry vedrete che a volte contiene alcune strane entrate come: “..” e “.“. Si tratta di due directory virtuali che troverete in ogni directory del file system. Rappresentano la directory corrente e la directory principale, rispettivamente.
Il secondo esempio mostra come recuperare solo i file contenuti in un determinato percorso.
[code lang=”php”] <?php

$filelist = array();
if ($handle = opendir(".")) {
while ($entry = readdir($handle)) {
if (is_file($entry)) {
$filelist[] = $entry;
}
}
closedir($handle);
}

?>
[/code]

Come si può immaginare, il codice di sopra produce la seguente uscita:
[code lang=”cpp”] array (
0 => ‘articolo.txt’,
1 => ‘admin.dat’,
2 => ‘script.php’,
3 => ‘test.dat’,
4 => ‘text.txt’
)
[/code]
Usando scandir()
E infine, la funzione scandir(). Ha un solo parametro obbligatorio: il percorso di lettura. Il valore restituito è un array di file e di directory contenuti nel percorso. Proprio come l’ultima soluzione, per recuperare un sottoinsieme di file e directory, dovete fare un post-filtraggio. D’altra parte, come si può vedere, questa soluzione è più concisa e non c’è bisogno di gestire lo handle della connessione.
Questo esempio mostra come recuperare i file e le directory che iniziano con la stringa di “te“:
[code lang=”php”] <?php

$entries = scandir(".");
$filelist = array();
foreach($entries as $entry) {
if (strpos($entry, "te") === 0) {
$filelist[] = $entry;
}
}

?>
[/code]

Con gli iteratori SPL
Ora parliamo di alcuni iteratori SPL. Ma prima di andare in profondità sul loro uso, s’indica rapidamente cos’è la biblioteca SPL. L’SPL fornisce una serie di classi per le strutture dati object-oriented, iteratori, gestori di file, e altre funzionalità.
Uno dei vantaggi è che iteratori sono classi e quindi è possibile estenderle in base alle proprie esigenze. Un altro vantaggio è che hanno metodi nativi che sono veramente utili per realizzare molti compiti in un solo ambito. Prendete come esempio l’uso di FilesystemIterator e readdir(), entrambi saranno utilizzati in un ciclo, ma durante l’utilizzo readdir() la sua entrata non sarà altro che una stringa, utilizzando invece il FilesystemIterator si dispone di un oggetto che può fornire un sacco di informazioni su questo file o directory (dimensione, il proprietario, i permessi e così via).
Naturalmente, PHP è in grado di fornire le stesse informazioni utilizzando funzioni come filesize() e fileowner(), ma PHP5 ha trasformato il suo approccio alla programmazione orientata agli oggetti. Quindi, in conclusione, il consiglio che danno è quello di seguire le nuove pratiche del linguaggio. Nel caso abbiate bisogno di ulteriori informazioni di carattere generale su iteratori SPL, date un’occhiata all’SPL Iterators Class Tree.
Come scritto nell’introduzione, verrà illustrato l’uso di FilesystemIterator, RecursiveDirectoryIterator e GlobIterator. Il primo di essi eredita da DirectoryIterator mentre gli altri ereditano da FilesystemIterator. Tutti hanno lo stesso costruttore, che ha solo due parametri:

  • $path (obbligatorio): Il percorso del file system da iterare
  • $flags (opzionale): Uno o più flag elencati nella documentazione ufficiale.

Di cosa si differenzia realmente in questi iteratori, è l’approccio che si usa per navigare nel percorso indicato.

Il FilesystemIterator
Utilizzare il FilesystemIterator è abbastanza semplice. Per vederlo in azione, si mostrerà due esempi. Nel primo, si fa la ricerca di tutti i file e le directory che iniziano con la stringa “te”, mentre nel secondo si utilizza un altro iteratore (il RegexIterator), per cercare tutti i file e le directory che contiene alla fine “t.dat” o “t.php”. Il RegexIterator viene utilizzato per filtrare un altro iteratore sulla base di una espressione regolare.
[code lang=”php”] <?php

$iterator = new FilesystemIterator(".");
$filelist = array();
foreach($iterator as $entry) {
if (strpos($entry->getFilename(), "te") === 0) {
$filelist[] = $entry->getFilename();
}
}

?>
[/code]

Con il codice di sopra, il risultato è lo stesso dell’esempio precedente.
Il secondo esempio, che utilizza il RegexIterator, è il seguente:
[code lang=”php”] <?php

$iterator = new FilesystemIterator(".");
$filter = new RegexIterator($iterator, ‘/t\.(php|dat)$/’);
$filelist = array();
foreach($filter as $entry) {
$filelist[] = $entry->getFilename();
}

?>
[/code]

In questo caso l’uscita è:
[code lang=”cpp”] array (
0 => ‘script.php’,
1 => ‘test.dat’
)
[/code]
Il RecursiveDirectoryIterator
Il RecursiveDirectoryIterator fornisce un’interfaccia per l’iterazione sulle directory del filesystem ricorsivamente. Grazie al suo scopo, ha alcuni metodi utili come getChildren() e hasChildren(), che restituiscono un iteratore per la voce corrente, se si tratta di una directory o se la voce corrente è una directory. Per vedere sia RecursiveDirectoryIterator e getChildren() in azione, proviamo a riscrivere l’ultimo esempio per ottenere lo stesso risultato.
[code lang=”php”] <?php

$iterator = new RecursiveDirectoryIterator(‘.’);
$filter = new RegexIterator($iterator->getChildren(), ‘/t\.(php|dat)$/’);
$filelist = array();
foreach($filter as $entry) {
$filelist[] = $entry->getFilename();
}
?>
[/code]

Il GlobIterator
Il GlobIterator consente di scorrere il file system in modo simile alla funzione glob(). Così il primo parametro può includere caratteri jolly. Il codice seguente mostra il solito esempio con l’uso del GlobIterator.
[code lang=”php”] <?php

$iterator = new GlobIterator("te*");
$filelist = array();
foreach($iterator as $entry) {
$filelist[] = $entry->getFilename();
}

?>
[/code]