Skip to content

Morph many

A polymorphic one-to-many relationship where a related model can belong to multiple types of parent models using a single relation.

When to Use

Use MorphMany when different models can have multiple related records of the same type. Common examples:

  • Post or Video has many Comments
  • Product or Service has many Reviews
  • User or Organization has many Activity Logs

Database Structure

posts
-----
id        (primary key)
title
content

videos
------
id        (primary key)
title
url

comments
--------
id                  (primary key)
commentable_type    (stores the parent model class name)
commentable_id      (stores the parent model ID)
content
created_at

The commentable_type and commentable_id columns work together to identify which parent record each comment belongs to.

Defining the Relation

use Michalsn\CodeIgniterRelations\Relations\MorphMany;
use Michalsn\CodeIgniterRelations\Traits\HasRelations;

class PostModel extends Model
{
    use HasRelations;

    protected $table = 'posts';
    protected $returnType = Post::class;

    public function comments(): MorphMany
    {
        return $this->morphMany(CommentModel::class, 'commentable');
    }
}
class VideoModel extends Model
{
    use HasRelations;

    protected $table = 'videos';
    protected $returnType = Video::class;

    public function comments(): MorphMany
    {
        return $this->morphMany(CommentModel::class, 'commentable');
    }
}

The 'commentable' parameter is the morph name. The relation will look for commentable_type and commentable_id columns in the comments table.

Custom Column Names

public function comments(): MorphMany
{
    return $this->morphMany(
        CommentModel::class,
        'commentable',        // Morph name
        'custom_type',        // Custom type column
        'custom_id'           // Custom ID column
    );
}

Reading Data

Eager Loading

// Load post with comments
$post = model(PostModel::class)->with('comments')->find(1);
foreach ($post->comments as $comment) {
    echo $comment->content;
}

// Load video with comments
$video = model(VideoModel::class)->with('comments')->find(1);
foreach ($video->comments as $comment) {
    echo $comment->content;
}

Lazy Loading

$post = model(PostModel::class)->find(1);
foreach ($post->comments as $comment) {
    echo $comment->content; // Comments are loaded automatically
}

With Query Constraints

// Only approved comments
$post = model(PostModel::class)
    ->with('comments', fn($model) => $model->where('comments.approved', 1))
    ->find(1);

// Recent comments ordered by date
$post = model(PostModel::class)
    ->with('comments', fn($model) => $model->orderBy('created_at', 'DESC')->limit(10))
    ->find(1);

Writing Data

Creating a Single Record

Use save() to create a new morphed record:

$post = model(PostModel::class)->find(1);

// Save with array
$post->comments()->save([
    'content' => 'Great article!',
    'user_id' => 1,
]);

// Save with entity
$comment = new Comment();
$comment->content = 'Thanks for sharing!';
$comment->user_id = 2;
$post->comments()->save($comment);

The save() method automatically sets both commentable_type (to the parent model class) and commentable_id (to the parent ID).

Creating Multiple Records

Use saveMany() to create multiple morphed records at once:

$post = model(PostModel::class)->find(1);

// Save multiple comments
$post->comments()->saveMany([
    ['content' => 'First comment', 'user_id' => 1],
    ['content' => 'Second comment', 'user_id' => 2],
    ['content' => 'Third comment', 'user_id' => 3],
]);

// With entities
$comment1 = new Comment(['content' => 'Comment 1', 'user_id' => 1]);
$comment2 = new Comment(['content' => 'Comment 2', 'user_id' => 2]);
$post->comments()->saveMany([$comment1, $comment2]);

Updating Existing Records

$post = model(PostModel::class)->with('comments')->find(1);

// Update individual comment
$comment = $post->comments[0];
$comment->content = 'Updated comment';
$comment->save();

Available Methods

Method Description
save($data) Create or update a single morphed record
saveMany($data, $useTransaction = true) Create or update multiple morphed records

Transaction Control

By default, saveMany() uses a database transaction:

// Default: uses transaction
$post->comments()->saveMany([...]);

// Disable transaction for partial success
$post->comments()->saveMany([...], useTransaction: false);