FileSystemWatcher: Filesystem Monitoring

What is FileSystemWatcher

Async\FileSystemWatcher is a persistent observer for changes in files and directories. Unlike one-shot approaches, FileSystemWatcher runs continuously and delivers events through standard foreach iteration:

<?php
use Async\FileSystemWatcher;

$watcher = new FileSystemWatcher('/path/to/dir');

foreach ($watcher as $event) {
    echo "{$event->filename}: renamed={$event->renamed}, changed={$event->changed}\n";
}
?>

Iteration automatically suspends the coroutine when the buffer is empty and resumes it when a new event arrives.

FileSystemEvent

Each event is an Async\FileSystemEvent object with four readonly properties:

Property Type Description
path string The path passed to the FileSystemWatcher constructor
filename ?string The name of the file that triggered the event (may be null)
renamed bool true – file was created, deleted, or renamed
changed bool true – file contents were modified

Two Buffering Modes

Coalesce (Default)

In coalesce mode, events are grouped by the path/filename key. If a file changed multiple times before the iterator processed it, only one event with merged flags remains in the buffer:

<?php
use Async\FileSystemWatcher;

// coalesce: true -- default
$watcher = new FileSystemWatcher('/tmp/dir');
?>

This is optimal for typical scenarios: hot-reload, config change rebuilds, synchronization.

Raw

In raw mode, each event from the OS is stored as a separate element in a circular buffer:

<?php
use Async\FileSystemWatcher;

$watcher = new FileSystemWatcher('/tmp/dir', coalesce: false);
?>

Suitable when exact order and count of events matters – auditing, logging, replication.

Constructor

new FileSystemWatcher(
    string $path,
    bool $recursive = false,
    bool $coalesce = true
)

path – path to a file or directory. If the path doesn’t exist, an Error is thrown.

recursive – if true, nested directories are also monitored.

coalesce – buffering mode: true – event merging (HashTable), false – all events (circular buffer).

Monitoring starts immediately upon object creation. Events are buffered even before iteration begins.

Lifecycle

close()

Stops monitoring. The current iteration finishes after processing remaining events in the buffer. Idempotent – repeated calls are safe.

<?php
$watcher->close();
?>

isClosed()

<?php
$watcher->isClosed(); // bool
?>

Automatic Closing

If the FileSystemWatcher object is destroyed (goes out of scope), monitoring automatically stops.

Examples

Hot-Reload Configuration

<?php
use Async\FileSystemWatcher;
use function Async\spawn;

spawn(function() {
    $watcher = new FileSystemWatcher('/etc/myapp', recursive: true);

    foreach ($watcher as $event) {
        if (str_ends_with($event->filename ?? '', '.yml')) {
            echo "Config changed: {$event->filename}\n";
            reloadConfig();
        }
    }
});
?>

Time-Limited Monitoring

<?php
use Async\FileSystemWatcher;
use function Async\spawn;
use function Async\delay;

$watcher = new FileSystemWatcher('/tmp/uploads');

spawn(function() use ($watcher) {
    delay(30_000);
    $watcher->close();
});

foreach ($watcher as $event) {
    processUpload($event->filename);
}

echo "Monitoring finished\n";
?>

Monitoring Multiple Directories

<?php
use Async\FileSystemWatcher;
use function Async\spawn;

$dirs = ['/var/log/app', '/var/log/nginx', '/var/log/postgres'];

foreach ($dirs as $dir) {
    spawn(function() use ($dir) {
        $watcher = new FileSystemWatcher($dir);

        foreach ($watcher as $event) {
            echo "[{$dir}] {$event->filename}\n";
        }
    });
}
?>

Raw Mode for Auditing

<?php
use Async\FileSystemWatcher;
use function Async\spawn;

spawn(function() {
    $watcher = new FileSystemWatcher('/secure/data', coalesce: false);

    foreach ($watcher as $event) {
        $type = $event->renamed ? 'RENAME' : 'CHANGE';
        auditLog("[{$type}] {$event->path}/{$event->filename}");
    }
});
?>

Cancellation via Scope

FileSystemWatcher terminates correctly when the scope is cancelled:

<?php
use Async\FileSystemWatcher;
use function Async\spawn;
use function Async\delay;

spawn(function() {
    $watcher = new FileSystemWatcher('/tmp/test');

    spawn(function() use ($watcher) {
        foreach ($watcher as $event) {
            echo "{$event->filename}\n";
        }
        echo "Iteration finished\n";
    });

    delay(5000);
    $watcher->close();
});
?>

See Also