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

Laravel Fileable Laravel Package

toneflix/laravel-fileable

Laravel Fileable provides simple file attachments for Eloquent models, letting you associate uploads with records, manage storage and retrieval, and keep file handling reusable across your Laravel app.

View on GitHub
Deep Wiki
Context7

Getting Started

Requirements: Laravel 12.x or 13.x only (3.0.0 dropped everything below 12). PHP version must satisfy the package's composer constraint — check composer.json before bumping.

Install / upgrade the vendor name first. The composer vendor changed from toneflix-code to toneflix:

composer remove toneflix-code/laravel-fileable
composer require toneflix/laravel-fileable:^3.0

If the codebase pins the old vendor in composer.json, composer update alone will not move you to 3.x — you have to swap the requirement explicitly.

Then publish and migrate:

php artisan vendor:publish --tag=fileable-config   # publishes config/laravel-fileable.php
php artisan vendor:publish --tag=fileable-migrations
php artisan migrate
php artisan storage:link                           # if serving from the public disk

Where to look first — in this order:

  1. config/laravel-fileable.php → the collections array. This is the heart of the package: every logical file slot (avatar, cover, document, gallery) is declared here with its disk, path, size, mime, and transformation rules.
  2. The Fileable trait (Toneflix\LaravelFileable\Traits\...) — that's the public surface you'll call from models and controllers.
  3. The File model — the polymorphic record behind every upload. Read its table schema before writing queries against it.

First use case — an avatar on User:

use Toneflix\LaravelFileable\Traits\Fileable;

class User extends Authenticatable
{
    use Fileable;
}

Declare the collection in config:

// config/laravel-fileable.php
'collections' => [
    'avatar' => [
        'disk'  => 'public',        // 3.0: per-collection disk is honoured
        'path'  => 'avatars',
        'mimes' => ['image/jpeg', 'image/png'],
        'size'  => 2048,            // KB
    ],
],

Then in a controller: accept the upload, attach it to the named collection, and persist. Read the trait's method signatures from the package source/tests before wiring this into a form — the fluent API is the part most likely to differ from your memory of 2.x.

Implementation Patterns

Config-driven collections, code-driven access. Treat collections as the single source of truth for validation and storage. Controllers should never hardcode a disk or path — resolve them from the collection name so that a later disk change is a one-line config edit rather than a hunt through the codebase.

Per-collection disks (new in 3.0). This is the headline feature and it changes the shape of a lot of setups:

'collections' => [
    'avatar'    => ['disk' => 'public',  'path' => 'avatars'],
    'contract'  => ['disk' => 's3',      'path' => 'contracts', 'mimes' => ['application/pdf']],
    'thumbnail' => ['disk' => 'r2',      'path' => 'thumbs'],
],

Previously you either ran one global disk or hand-rolled a second storage abstraction. Now a single model can legitimately hold a public avatar and a private S3 contract. Use this to keep PII/contracts on a private disk and profile imagery on public — and note that per-collection disk means your Storage::disk() calls in Blade/API resources must resolve the disk from the collection, not from a global config value.

Validation mirrors the collection. Derive form-request rules from the same config block rather than duplicating mime/size limits:

$rules = [
    'avatar' => ['required', 'file', 'max:'.config('laravel-fileable.collections.avatar.size'),
                 'mimes:jpg,jpeg,png'],
];

Duplicated limits drift; the config wins.

Serving files. Never expose raw paths. Store whatever the package returns on the model and resolve URLs at render time via the collection's disk (Storage::disk($disk)->url($path)), or via a signed temporary URL for private disks. Add a small FileResource so the API contract (url, thumb, mime, size) is stable even if the storage strategy changes.

Testing. Storage::fake('public') in setUp() and assert with Storage::disk('public')->assertExists(...). This catches the classic bug where a test passes because it wrote to the real disk. For S3/R2 collections, fake the configured disk name explicitly — with per-collection disks you now have several to fake.

Namespace migration pattern (2.x → 3.0). Do this as a standalone commit before any feature work:

# 1. Composer vendor swap (above)
# 2. Codebase-wide namespace rename
grep -rl 'ToneflixCode\\LaravelFileable' app/ config/ database/ routes/ tests/ \
  | xargs sed -i 's/ToneflixCode\\\\LaravelFileable/Toneflix\\\\LaravelFileable/g'

# 3. Rebuild autoload + clear caches
composer dump-autoload
php artisan optimize:clear

Also update, explicitly and by hand:

  • config/app.php — any providers / aliases entries still pointing at ToneflixCode\.... If you rely on package discovery you can drop them entirely.
  • Custom classes that extend or implement package contracts — the parent class moved, and any type-hinted constructor params moved with it.
  • Event listeners for package events (file attached/removed) — the event classes are under the new namespace, so string-based $listen keys and queued listener payloads need updating too.
  • Any config('laravel-fileable...') key that was renamed alongside the namespace — diff the published config against your existing one (php artisan vendor:publish --force into a scratch dir and diff).

Queued processing. Image resizing/conversion is the slow part. Dispatch the attach step from a job so the HTTP request returns immediately, and make the job idempotent — retries must not create duplicate File rows for the same collection slot.

Gotchas and Tips

This is a hard-major release; budget for it. Three separate breaking axes at once — vendor name, PHP namespace, and Laravel floor. Do them as one mechanical PR, run the full test suite, and only then start using collection.*.disk.

class not found after upgrading is almost always one of: stale vendor/composer/autoload_classmap.php (run composer dump-autoload), a cached config still naming old classes (php artisan config:clear), an opcache issue in a long-running Octane worker (restart the workers), or one straggler use statement grepped out of a Blade file or a queued job's serialized payload. Old queued jobs serialized with the ToneflixCode namespace will fail to unserialize — drain the queue before deploying 3.0.

Per-collection disk fails silently if the disk isn't defined. config/filesystems.php must declare every disk you reference. If the collection's disk is missing or misspelled the upload may fall back to the default disk, which looks like it worked until the URL 404s in production. Add a boot-time sanity check or a test that iterates config('laravel-fileable.collections') and asserts each disk exists.

Private disks need URL strategy, not just a disk name. Moving contract to s3 is one config line; making the download link work for an authenticated user is a signed-URL route, a controller action, and possibly a CORS/visibility tweak. Don't conflate "I changed the disk" with "downloads work".

Config caching. php artisan config:cache will bake the collection map (and disk names) at deploy time. Changing collections per-environment (local disk vs s3) is fine — but you must re-cache on deploy, and anything reading collection config at container-boot time rather than per-request will freeze the first environment it saw.

Orphaned files. Deleting an Eloquent model does not reliably delete the underlying files, and forceDelete vs soft-delete semantics matter. Wire up an observer or a deleting hook so files and File rows are cleaned together, and add a scheduled reconciliation command for rows whose disk object has vanished (or vice versa) — cheap to write, saves a storage bill later.

File replacement leaks. Replacing an avatar should delete the previous object. If your save path only inserts, you'll accumulate versions silently. Assert Storage::disk(...)->allFiles('avatars') count in a test that uploads the same collection twice.

Validation belongs at the boundary. Enforce mime and size in a FormRequest before the trait runs — the collection's mimes/size are a second line of defence, not the first, and relying solely on them produces unhelpful 500s instead of 422s.

Debugging tip. When a file "saves but doesn't show", check in this order: (1) the File row exists and points at the right collection name — a typo in the collection string creates a row against an undefined collection with default settings; (2) the resolved disk from config; (3) storage:link exists and the web server isn't blocking storage/; (4) the URL scheme/host in filesystems.disks.public.url matches the environment. php artisan tinker plus Storage::disk($d)->exists($path) short-circuits all of this in seconds.

Extension points to prefer over forking. New formats and sizes → collection config. Custom paths/filenames → the package's path or naming hooks if exposed. Cross-cutting concerns (virus scanning, watermarking, EXIF stripping) → a job or a decorator around your own upload service, so a future 4.0 can be adopted by bumping composer rather than re-merging a patch. Avoid subclassing the package's File model; you'll inherit every migration in the next major.

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.
frankkessler/guzzle-oauth2-middleware
delatbabel/site-config
delatbabel/applog
eusonlito/laravel-gettext
henzeb/housepedia-intelligence-laravel
aliqsm/laravel-authkit
itsjustvita/laravel-wp-migrator
uslanozan/laravel-cache-contract
adilazhari/laravel-trace
ulsyairil/ai-local-guardrails
dzwallet/laravel-sdk
glc36/maxloop-kernel
ikhbarnuridev/filament-mega-menu
mydnic/volet-chatbot
enadstack/laravel-localization
mydnic/volet-feature-board-filament-plugin
wmde/email-address
refinery29/test-util
dees040/laravel-api-responses
teamzac/geocoder