Introduction
Filesystem
Installation
For detailed installation instructions, see the installation page.
Flow Filesystem is a unified solution to store and retrieve data at remote and local filesystems. What differentiates Flow Filesystem from other libraries is the ability to store data in Blocks and read it by byte ranges.
This means, that while writing data to a large remote file, instead we can literally stream the data and based on the implementation of the filesystem, it will be saved in blocks.
When reading, instead of iterating through the whole file to find the data you need, you can directly access the data you need by specifying the byte range.
Available Filesystems
- Native Local Filesystem
- Memory Filesystem
- StdOut Filesystem
- Azure Blob Filesystem -
flow-php/filesystem-azure-bridge - AWS S3 Filesystem -
flow-php/filesystem-async-aws-bridge
Building Blocks
SourceStream- source streams interface represents readonly data streams
<?php
SourceStream::content() : string;
SourceStream::iterate(int $length = 1) : \Generator;
SourceStream::read(int $length, int $offset) : string;
SourceStream::readLines(string $separator = "\n", ?int $length = null) : \Generator;
SourceStream::size() : ?int;
DestinationStream- destination streams interface represents writable data streams
DestinationStream::append(string $data) : self;
DestinationStream::fromResource($resource) : self;
Mount- a value object identifying a filesystem mount by its URI protocol (file,memory,aws-s3,warehouse, …)
<?php
final readonly class Mount
{
public string $protocol;
public function __construct(string $protocol); // validates the protocol against PROTOCOL_REGEX
public function supports(Path|string $path) : bool; // true when $path's URI protocol matches
}
Filesystem- filesystem interface represents a remote/local filesystem
<?php
Filesystem::appendTo(Path $path) : DestinationStream;
Filesystem::getSystemTmpDir() : Path;
Filesystem::list(Path $path, Filter $pathFilter = new KeepAll()) : \Generator; // yields FileStatus
Filesystem::mount() : Mount;
Filesystem::mv(Path $from, Path $to) : bool;
Filesystem::readFrom(Path $path) : SourceStream;
Filesystem::rm(Path $path) : bool;
Filesystem::status(Path $path) : ?FileStatus;
Filesystem::writeTo(Path $path) : DestinationStream;
FileStatus- metadata returned fromstatus()/list()
<?php
final readonly class FileStatus
{
public Path $path;
public ?int $size; // bytes, null for directories
public ?\DateTimeImmutable $lastModifiedAt; // populated when the backend exposes it
public function isFile() : bool;
public function isDirectory() : bool;
}
size and lastModifiedAt come from the backend's list/head response for free — no extra stream is
opened, no extra HTTP call made. They're null when the backend can't provide them (e.g. StdOutFilesystem).
-
Path::protocol() : string— returns the raw URI scheme ('file','aws-s3','warehouse'). -
FilesystemTable- a registry of filesystems keyed by mount protocol
<?php
FilesystemTable::for(Path|string $protocol) : Filesystem;
FilesystemTable::mount(Filesystem $filesystem) : void;
FilesystemTable::unmount(Filesystem $filesystem) : void;
Every mount must have a unique protocol. Two mounts of the same protocol throw
InvalidArgumentException. A filesystem can be mounted under any protocol you choose — e.g. two S3
buckets mounted under warehouse and archive — and resolved at runtime via
$table->for('warehouse') or $table->for($path).
Usage
<?php
use function Flow\Azure\SDK\DSL\azure_blob_service;
use function Flow\Azure\SDK\DSL\azure_blob_service_config;
use function Flow\Azure\SDK\DSL\azure_shared_key_authorization_factory;
use function Flow\Filesystem\Bridge\Azure\DSL\azure_filesystem;
use function Flow\Filesystem\Bridge\Azure\DSL\azure_filesystem_options;
use function Flow\Filesystem\DSL\fstab;
use function Flow\Filesystem\DSL\path;
$fstab = fstab(
azure_filesystem(
azure_blob_service(
azure_blob_service_config($account, $container),
azure_shared_key_authorization_factory($account, $accountKey),
),
azure_filesystem_options()
)
);
$stream = $fstab->for('azure-blob')->writeTo(path('azure-blob://orders.csv'));
$stream->append('id,name,active');
$stream->append('1,norbert,true');
$stream->append('2,john,true');
$stream->append('3,jane,true');
$stream->close();
Cross-filesystem copy & move
FilesystemTable coupled with the Copy / Move operations lets you copy or move files between any
two mounted filesystems. Same-filesystem moves use the backend's native mv (local rename, S3
CopyObject + DeleteObject, Azure CopyBlob + DeleteBlob). Cross-filesystem moves stream bytes from
source to destination in chunks and remove the source afterwards — not atomic: if the source
removal fails after a successful write, the destination is present and the source remains; re-running
is idempotent.
<?php
use function Flow\Filesystem\DSL\{file_copy, file_move, fstab, memory_filesystem, native_local_filesystem, operation_options, path};
$table = fstab(memory_filesystem(), native_local_filesystem());
// Seed a memory file
$table->for('memory')->writeTo(path('memory://hello.txt'))->append('hello')->close();
// Cross-filesystem copy: memory → local
file_copy($table)->execute(path('memory://hello.txt'), path('/tmp/hello.txt'));
// Cross-filesystem move with custom chunk size
file_move($table, operation_options(chunkSize: 64 * 1024))
->execute(path('memory://hello.txt'), path('/tmp/moved.txt'));
operation_options() accepts a single chunkSize (default 8192) that controls the byte-chunk
size for cross-filesystem streaming copies.
Size formatting
Flow\Filesystem\SizeUnits::humanReadable() formats byte counts using binary units
(B, KiB, MiB, GiB, TiB, PiB). It mirrors PHP's number_format signature for the fractional part:
<?php
use Flow\Filesystem\SizeUnits;
SizeUnits::humanReadable(0); // "0 B"
SizeUnits::humanReadable(1023); // "1,023 B"
SizeUnits::humanReadable(1536); // "1.50 KiB"
SizeUnits::humanReadable(1_048_576); // "1.00 MiB"
SizeUnits::humanReadable(null); // "-"
// Custom formatting
SizeUnits::humanReadable(1536, decimals: 0); // "2 KiB"
SizeUnits::humanReadable(1536, decimalSeparator: ','); // "1,50 KiB"
SizeUnits::humanReadable(null, null: 'n/a'); // "n/a"
Telemetry
Flow Filesystem supports OpenTelemetry-compatible tracing and metrics for observability of all filesystem operations. Flow Filesystem uses Flow Telemetry library.
In order to use telemetry, you need to create an instance of TraceableFilesystem which
wraps an existing filesystem and adds telemetry to it.
Alternatively you can pass FilesystemTelemetryConfig to FilesystemTable and let it
automatically wrap all mounted filesystems with telemetry.
DSL Functions
filesystem_telemetry_options()- configure what to trace and measurefilesystem_telemetry_config()- create telemetry configuration from optionstraceable_filesystem()- wrap an individual filesystem with telemetry
Configuration Options
| Option | Default | Description |
|---|---|---|
traceStreams |
true |
Create spans for stream lifecycle (open to close) |
collectMetrics |
true |
Collect bytes and operation counters |
What Gets Traced
Spans:
SourceStream- spans the lifecycle of a read stream from creation to closeDestinationStream- spans the lifecycle of a write stream from creation to close
Metrics:
filesystem.source.bytes_read- total bytes read from source streamsfilesystem.source.operations- number of read operationsfilesystem.destination.bytes_written- total bytes written to destination streamsfilesystem.destination.operations- number of write operations
Metadata operations (list, status, rm, mv) are logged but do not create spans.
Examples
Wrap an individual filesystem:
<?php
use function Flow\Filesystem\DSL\{
filesystem_telemetry_config,
filesystem_telemetry_options,
native_local_filesystem,
path,
traceable_filesystem
};
use function Flow\Telemetry\DSL\telemetry;
use Psr\Clock\ClockInterface;
$telemetry = telemetry(/* your configuration */);
$clock = new class implements ClockInterface {
public function now(): \DateTimeImmutable {
return new \DateTimeImmutable();
}
};
$config = filesystem_telemetry_config(
$telemetry,
$clock,
filesystem_telemetry_options(traceStreams: true, collectMetrics: true)
);
$fs = traceable_filesystem(native_local_filesystem(), $config);
// All operations on $fs are now traced
$stream = $fs->readFrom(path('/path/to/file.csv'));
Enable telemetry on FilesystemTable:
<?php
use function Flow\Filesystem\DSL\{
filesystem_telemetry_config,
filesystem_telemetry_options,
fstab
};
$config = filesystem_telemetry_config($telemetry, $clock);
$fstab = fstab();
$fstab->withTelemetry($config);
// All filesystems in the table are now wrapped with telemetry
Disable specific features:
<?php
use function Flow\Filesystem\DSL\filesystem_telemetry_options;
// Collect metrics only, no spans
$options = filesystem_telemetry_options(
traceStreams: false,
collectMetrics: true
);
// Trace streams only, no metrics
$options = filesystem_telemetry_options(
traceStreams: true,
collectMetrics: false
);