Morph to
The inverse of a polymorphic relationship, allowing a model to belong to multiple different parent types.
When to Use
Use MorphTo on the morphed model to define the inverse of MorphOne or MorphMany relations. Common examples:
- Comment belongs to Post or Video (commentable)
- Image belongs to User or Product (imageable)
- Activity belongs to User or Organization (trackable)
Database Structure
posts
-----
id (primary key)
title
content
videos
------
id (primary key)
title
url
comments
--------
id (primary key)
commentable_type (stores parent model class: PostModel or VideoModel)
commentable_id (stores parent model ID)
content
created_at
Defining the Relation
use Michalsn\CodeIgniterRelations\Relations\MorphTo;
use Michalsn\CodeIgniterRelations\Traits\HasRelations;
class CommentModel extends Model
{
use HasRelations;
protected $table = 'comments';
protected $returnType = Comment::class;
public function commentable(): MorphTo
{
return $this->morphTo('commentable');
}
}
The 'commentable' parameter must match the morph name used in the parent models.
Custom Column Names
public function commentable(): MorphTo
{
return $this->morphTo(
'commentable', // Morph name
'custom_type', // Custom type column
'custom_id' // Custom ID column
);
}
Reading Data
Eager Loading
// Load comment with its parent (Post or Video)
$comment = model(CommentModel::class)->with('commentable')->find(1);
if ($comment->commentable instanceof Post) {
echo "Comment on post: " . $comment->commentable->title;
} elseif ($comment->commentable instanceof Video) {
echo "Comment on video: " . $comment->commentable->title;
}
// Load multiple comments with their parents
$comments = model(CommentModel::class)->with('commentable')->findAll();
foreach ($comments as $comment) {
echo $comment->commentable->title; // Works for both Post and Video
}
Lazy Loading
$comment = model(CommentModel::class)->find(1);
echo $comment->commentable->title; // Parent is loaded automatically
With Query Constraints
$comments = model(CommentModel::class)
->with('commentable', fn($model) => $model->where('published', 1))
->findAll();
Writing Data
MorphTo is a read-only relation. You cannot save through this relation. Instead, use associate() and dissociate() to manage the relationship.
Associating with a Parent
Use associate() to link a morphed model to a parent:
$comment = model(CommentModel::class)->find(1);
// Associate with a post (using entity)
$post = model(PostModel::class)->find(5);
$comment->commentable()->associate($post);
// Associate with a video (using model class and ID)
$comment->commentable()->associate(VideoModel::class, 3);
Behavior:
- When passing an entity: Sets both
commentable_typeandcommentable_id, updates in memory and database - When passing model class and ID: Sets both fields, persists to database
Dissociating from Parent
Use dissociate() to remove the association:
$comment = model(CommentModel::class)->with('commentable')->find(1);
$comment->commentable()->dissociate();
// Both commentable_type and commentable_id are now null
echo $comment->commentable_type; // null
echo $comment->commentable; // null
Available Methods
| Method | Description |
|---|---|
associate($parent, $id = null) |
Link to a parent (entity or model class + ID) |
dissociate() |
Remove the association (set morph fields to null) |
Usage Patterns
Two Ways to Associate
$comment = model(CommentModel::class)->find(1);
// Method 1: Pass entity
$post = model(PostModel::class)->find(5);
$comment->commentable()->associate($post);
// Method 2: Pass model class and ID
$comment->commentable()->associate(VideoModel::class, 3);
Checking Parent Type
$comment = model(CommentModel::class)->with('commentable')->find(1);
if ($comment->commentable instanceof Post) {
// Handle post comment
} elseif ($comment->commentable instanceof Video) {
// Handle video comment
}
// Or check the type column directly
if ($comment->commentable_type === PostModel::class) {
// Post comment
}