php 148 lines · 4 tabs

Polymorphic Media Attachments in Laravel With a Queued Image Resize Job

Shared by codesnips Sep 2026
4 tabs
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('media', function (Blueprint $table) {
            $table->id();
            $table->morphs('mediable');
            $table->string('disk')->default('public');
            $table->string('path');
            $table->string('filename');
            $table->string('mime_type');
            $table->unsignedBigInteger('size')->default(0);
            $table->json('variants')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('media');
    }
};
4 files · php Explain with highlit

This snippet shows how a single media table can be attached to any Eloquent model through a polymorphic relationship, with the actual image resizing pushed onto a queue so uploads stay fast. The polymorphic approach avoids one join table per model type — instead of post_media, user_media, and so on, every attachment lives in one place and points back to its owner through two columns.

In create_media_table migration, the schema defines those two columns via $table->morphs('mediable'), which generates mediable_id and mediable_type plus a composite index. The row also carries a disk, the stored path, the original filename, a mime_type, and a nullable variants JSON column where resized derivatives are recorded once the job finishes. Keeping variants as JSON means new sizes can be added without a migration.

The Media model declares the inverse side with mediable() returning a morphTo relation, so $media->mediable resolves to whatever model owns it. The HasMedia trait is the reusable other half: any model that uses it gains a media() morphMany relation and an attachMedia() helper. That helper calls store() on the uploaded file to persist the original, creates the Media row, and then dispatches ResizeMediaJob — the write to storage and the database happen synchronously, but the CPU-heavy resize is deferred.

ResizeMediaJob implements ShouldQueue and receives only the Media id-bearing model, relying on Laravel's model serialization so the freshest row is loaded on the worker. It reads the original off the configured disk, generates each named size with the Intervention Image library, writes the derivative back to storage, and records the resulting paths in the variants column in one update(). Because the job is idempotent-ish — it overwrites the same variant paths — a retry simply regenerates them, which matters since queued jobs can run more than once.

The trade-off is eventual consistency: right after upload, variants is empty until the worker catches up, so views should fall back to the original path. This pattern fits any app where many model types need attachments and where resizing large images would otherwise block the request cycle.


Related snips

Share this code

Here's the card — post it anywhere.

Polymorphic Media Attachments in Laravel With a Queued Image Resize Job — share card
Link copied