This package provides type-constrained polymorphic relationships for Laravel Eloquent. It extends Laravel’s morphTo relationship to only accept specific model types, ensuring data integrity and type safety for your polymorphic relations.
Laravel’s polymorphic relationships are flexible - a single relationship can relate to multiple model types. But sometimes you want the flexibility of a polymorphic database structure while enforcing that certain relationships only accept specific types:
// Standard Laravel morphTo - returns ANY model type
public function commentable()
{
return $this->morphTo();
}
$comment->commentable; // Returns any model typeThis package lets you constrain polymorphic relationships to specific types:
use pindab0ter\ConstrainedMorphtoForLaravel\HasConstrainedMorphTo;
class Comment extends Model
{
use HasConstrainedMorphTo;
/** @return ConstrainedMorphTo<Post, $this> */
public function post()
{
return $this->constrainedMorphTo(Post::class, 'commentable_type', 'commentable_id');
}
/** @return ConstrainedMorphTo<Post|Video, $this> */
public function commentable()
{
return $this->constrainedMorphTo(
[Post::class, Video::class], // Accept multiple types
'commentable_type',
'commentable_id'
);
}
}
$comment->post; // Returns a Post if the type matches, null if it doesn't
$comment->commentable; // Returns a Post or Video if the type matches, null otherwise- PHP 8.2+
- Laravel 10.48+, 11.x, or 12.x
Install the package via Composer:
composer require pindab0ter/constrained-morph-to-for-laravel-
Add the
HasConstrainedMorphTotrait to your model:use Illuminate\Database\Eloquent\Model; use pindab0ter\ConstrainedMorphtoForLaravel\HasConstrainedMorphTo; class Comment extends Model { use HasConstrainedMorphTo; /** @return ConstrainedMorphTo<Post, $this> */ public function commentable() { return $this->constrainedMorphTo( Post::class, // Only allow Post models 'commentable_type', // The type column name 'commentable_id' // The ID column name ); } }
-
When the relationship type matches, it works like normal
morphTo:$post = Post::create([...]); $comment = Comment::create([ 'commentable_id' => $post->id, 'commentable_type' => Post::class, ]); $comment->commentable; // Returns the Post instance
-
When the type doesn't match the constraint, it returns
null:$image = Image::create([...]); $comment = Comment::create([ 'commentable_id' => $image->id, 'commentable_type' => Image::class, // Wrong type! ]); $comment->commentable; // Returns null (constraint not met)
You can allow multiple model types in a single relationship by passing an array:
/** @return ConstrainedMorphTo<Post|Video, $this> */
public function commentable()
{
return $this->constrainedMorphTo(
[Post::class, Video::class], // Accept both Post and Video models
'commentable_type',
'commentable_id'
);
}Now the relationship will accept either type:
$post = Post::create([...]);
$comment1 = Comment::create([
'commentable_id' => $post->id,
'commentable_type' => Post::class,
]);
$comment1->commentable; // Returns the Post instance
$video = Video::create([...]);
$comment2 = Comment::create([
'commentable_id' => $video->id,
'commentable_type' => Video::class,
]);
$comment2->commentable; // Returns the Video instance
$image = Image::create([...]);
$comment3 = Comment::create([
'commentable_id' => $image->id,
'commentable_type' => Image::class, // Not in allowed types!
]);
$comment3->commentable; // Returns nullYou can optionally specify the relationship name and owner key:
public function commentable()
{
return $this->constrainedMorphTo(
Post::class, // Constrained type (or array of types)
'commentable_type', // Type column
'commentable_id', // ID column
'commentable', // Relationship name (optional)
'id' // Owner key (optional)
);
}Please see CHANGELOG for more information on what has changed recently.
The MIT License (MIT). Please see License File for more information.