Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Resource Watcher Laravel Package

yosymfony/resource-watcher

A PHP library for watching files and directories for changes. It detects file creations, modifications, and deletions using inotify, polling, or hashing strategies, with a simple event-based API for building file monitors, hot-reload tools, and build watchers.

Deep Wiki
Context7

Getting Started

Install the package and the two Symfony components it builds on:

composer require yosymfony/resource-watcher symfony/finder symfony/event-dispatcher

The mental model: a Finder decides what to watch, a ResourceCache remembers the last-seen checksums, and a ResourceWatcher diffs the current filesystem against that cache on every call to findChanges().

use Symfony\Component\Finder\Finder;
use Yosymfony\ResourceWatcher\ResourceFinder;
use Yosymfony\ResourceWatcher\ResourceWatcher;
use Yosymfony\ResourceWatcher\ResourceCache\ResourceCacheArray;

$finder = new Finder();
$finder->files()->in(__DIR__.'/src')->name('*.php');

$watcher = new ResourceWatcher(new ResourceFinder($finder), new ResourceCacheArray());
$watcher->initialize();          // seed baseline hashes — no events yet

while (true) {
    $changes = $watcher->findChanges();

    foreach ($changes->getCreated() as $path) { echo "created: $path\n"; }
    foreach ($changes->getUpdated() as $path) { echo "updated: $path\n"; }
    foreach ($changes->getDeleted() as $path) { echo "deleted: $path\n"; }

    sleep(1);
}

First use case to try: a bin/watch script that just prints the three lists. It is the fastest way to confirm your Finder scope and your cache choice are right before wiring any real action onto the events. Read ResourceWatcher first, then ResourceFinder and the ResourceCache\* classes — those three files are the whole library.

Implementation Patterns

Pick the cache based on process lifetime. ResourceCacheArray is per-process only — good for tests and one-shot scripts. For a CLI daemon you want persistence across restarts so a reboot doesn't re-announce the whole tree: the JSON-backed cache for a single watcher, or the Symfony-cache-backed implementation when you already have a CacheInterface pool in the app.

Wrap the loop in a console command. Don't put the loop in a controller. A command gives you --interval, --once, verbosity, and a clean place to trap signals:

protected function execute(InputInterface $in, OutputInterface $out): int
{
    $this->watcher->initialize();
    $running = true;
    pcntl_signal(SIGINT, function () use (&$running) { $running = false; });

    while ($running) {
        pcntl_signal_dispatch();
        $changes = $this->watcher->findChanges();
        $this->dispatchChanges($changes);
        usleep($this->intervalMs * 1000);
    }
    return Command::SUCCESS;
}

Turn results into events yourself. ResourceWatcherResult is just three path lists. Map them to your own domain events (SourceFileChanged, TemplateDeleted, …) and push those through Symfony's EventDispatcher. This decouples detection from reaction, makes listeners unit-testable, and keeps you independent of whatever the package's own dispatcher wiring looks like (see Gotchas). Keep listeners idempotent and cheap — they run on every triggering poll.

Scope the Finder aggressively. Finder walks the tree on every findChanges(), so the scan is the dominant cost. ->files()->name('*.php')->exclude('vendor')->exclude('var')->notPath('node_modules') is not optional polish; it is the difference between a 20 ms poll and a 2 s poll. Because Finder is re-evaluated each time, files that start matching the pattern later are picked up as "created" automatically.

Use it as a trigger, not a build system. The common integration is "on change, shell out": re-run the test suite, kick npm run build, invalidate an opcode/DI cache, or push a reload signal to a browser over SSE/WebSocket. Debounce with a short sleep after firing so a burst of saves produces one build, not five.

Testing pattern. Use ResourceCacheArray, write files into a sys_get_temp_dir() fixture, call initialize(), mutate the fixture, then assert on findChanges(). Call findChanges() a second time with no mutation and assert all three lists are empty — that single assertion catches most cache misuse.

Gotchas and Tips

  • It is polling-only. There is no inotify/FSEvents backend. Worst-case detection latency is your sleep plus a full Finder scan. On large trees, prefer a 1–2 s interval and a tight Finder scope over a 100 ms interval.
  • Checksums, not mtimes. Rewriting a file with byte-identical content produces no update. touch alone does nothing. If you need mtime semantics, that's the hook for a custom hasher (see below).
  • Big files are re-hashed every poll. A generated bundle or a 50 MB fixture will quietly dominate your loop time. Exclude them explicitly.
  • initialize() is a policy decision. Call it on first start and you get "no events until something actually changes." Skip it against an empty cache and every existing file arrives as created — occasionally what you want (initial build), usually a surprise.
  • Re-initialize when you change the Finder scope. Add a new directory or pattern and the cache still holds entries from the old snapshot; you'll get phantom deletions or misses. Bump/clear the cache alongside the config change.
  • Editors write via temp-file + rename. Expect a flurry of created/deleted/updated for a single save. Coalesce by path (or by directory) before firing expensive work.
  • Guard the loop against listener exceptions. One uncaught exception in a listener kills the daemon. Wrap the dispatch, log, and continue.
  • Ordering is not guaranteed across the three categories. If downstream work depends on order, sort the paths before handing them off.
  • Deleted paths linger in the cache. Long-running watchers accumulate entries for files that no longer exist; a periodic initialize() on a quiet moment, or a cache implementation that prunes, keeps memory honest.
  • Path representation matters. Work with getRealPath() consistently — mixing SplFileInfo objects, relative paths, and absolute strings is the usual cause of "the listener never matches."
  • Version pinning is real. Last release was mid-2020. Check that symfony/finder and symfony/event-dispatcher constraints resolve on your PHP version before committing; if they don't, pin the compatible Symfony major or fork. Lock the versions in composer.json.
  • Extension points worth knowing: implement ResourceCacheInterface for Redis/DB-backed persistence across machines, and supply your own hasher (mtime + size) to trade correctness for speed on huge trees. Verify the exact interface signatures against the installed source before implementing — the library is small enough that reading src/ is faster than trusting any tutorial, including this one.
  • EventDispatcher wiring: the package advertises Symfony EventDispatcher integration, but the exact class and event-name constants have shifted across versions. Rather than depend on them, dispatching your own events from findChanges() results is two extra lines and immune to that churn.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle