Let’s create a Multi-languages product table, model, and controller, and Calculate the price according to a discount for a period of time automatically.
We will focus in this tutorial on creating the product model only. If you want to learn more about Laravel localization and its middleware and category model. please check this article here Laravel 9 Localization With Multi-languages Database Tutorial
What we are going to code:
- Multi-languages product table so we can add as many translations as we want.
- product reference table for storing price, discount, and other shared data between all translations.
- Adding an attribute for calculating the current price for a product.
- Creating Laravel API resources for a better JSON response.
Creating Product Tables.
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’.
php artisan make:model ProductRef -m
php artisan make:model Product -m
The above command will create the models and migrations for our tables.
Let’s create our tables’ columns:
The product table will store the product’s name and slug and all SEO inputs plus the images.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
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->text('video_url')->nullable();
$table->string('image')->nullable();
$table->string('thumbnailsm')->nullable();
$table->string('thumbnailmd')->nullable();
$table->string('thumbnailxl')->nullable();
$table->string('cover')->nullable();
$table->string('cothumbnailsm')->nullable();
$table->string('cothumbnailmd')->nullable();
$table->string('cothumbnailxl')->nullable();
$table->string('alt_image')->nullable();
$table->string('alt_cover')->nullable();
$table->string('short_decription')->nullable();
$table->string('keywords')->nullable();
$table->string('robot')->nullable();
$table->longText('description')->nullable();
$table->unsignedBigInteger('ref_id')->nullable() ;
$table->unsignedBigInteger('user_id');
$table->string('status')->nullable();
$table->timestamp('publish_date')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('products');
}
};
The product reference will store all the shared data.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('product_refs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('status')->nullable();
$table->decimal('price', $precision = 8, $scale = 2)->nullable();
$table->decimal('discount', $precision = 8, $scale = 2)->nullable();
$table->decimal('shipping_fee', $precision = 8, $scale = 2)->nullable();
$table->timestamp('discount_start')->nullable();
$table->timestamp('discount_end')->nullable();
$table->unsignedInteger('stock')->nullable() ;
$table->string('barcode')->nullable();
$table->unsignedInteger('sort')->nullable();
$table->unsignedInteger('featured')->nullable();
$table->unsignedInteger('views')->nullable();
$table->unsignedInteger('rate')->nullable();
$table->unsignedInteger('rate_count')->nullable();
$table->unsignedBigInteger('brand_id')->nullable();
$table->unsignedBigInteger('category_id')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('product_refs');
}
};
Let’s migrate all the tables.
php artisan migrate
Product Models
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $guarded =['id'];
public function ref()
{
return $this->belongsTo(ProductRef::class,'ref_id');
}
public function user()
{
return $this->belongsTo(User::class,'user_id');
}
public function getCategoryAttribute()
{
$categoryRef= $this->ref->category;
return $categoryRef->children()->where('lang_id',$this->lang_id)
->first(['name','slug','id']);
}
}
Let’s explain the product model code.
ref()
for getting the product’s shared data fromprodcut_refs
table usingref_id
.- user() for the admin who made the last update.
- getCategoryAttribute() to return the product’s category.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
class ProductRef extends Model implements HasMedia
{
use HasFactory;
protected $guarded =['id'];
public function childern()
{
return $this->hasMany(Product::class,'ref_id');
}
public function category()
{
return $this->belongsTo(categoryRef::class, 'category_id');
}
public function getHasDiscountAttribute()
{
if(!empty($this->discount)){
$check = Carbon::now()->between($this->discount_start,$this->discount_end);
}
return $this->discount;
}
public function getPriceForSellingAttribute()
{
if($this->HasDiscount) { return ($this->price - ($this->price * $this->discount/100));} ;
return $this->price;
}
public function getNameAttribute(){
$item_name = $this->childern()->where('lang_id',app()->getLocale())->first('name');
return $item_name?->name;
}
}
The productRef
class has all the work, let’s explain.
childern
() it’s for getting all the available translations of the product.- category() to return the product category.
getHasDiscountAttribute()
for checking if the product is in a discount duration.PriceForSelling
() it gets the current price after the discount or the product price to sell.- getNameAttribute() it’s get’s the product name according to the app language, it will be used for the admin panel more than the end user.
Product API Resources
Let’s create the API resource for our product model for a better JSON response.
php artisan make:resource ProductResource
It will create the resource file app\Http\Resources\ProductResource.php
. Let’s modify our file as the following.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Storage;
use App\Models\Product;
class ProductResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'lang_id' => $this->lang_id,
'slug' => $this->slug,
'name' => $this->name,
'category' => $this->Category,
'image' => $this->image? Storage::disk('imagesfp')->url($this->image):'',
'thumbnailsm' => $this->thumbnailsm? Storage::disk('imagesfp')->url($this->thumbnailsm):'',
'thumbnailmd' => $this->thumbnailmd? Storage::disk('imagesfp')->url($this->thumbnailmd):'',
'thumbnailxl' => $this->thumbnailxl? Storage::disk('imagesfp')->url($this->thumbnailxl):'',
'cover' => $this->cover? Storage::disk('imagesfp')->url($this->cover):'',
'cothumbnailsm' => $this->cothumbnailsm? Storage::disk('imagesfp')->url($this->cothumbnailsm):'',
'cothumbnailmd' => $this->cothumbnailmd? Storage::disk('imagesfp')->url($this->cothumbnailmd):'',
'cothumbnailxl' => $this->cothumbnailxl? Storage::disk('imagesfp')->url($this->cothumbnailxl):'',
'alt_image' => $this->alt_image,
'alt_cover' => $this->alt_cover,
'short_decription' => $this->short_decription,
'key_words' => $this->key_words,
'robot' => $this->robot,
'description' => $this->description,
'publish_date' => $this->publish_date,
'sort' => $this->ref->sort,
'featured' => $this->ref->featured,
'views' => $this->ref->views,
'rate' => $this->ref->rate,
'rate_count' => $this->ref->rate_count,
'status' => $this->status,
'user_id' => $this->user_id,
'user_name' => $this->user?->FullName,
'updated_at' => $this->updated_at,
'refName' => $this->ref?->Name,
'price' => $this->ref?->price,
'priceForSelling' => $this->ref?->PriceForSelling,
'hasDiscount' => $this->ref?->HasDiscount,
'discount' => $this->ref?->discount,
'discount_start' =>$this->ref->discount_start?
date('Y-m-d\TH:i:s', strtotime($this->ref->discount_start)):"" ,
'discount_end' => $this->ref->discount_end?
date('Y-m-d\TH:i:s', strtotime($this->ref->discount_end)):"" ,
'barcode' => $this->ref?->barcode,
'stock' => $this->ref?->stock,
'updated_at' => $this->updated_at,
'translations'=> Product::where('ref_id',$this->ref_id)->
where('id','!=',$this->id)->get(['id','lang_id']),
];
}
}
Let’s explain the API resource:
- we create a property for every single data we need.
- We get the category by
getCategoryAttribute()
in the product model. - We used the
Storage
class for getting the URLs of the images. priceForSelling
is for the current price of the product from thegetPriceForSellingAttribute()
ofProductRef
model.- We have a flag here
hasDiscount
so we can know if there is a discount applied to the product price. - Finally, we get the translation in
translations
To make sure your website is fast you should cache the data. so you don’t have to do all the queries and processing with every single request. learn more about using Laravel cache here Laravel Cache Redis And File Store Tutorial With Example.
One thought on “Laravel E-commerce Product Model With Price and Discount Concept.”