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

Local File Laravel Package

michaeldrennen/local-file

A lightweight Laravel package for working with files on the local filesystem. Read, write, and manage local files through a simple, expressive API that keeps file handling clean and consistent in your PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're adopting first. michaeldrennen/local-file is a plain-PHP utility library (MIT, ~3 stars, last tagged release March 2021, zero known dependents). It is not a Laravel package in the usual sense: expect no service provider, no auto-discovery, no facade, no config file to publish. It is a thin wrapper over fopen/file_put_contents/unlink-style operations. Before writing a single line against it, read the single source class end-to-end — at this size the entire API surface fits on one screen, and that read is faster than guessing method names.

Install:

composer require michaeldrennen/local-file

Then confirm what you actually got:

# What namespace, what methods, what PHP constraint?
composer show michaeldrennen/local-file
cat vendor/michaeldrennen/local-file/composer.json
ls vendor/michaeldrennen/local-file/src/

Where to look first: the constructor signature (does it take a path, or is the path per-call?) and the exception behaviour (does it throw, return false, or silently warn?). Everything else in your integration follows from those two answers.

First use case — the one that justifies the dependency: a one-off CLI/artisan command or importer that needs to read a CSV, write a temp file, and clean up. Something small, local, and not worth a full service class:

use MichaelDrennen\LocalFile\...; // verify exact FQCN from src/

$file = new LocalFile('/path/to/import.csv'); // verify ctor shape
if ($file->exists()) {
    $contents = $file->read(); // verify method name/return
}

The pragmatic first decision: if your use case is inside a Laravel app, ask whether Illuminate\Support\Facades\File or Storage::disk('local') already covers it. They almost certainly do, and they are maintained. Adopt this package only where its specific convenience methods save you real code, and keep it behind your own boundary so that trade-off stays reversible.

Implementation Patterns

Bind it once, don't scatter new

Because there's no service provider, register the binding yourself in a FilesystemServiceProvider or AppServiceProvider:

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->bind(LocalFileFactory::class, function () {
        return new LocalFileFactory(); // or whatever the entry point is
    });
}

Then inject your own wrapper, never the vendor class, into application code. This is the single highest-value pattern here: a 3-star, 2021-era dependency should be one use statement away from removal.

Wrap it in a domain-facing service

namespace App\Support;

use MichaelDrennen\LocalFile\...; // the vendor class

class LocalFileStore
{
    public function put(string $absolutePath, string $contents): void
    {
        // normalise, then delegate
        $this->guardPath($absolutePath);
        // ... vendor call, verified against src/ ...
    }

    public function get(string $absolutePath): string
    {
        $this->guardPath($absolutePath);
        // ... vendor call ...
    }

    private function guardPath(string $path): void
    {
        $real = realpath($path) ?: $path;
        if (! str_starts_with($real, base_path())) {
            throw new \RuntimeException("Path escapes app root: {$real}");
        }
    }
}

This gives you the package's ergonomics today and a five-minute swap to File:: tomorrow.

Bridging Laravel Storage paths to real local paths

The package works on real filesystem paths. Laravel's Storage works on disk-relative paths. If your files live on the local disk, convert explicitly — this is the most common integration seam:

$absolute = Storage::disk('local')->path('imports/2024-06.csv');
// now pass $absolute to the vendor class

Do not do this for s3 or any remote disk — ->path() will throw or lie. If the disk could be remote, branch on the driver:

if (Storage::disk($disk)->getDriver()->getAdapter() instanceof LocalFilesystemAdapter) {
    // safe to use local-file
} else {
    // use Storage:: API only
}

Read-then-act for import/export jobs

The classic day-to-day shape — a queued job that ingests a dropped file, writes an artefact, and deletes the source on success:

public function handle(LocalFileStore $files): void
{
    $source = Storage::disk('local')->path($this->incomingPath);
    $rows   = $files->get($source);              // read
    $out    = $files->put($this->outputPath, $this->transform($rows)); // write
    $files->delete($source);                     // cleanup — only after the write succeeds
}

Order matters: delete last, after the write is durable. A 2021-era helper will not give you transactional semantics for you.

Test doubles

Since the wrapper is yours, fake it in tests instead of touching the real filesystem:

$this->mock(LocalFileStore::class, function ($mock) {
    $mock->shouldReceive('get')->once()->andReturn("a,b\n1,2");
});

For the few tests that must hit disk, use Storage::fake('local') for the Laravel side and tmpfile()/sys_get_temp_dir() for the vendor side — never write into the repo.

Gotchas and Tips

It is unmaintained — treat that as a design constraint, not a warning label. Last release 2021-03-23. It may predate PHP 8.1+ features, may use create_function-era idioms, and will not receive security patches (path traversal, symlink handling, TOCTOU). Pin the exact version in composer.json ("michaeldrennen/local-file": "1.0.*" — verify the actual constraint) so a composer update on a transitive dep can't shift behaviour under you.

Zero dependents means zero field-testing. You are the field test. Budget an hour to write characterisation tests against the real filesystem — existence checks, empty files, unicode filenames, filenames with spaces, nested dir creation, and what happens when the parent directory doesn't exist. You will find at least one surprise around return values or missing-directory handling.

Return-value ambiguity is the top bug source. Small helper libs commonly mix conventions: false on failure, 0 bytes written, true on unlink, and PHP warnings on top. Never write if ($file->write($data)) until you've read the source and confirmed. Prefer checking the observable result (file_exists, filesize) over trusting a boolean.

Path traversal if any path segment is user-supplied. The library has no reason to validate paths — it's a file helper, not a security boundary. Any filename arriving from a request must go through basename() plus an allowlist before it reaches the package. The guardPath() helper above is the cheap version of this.

Symlinks and realpath. If you validate paths by prefix, resolve with realpath() first, or a symlink inside your app root will happily point at /etc/passwd. Note realpath() returns false for paths that don't exist yet, so validate before creation carefully.

Atomic writes. If a reader (web request, queue worker) can touch the file while you write it, you get partial reads. The fix is universal and library-agnostic: write to $path . '.tmp', then rename() — rename is atomic within a filesystem. If the package offers a write that opens the destination directly, wrap it or skip it.

Permissions and umask. Files created by a queue worker may not be readable by your web user. Set the group/umask deliberately, or chmod post-write. This bites hardest in Docker where CLI and FPM run as different users.

Large files — check whether it's file_get_contents under the hood. If so, a 200 MB import will hit memory_limit. For anything above a few MB, stream with native fopen/fgets and use the package only for the small-file conveniences.

Windows path separators. If dev machines are Windows and prod is Linux, hardcoded / in constructed paths will work on both for the most part, but DIRECTORY_SEPARATOR-sensitive comparisons in your own validation code will not. Test the guard logic on both.

Extension points are effectively "none" — so create them yourself. No interfaces, no events, no config. Your LocalFileStore wrapper is the extension point. Add a Filesystem interface with your three or four methods (get, put, delete, exists) and bind it to the vendor-backed implementation. When you're ready to drop the dependency — and given the maintenance signal, plan for that — you swap the binding to an Illuminate\Filesystem implementation and delete one class.

Escalation threshold. If you find yourself needing more than ~3 calls into this package, or wanting to wrap most of its methods, that's the signal to stop and use Illuminate\Support\Facades\File instead. The convenience-to-risk ratio flips quickly with a 3-star, four-year-stale library.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core