Skip to content

Morph one of many

A specialized polymorphic one-to-one relationship where a parent model has multiple morphed records, but only one is considered active or relevant based on a specific condition.

When to Use

Use Morph One of Many when different parent types need to retrieve a single representative record from their polymorphic collection. Common examples:

  • Post or Video's latest Comment
  • Product or Service's highest rated Review
  • User or Organization's most recent Activity
  • Article or Tutorial's newest Revision

Database Structure

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

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

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

Same structure as MorphMany, but we retrieve only one record based on specific criteria.

Defining the Relation

use Michalsn\CodeIgniterRelations\Relations\MorphOne;
use Michalsn\CodeIgniterRelations\Traits\HasRelations;
use Michalsn\CodeIgniterRelations\Enums\OrderTypes;

class PostModel extends Model
{
    use HasRelations;

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

    public function latestComment(): MorphOne
    {
        return $this->morphOne(CommentModel::class, 'commentable')->latestOfMany();
    }

    public function oldestComment(): MorphOne
    {
        return $this->morphOne(CommentModel::class, 'commentable')->oldestOfMany();
    }

    public function bestComment(): MorphOne
    {
        return $this->morphOne(CommentModel::class, 'commentable')
            ->ofMany('rating', OrderTypes::DESC);
    }
}
class VideoModel extends Model
{
    use HasRelations;

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

    public function latestComment(): MorphOne
    {
        return $this->morphOne(CommentModel::class, 'commentable')->latestOfMany();
    }

    public function bestComment(): MorphOne
    {
        return $this->morphOne(CommentModel::class, 'commentable')
            ->ofMany('rating', OrderTypes::DESC);
    }
}

Available Methods

latestOfMany()

Returns the most recent morphed record. If the model uses timestamps, it orders by createdField, otherwise by primaryKey.

public function latestComment(): MorphOne
{
    return $this->morphOne(CommentModel::class, 'commentable')->latestOfMany();
}

oldestOfMany()

Returns the oldest morphed record. If the model uses timestamps, it orders by createdField, otherwise by primaryKey.

public function oldestComment(): MorphOne
{
    return $this->morphOne(CommentModel::class, 'commentable')->oldestOfMany();
}

ofMany($column, $order)

Returns one morphed record ordered by a specific column and direction.

Parameters: - $column - The column to order by - $order - OrderTypes::ASC or OrderTypes::DESC

public function bestComment(): MorphOne
{
    return $this->morphOne(CommentModel::class, 'commentable')
        ->ofMany('rating', OrderTypes::DESC);
}

public function lowestRatedReview(): MorphOne
{
    return $this->morphOne(ReviewModel::class, 'reviewable')
        ->ofMany('rating', OrderTypes::ASC);
}

Reading Data

Eager Loading

// Get post with latest comment
$post = model(PostModel::class)->with('latestComment')->find(1);
echo $post->latestComment->content;

// Get video with best comment
$video = model(VideoModel::class)->with('bestComment')->find(1);
echo "Best comment: {$video->bestComment->content} (Rating: {$video->bestComment->rating})";

// Load multiple relations
$post = model(PostModel::class)
    ->with(['latestComment', 'bestComment'])
    ->find(1);

Lazy Loading

$post = model(PostModel::class)->find(1);
echo $post->latestComment->content; // Latest comment is loaded automatically

Writing Data

Morph One of Many is primarily a read-only pattern. To create or update records, use the standard MorphMany relation:

class PostModel extends Model
{
    // Standard relation for writing
    public function comments(): MorphMany
    {
        return $this->morphMany(CommentModel::class, 'commentable');
    }

    // "Morph One of Many" for reading
    public function latestComment(): MorphOne
    {
        return $this->morphOne(CommentModel::class, 'commentable')->latestOfMany();
    }
}

// Create comments through standard relation
$post->comments()->save(['content' => 'New comment', 'rating' => 5]);

// Read through "morph one of many"
echo $post->latestComment->content;

Common Patterns

Multiple Criteria Across Different Parent Types

class PostModel extends Model
{
    public function latestComment(): MorphOne
    {
        return $this->morphOne(CommentModel::class, 'commentable')->latestOfMany();
    }

    public function latestApprovedComment(): MorphOne
    {
        return $this->morphOne(CommentModel::class, 'commentable')
            ->latestOfMany();
    }

    public function highestRatedComment(): MorphOne
    {
        return $this->morphOne(CommentModel::class, 'commentable')
            ->ofMany('rating', OrderTypes::DESC);
    }
}

class VideoModel extends Model
{
    // Same relations work for videos too
    public function latestComment(): MorphOne
    {
        return $this->morphOne(CommentModel::class, 'commentable')->latestOfMany();
    }
}

With Query Constraints

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