FileSystemWatcher: наблюдение за файловой системой

Что такое FileSystemWatcher

Async\FileSystemWatcher — персистентный наблюдатель за изменениями в файлах и директориях. В отличие от one-shot подходов, FileSystemWatcher работает непрерывно и доставляет события через стандартную foreach-итерацию:

<?php
use Async\FileSystemWatcher;

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

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

Итерация автоматически приостанавливает корутину, когда буфер пуст, и возобновляет при поступлении нового события.

FileSystemEvent

Каждое событие — объект Async\FileSystemEvent с четырьмя readonly-свойствами:

Свойство Тип Описание
path string Путь, переданный в конструктор FileSystemWatcher
filename ?string Имя файла, вызвавшего событие (может быть null)
renamed bool true — файл создан, удалён или переименован
changed bool true — содержимое файла изменено

Два режима буферизации

Coalesce (по умолчанию)

В режиме coalesce события группируются по ключу path/filename. Если файл изменился несколько раз до того, как итератор его обработал — в буфере останется одно событие с объединёнными флагами:

<?php
use Async\FileSystemWatcher;

// coalesce: true — по умолчанию
$watcher = new FileSystemWatcher('/tmp/dir');
?>

Это оптимально для типичных сценариев: hot-reload, пересборка при изменении конфигов, синхронизация.

Raw

В raw-режиме каждое событие от ОС сохраняется как отдельный элемент в циклическом буфере:

<?php
use Async\FileSystemWatcher;

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

Подходит когда важен точный порядок и количество событий — аудит, логирование, репликация.

Конструктор

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

path — путь к файлу или директории. Если путь не существует — выбрасывается Error.

recursive — если true, отслеживаются также вложенные директории.

coalesce — режим буферизации: true — объединение событий (HashTable), false — все события (circular buffer).

Наблюдение начинается сразу при создании объекта. События буферизуются даже до начала итерации.

Жизненный цикл

close()

Останавливает наблюдение. Текущая итерация завершается после обработки оставшихся событий в буфере. Идемпотентен — повторный вызов безопасен.

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

isClosed()

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

Автоматическое закрытие

Если объект FileSystemWatcher уничтожается (выходит из scope), наблюдение автоматически останавливается.

Примеры

Hot-reload конфигурации

<?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 "Конфиг изменён: {$event->filename}\n";
            reloadConfig();
        }
    }
});
?>

Ограничение по времени

<?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 "Наблюдение завершено\n";
?>

Обработка нескольких директорий

<?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-режим для аудита

<?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}");
    }
});
?>

Отмена через scope

FileSystemWatcher корректно завершается при отмене scope:

<?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 "Итерация завершена\n";
    });

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

См. также