miércoles, 27 de febrero de 2008

Consultas sobre archivos de un directorio en PHP

Problema: necesidad de obtener los nombres de archivos de un directorio filtrados por alguna condición.
Solución: utilizar expresiones regulares para las condiciones sobre los nombres de archivos del directorio.

function getFileNames($path, $match = null, $groups = null)
{
if (is_dir($path))
{
$res = array();
$d = dir($path);

while (false !== ($entry = $d->read()))
{
if (is_file($path . "/" . $entry))
{
$matches = null;
if ($match)
{
if (preg_match($match, $entry, $matches))
{
if (!$groups) $res[] = $entry;
else
{
$gentry = "";
foreach($groups as $i)
{
$gentry .= $matches[$i];
}
$res[] = $gentry;
}
}
}
else // Si no paso match, le entrego derecho la entrada.
{
$res[] = $entry;
}
}
}
$d->close();
return $res;
}
else
{
throw new Exception("El directorio: $path no existe.");
}
}


Explico el código:

function getFileNames($path, $match = null, $groups = null)

path: directorio del cual se quieren leer los nombres de archivos
match: expresión regular con la cual se filtran los nombres de archivos (si no se pasa nada, se devuelven los nombres de todos los archivos del directorio)
groups: solo se usa si match no es null, y se usa para seleccionar los grupos de la expresión regular cuando coinciden los nombres de los archivos con match. Más adelante explicaré como es que se usa, pero la idea es poder sacar pedazos del nombre del archivo y devolver eso, por ejemplo si tenemos infomración codificada en el nombre como: paquete.Clase.class.php, y me interesa devolver solo el nombre de la clase, o sea "Clase". Si groups es null se devuelven los nombres de todos los archivos que coincidan con la expresión regular match.

if (is_dir($path))
Verifica si path es un directorio válido, si no lo es lanza una excepción.

$res = array();
Se crea la lista de nombres a devolver, todos los nombres de archivos que coincidan con los criterios serán incluidos en res.

$d = dir($path);
Se abre el directorio para comenzar a leer sus entradas. Para entender como trabaja dir() visita el manual.

while (false !== ($entry = $d->read()))
Recorre cada entrada del directorio, estas son archivos y subdirectorios, entry es el nombre del archivo o subdirectorio actual en la recorrida.

if ($match)
{
if (preg_match($match, $entry, $matches))
Si match no es null, verifico que entry coincida con él. Para entender como trabaja preg_match, visita el manual. En matches se guardan los grupos definidos en match que coincidieron con entry, y si groups no es null los voy a usar para armar los nombres que voy a devolver.

if (!$groups) $res[] = $entry;
else
{
$gentry = "";
foreach($groups as $i)
{
$gentry .= $matches[$i];
}
$res[] = $gentry;
}
Si groups es null, simplemente agrego entry a la solución, o sea, devuelvo el nombre del archivo así como está y se que ese nombre coincide con la expresión regular match.
Si groups no es null, busco en matches los grupos indicados por groups, concatenando dichos grupos, la concatenación de todos los grupos seleccionados es el nombre a incluir en la solución. De esta forma, si los archivos tienen nombres por ejemplo "paquetes.Clase.class.php", podría devolver "Clase.php", luego mostraré ejemplos de como se hace esto.

else // Si no paso match, le entrego derecho la entrada.
{
$res[] = $entry;
Si match es null, simplemente agrego a la solución el nombre del archivo sin procesarlo.

$d->close();
return $res;
Cierra el directorio y devuelve la solución.


Algunos ejemplos de uso:

Para entender que hacen necesitas entender las expresiones regulares usadas, si no sabes mucho de expresiones regulares te aconsejo que busques en google algún manual de expresiones regulares.

En estos casos usaremos como directorio "." que es el directorio actual. Puedes cambiarlo y utilizar otro directorio si lo deseas.

getFileNames("."); // Todos los archivos, no hay ninguna restricción.

getFileNames(".", "/\.php$/i"); // Todos los archivos con extensión ".php"

getFileNames(".", "/^utils\.(.*)\.php$/i"); // Todos los ".php" del paquete utils (pensado para nombres con la forma "paquete.Class.php", en este caso paquete es "utils"). Observar el "(.*)", eso es un grupo y se podría usar el parámetro groups para obtenerlo, como veremos en el siguiente ejemplo.

getFileNames(".", "/(.*)\.php$/i", array(1)) ); // Todos los ".php", pero sin el .php (ver que la regexp match tiene un grupo y en el array selecciono ese grupo). El grupo 0 (cero) coincide con el nombre del archivo.

getFileNames(".", "/^utils\.(.*)\.php$/i", array(1,5,0)) ); // Todos los php del paquete utils, idem anterior, ahora sin el "utils." Observar todo lo que se pasa en groups, ver que el grupo 5 no existe y por lo tanto no es considerado, luego si tengo un archivo "utils.Class.php", el resultado de esto es "Classutils.Class.php", porque concatena el grupo 1 ("Class") y el grupo 0 (que es el nombre del archivo), y ver que se respeta el orden establecido en groups.


En conclusión, no solo se logró hacer una función que resuelve el problema, si no que se obtuvo una solución que puede hacer un preproceso de los nombres a devolver (gracias a groups), lo que permite extraer información presente en los nombres de los archivos y concatenarla a gusto.

Siéntanse libres de utilizar este script, modificarlo a gusto, hacer comentarios y mejoras sobre él. Y si lo incluyen en algún proyecto no se olviden de hacer referencia a este blog y/o a este post.

No hay comentarios:

Publicar un comentario en la entrada