amphp/file
amphp/file provides non-blocking file access for PHP 8.1+ applications built with fibers and concurrency. Read, write, and stream files without blocking, using process, thread, or extension-based drivers for responsive I/O.
amphp/file answers a specific question: can we do file reads, writes, streaming, and metadata checks without blocking the event loop? If any planned feature touches disk inside a long-running worker (Laravel Octane, RoadRunner, Swoole, or a native Amp/Revolt service), this package is the reference implementation to evaluate against.ext-eio / ext-uv / ext-parallel plus a fallback process-pool implementation, and maintaining it. That is weeks of specialist work plus ongoing PHP-version upkeep. MIT-licensed, composer require-able, semver-managed, and abstracted behind a FilesystemDriver interface — the build case is weak.BlockingFilesystemDriver, ParallelFilesystemDriver, EioFilesystemDriver, UvFilesystemDriver, plus FileCache and status caching) lets us make one platform decision — "how does this service touch the disk?" — and apply it consistently, rather than every team hand-rolling file_get_contents inside fibers.File::lock() / tryLock() / unlock() additions plus FileMutex and KeyedFileMutex let us decide, at design time, whether we need cross-process file locking primitives or whether the database is the correct coordination layer. That is a product-visible decision (e.g., duplicate export jobs, racing cache warmers).Consider adoption when all or most of these hold:
amphp/byte-stream, amphp/http-server, Revolt). Here it is essentially free: fibers mean the API reads like the blocking one (Amp\File\read() returns a string, not a promise).File::lock, tryLock, FileMutex, KeyedFileMutex) for job deduplication or serialized writes.Look elsewhere when:
Storage:: calls are fine; adding an event loop to gain async file I/O is a net complexity increase with no user-visible benefit.Storage facade. The pragmatic pattern is Storage for abstraction, amphp/file only as a low-level driver for local disk inside async workers.File interface and driver constructors; anything implementing custom File classes or configuring ParallelFilesystemDriver worker counts breaks.Adoption gate: pilot it in exactly one async worker path with a measurable baseline (p95 latency, event-loop stall count, memory peak), and pin the major version.
For executives (one paragraph): We have a small, low-cost engineering decision to make about how our long-running PHP services touch the disk. Today, file reads and writes inside our worker processes block the process entirely, so one slow upload or large export stalls every other request that worker is handling. amphp/file is a mature, MIT-licensed, actively maintained library that makes those operations non-blocking with a near-identical API. It is not a platform bet — it is a drop-in for one specific failure mode, requires no paid license and no new infrastructure, and can be trialed in a single service before any broader commitment. The risk to manage is dependency concentration: it is a specialist library with a small maintainer base, so we should pin versions and keep the blast radius to the async services that actually benefit.
For engineering leads:
Amp\File\read($path) returns the file contents directly; the blocking-looking code is the async code. Onboarding cost is understanding the event loop, not learning a new API surface.ext-eio, ext-uv, or ext-parallel when installed. That means we can ship without changing our PHP build, and improve performance later as an ops decision rather than a code change.Storage/Flysystem stack is blocking and stays that way. The realistic pattern is: keep Storage as the abstraction for feature code and cloud disks; use amphp/file as the local-disk implementation only inside Octane/Amp worker paths where the event loop is already running. Do not introduce it into FPM request handlers.File implementations are a real upgrade cost; and misuse outside an event loop yields no benefit at all. Mitigations: pin the major, isolate usage behind our own thin adapter so the dependency stays swappable, and benchmark before/after rather than assuming.How can I help you explore Laravel packages today?