Advertisement

Google Ad Slot: content-top

Polymorphic One To Many


What is Polymorphic One To Many?

  • A Polymorphic One To Many relationship allows a model to belong to more than one type of model using a single association.
  • Polymorphic One To Many allows a single model (like Review) to belong to multiple other models (like Product, Hotel, Movie, Book) using the same relation.

That means:

  • A product can have many reviews.
  • A hotel can have many reviews.
  • A movie can have many reviews.
  • A book can have many reviews.
  • But all reviews are stored in one table (reviews), instead of creating separate tables like product_reviews, hotel_reviews, etc.

Flow diagram


Migration:

Use this command to generate a migration for creating a tables:

php artisan make:migration create_polymorphic_one_to_many_tables


Migration File

public function up(): void
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->text('description')->nullable();
        $table->timestamps();
    });

    Schema::create('hotels', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('location')->nullable();
        $table->timestamps();
    });

    Schema::create('movies', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->year('release_year')->nullable();
        $table->timestamps();
    });

    Schema::create('reviews', function (Blueprint $table) {
        $table->id();
        $table->text('review_text');
        $table->unsignedBigInteger('reviewable_id');   // Polymorphic ID
        $table->string('reviewable_type');             // Polymorphic Type
        $table->timestamps();
    });
}

public function down(): void
{
    Schema::dropIfExists('reviews');
    Schema::dropIfExists('movies');
    Schema::dropIfExists('hotels');
    Schema::dropIfExists('products');
}


Run Migration

php artisan migrate

Tables

products table
id name description created_at updated_at
hotels table
id name location created_at updated_at
movies table
id title release_year created_at updated_at
reviews table
id review_text reviewable_id reviewable_type created_at updated_at

Model:

Generate a model with Artisan:

php artisan make:model Review
php artisan make:model Product
php artisan make:model Hotel
php artisan make:model Movie

This generates 4 files Review.php , Product.php ,Hotel.php and Movie.php


Review Model

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Review extends Model
{
    protected $fillable = ['review_text'];

    public function reviewable()
    {
        return $this->morphTo();
    }
}


Product Model

namespace App\Models;

use App\Models\Review;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $fillable = ['name', 'description'];

    public function reviews()
    {
        return $this->morphMany(Review::class, 'reviewable');
    }
}


Hotel Model

namespace App\Models;

use App\Models\Review;
use Illuminate\Database\Eloquent\Model;

class Hotel extends Model
{
    protected $fillable = ['name', 'location'];

    public function reviews()
    {
        return $this->morphMany(Review::class, 'reviewable');
    }
}


Movie Model

namespace App\Models;

use App\Models\Review;
use Illuminate\Database\Eloquent\Model;

class Movie extends Model
{
    protected $fillable = ['title', 'release_year'];

    public function reviews()
    {
        return $this->morphMany(Review::class, 'reviewable');
    }
}



CRUD Examples

Create

use App\Models\Hotel;
use App\Models\Movie;
use App\Models\Product;

// Add review for a Product
$product = Product::create(['name' => 'Laptop', 'description' => 'A high-end laptop']);
$product->reviews()->create(['review_text' => 'Great Product!']);

// Add review for a Hotel
$hotel = Hotel::create(['name' => 'Grand Hotel', 'location' => 'New York']);
$hotel = Hotel::find(1);
$hotel->reviews()->create(['review_text' => 'Excellent stay!']);

// Add review for a Movie
$movie = Movie::create(['title' => 'Inception', 'release_year' => 2010]);
$movie->reviews()->create(['review_text' => 'Mind-blowing movie!']);
Output
products table
id name description created_at updated_at
1 Laptop A high-end laptop 2025-08-26 13:18:32 2025-08-26 13:18:32
hotels table
id name location created_at updated_at
1 Grand Hotel New York 2025-08-26 13:18:32 2025-08-26 13:18:32
movies table
id title release_year created_at updated_at
1 Inception 2010 2025-08-26 13:18:32 2025-08-26 13:18:32
reviews table
id review_text reviewable_id reviewable_type created_at updated_at
1 Great Product! 1 App\Models\Product 2025-08-26 13:18:32 2025-08-26 13:18:32
2 Excellent stay! 1 App\Models\Hotel 2025-08-26 13:18:32 2025-08-26 13:18:32
3 Mind-blowing movie! 1 App\Models\Movie 2025-08-26 13:18:32 2025-08-26 13:18:32

Read

use App\Models\Hotel;
use App\Models\Movie;
use App\Models\Product;

$product = Product::find(1);
foreach ($product->reviews as $review) {
  echo $review->review_text;
}

// Get hotel reviews
$hotel = Hotel::find(1);
foreach ($hotel->reviews as $review) {
  echo $review->review_text;
}

// Get movie reviews
$movie = Movie::find(1);
foreach ($movie->reviews as $review) {
  echo $review->review_text;
}

// Get Review with Parent
$review = Review::find(1);
echo $review->reviewable->name;

Update Review

use App\Models\Hotel;
use App\Models\Movie;
use App\Models\Product;

// Find review and update
$review = Review::find(1);
$review->update(['content' => 'Updated review content']);

// Or Update via Product
$product = Product::find(1);
$product->reviews()->where('id', 1)->update(['review_text' => 'Updated review content']);

// Or Update via Hotel
$hostel = Hotel::find(1);
$hostel->reviews()->where('id', 2)->update(['review_text' => 'Updated review content']);

// Or Update via Movie
$movie = Movie::find(1);
$movie->reviews()->where('id', 3)->update(['review_text' => 'Updated review content']);

Delete Review

use App\Models\Hotel;
use App\Models\Movie;
use App\Models\Product;

// Delete a review
$review = Review::find(1);
$review->delete();

// Delete all reviews for a Product
$product = Product::find(1);
$product->reviews()->where('id', 1)->delete();  // where condition delete
$product->reviews()->delete();                  // Delete all reviews

// Delete all reviews for a Hotel
$hotel = Hotel::find(1);
$hotel->reviews()->where('id', 2)->delete();  // where condition delete
$hotel->reviews()->delete();                  // Delete all reviews

// Delete all reviews for a Movie
$movie = Movie::find(1);
$movie->reviews()->where('id', 3)->delete();  // where condition delete
$movie->reviews()->delete();

Eager loading (optimizes queries)

use App\Models\Hotel;
use App\Models\Movie;
use App\Models\Product;

$product = Product::with('reviews')->find(1);
foreach ($product->reviews as $review) {
  echo $review->review_text;
}

// Get hotel reviews
$hotel = Hotel::with('reviews')->find(1);
foreach ($hotel->reviews as $review) {
  echo $review->review_text;
}

// Get movie reviews
$movie = Movie::with('reviews')->find(1);
foreach ($movie->reviews as $review) {
  echo $review->review_text;
}

// Get Review with Parent
$review = Review::with('reviewable')->find(1);
echo $review->reviewable->name;

Always use with() when fetching related data in bulk.