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

Files Laravel Package

makinacorpus/files

A Laravel package for attaching and managing files on Eloquent models, with polymorphic file associations, configurable storage disks, image handling, and helpers for uploading, retrieving, and cleaning up stored files.

View on GitHub
Deep Wiki
Context7

Getting Started

This is not a Laravel package — it ships no service provider, no facade, no config file, and no Flysystem bridge. You install it and wire it yourself. That shapes every step below.

Install

composer require makinacorpus/files

Before it lands in composer.json, get a license ruling: the package is GPL-2.0, which is a hard gate for most commercial Laravel applications. If legal is a no-go, stop here and use Storage instead.

Where to look first

  • src/ — the MakinaCorpus\Files\File class and its abstract base are the entire API surface. Read the abstract class top-to-bottom; it defines exists, isFile, isDirectory, getSize, getMTime, getPerms, open, unlink, rename, copy, and path helpers (basename, dirname, getUri).
  • composer.json — verify the php constraint against your runtime and eyeball the license field.
  • README.md — a short method table at best. The source is the source of truth; the marketing copy ("polymorphic attachments", "Eloquent traits") describes capabilities you would have to build on top of File, not features the package provides.

First use case

Wrap a path you already have (an uploaded temp file, a storage_path() result, a Stream::url()) and do inspection/metadata before handing off to the rest of the app:

use MakinaCorpus\Files\File;

$file = new File(storage_path('app/private/report.pdf'));

if ($file->exists() && $file->isFile()) {
    $size = $file->getSize();

    $handle = $file->open('rb'); // raw PHP resource
    // stream, hash, or push the handle into a queued job
    fclose($handle);
}

The value it adds is honestly thin: one object that answers "does this exist, how big is it, let me open/rename/delete it" against any URI PHP's stream wrappers can resolve. Reach for it for internal tooling and one-off admin flows; reach for Storage for anything user-facing or production-critical.

Implementation Patterns

Wrap it — do not sprinkle it. Never import MakinaCorpus\Files\File in a controller, job, or model. Put one thin interface in front of it so the dependency stays replaceable (you will almost certainly replace it with Storage later):

namespace App\Files;

interface FileStore
{
    public function put(string $path, $contents): void;
    public function get(string $path): string;
    public function exists(string $path): bool;
    public function delete(string $path): void;
    public function size(string $path): int;
}
final class MakinaFileStore implements FileStore
{
    public function __construct(private string $root) {}

    private function file(string $path): File
    {
        $path = ltrim($path, '/');
        if (str_contains($path, '..')) {
            throw new \InvalidArgumentException("Illegal path: {$path}");
        }
        return new File($this->root . '/' . $path);
    }

    public function exists(string $path): bool { return $this->file($path)->exists(); }
    public function size(string $path): int     { return $this->file($path)->getSize(); }

    public function put(string $path, $contents): void
    {
        $f = $this->file($path);
        $h = $f->open('wb');
        fwrite($h, $contents);
        fclose($h);
    }

    // ...
}

Bind it in a service provider — that's the seam you swap for Storage when the license or maintenance question comes due.

Build the "Eloquent integration" yourself. The package advertises polymorphic attachments and traits; it does not ship them. If you need files attached to models, this is the pattern:

// migration
$table->morphs('attachable');
$table->string('disk');
$table->string('path');
$table->string('original_name');
$table->unsignedBigInteger('size');
trait HasFiles
{
    public function files(): MorphMany
    {
        return $this->morphMany(FileRecord::class, 'attachable');
    }
}

The upload flow becomes: store the file with your FileStore (or Storage), then persist a FileRecord row carrying the path and metadata. The File object is only useful while the handle is open — don't try to persist it.

Stream, don't slurp. For anything larger than a few MB, hand the resource from $file->open() into a streaming response or a queued job rather than reading it into a string. This is where the package's lack of chunked-upload or StreamedResponse helpers bites — you build those yourself.

Path arithmetic is the one clean win. basename, dirname, and getUri are handy when moving, renaming, or classifying files in an import/export job, and they beat hand-written explode('/', ...) chains:

$base = $file->basename();                  // 'report.pdf'
$dir  = $file->dirname();

Use it as a scratch space for import jobs. A typical pattern: an admin uploads a CSV, a job stages it under storage_path('imports'), iterates rows, and deletes the file via $file->unlink() when done. That's an honest fit for the package's scope.

Gotchas and Tips

The license is the first pitfall, not the API. GPL-2.0 propagates to linked application code. Laravel's ecosystem convention is MIT; a GPL dependency in a proprietary or SaaS product is a legal-review trigger, and most teams end the conversation there. If you're shipping GPL-compatible software, fine; otherwise this is likely a non-starter regardless of how clean the code looks.

No adoption signal = you own the maintenance. Zero stars, zero dependents, no CI matrix visible. Assume upstream fixes arrive on the maintainer's timetable or not at all. Fork to an internal namespace, pin the commit, and name an owner before your first production use.

No service provider means no auto-configuration. Nothing registers File in the container, nothing publishes config, nothing binds a disk. Every construction site is your responsibility — which is exactly why the adapter interface above matters.

Path traversal is your job. new File($userSuppliedPath) will happily resolve ../../etc/passwd if PHP's stream layer allows it. Validate and normalize before constructing the object:

$safe = basename($request->input('name')); // strip any directory component

Never pass a raw request value into new File(...).

Silent overwrite on name collision. open('wb') and copy() follow normal PHP semantics — they clobber. Generate a UUID prefix before storing user uploads, and add a uniqueness check in the DB layer.

Orphaned files on transaction rollback. If you put() and then the DB transaction fails, the file remains on disk. Do the write after commit, or sweep orphans on a schedule. The package gives you no transactional hooks.

MIME inference is not validation. mime_content_type() sniffs content; it does not verify the file is safe. Cross-check extension against sniffed MIME, enforce a size cap at the UploadedFile layer, and never serve uploads with a Content-Type derived from the client's claim.

Memory behavior on large uploads. If your code path does $file->getContents() (or you build one) and feeds it to fwrite, a 500 MB upload becomes a 500 MB string in memory. Stick to resource handles from open() and stream both sides.

Exceptions are the generic PHP kind. open() on a missing path, permission errors on unlink(), and rename across filesystems all surface as \RuntimeException/\ErrorException/false rather than typed domain exceptions. Catch specifically in your adapter and rethrow as your own StorageException; do not let raw PHP errors leak into controllers.

Check the stream wrapper before blaming the package. File works against whatever PHP's stream functions can resolve — file://, phar://, registered S3 wrappers, etc. But the package ships no wrapper; if you expect s3:// to work, someone has to register a flysystem stream wrapper via something like league/flysystem (which defeats the point of using this package on Laravel). For S3, use Storage — it's the same underlying Flysystem, wired and maintained by the framework.

Extension point is the File subclass, not a driver system. The abstract base class is protected-heavy but not designed for a plugin ecosystem. If you need custom behavior, subclass and override — don't expect config-driven storage adapters. In practice, wrapping is easier than extending.

Before you commit, run the 30-second sanity check that should have caught this: composer show makinacorpus/files (license + version), GitHub stars/dependents, and last commit date. Zero stars, zero dependents, and GPL-2.0 is the entire evaluation in one glance — then reach for Storage, and reserve spatie/laravel-medialibrary for media-specific needs (conversions, collections, responsive images).

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib