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

Media Bundle Laravel Package

vlabs/media-bundle

vlabs/media-bundle is a Laravel package for managing media files and attaching them to Eloquent models. It simplifies uploads, storage, image conversions, and reusable media collections with a clean API for PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

First, a reality check. vlabs/media-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/media.php to publish, and no artisan command. So "getting started" in a Laravel app really means one of three things:

  1. You are building a Symfony app (or a Laravel app that consumes Symfony components directly) and want a batteries-included file abstraction.
  2. You want to mine the design of the bundle (entity + form + filesystem adapter triad) and reimplement it idiomatically in Laravel.
  3. You want a hybrid setup where the underlying Symfony components (symfony/form, symfony/http-foundation, Doctrine) are pulled in alongside Eloquent — which is almost always more pain than it's worth.

Where to look first (in the repo):

  • The Doctrine Media entity — this is the core of the abstraction. Read its fields (name, path/filename, mime type, size, timestamps) and decide what you'd mirror in an Eloquent model.
  • The MediaType form type — shows how the bundle wires an upload widget into a form with metadata.
  • The filesystem abstraction layer — the adapter/service that actually writes bytes to disk. This is the piece worth copying.
  • DependencyInjection/ and Resources/config/ — the YAML service wiring that you'd translate into a Laravel service provider.

Minimal Symfony steps (for reference):

composer require vlabs/media-bundle
// config/bundles.php
Vlabs\MediaBundle\VlabsMediaBundle::class => ['all' => true],

Then configure the filesystem root/adapter, run the Doctrine migration for the media table, and embed MediaType in your own form.

Minimal Laravel equivalent (the realistic path):

composer require spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"

Spatie's HasMedia trait + addMedia() is the direct Laravel-native analogue of what this bundle gives Symfony, and it's battle-tested at a scale this bundle (44 stars, 0 dependents) is not.

First use case to target either way: a single "attach one file to one record, store the path, keep the mime type and size" flow. Nail that before touching galleries, conversions, or multi-disk setups.

Implementation Patterns

Pattern 1: Eloquent model mirroring the bundle's Media entity

class Media extends Model
{
    protected $fillable = ['disk', 'path', 'filename', 'mime_type', 'size'];

    public function mediable(): MorphTo
    {
        return $this->morphTo();
    }
}
trait HasMedia
{
    public function media(): MorphMany
    {
        return $this->morphMany(Media::class, 'mediable');
    }

    public function attachUpload(UploadedFile $file, string $disk = 'public'): Media
    {
        $path = $file->store('media', $disk);

        return $this->media()->create([
            'disk'      => $disk,
            'path'      => $path,
            'filename'  => $file->getClientOriginalName(),
            'mime_type' => $file->getMimeType(),
            'size'      => $file->getSize(),
        ]);
    }
}

Pattern 2: Value-object / cast for the filesystem adapter

The bundle's best idea is treating storage as an injected abstraction rather than hard-coding a path. In Laravel, push that into a custom cast or a small MediaStore service:

class MediaStore
{
    public function __construct(private string $disk) {}

    public function put(UploadedFile $file, string $dir = 'media'): Media
    {
        return Media::create([
            'disk' => $this->disk,
            'path' => $file->store($dir, $this->disk),
            // ...
        ]);
    }

    public function url(Media $media): string
    {
        return Storage::disk($media->disk)->url($media->path);
    }
}

Bind it in a service provider so tests can swap fake() disks. This is the closest Laravel analogue to the bundle's adapter wiring.

Pattern 3: Form/validation integration

The bundle leans on Symfony's Form component; Laravel leans on FormRequests + StoreMediaRequest rules. Map the bundle's form type to:

class StoreMediaRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'file' => ['required', 'file', 'mimes:jpg,png,pdf', 'max:10240'],
        ];
    }
}

If you're genuinely running Symfony forms inside Laravel (rare), you're re-implementing laravelcollective/html and the CSRF bridge — don't.

Pattern 4: Lifecycle hooks

The bundle hooks entity persist/remove to clean up files. In Laravel, mirror that in model events:

protected static function booted(): void
{
    static::deleting(fn (Media $m) => Storage::disk($m->disk)->delete($m->path));
}

Be careful — deleting a mediable should cascade to media rows and files; use a deleted observer on the parent too.

Pattern 5: Serving files

The bundle typically stores a path and lets you build a URL. In Laravel, either use the public disk + storage:link, or stream through a signed route for private files:

Route::get('/media/{media}', fn (Media $media) =>
    Storage::disk($media->disk)->download($media->path, $media->filename)
)->middleware('signed');

Gotchas and Tips

  • It is not installable in a vanilla Laravel app. The bundle declares Symfony bundle metadata and expects Kernel/bundles.php registration. composer require will pull Symfony packages into your app and can conflict with Laravel's pinned symfony/* versions. Check composer why-not symfony/http-foundation before attempting.
  • Doctrine vs Eloquent. The bundle's model layer is Doctrine ORM. If you're mapping its patterns to Eloquent, don't try to run both ORMs in one app — the identity map, migrations, and connection handling will fight each other.
  • Config is YAML, not PHP. Any "config quirks" you find are in config/packages/vlabs_media.yaml in Symfony, not config/media.php. There's no php artisan vendor:publish.
  • Low maintenance signal. 44 stars, zero dependents. Treat it as a design reference, not a dependency. Pin the version if you do use it, and be prepared to fork — the surface area is small enough that a fork is usually cheaper than waiting for upstream.
  • Orphaned files are the classic failure. If an upload succeeds but the DB write rolls back (or vice versa), you leak bytes. Wrap attach operations so the file write and the row insert are in the same DB::transaction, and clean up on failure. Conversely, deleting the row should delete the blob — do it in a deleting hook, not in a controller.
  • Mime detection is unreliable. UploadedFile::getMimeType() trusts the client sniff; getMimeType() on UploadedFile uses PHP's finfo and is safer. The bundle delegates to the filesystem/HTTP layer, so verify what your adapter actually reports — especially for PDFs and HEIC.
  • Disk config drift. The bundle's filesystem abstraction assumes one configured root. If you're porting to Laravel, put disk on the row, not in the config, so records survive a storage migration (public → S3).
  • Debugging path. When a file "disappears": (1) check the path column vs Storage::disk()->path(''), (2) confirm storage:link exists for public URLs, (3) check disk permissions for the PHP-FPM user, (4) Storage::disk()->exists($media->path) from php artisan tinker.
  • Extension points worth copying. The bundle's form type and filesystem adapter are its two clean seams — in Laravel these map to a custom FormRequest and a MediaStore service bound to an interface. Everything else (the entity, the URL building) is thin glue you should reimplement natively.
  • Don't mix abstractions. If you adopt Spatie's laravel-medialibrary for the Laravel path, resist also pulling in this bundle "for the form bits" — you'll end up with two media tables and two cleanup strategies.
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