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

Safe Stream Laravel Package

nette/safe-stream

Nette SafeStream provides a stream wrapper for atomic file operations, ensuring files are never left partially written or corrupted. It supports file locking and safe concurrent access, making it ideal for writing configs, caches, and logs reliably in PHP applications.

Deep Wiki
Context7

Getting Started

  • Install with Composer:
    composer require nette/safe-stream
    
  • Register the wrapper once, as early as possible in the request lifecycle, before any safe:// operation:
    use Nette\Utils\SafeStream;
    
    SafeStream::register();
    
    In Laravel this belongs in AppServiceProvider::boot() or in bootstrap/app.php. Registration is process-wide; verify with:
    in_array('safe', stream_get_wrappers(), true);
    
  • First use case: atomic replacement of a small file such as a JSON config, cache entry, or application state file. Build the URL from an absolute path:
    $realPath = storage_path('app/settings.json');
    $url = 'safe://' . str_replace('\\', '/', $realPath); // safe:///var/www/... on Unix
    
    file_put_contents($url, $json);
    $contents = file_get_contents($url);
    
  • Where to look first:
    • src/SafeStream.php — the class is small; read it before relying on any stream function.
    • README.md and CHANGELOG.md — confirm supported PHP versions and recent behavioral changes.
    • The test suite — it documents which native functions are expected to work through the wrapper.
  • The mental model: SafeStream does not introduce a new API. It registers a safe:// stream wrapper so existing native functions (file_get_contents, file_put_contents, fopen, unlink, rename, etc.) operate on the underlying file with atomic write semantics.

Implementation Patterns

Register once in bootstrap, not per call. Calling SafeStream::register() repeatedly is harmless, but scattering it through the codebase makes failures harder to trace. Register it in one bootstrap location and treat safe:// as an application capability.

Wrap URL construction in a helper. The leading-slash behavior is easy to get wrong. Centralize it:

function safe_path(string $absolutePath): string
{
    if (!str_starts_with($absolutePath, '/') && !preg_match('#^[A-Za-z]:/#', $absolutePath)) {
        throw new InvalidArgumentException('Absolute path required');
    }

    return 'safe://' . str_replace('\\', '/', $absolutePath);
}

Use it for whole-file atomic replacement. The canonical pattern is write-then-rename under the hood; you do not need to implement that yourself:

final class AtomicJsonStore
{
    public function __construct(private string $path) {}

    public function read(): array
    {
        $raw = @file_get_contents($this->url());
        return $raw === false ? [] : json_decode($raw, true, flags: JSON_THROW_ON_ERROR);
    }

    public function write(array $data): void
    {
        $json = json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT);
        if (file_put_contents($this->url(), $json) === false) {
            throw new RuntimeException("Unable to write {$this->path}");
        }
    }

    private function url(): string
    {
        return safe_path($this->path);
    }
}

This is the highest-value pattern: configuration files, small state files, generated manifests, and cache metadata that must never be observed half-written.

Use fopen for large files. file_get_contents loads everything into memory. For larger payloads, stream through the wrapper:

$in = fopen(safe_path($source), 'rb');
$out = fopen($destination, 'wb');
stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);

Read and write through the wrapper consistently where correctness matters. Atomic rename means a plain reader usually sees either the old or the new complete file, which is a major benefit. However, if any writer bypasses the wrapper and writes directly, wrapper-based readers lose the guarantee. Standardize writers first, then readers.

Perform stat operations on the real path. Use file_exists, filesize, filemtime, is_dir, glob, and scandir against the real filesystem path, not the safe:// URL. The wrapper is intended for stream I/O, not as a full replacement for filesystem metadata APIs.

Integrate as a small infrastructure service. In Laravel, bind an AtomicFileStore or AtomicJsonStore in the container and inject it into the services that need it. This keeps safe:// string manipulation out of controllers and jobs, and gives you one place to add logging, permissions handling, or fallbacks.

Use it behind queue jobs and scheduled tasks. A common day-to-day use is writing the result of a scheduled command or queue job to a status file while another request reads it. Atomic replacement prevents readers from consuming truncated JSON or half-written CSV.

Test the behavior, not the wrapper. Write a test that writes a large payload and asserts the file is either absent or fully valid, then a test that readers never observe a partial JSON document. The wrapper is an implementation detail; your repository/store contract is what matters.

Gotchas and Tips

  • Absolute paths are required, and Unix paths need three slashes. 'safe://' . '/var/www/file' becomes safe:///var/www/file. 'safe://' . 'relative/file' resolves relative to the current working directory, which is fragile in long-running workers.
  • Windows drive paths can be surprising. Use forward slashes and preserve the drive letter: safe://C:/path/file. Avoid parse_url-style parsing of safe:// paths.
  • It is not a sandbox. SafeStream provides atomicity, not path confinement. User-supplied paths still need validation against traversal and against your allowed directories.
  • Read-modify-write is not atomic. Two concurrent processes can each read, modify, and rename; the last writer wins. If you need serialized updates, use a separate lock file with flock() around the read/modify/write sequence.
  • Do not rely on append semantics. Atomic replacement is the intended use case. FILE_APPEND, partial writes, and random-access updates are not the contract; use a lock or a real database for those.
  • Stat-family functions through safe:// are unreliable. file_exists, filesize, filemtime, realpath, glob, and scandir should use the real path. Stat caching and wrapper limitations can produce misleading results.
  • The target directory must be writable and on the same filesystem. Atomic rename requires the temporary file to be created alongside the target. mkdir the real directory first; the wrapper will not create missing directories.
  • Permissions and ownership can change. The replacement file is created with the process umask, so a file that was 0600 may become 0644. If permissions matter, chmod the destination after the write or manage umask deliberately.
  • Symlinks may be replaced. Atomic rename over a symlink path generally replaces the link itself rather than the file it points to. Resolve symlinks first if that behavior is undesirable.
  • Failed renames can surface late. The write may succeed while the final rename fails (for example, permissions or a Windows locking issue). Check warnings and, for critical files, verify by reading back or inspecting error_get_last().
  • Temp files can be left behind. If the process crashes between creating the temporary file and renaming it, a stray temp file may remain in the directory. Exclude such files from directory scans and clean them up periodically if the directory is watched.
  • Do not include or require safe:// paths. Opcache and __DIR__ behavior become confusing. Include the real path; use SafeStream for data files, not PHP code.
  • Global registration can collide. The safe protocol is process-wide; another library may register the same name. register() typically skips if already present, so verify which wrapper is active if behavior looks wrong.
  • No meaningful extension points. The class hardcodes the safe prefix and is not designed for subclassing or registering under another protocol name. If you need custom behavior, wrap it in your own service rather than extending the wrapper.
  • Avoid it for hot, high-frequency writes. Every atomic write creates a temporary file and renames it. For counters or per-request update files, use a database, Redis, or a dedicated append-only log.
  • Debugging checklist: confirm safe is in stream_get_wrappers(), confirm the real parent directory is writable, run the same operation against the real path to isolate wrapper issues, and inspect error_get_last() when a native function returns false.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource