0

PHP streams — stream wrappers, filters, context

Advanced5 min read·php-13-004

Concept

PHP provides functions to inspect file metadata — existence, size, modification time, type, and permissions. These are important for validation, caching strategies, and conditional file operations.

Existence and type checks:

  • file_exists(string $path): Returns true if path exists (file or directory).
  • is_file(string $path): True only for regular files (not directories or symlinks to directories).
  • is_dir(string $path): True for directories.
  • is_link(string $path): True for symbolic links.
  • is_readable(string $path) / is_writable() / is_executable(): Check permissions for the current process user.

File metadata:

  • filesize(string $path): File size in bytes. Returns int (or false). Note: on 32-bit PHP, files > 2GB need special handling.
  • filemtime(string $path): Last modification timestamp (Unix timestamp). Useful for cache invalidation.
  • fileatime(string $path): Last access time. Often disabled on modern filesystems for performance.
  • filectime(string $path): Inode change time (not creation time on POSIX).
  • stat(string $path): Returns an array of detailed file info: size, inode, owner UID/GID, permissions, atime, mtime, ctime, block count.
  • lstat(string $path): Like stat() but doesn't follow symlinks (returns symlink's own info).

clearstatcache(): PHP caches stat() results for performance. If you modify a file and then call filesize() or filemtime() on it, you might get stale values. Call clearstatcache() to force a fresh stat. Or pass clearstatcache(true, $path) to clear only a specific file.

Code Example

php
<?php
declare(strict_types=1);

// Check before acting
$path = '/var/www/uploads/photo.jpg';

if (!file_exists($path)) {
    throw new \RuntimeException("File not found: $path");
}
if (!is_readable($path)) {
    throw new \RuntimeException("File not readable: $path");
}

// File metadata
$size     = filesize($path);         // bytes: 204800
$modified = filemtime($path);        // Unix timestamp
$type     = mime_content_type($path); // "image/jpeg"

echo "Size: " . number_format($size / 1024, 1) . " KB\n";
echo "Modified: " . date('Y-m-d H:i:s', $modified) . "\n";
echo "Type: $type\n";

// Cache invalidation based on file modification time
function getCachedOrFresh(string $file, string $cacheKey, callable $generate): mixed
{
    $lastModified = filemtime($file);
    $cacheVersion = cache()->get("$cacheKey:version");

    if ($cacheVersion === $lastModified) {
        return cache()->get($cacheKey);
    }

    $data = $generate();
    cache()->put($cacheKey, $data);
    cache()->put("$cacheKey:version", $lastModified);
    return $data;
}

// Full stat array
$info = stat($path);
// [
//   'dev'     => device number,
//   'ino'     => inode number,
//   'mode'    => permissions (octal),
//   'nlink'   => number of hard links,
//   'uid'     => owner UID,
//   'gid'     => owner GID,
//   'size'    => size in bytes,
//   'atime'   => last access timestamp,
//   'mtime'   => last modification timestamp,
//   'ctime'   => inode change timestamp,
//   'blksize' => filesystem block size,
//   'blocks'  => number of 512-byte blocks,
// ]

echo "Owner UID: " . $info['uid'] . "\n";
echo "Permissions: " . decoct($info['mode'] & 0777) . "\n"; // e.g., "644"

// clearstatcache after modifying file
file_put_contents($path, "new content");
clearstatcache(true, $path);
echo filesize($path); // now returns updated size

// SplFileInfo — OOP stat wrapper
$info = new \SplFileInfo($path);
echo $info->getSize() . "\n";
echo $info->getMTime() . "\n";
echo $info->isReadable() ? "readable\n" : "not readable\n";
echo $info->getExtension() . "\n"; // "jpg"