Let’s create Many To Many Polymorphic relations Between Multi-language categories and products.
Many To Many Polymorphic relations are slightly complicated. So what about adding Localization to this relationship?! we are making a toxic relationship here! you may feel unsupported, misunderstood, depressed, and angry while coding. But don’t worry let’s make it right and easy. You deserve a happy life.
Before we start you can check out those articles for more information about Localization and the example we use here.
- Laravel 9 Localization With Multi-languages Database Tutorial.
- Laravel E-commerce Product Model With Price and Discount Concept.
Understanding Many To Many Polymorphic With Localization
Many To Many Polymorphic is about making a pivot table that carries 2 id and a model’s name something like this
categorizables table:
category_id
categorizables_id
categorizables_type
category_id
for the category id and categorizables_id
could hold a post id or video id or product id but we should add the model name in categorizables_type
like that App\Models\Product
or App\Models\Video
so that we know what the categorizables_id is for And thanks god, Laravel does that for us.
Here we say Many To Many means one category can have a relationship with many products and posts and a product or post can have a relationship with many categories.
For localizing our products, for example, we added another table for translations. So products and their translations have a one-to-many relationship and the same thing for categories. so here comes the complication. So that we need to create an attribute in the product translations model to get us the category with the right translation.
What are we going to do?
We will create two tables the first one for store the product data in multi-language such as Arabic and English and we will name this table ‘products’ then we create the table for storing the shared data such as price, discount, and images and we name it ‘product_refs’. And we will do the same with categories.
products
table for translationsproduct_refs
for shared data between translation and its id will be incategorizables
table incategorizables_id
column and model name forcategorizables_type
column- categories table for translations
category_refs
for shared data between translation and its id will be incategorizables
table incategory_ref_id
column.
<?php
//
return new class extends Migration
{
//
public function up()
{
Schema::create('product_refs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->decimal('price', $precision = 8, $scale = 2)->nullable();
$table->string('image')->nullable();
$table->timestamps();
});
}
//
};
<?php
//
return new class extends Migration
{
//
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->bigIncrements('id')->index();
$table->string('lang_id')->nullable();
$table->string('name')->nullable();
$table->string('slug')->nullable();
$table->unsignedBigInteger('ref_id')->nullable() ;
$table->timestamps();
});
}
//
};
<?php
//
return new class extends Migration
{
//
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->bigIncrements('id')->index();
$table->string('lang_id')->nullable();
$table->string('name')->nullable();
$table->string('slug')->nullable();
$table->unsignedBigInteger('ref_id')->nullable() ;
$table->timestamps();
});
}
//
};
<?php
//
return new class extends Migration
{
//
public function up()
{
Schema::create('category_refs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('image')->nullable();
$table->timestamps();
});
}
//
};
<?php
//
return new class extends Migration
{
{
Schema::create('categorizables', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('category_ref_id');
$table->unsignedBigInteger('categorizables_id');
$table->string('categorizables_type');
$table->timestamps();
});
}
//
};
Many To Many Polymorphic Configuration
After we created our tables, we will add the relationship to our models. as the following.
class ProductRef extends Model
{
//
public function categories()
{
return $this->morphToMany(categoryRef::class, 'categorizables');
}
//
}
class CategoryRef extends Model
{
//
public function products()
{
return $this->morphedByMany(ProductRef::class,'categorizables');
}
//
}
We just configurated the relation, so now we need to create functions that get us the translations.
class CategoryRef extends Model
{
//
public function products()
{
return $this->morphedByMany(ProductRef::class,'categorizables');
}
public function children()
{
return $this->hasMany(Category::class,'ref_id');
}
//
}
class Product extends Model
{
public function ref()
{
return $this->belongsTo(ProductRef::class,'ref_id');
}
public function getCategoriesAttribute(){
$categoryRefs= $this->ref->categories;
$categories = [];
foreach($categoryRefs as $categoryRef){
$cate = $categoryRef->children()->where('lang_id',$this->lang_id)->first(['name','slug','id']);
array_push($categories,$cate);
}
return $categories;
}
}
We created the ref()
function to get us the productRef record then added the getCategoriesAttribute()
function to add categories
attribute to the product model. Inside this function, we get the categoryRefs
and loop them with foreach
to get the category transition according to the product lang_id
then we push it to the $categories
array and return it. Of course, you need to cache the product model to keep your website fast. Here is how you can do this Laravel Cache Redis And File Store Tutorial With Example.
In the product controller, we attach an array of categoryRef
ids to the productRef
model as the following
$productRef->categories()->attach($request->categories);