![Building Ecommerce Website With PHP Lumen Laravel And Nuxtjs](https://webmobtuts.com/wp-content/uploads/2020/12/Building-Ecommerce-Website-With-PHP-Lumen-Laravel-And-Nuxtjs-2-800x400.jpg)
In this article of this series we will implement the Apis for the website home like menu categories, slider products, featured products, latest items and the featured categories.
In the previous part we extracted the template we downloaded into Nuxt pages and components, so in this part we will start working on the homepage Apis.
From the above figure we will implement the below Apis:
- Displaying the categories in the top menu as a tree.
- Displaying the slider section with the latest added products or products who have discount
- Displaying the latest products.
- Displaying some featured categories beside the latest products in each category
- Displaying the featured products.
Â
Preparing Apis In Lumen
Go to the Lumen project first update the Helpers trait add this method into the end of the trait:
app/traits/Helpers.php
public static function slugify($string, $separator = "-") { // Slug $string = mb_strtolower($string); $string = @trim($string); $replace = "/(\\s|\\" . $separator . ")+/mu"; $subst = $separator; $string = preg_replace($replace, $subst, $string); // Remove unwanted punctuation, convert some to '-' $puncTable = [ // remove "'" => '', '"' => '', '`' => '', '=' => '', '+' => '', '*' => '', '&' => '', '^' => '', '' => '', '%' => '', '$' => '', '#' => '', '@' => '', '!' => '', '<' => '', '>' => '', '?' => '', // convert to minus '[' => '-', ']' => '-', '{' => '-', '}' => '-', '(' => '-', ')' => '-', ' ' => '-', ',' => '-', ';' => '-', ':' => '-', '/' => '-', '|' => '-', '\\' => '-', ]; $string = str_replace(array_keys($puncTable), array_values($puncTable), $string); // Clean up multiple '-' characters $string = preg_replace('/-{2,}/', '-', $string); // Remove trailing '-' character if string not just '-' if ($string != '-') { $string = rtrim($string, '-'); } return $string; }
The slugify() method used to generate a slug in the url from specific string, we will use it with categories and products.
Also create a new trait in app/Traits called HomeApi. This trait will contain our the Apis for the homepage.
app/traits/HomeApi
<?php namespace App\Traits; use App\Models\Category; use App\Models\Product; trait HomeApi { public function getCategoryMenuTree() { } public function getSliderProducts() { } public function getLatestProducts() { } public function getFeaturedCategories() { } public function getFeaturedProducts() { } }
The above trait contain the home apis we need right now. We will invoke this trait methods from the category and product controllers, so let’s start with the categories apis.
Category Apis
Let’s start with the two methods related to category apis which are getCategoryMenuTree() and getFeaturedCategories(). Update the trait as shown below:
<?php namespace App\Traits; use App\Models\Category; use App\Models\Product; trait HomeApi { public function getCategoryMenuTree() { Category::getCategoryMenuTree(null, $output); return response()->json($output); } public function getSliderProducts() { } public function getLatestProducts() { } public function getFeaturedCategories() { $categories = Category::with(['products' => function($query) { $query->with('gallery') ->where('amount', '>', 0) ->has('gallery') ->orderBy('id', 'DESC'); }])->where('featured', 1) ->has('products') ->limit(9) ->orderBy('id', 'ASC') ->get(); return response()->json(['categories' => $categories]); } public function getFeaturedProducts() { } }
As shown in the above code getCategoryMenuTree() retrieves all categories in an array like tree identified by the children. The method invokes another static method Category::getCategoryMenuTree() from the Category model saving the result in $output variable.
The getFeaturedCategories() method retrieves the top 9 categories where featured = 1 and already has products, for this prurpose i use laravel with() method to eager load the relationship ‘products’ and apply a constraint to this relation as shown:
Category::with(['products' => function($query) { $query->with('gallery') ->where('amount', '>', 0) ->has('gallery') ->orderBy('id', 'DESC'); }])
Now update the Category model and add getCategoryMenuTree() method like this:
app/Models/Category.php
<?php namespace App\Models; use App\Traits\Helpers; use Illuminate\Database\Eloquent\Model; class Category extends Model { use Helpers; ..................... ..................... ..................... public static function getCategoryMenuTree($parent_id = null, &$output = []) { $categories = self::where('parent_id', $parent_id)->get(); foreach ($categories as $category) { $arr = [ 'id' => $category->id, 'title' => $category->title, 'path' => ($category->children->count() > 0 ? '#' : '/' . $category->id . '/' . self::slugify($category->title)), 'children' => [] ]; if($category->children->count() > 0) { self::getCategoryMenuTree($category->id, $arr['children']); } $output[] = $arr; } return $output; } }
As shown above i included the Helpers trait then the getCategoryMenuTree() is a recursive method which retrieves the categories from root (parent_id = 0) and appends the children if found into their parents and return them as array that contains the id, title, path, and children.
Product Apis
Now it’s time to implement the remaining apis related to product which are getSliderProducts(), getLatestProducts(), getFeaturedProducts(). So update the HomeApi trait as shown:
app/traits/HomeApi.php
<?php namespace App\Traits; use App\Models\Category; use App\Models\Product; trait HomeApi { /** * getCategoryMenuTree */ public function getCategoryMenuTree() { Category::getCategoryMenuTree(null, $output); return response()->json($output); } /** * get slider products */ public function getSliderProducts() { $allProducts = []; // get top 5 products who have active discount $products = Product::with('gallery')->select('id', 'title', 'description', 'price', 'discount', 'discount_start_date', 'discount_end_date') ->where('amount', '>', 0) ->whereNotNull('discount') ->where('discount', '>', 0) ->has('gallery') ->where(function($query) { $query->discountWithStartAndEndDates() ->orWhere->discountWithStartDate() ->orWhere->discountWithEndDate(); }) ->orderBy('id', 'DESC')->limit(5)->get(); foreach ($products as $product) { $allProducts[] = $product; } // if retrieved products < 5 then try to retrieve some other products and append them to the products array if(count($allProducts) < 5) { $otherProducts = Product::with('gallery') ->select('id', 'title', 'description', 'price', 'discount', 'discount_start_date', 'discount_end_date') ->where('amount', '>', 0) ->has('gallery') ->whereNotIn('id', array_column($allProducts, 'id')) ->orderBy('id', 'DESC') ->limit(5)->get(); foreach ($otherProducts as $product) { if (count($allProducts) == 5) { break; } $allProducts[] = $product; } } return response()->json(['products' => $allProducts], 200); } /** * get latest products */ public function getLatestProducts() { $categories = Category::all(); $products = []; foreach ($categories as $category) { if(count($products) == 8) { break; } $items = Product::with('gallery') ->select('id', 'title', 'description', 'price', 'discount', 'discount_start_date', 'discount_end_date') ->where('amount', '>', 0) ->where('category_id', $category->id) ->has('gallery') ->limit(1)->orderBy('id', 'DESC') ->get(); if($items && $items->count() == 1) { $products[] = $items[0]; } } return response()->json(['products' => $products]); } /** * getFeaturedCategories * */ public function getFeaturedCategories() { $categories = Category::with(['products' => function($query) { $query->with('gallery') ->where('amount', '>', 0) ->has('gallery') ->orderBy('id', 'DESC'); }])->where('featured', 1) ->has('products') ->limit(9) ->orderBy('id', 'ASC') ->get(); return response()->json(['categories' => $categories]); } /** * getFeaturedProducts */ public function getFeaturedProducts() { $products = Product::with('gallery') ->select('id', 'title', 'description', 'price', 'discount', 'discount_start_date', 'discount_end_date') ->where('amount', '>', 0) ->where('featured', 1) ->has('gallery') ->orderBy('id', 'DESC') ->limit(12)->get(); return response()->json(['products' => $products]); } }
As shown in this code the getSliderProducts() retrieves top 5 products who have active discount or the top 5 latest products. Here when retrieving products with active discount we have three cases the first if the discount start and end date is present,the second if the start date only is present and the last case is if the end date only is present. For this purpose i use a where() statement with callback to check for the discount like this:
->where(function($query) { $query->discountWithStartAndEndDates() ->orWhere->discountWithStartDate() ->orWhere->discountWithEndDate(); })
The callback approach is used when you need to add a condition between parenthesis when translated to SQL code. But you may wonder about something like $query->discountWithStartAndEndDates(), discountWithStartDate(), discountWithEndDate(), these methods we will be adding later in the Product model are called scopes. Laravel scope enable us to write complex queries using eloquent instead of using the query builder, you can read more about laravel scope in laravel docs.
So to check for the discount we should check the three cases, this is done as shown above using laravel scope approach.
The getLatestProducts() method retrieves the top 8 products provided that each product should be in a different category, so here i retrieved the categories first then inside the foreach statement i retrieved single product from this category.
The getFeaturedProducts() simply returns the top 12 featured products who have featured = 1
At this point let’s update the Product model
app/Models/Product.php
<?php namespace App\Models; use App\Traits\Helpers; use Illuminate\Database\Eloquent\Model; class Product extends Model { use Helpers; protected $appends = ["slug", "description_short", "title_short", "is_discount_active", "price_after_discount"]; public function category() { return $this->belongsTo(Category::class, 'category_id'); } public function user() { return $this->belongsTo(User::class, 'created_by'); } public function features() { return $this->hasMany(ProductFeature::class, 'product_id'); } public function gallery() { return $this->hasMany(ProductGallery::class, 'product_id'); } public function brand() { return $this->belongsTo(Brand::class, 'brand_id'); } public function getSlugAttribute() { return self::slugify($this->title); } public function getTitleShortAttribute() { return mb_substr($this->title, 0, 73, 'utf-8'); } public function getDescriptionShortAttribute() { return mb_substr(strip_tags($this->description), 0, 70, 'utf-8'); } public function getIsDiscountActiveAttribute() { if($this->discount > 0) { if($this->discount_start_date && $this->discount_end_date) { if($this->discount_start_date <= date("Y-m-d") && $this->discount_end_date >= date("Y-m-d")) { return true; } return false; } else if($this->discount_start_date && !$this->discount_end_date) { if($this->discount_start_date <= date("Y-m-d")) { return true; } return false; } else if(!$this->discount_start_date && $this->discount_end_date) { if($this->discount_end_date >= date("Y-m-d")) { return true; } return false; } } return false; } public function getPriceAfterDiscountAttribute() { if($this->getIsDiscountActiveAttribute()) { return number_format($this->price - ($this->price * ($this->discount / 100)), 1); } return number_format($this->price, 1); } public function scopeDiscountWithStartAndEndDates($query) { return $query->whereNotNull('discount_start_date') ->whereNotNull('discount_end_date') ->whereDate('discount_start_date', '<=', date('Y-m-d')) ->whereDate('discount_end_date', '>=', date('Y-m-d')); } public function scopeDiscountWithStartDate($query) { return $query->whereNotNull('discount_start_date') ->whereNull('discount_end_date') ->whereDate('discount_start_date', '<=', date('Y-m-d')); } public function scopeDiscountWithEndDate($query) { return $query->whereNotNull('discount_end_date') ->whereNull('discount_start_date') ->whereDate('discount_end_date', '>=', date('Y-m-d')); } }
As shown above i added a lot of stuff but i am not going over every detail. We have added the $appends member variable, this array tells Lumen to append these attributes when querying the product model, each attribute must correspond to a method for example slug attribute correspond to getSlugAttribute() this returns the slug of the product title, the same thing applies for the other attributes.
The other methods that start with scope as in scopeDiscountWithStartAndEndDates(), those methods that i used above when retrieving the slider products, each method accept $query parameter so that we can add constraints over it.
Updating Controllers
Let’s update the CategoriesController and ProductsController
Add these methods at the end of the CategoriesController
app/Http/Controllers/CategoriesController.php
<?php namespace App\Http\Controllers; use App\Models\Category; use App\Models\CategoryFeature; use App\Traits\Helpers; use App\Traits\HomeApi; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; class CategoriesController extends Controller { use Helpers, HomeApi; ........................... ........................... ........................... ........................... public function getCategoryMenuHtmlTree(Request $request) { return $this->getCategoryMenuTree(); } public function featuredCategories(Request $request) { return $this->getFeaturedCategories(); } }
Next add these methods at the end of the ProductsController
app/Http/Controllers/ProductsController.php
<?php namespace App\Http\Controllers; use App\Models\Product; use App\Models\ProductFeature; use App\Models\ProductGallery; use App\Traits\Helpers; use App\Traits\HomeApi; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; class ProductsController extends Controller { use Helpers, HomeApi; public function sliderProducts() { return $this->getSliderProducts(); } public function latestProducts() { return $this->getLatestProducts(); } public function featuredProducts() { return $this->getFeaturedProducts(); } }
Â
Updating Routes
The last thing is to routes/web.php
routes/web.php
<?php $router->get('/', function () use ($router) { return $router->app->version(); }); $router->group(['prefix' => 'api'], function () use ($router) { $router->post('/login', 'Auth\\LoginController@login'); $router->post('/register', 'Auth\\RegisterController@register'); $router->group(['prefix' => 'category'], function () use ($router) { $router->get('/', 'CategoriesController@index'); $router->get('/htmltree', 'CategoriesController@getCategoryHtmlTree'); $router->get('/menutree', 'CategoriesController@getCategoryMenuHtmlTree'); $router->get('/featured-categories', 'CategoriesController@featuredCategories'); $router->get('/{id}', 'CategoriesController@show'); }); $router->group(['prefix' => 'brand'], function () use ($router) { $router->get('/', 'BrandsController@index'); $router->get('/{id}', 'BrandsController@show'); }); $router->group(['prefix' => 'product'], function () use ($router) { $router->get('/', 'ProductsController@index'); $router->get('/slider-products', 'ProductsController@sliderProducts'); $router->get('/latest-products', 'ProductsController@latestProducts'); $router->get('/featured-products', 'ProductsController@featuredProducts'); $router->get('/{id}', 'ProductsController@show'); }); $router->group(['prefix' => 'user'], function () use ($router) { $router->get('/', 'UsersController@index'); $router->get('/{id}', 'UsersController@show'); }); $router->group(['middleware' => 'auth:api'], function () use ($router) { $router->get('/me', 'Auth\\LoginController@userDetails'); $router->get('/logout', 'Auth\\LoginController@logout'); $router->get('/check-login', 'Auth\\LoginController@checkLogin'); $router->group(['prefix' => 'category'], function () use ($router) { $router->post('/', 'CategoriesController@store'); $router->put('/{id}', 'CategoriesController@update'); $router->delete('/{id}', 'CategoriesController@destroy'); }); $router->group(['prefix' => 'brand'], function () use ($router) { $router->post('/', 'BrandsController@store'); $router->put('/{id}', 'BrandsController@update'); $router->delete('/{id}', 'BrandsController@destroy'); }); $router->group(['prefix' => 'product'], function () use ($router) { $router->post('/', 'ProductsController@store'); $router->put('/{id}', 'ProductsController@update'); $router->delete('/delete-image/{id}', 'ProductsController@destroyImage'); $router->delete('/{id}', 'ProductsController@destroy'); }); $router->group(['prefix' => 'user'], function () use ($router) { $router->post('/', 'UsersController@store'); $router->put('/{id}', 'UsersController@update'); $router->delete('/{id}', 'UsersController@destroy'); }); }); });
Now after we added the Apis in next part we will return to the Nuxt project and call those apis to display some dynamic content into the homepage.
Continue to Part12: Website Homepage Data Display