It is my fervent wish that this file guide every AI coding agent working with code in this repository.
Any distilled, agent-facing documentation for this package - how it works
internally and the rationale behind key design decisions - lives in docs/.
Consult it before non-trivial changes; it is the source of truth from which the
public manual is distilled.
SafeStream is a lightweight PHP library providing thread-safe file operations through a custom stream wrapper protocol (nette.safe://). The library ensures that file reads and writes are isolated, preventing race conditions in concurrent PHP execution contexts.
Core mechanism: PHP stream wrapper that implements file locking (LOCK_SH for reads, LOCK_EX for writes) to guarantee atomicity of file operations.
SafeStream works transparently with all standard PHP file functions by prefixing paths with nette.safe://:
// Writing to a file
file_put_contents('nette.safe://file.txt', $content);
// Reading from a file
$content = file_get_contents('nette.safe://file.txt');
// Using file handles
$handle = fopen('nette.safe://file.txt', 'r');
fread($handle, 1024);
fclose($handle);
// Works with any PHP file function
$ini = parse_ini_file('nette.safe://config.ini');
unlink('nette.safe://file.txt');The problem SafeStream solves: Without isolation, concurrent file operations lead to race conditions. Example:
// Running this in multiple threads simultaneously will fail
$s = str_repeat('Long String', 10000);
while ($counter--) {
file_put_contents('file', $s); // Thread A writes
$read = file_get_contents('file'); // Thread B reads incomplete data
if ($s !== $read) { // Race condition occurs
echo 'Data corruption detected!';
}
}With nette.safe:// prefix, the above code becomes thread-safe: writers get exclusive locks, readers wait for writes to complete.
# Run all tests
composer run tester
# Run specific test file
vendor/bin/tester tests/SafeStream/SafeStream.basic.phpt -s
# Run tests in specific directory
vendor/bin/tester tests/SafeStream/ -s
# Run PHPStan static analysis
composer run phpstanThe library implements PHP's stream wrapper interface to intercept file operations:
- Protocol registration (
src/loader.php): Automatically registersnette.safe://protocol viaWrapper::register() - Wrapper class (
src/SafeStream/Wrapper.php): Implements stream wrapper methods (stream_open,stream_read,stream_write, etc.) - Backward compatibility (
src/SafeStream/SafeStream.php): Deprecated class for legacyNette\Utils\SafeStreamnamespace
The Wrapper class is marked @internal; the public API is the nette.safe:// protocol used through native PHP file functions.
File locking strategy:
- Read operations (plain
rmode only):LOCK_SH(shared lock) - multiple readers allowed - All other modes, including
r+:LOCK_EX(exclusive lock) - single writer, blocks all readers - Append mode tracking:
$startPosrecords file size before writing to enable rollback on errors (it stays 0 for all other modes)
Write error handling:
$writeErrorflag tracks failed or incomplete writes (e.g., disk full)- On close, truncates file back to
$startPosif write error detected - Ensures files are never left in partial/corrupt state
Race condition mitigation:
- For reads: if the file is empty, releases and re-acquires the lock up to 100 times (another thread may be mid-write); after that the open succeeds anyway and the empty file is read
- For writes using mode 'w': converts to mode 'c' (create without truncate) then manually truncates after acquiring lock
Thread-safety boundaries:
url_stat()method is explicitly NOT thread-safe (documented in code)unlink()on Windows has limitations when file handles are open
Uses Nette Tester with .phpt files:
SafeStream.basic.phpt: Basic read/write/delete operationsSafeStream.stress.phpt: Concurrent access simulation (@multiple 5annotation runs 5 processes in parallel)
Tests must use require __DIR__ . '/../bootstrap.php' for proper environment setup.
Supports PHP 8.0 through 8.5 (as declared in composer.json). GitHub Actions tests against all supported versions.
Nette\Utils\SafeStreamclass is deprecated; useNette\SafeStream\WrapperWrapper::PROTOCOLconstant deprecated in favor ofWrapper::Protocol