Laravel many to many polymorphic Eloquent relationship tutorial
Laravel provides eloquent relationship which provides powerful query builders. In Laravel, eloquent relationships are defined in model classes. Laravel eloquent provides easy ways to create common relationships:
- one to one
- one to many
- many to many
- has one of many
- has one through
- has many through
- one to one polymorphic
- one to many polymorphic
- many to many polymorphic
In this article, we will discuss on Laravel's many to many polymorphic eloquent relationship. This is slightly complicated than many to many relationship. For example, in a e-commerce application, an order and wishlist may have multiple items. Same way, an item may be associated with multiple orders and wishlists model. So, a multiple Item
model may be associated with multiple Order
model as well as multiple Wishlist
model.
Example:
Now, let's create an simple example with these models. In this example, we will build relationship between Item
model with Order
and Wishlist
model. We assume that you have created fresh Laravel application. We also assume that you have confiured database connection.
Database tables
In this relationship, we will need four database tables: orders, wishlists, items
and itemables
. An items
table will be associated with orders
and wishlists
table using itemables
intermediate table.
Migration
We need to create four migration table. Run the following four commands into Terminal to create migration classes at database/migrations
directory.
php artisan make:migration create_orders_table
php artisan make:migration create_wishlists_table
php artisan make:migration create_items_table
php artisan make:migration create_itemables_table
Below are the migration table fields for these table:
orders migration
In the orders migration, we have defined the following fields:
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->integer('user_id');
$table->string('number');
$table->timestamps();
});
}
wishlists migration
Same migration fields are for wishlists table.
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('wishlists', function (Blueprint $table) {
$table->id();
$table->integer('user_id');
$table->string('number');
$table->timestamps();
});
}
items migration
The items migration generally contains name and price fields.
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('items', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->decimal('price', 10, 2);
$table->timestamps();
});
}
itemables migration
Now we have one intermediate table which stores relationship between items and orders/wishlists tables.
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('itemables', function (Blueprint $table) {
$table->id();
$table->integer('item_id');
$table->integer('itemable_id');
$table->string('itemable_type');
$table->timestamps();
});
}
Let's look at the migration fields. In the itemables
table, We have item_id
, itemable_id
and itemable_type
fields. The item_id
will store id value of items
table, itemable_id
will store id value of orders
or wishlists
table and itemable_type
will store the Order
or Wishlist
class name like App\Models\Order
or App\Models\Wishlist
.
Now run the migrate command to create tables into database.
php artisan migrate
Model
Laravel models are located at app/Models
directory. Now, we also need to create the model classes for these tables using following Artisan commands one by one into Terminal.
php artisan make:model Order
php artisan make:model Wishlist
php artisan make:model Item
After we have created the model classes, we will define relationship. Order
and Wishlist
both model will contain items()
method that will call eloquent's morphToMany()
method.
First create items()
method into Order
model which will return morphToMany()
method. The morphToMany()
method accept two arguments: The name of related model and the name we have that we used for intermediate table name which we will refer to the relationship. In our cases, it is "itemable
".
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
use HasFactory;
/**
* Get all of the items for the order.
*/
public function items()
{
return $this->morphToMany(Item::class, 'itemable');
}
}
Same way add, items method into Wishlist model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Wishlist extends Model
{
use HasFactory;
/**
* Get all of the items for the wishlist.
*/
public function items()
{
return $this->morphToMany(Item::class, 'itemable');
}
}
Now we need to define reverse relationship. For example, we have an item model record and we want to get all orders and wishlists records that contains the selected item.
For that we need to create orders()
and wishlists()
method into Item model that will return eloquent's morphedByMany()
method. The morphedByMany()
method accept two arguments: The name of related model and the name we have that we used for intermediate table name which we will refer to the relationship. In our cases, it is "itemable
".
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Item extends Model
{
use HasFactory;
/**
* Get all of the orders that includes this item.
*/
public function orders()
{
return $this->morphedByMany(Order::class, 'itemable');
}
/**
* Get all of the wishlists that includes this item.
*/
public function wishlists()
{
return $this->morphedByMany(Wishlist::class, 'itemable');
}
}
Route
In routes/web.php
file, we have added two new routes for relationship testing.
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ItemController;
Route::get('/order-items', [ItemController::class, 'orderItems']);
Route::get('/wishlist-items', [ItemController::class, 'wishlistItems']);
Controller
As we have added route, also create ItemController
using following command.
php artisan make:controller ItemController
Open the controller at app/Http/Controllers/ItemController
and create orderItems()
and wishlistItems()
method. To retrieve the items of the order or wishlist, you can access items
property of the model object.
<?php
namespace App\Http\Controllers;
use App\Models\Item;
use App\Models\Order;
use App\Models\Wishlist;
use Illuminate\Http\Request;
class ItemController extends Controller
{
/**
* get all items of order
*
* @return \Illuminate\Http\Response
*/
public function orderItems()
{
$order_items = Order::find(1)->items;
dd($order_items);
}
/**
* get all items of wishlist
*
* @return \Illuminate\Http\Response
*/
public function wishlistItems()
{
$wishlist_items = Wishlist::find(1)->items;
dd($wishlist_items);
}
}
This will return all items record with related model. If the relationship not found, the eloquent will return null record.
In the reverse, you may already have the item model record and you may want to retrieve all orders and wishlists of the item, you may access orders or wishlists property of item model.
/**
* return all orders and wishlists of item
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$item = Item::find(2);
// all orders that include item
$item_orders = $item->orders
// all wishlists that include item
$item_wishlists = $item->wishlists
}
This will return parent model record which is associated with item model. If no records found, it will return null records.
I hope this will help you to understand many to many polymorphic relationship.
Copyright 2023 ErrorGram