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 Module Laravel Package

anomaly/files-module

Files module for the Anomaly CMS brings file and folder management to Laravel: upload, organize, browse, and attach files through a unified filesystem interface, complete with an admin UI and a field type for linking uploads to your content.

View on GitHub
Deep Wiki
Context7

Getting Started

anomaly/files-module is the asset management addon for PyroCMS 3 / the Anomaly "Streams Platform", not a standalone Laravel package. It gives you a DB-backed abstraction over Laravel's filesystem: Disks → Folders → Files, with an admin UI, repositories, and a file uploader service. If you are not already inside a PyroCMS/Streams application, treat everything below as "port the ideas, not the package."

Minimal setup (inside a PyroCMS 3 app):

composer require anomaly/files-module
php artisan addon:install anomaly.files-module
php artisan addon:publish anomaly.files-module   # if you want to override config/views

Installation runs migrations creating three tables — files_disks, files_folders, files_files — and seeds disks from your config/filesystems.php.

Where to look first, in this order:

  1. src/FilesModuleServiceProvider.php — bindings, routes, event listeners, commands.
  2. src/Disk/, src/Folder/, src/File/ — each contains a *Model, *Repository, *Interface/contract, and criteria classes. This trio is the whole mental model.
  3. src/File/FileUploader.php — the single entry point for putting bytes on a disk.
  4. src/Http/Controller/Admin/ — the behaviour you get for free in the control panel (browse, upload, rename, move).
  5. resources/config/ — anything you'll want to publish and tweak.

First use case — resolve and render a file:

use Anomaly\FilesModule\File\Contract\FileRepositoryInterface;

public function show(FileRepositoryInterface $files)
{
    $file = $files->find(1);          // or ->findBySlug('hero.jpg')

    return view('welcome', [
        'url'  => $file->url(),        // public URL, respects the disk adapter
        'size' => $file->size(),
    ]);
}

The File model wraps a disk_id + relative path; calling url()/download()/content() delegates to the right Laravel filesystem disk, so the same code works whether the file lives on local, s3, or sftp.

Implementation Patterns

Repositories over models

Every entity has a contract. Inject the interface, not the model, and use the bundled criteria for lookups:

use Anomaly\FilesModule\Folder\Contract\FolderRepositoryInterface;
use Anomaly\FilesModule\Disk\Contract\DiskRepositoryInterface;

$disk   = app(DiskRepositoryInterface::class)->findBySlug('local');
$folder = app(FolderRepositoryInterface::class)->findByPath('images/banners');

This is what makes the module swappable — the admin UI, field types, and your code all resolve through the same repositories.

The upload flow

The canonical path is: resolve disk → resolve/create folder → hand an UploadedFile to the uploader.

use Anomaly\FilesModule\File\FileUploader;

$file = app(FileUploader::class)->upload(
    $request->file('upload'),
    $folder              // FolderInterface
);

Do not write files directly with Storage::put() and then hand-insert rows into files_files — the uploader sets mime type, size, extension, hashing, and fires the upload events (which other addons listen to for thumbnails, search indexing, etc.). Always go through it.

Attaching files to your own content

Two idioms, pick one and stay consistent:

  • Field types (recommended in PyroCMS). Use anomaly.field_type.file / anomaly.field_type.files / anomaly.field_type.folder on your Streams or form builders. You get the picker UI, validation, and a hydrated FileInterface back on the entry.
  • Plain relation. Add file_id / folder_id columns and a belongsTo(FileModel::class). Simple, portable, but you lose the picker and any cascade behaviour on delete.

Serving and streaming files

For anything that must not be public (contracts, invoices), don't expose url() — route it and stream through a controller:

public function download($id, FileRepositoryInterface $files)
{
    $file = $files->find($id);

    abort_unless($file, 404);

    return $file->download();   // or response()->streamDownload(...) from ->content()
}

Keep the authorization check in your controller — the module knows nothing about who owns a file.

Folder trees

Folders are stored with a parent_id and a materialised path. Prefer findByPath('clients/acme/2024') over walking parents by hand, and let the repository's move/rename routines handle the tree — they keep path consistent across descendants. Physically relocating blobs is a separate concern on remote disks; verify behaviour before assuming a rename moves bytes.

Let the admin UI do the CRUD

The module ships a complete Files section in the control panel (browse, upload, rename, move, delete, disk management). Don't rebuild it. Build around it: your own screens should link into the file picker rather than reimplementing upload forms.

Gotchas and Tips

  • It is not a standalone package. The anomaly/* dependency chain (Streams Platform, addon system, addon:install) is mandatory. In a vanilla Laravel app, composer require will get you classes you cannot boot. Budget for the platform, not just the module.
  • Version drift is the real risk. This is a PyroCMS 3 / Laravel 5.x-era codebase with a tiny install base. Pin your PHP and Laravel constraints deliberately and read composer.json before upgrading anything around it.
  • Two sources of truth for disks. Disk definitions exist both in config/filesystems.php and as rows in files_disks. Editing one does not update the other. When a disk "disappears" in the admin, check the DB row first, then the config. Look in src/Command/ for a sync command to reconcile them.
  • Local disk URLs need the symlink. If url() returns a 404 for a local disk, run php artisan storage:link and confirm the disk's root is under storage/app/public. Remote adapters (S3, etc.) are unaffected.
  • path is relative to the disk root. Never concatenate it with an absolute app path; always resolve through the disk: Storage::disk($disk->slug)->exists($file->path()).
  • Name collisions. Depending on config, the uploader may overwrite or uniquify duplicate names. Test the behaviour explicitly if your users upload bulk files with identical names — silent overwrites here are painful to audit after the fact.
  • Permissions are a separate step. The admin section is gated by anomaly.module.files::* permission keys. A role with no file permissions sees an empty section and no error, which reads as "broken module" but isn't.
  • Debugging checklist when a file won't display: (1) row exists in files_files with the expected disk_id and folder_id; (2) Storage::disk(...)->exists($path) returns true; (3) disk row's adapter/config matches the config file; (4) the resolved URL is what you expect for that adapter. Failures almost always sit at step 2 or 3.
  • Hooking in. Events (FileWasUploaded, folder created/moved, etc.) are the clean extension point for image processing, virus scanning, or search indexing — register listeners in your own service provider rather than patching the module.
  • Overriding, not forking. Publish views and config via addon:publish and override bindings in a service provider. Forking this package is a dead end given the maintenance level.
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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json