Filesystem

composer require php-standard-library/filesystem

The Filesystem component provides type-safe functions for common file system operations. It replaces PHP's procedural file functions with proper error handling through exceptions, clear parameter types, and consistent behavior.

Unlike the File component (which deals with reading and writing file contents through handles), Filesystem focuses on managing files and directories themselves: checking existence, creating, copying, deleting, and inspecting metadata.

Checking Existence and Type

use Psl\Filesystem;
use Psl\IO;

// Create temp directory and file for demonstration
$dir = sys_get_temp_dir() . '/psl-fs-check-' . uniqid();
Filesystem\create_directory($dir);
$file = $dir . '/test.txt';
Filesystem\create_file($file);
$link = $dir . '/test-link';
Filesystem\create_symbolic_link($file, $link);

IO\write_line('exists(file):       %s', Filesystem\exists($file) ? 'true' : 'false');
IO\write_line('is_file(file):      %s', Filesystem\is_file($file) ? 'true' : 'false');
IO\write_line('is_directory(dir):  %s', Filesystem\is_directory($dir) ? 'true' : 'false');
IO\write_line('is_symbolic_link:   %s', Filesystem\is_symbolic_link($link) ? 'true' : 'false');
IO\write_line('is_readable(file):  %s', Filesystem\is_readable($file) ? 'true' : 'false');
IO\write_line('is_writable(file):  %s', Filesystem\is_writable($file) ? 'true' : 'false');

Filesystem\delete_file($link);
Filesystem\delete_file($file);
Filesystem\delete_directory($dir);

Creating Files and Directories

use Psl\Filesystem;
use Psl\IO;

$base = sys_get_temp_dir() . '/psl-fs-create-' . uniqid();

// Create a file (parent directories are created automatically)
$file = $base . '/nested/file.txt';
Filesystem\create_file($file);
IO\write_line('File created: %s', Filesystem\is_file($file) ? 'yes' : 'no');

// Create a directory (recursive by default)
$dir = $base . '/another/nested/dir';
Filesystem\create_directory($dir);
Filesystem\create_directory($dir, 0o755);
IO\write_line('Directory created: %s', Filesystem\is_directory($dir) ? 'yes' : 'no');

// Create a temporary file
$tmp = Filesystem\create_temporary_file();
IO\write_line('Temp file: %s', $tmp);

$tmp2 = Filesystem\create_temporary_file($base, prefix: 'app_');
IO\write_line('Prefixed temp file: %s', Filesystem\get_basename($tmp2));

// Clean up
Filesystem\delete_file($tmp);
Filesystem\delete_file($tmp2);
Filesystem\delete_directory($base, recursive: true);

Copying and Deleting

use Psl\File;
use Psl\Filesystem;
use Psl\IO;

$base = sys_get_temp_dir() . '/psl-fs-copy-' . uniqid();
Filesystem\create_directory($base);

$src = $base . '/source.txt';
$dst = $base . '/dest.txt';
File\write($src, 'hello world');

// Copy a file (preserves executable bits)
Filesystem\copy($src, $dst);
IO\write_line('Copied: %s', File\read($dst));

// Copy with overwrite
File\write($src, 'updated content');
Filesystem\copy($src, $dst, overwrite: true);
IO\write_line('Overwritten: %s', File\read($dst));

// Delete a file
Filesystem\delete_file($dst);
IO\write_line('File deleted: %s', !Filesystem\exists($dst) ? 'yes' : 'no');

// Delete a directory (empty, or recursive)
$subDir = $base . '/subdir';
Filesystem\create_directory($subDir);
Filesystem\delete_directory($subDir);

// Delete a directory with contents recursively
Filesystem\delete_directory($base, recursive: true);
IO\write_line('Directory deleted: %s', !Filesystem\exists($base) ? 'yes' : 'no');

Metadata and Permissions

use Psl\File;
use Psl\Filesystem;
use Psl\IO;

$tmp = Filesystem\create_temporary_file(prefix: 'psl_meta_');
File\write($tmp, 'some content here');

$bytes = Filesystem\file_size($tmp);
$mtime = Filesystem\get_modification_time($tmp);
$perms = Filesystem\get_permissions($tmp);
$owner = Filesystem\get_owner($tmp);

IO\write_line('Size: %d bytes', $bytes);
IO\write_line('Modified: %s', date('Y-m-d H:i:s', $mtime));
IO\write_line('Permissions: %o', $perms);
IO\write_line('Owner UID: %d', $owner);

Filesystem\change_permissions($tmp, 0o644);
IO\write_line('New permissions: %o', Filesystem\get_permissions($tmp));

Filesystem\delete_file($tmp);

Symbolic Links

use Psl\Filesystem;
use Psl\IO;

$base = sys_get_temp_dir() . '/psl-fs-links-' . uniqid();
Filesystem\create_directory($base);

$target = $base . '/target.txt';
$symlink = $base . '/symlink.txt';
$hardlink = $base . '/hardlink.txt';

Filesystem\create_file($target);

Filesystem\create_symbolic_link($target, $symlink);
Filesystem\create_hard_link($target, $hardlink);

$resolved = Filesystem\read_symbolic_link($symlink);
IO\write_line('Symlink points to: %s', $resolved);
IO\write_line('Is symbolic link: %s', Filesystem\is_symbolic_link($symlink) ? 'true' : 'false');
IO\write_line('Hard link exists: %s', Filesystem\is_file($hardlink) ? 'true' : 'false');

Filesystem\delete_directory($base, recursive: true);

Path Operations

use Psl\Filesystem;
use Psl\IO;

$base = sys_get_temp_dir() . '/psl-fs-paths-' . uniqid();
Filesystem\create_directory($base . '/subdir', 0o755);
$file = $base . '/subdir/file.txt';
Filesystem\create_file($file);

// Resolve to absolute path (returns null if path does not exist)
$real = Filesystem\canonicalize($base . '/subdir/../subdir/file.txt');
IO\write_line('Canonical: %s', $real ?? 'null');

IO\write_line('Basename: %s', Filesystem\get_basename($file)); // "file.txt"
IO\write_line('Without ext: %s', Filesystem\get_basename($file, '.txt')); // "file"
IO\write_line('Directory: %s', Filesystem\get_directory($file));
IO\write_line('Extension: %s', Filesystem\get_extension($file) ?? 'null'); // "txt"

$noExt = $base . '/subdir/Makefile';
Filesystem\create_file($noExt);
IO\write_line('No extension: %s', Filesystem\get_extension($noExt) ?? 'null'); // null

Filesystem\delete_directory($base, recursive: true);

Reading Directory Contents

use Psl\Filesystem;
use Psl\IO;

$dir = sys_get_temp_dir() . '/psl-fs-readdir-' . uniqid();
Filesystem\create_directory($dir);
Filesystem\create_file($dir . '/alpha.txt');
Filesystem\create_file($dir . '/beta.txt');
Filesystem\create_directory($dir . '/subdir');

$entries = Filesystem\read_directory($dir);
// Returns a list of absolute path strings (excludes . and ..)
foreach ($entries as $entry) {
    IO\write_line('  %s', Filesystem\get_basename($entry));
}

Filesystem\delete_directory($dir, recursive: true);

Error Handling

All operations throw specific exceptions on failure:

use Psl\Filesystem;
use Psl\IO;

$missing = sys_get_temp_dir() . '/psl-fs-missing-' . uniqid() . '.txt';

try {
    Filesystem\delete_file($missing);
} catch (Filesystem\Exception\NotFoundException) {
    IO\write_line('Caught NotFoundException: file does not exist');
} catch (Filesystem\Exception\NotFileException) {
    IO\write_line('Caught NotFileException: path exists but is not a file');
}

See src/Psl/Filesystem/ for the full API.