Skip to content

Constrained polymorphic relationships for Laravel Eloquent - limit morphTo relationships to specific model types

License

Notifications You must be signed in to change notification settings

pindab0ter/constrained-morph-to-for-laravel

Repository files navigation

Constrained MorphTo for Laravel

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

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.

The Problem

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 type

The Solution

This 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

Requirements

  • PHP 8.2+
  • Laravel 10.48+, 11.x, or 12.x

Installation

Install the package via Composer:

composer require pindab0ter/constrained-morph-to-for-laravel

Usage

Basic Usage

  1. Add the HasConstrainedMorphTo trait 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
            );
        }
    }
  2. 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
  3. 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)

Multiple Allowed Types

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 null

Advanced Usage

You 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)
    );
}

Changelog

Please see CHANGELOG for more information on what has changed recently.

License

The MIT License (MIT). Please see License File for more information.

About

Constrained polymorphic relationships for Laravel Eloquent - limit morphTo relationships to specific model types

Resources

License

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published

Languages