Skip to content

Morph one

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

When to Use

Use MorphOne when different models can have the same type of single related record. Common examples:

  • Post or Video has one Image (featured image)
  • User or Company has one Address
  • Product or Service has one Rating

Database Structure

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

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

images
------
id               (primary key)
imageable_type   (stores the parent model class name)
imageable_id     (stores the parent model ID)
url
alt_text

The imageable_type and imageable_id columns work together to identify which parent record the image belongs to.

Defining the Relation

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

class PostModel extends Model
{
    use HasRelations;

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

    public function image(): MorphOne
    {
        return $this->morphOne(ImageModel::class, 'imageable');
    }
}
class VideoModel extends Model
{
    use HasRelations;

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

    public function image(): MorphOne
    {
        return $this->morphOne(ImageModel::class, 'imageable');
    }
}

The 'imageable' parameter is the morph name. The relation will look for imageable_type and imageable_id columns in the images table.

Custom Column Names

public function image(): MorphOne
{
    return $this->morphOne(
        ImageModel::class,
        'imageable',          // Morph name
        'custom_type',        // Custom type column
        'custom_id'           // Custom ID column
    );
}

Reading Data

Eager Loading

// Load post with image
$post = model(PostModel::class)->with('image')->find(1);
echo $post->image->url;

// Load video with image
$video = model(VideoModel::class)->with('image')->find(1);
echo $video->image->url;

// Load multiple posts with images
$posts = model(PostModel::class)->with('image')->findAll();
foreach ($posts as $post) {
    if ($post->image) {
        echo $post->image->url;
    }
}

Lazy Loading

$post = model(PostModel::class)->find(1);
echo $post->image->url; // Image is loaded automatically

With Query Constraints

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

Writing Data

Use save() to create a new morphed record:

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

// Save with array
$post->image()->save([
    'url' => 'https://example.com/image.jpg',
    'alt_text' => 'Featured image',
]);

// Save with entity
$image = new Image();
$image->url = 'https://example.com/image2.jpg';
$image->alt_text = 'Another image';
$post->image()->save($image);

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

Updating an Existing Record

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

// Update and save
$post->image->alt_text = 'Updated alt text';
$post->image->save();

Available Methods

Method Description
save($data) Create or update the morphed record