Skip to content

Polymorphic Relations

The variableMorphs() macro eliminates verbose match expressions when defining polymorphic relationship columns, making your migrations cleaner and type-safe.

use Cline\VariableKeys\Enums\MorphType;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->text('body');
// Polymorphic relationship
$table->variableMorphs('commentable', MorphType::ULID);
$table->timestamps();
});
$table->variableMorphs(string $name, MorphType $type, bool $nullable = false)

Parameters:

  • $name - The morph relationship name (e.g., 'commentable', 'taggable')
  • $type - The morph type enum value
  • $nullable - Whether the relationship is optional (default: false)

Laravel’s default morphs that automatically detect the appropriate column type.

$table->variableMorphs('commentable', MorphType::String);

Equivalent to:

$table->morphs('commentable');

Explicitly use integer foreign keys for the morph relationship. Best when models use auto-incrementing integer primary keys.

$table->variableMorphs('taggable', MorphType::Numeric);

Equivalent to:

$table->numericMorphs('taggable');

Use 36-character UUIDs for the morph relationship.

$table->variableMorphs('imageable', MorphType::UUID);

Equivalent to:

$table->uuidMorphs('imageable');

Use 26-character ULIDs for the morph relationship.

$table->variableMorphs('attachable', MorphType::ULID);

Equivalent to:

$table->ulidMorphs('attachable');

Set the third parameter to true for optional polymorphic relationships:

$table->variableMorphs('parent', MorphType::UUID, nullable: true);

This creates nullable columns for both the type and ID:

// Equivalent to:
$table->nullableUuidMorphs('parent');

Centralize your morph type configuration:

config/database.php
return [
'morph_type' => env('DB_MORPH_TYPE', 'string'),
];

Use in migrations:

use Cline\VariableKeys\Enums\MorphType;
$morphType = MorphType::tryFrom(config('database.morph_type'))
?? MorphType::String;
Schema::create('images', function (Blueprint $table) use ($morphType) {
$table->id();
$table->string('url');
$table->variableMorphs('imageable', $morphType);
$table->timestamps();
});
match ($morphType) {
MorphType::ULID => $table->ulidMorphs('commentable'),
MorphType::UUID => $table->uuidMorphs('commentable'),
MorphType::Numeric => $table->numericMorphs('commentable'),
MorphType::String => $table->morphs('commentable'),
};
$table->variableMorphs('commentable', $morphType);
use Cline\VariableKeys\Enums\MorphType;
Schema::create('comments', function (Blueprint $table) {
$table->ulid('id')->primary();
$table->text('body');
$table->foreignUlid('user_id')->constrained()->cascadeOnDelete();
// Comments can belong to posts, videos, etc.
$table->variableMorphs('commentable', MorphType::ULID);
$table->timestamps();
});
Schema::create('taggables', function (Blueprint $table) {
$table->id();
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
// Tags can be attached to posts, products, etc.
$table->variableMorphs('taggable', MorphType::Numeric);
$table->unique(['tag_id', 'taggable_type', 'taggable_id']);
$table->timestamps();
});
Schema::create('media', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('file_name');
$table->string('mime_type');
$table->unsignedBigInteger('size');
// Media can be attached to users, posts, products, etc.
$table->variableMorphs('mediable', MorphType::UUID);
$table->timestamps();
});
Schema::create('activity_log', function (Blueprint $table) {
$table->id();
$table->string('description');
$table->variableMorphs('causer', MorphType::ULID);
// Subject is optional (some activities don't have a subject)
$table->variableMorphs('subject', MorphType::ULID, nullable: true);
$table->timestamps();
});
Schema::create('notifications', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('type');
$table->text('data');
// The entity that can receive notifications
$table->variableMorphs('notifiable', MorphType::UUID);
$table->timestamp('read_at')->nullable();
$table->timestamps();
});
TypeUse WhenCharacteristics
StringDefault, flexible setupAuto-detects column type
NumericInteger primary keysEfficient, traditional IDs
UUIDUUID primary keysGlobally unique, random
ULIDULID primary keysSortable, time-ordered

After creating the morph columns in your migration, define the relationship in your models:

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Comment extends Model
{
public function commentable(): MorphTo
{
return $this->morphTo();
}
}
class Post extends Model
{
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}