Laravel Many To Many Polymorphic Relationships With Localization Tutorial

Laravel Polymorphic Localization

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.

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 translations
  • product_refs for shared data between translation and its id will be in categorizables table in categorizables_id column and model name for categorizables_type column
  • categories table for translations
  • category_refs for shared data between translation and its id will be in categorizables table in category_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);

Leave a Reply

Your email address will not be published. Required fields are marked *