Filesystem
in package
Every disk access the bridge makes while the event loop is running, in one place, behind promises.
Why this exists
A blocking file_put_contents() stops the loop: for as long as it runs, no
Discord heartbeat is sent, no Telegram update is read, nothing is relayed.
Small as those pauses are here, they are real — this bot's own saves measure
around 2-3 ms on a Windows host — and they are exactly the kind of thing
that gets worse on the machine you did not test on.
So writes go through react/filesystem, which performs them off the loop when a native async backend is available.
What actually happens on each platform
react/filesystem picks a backend at runtime, and this matters more than it
looks:
- ext-eio (POSIX only) — genuinely asynchronous.
- ext-uv (POSIX and Windows;
php_uv.dllis published on PECL) — genuinely asynchronous, provided the process is running ReactPHP'sExtUvLoop, which it does automatically when the extension is loaded. - neither —
react/filesystemfalls back to an adapter that callsfile_get_contents()andfile_put_contents()and wraps the result in an already-resolved promise. It has the asynchronous shape and none of the asynchronous behaviour.
That last case is the common one on Windows, so this class does not pretend
otherwise. When no async backend is present it does the write itself,
blocking — and since it is going to block anyway, it blocks properly:
fflush() and fsync(), so a machine that loses power cannot leave a
zero-length file where the configuration was. putContents() cannot express
that, and trading durability for a promise that resolves just as late would
be a bad deal.
adapter() reports which of the three is in use, and the bot says so at startup, so nobody has to guess whether they are getting real async I/O.
What is not routed through here
rename(), mkdir() and is_file() are metadata operations measured in
microseconds, and react/filesystem offers no asynchronous equivalent of
the first two at all. They stay as direct calls, deliberately.
Tags
Table of Contents
Constants
- ADAPTER_BLOCKING : mixed = 'blocking'
- ADAPTER_EIO : mixed = 'ext-eio'
- ADAPTER_UV : mixed = 'ext-uv'
Properties
Methods
- adapter() : string
- Which backend is in use: `ext-eio`, `ext-uv`, or `blocking`.
- blocking() : self
- Durable, blocking I/O — what runs when no async backend is installed.
- create() : self
- Picks the best backend this machine offers.
- delete() : PromiseInterface<string|int, bool>
- Deletes a file. Resolves `true` when it is gone, including when it was never there.
- describe() : string
- One line for the startup log, including what to do about it.
- ensureDirectory() : bool
- Creates a directory and its parents if they are not there yet.
- isAsynchronous() : bool
- Whether disk access actually happens off the event loop.
- move() : bool
- Renames a file over another.
- read() : PromiseInterface<string|int, string|null>
- The contents of a file, or `null` when it is not there or cannot be read.
- readBlocking() : string|null
- Reads a file without involving the loop at all.
- with() : self
- A specific adapter, for tests.
- write() : PromiseInterface<string|int, bool>
- Writes a file, replacing whatever was there.
- writeDurably() : bool
- Writes a file and waits for the disk to acknowledge it.
- __construct() : mixed
Constants
ADAPTER_BLOCKING
public
mixed
ADAPTER_BLOCKING
= 'blocking'
ADAPTER_EIO
public
mixed
ADAPTER_EIO
= 'ext-eio'
ADAPTER_UV
public
mixed
ADAPTER_UV
= 'ext-uv'
Properties
$adapter read-only
private
AdapterInterface|null
$adapter
$name read-only
private
string
$name
Methods
adapter()
Which backend is in use: `ext-eio`, `ext-uv`, or `blocking`.
public
adapter() : string
Return values
stringblocking()
Durable, blocking I/O — what runs when no async backend is installed.
public
static blocking() : self
Return values
selfcreate()
Picks the best backend this machine offers.
public
static create() : self
react/filesystem's own factory is asked first, and its answer is only
kept when it is one of the two native backends: its fallback adapter is
synchronous, and this class has a better synchronous path of its own.
Return values
selfdelete()
Deletes a file. Resolves `true` when it is gone, including when it was never there.
public
delete(string $path) : PromiseInterface<string|int, bool>
Parameters
- $path : string
Return values
PromiseInterface<string|int, bool>describe()
One line for the startup log, including what to do about it.
public
describe() : string
Return values
stringensureDirectory()
Creates a directory and its parents if they are not there yet.
public
static ensureDirectory(string $path) : bool
Parameters
- $path : string
Return values
boolisAsynchronous()
Whether disk access actually happens off the event loop.
public
isAsynchronous() : bool
Return values
boolmove()
Renames a file over another.
public
static move(string $from, string $to) : bool
Stays a direct call on every backend: react/filesystem has no
asynchronous rename, and this is a metadata operation that does not move
bytes. On Windows PHP maps it to MoveFileEx with
MOVEFILE_REPLACE_EXISTING, so replacing an existing target works there
as it does on POSIX — which is what makes the atomic-save dance
portable.
Parameters
- $from : string
- $to : string
Return values
boolread()
The contents of a file, or `null` when it is not there or cannot be read.
public
read(string $path) : PromiseInterface<string|int, string|null>
The existence check is deliberate rather than left to the adapter: the
fallback adapter hands a missing path straight to file_get_contents(),
which emits a warning and resolves with false instead of rejecting.
Parameters
- $path : string
Return values
PromiseInterface<string|int, string|null>readBlocking()
Reads a file without involving the loop at all.
public
static readBlocking(string $path) : string|null
For the one moment when that is the right thing to do: loading
configuration during construction, before run() has been called and
before there is a loop to block.
Parameters
- $path : string
Return values
string|nullwith()
A specific adapter, for tests.
public
static with(AdapterInterface $adapter[, string $name = 'custom' ]) : self
React\Filesystem\Fallback\Adapter is a real AdapterInterface that
resolves immediately, which makes it an honest stand-in for the async
path on a machine that has no async extension.
Parameters
- $adapter : AdapterInterface
- $name : string = 'custom'
Return values
selfwrite()
Writes a file, replacing whatever was there.
public
write(string $path, string $contents) : PromiseInterface<string|int, bool>
Parameters
- $path : string
- $contents : string
Return values
PromiseInterface<string|int, bool> —whether the contents reached the disk
writeDurably()
Writes a file and waits for the disk to acknowledge it.
public
static writeDurably(string $path, string $contents) : bool
The fsync is the point: without it a rename can land while the content
is still in the page cache, so a machine that loses power comes back to
a zero-length file where the configuration used to be.
Parameters
- $path : string
- $contents : string
Return values
bool__construct()
private
__construct(AdapterInterface|null $adapter, string $name) : mixed
Parameters
- $adapter : AdapterInterface|null
- $name : string