Metadatos de ficheros en PHP: file_exists, filesize, filemtime, is_file, is_dir

Antes de leer, escribir o procesar un fichero, conviene saber si existe, qué tipo es, cuánto ocupa y cuándo fue modificado. PHP tiene funciones específicas para cada uno de estos metadatos, más una trampa importante: el caché interno de stat que puede devolver información obsoleta.

file_exists(), is_file() e is_dir()

<?php
$ruta = '/var/www/app/uploads/imagen.jpg';

// file_exists() devuelve true tanto para ficheros como para directorios
var_dump(file_exists($ruta));      // true si existe (sea lo que sea)
var_dump(file_exists('/no-existe')); // false

// is_file() es true solo para ficheros regulares (no directorios, no enlaces simbólicos rotos)
var_dump(is_file($ruta));           // true si es un fichero regular

// is_dir() es true solo para directorios
var_dump(is_dir('/var/www/app/uploads')); // true
var_dump(is_dir($ruta));                  // false (es un fichero)

// is_link() para enlaces simbólicos
var_dump(is_link('/var/www/app/storage')); // true si es un symlink
?>

filesize() y filemtime()

<?php
$ruta = '/var/www/app/uploads/documento.pdf';

// Tamaño en bytes
$bytes = filesize($ruta);
echo number_format($bytes / 1024, 2) . ' KB';
echo number_format($bytes / 1024 / 1024, 2) . ' MB';

// Timestamp de última modificación
$mtime = filemtime($ruta);
echo date('d/m/Y H:i:s', $mtime);

// Timestamp de creación (en Linux = tiempo de cambio de inodo, no creación real)
$ctime = filectime($ruta);
echo date('d/m/Y H:i:s', $ctime);

// Timestamp de último acceso
$atime = fileatime($ruta);
echo date('d/m/Y H:i:s', $atime);
?>

is_readable() e is_writable()

<?php
$ruta = '/var/www/app/uploads';

// Antes de intentar escribir, verificar que el directorio tiene permisos
if (!is_dir($ruta)) {
    mkdir($ruta, 0755, true);
}

if (!is_writable($ruta)) {
    throw new RuntimeException("El directorio $ruta no tiene permisos de escritura");
}

// Antes de leer un fichero de configuración
$config = '/etc/app/config.json';
if (!is_readable($config)) {
    throw new RuntimeException("No se puede leer $config");
}
?>

stat(): todos los metadatos de una vez

stat() devuelve un array con 13 campos de metadatos, igual que la llamada al sistema stat():

<?php
$info = stat('/var/www/app/uploads/imagen.jpg');

echo $info['size'];  // tamaño en bytes (igual que filesize())
echo $info['mtime']; // timestamp de modificación (igual que filemtime())
echo $info['atime']; // timestamp de acceso
echo $info['ctime']; // timestamp de cambio de inodo
echo $info['uid'];   // ID del propietario
echo $info['gid'];   // ID del grupo
echo $info['mode'];  // permisos + tipo (octal)
echo $info['nlink']; // número de enlaces duros
echo $info['ino'];   // número de inodo
?>

Ejemplo real: validar subidas de ficheros

<?php
function validarSubida(array $fichero, int $maxBytes = 5_242_880): void
{
    if (!isset($fichero['tmp_name']) || !is_uploaded_file($fichero['tmp_name'])) {
        throw new InvalidArgumentException('Fichero no válido');
    }

    $tamano = filesize($fichero['tmp_name']);
    if ($tamano === false || $tamano > $maxBytes) {
        throw new InvalidArgumentException(
            'El fichero supera el límite de ' . number_format($maxBytes / 1024 / 1024, 1) . ' MB'
        );
    }

    $ext = strtolower(pathinfo($fichero['name'], PATHINFO_EXTENSION));
    $permitidas = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf'];
    if (!in_array($ext, $permitidas, true)) {
        throw new InvalidArgumentException("Extensión .$ext no permitida");
    }
}
?>

La trampa del caché de stat

PHP mantiene un caché interno de las llamadas a stat() para evitar llamadas repetidas al sistema de ficheros. Si modificas un fichero desde PHP y luego llamas a filesize() o filemtime(), puedes obtener los valores antiguos del caché:

<?php
$ruta = '/tmp/contador.txt';

file_put_contents($ruta, '100');
echo filesize($ruta); // 3 (correcto)

file_put_contents($ruta, '10000');
echo filesize($ruta); // 3 ? ¡caché! sigue devolviendo el valor anterior

// Solución: limpiar el caché explícitamente
clearstatcache(true, $ruta); // solo para esta ruta
// o clearstatcache() para limpiar todo el caché
echo filesize($ruta); // 5 ? correcto ahora
?>

Caché por tiempo de modificación

<?php
function necesitaActualizacion(string $cacheFichero, int $maxEdadSegundos = 3600): bool
{
    if (!file_exists($cacheFichero)) {
        return true;
    }
    clearstatcache(true, $cacheFichero);
    return (time() - filemtime($cacheFichero)) > $maxEdadSegundos;
}

if (necesitaActualizacion('/tmp/datos-cache.json')) {
    // regenerar caché
}
?>

La documentación de stat() y la de clearstatcache() detallan todos los campos disponibles y el comportamiento del caché con enlaces simbólicos.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP