In this post i will go further and start with adding the categories CRUD into lumen, i will use resource based controllers like you have used in laravel to compose restful apis.

 

 

We will use laravel resource controllers because it’s better for restful apis so let’s go to the lumen project and add a new controller in app/Http/Controllers, i will name it CategoriesController.

Let’s add first a php trait that we will use to add some helper functions

app/Traits/Helpers.php

<?php


namespace App\Traits;


trait Helpers
{
    function superAdminCheck()
    {
        return auth()->user()->is_super_admin == 1;
    }
}

I added one method to check for the super admin user, now we need to use this helper method in our categories controller or we can create a middleware for this purpose.

Create app/Http/Middleware/SuperAdminMiddlware.php

<?php

namespace App\Http\Middleware;

use App\Traits\Helpers;
use Closure;

class SuperAdminMiddleware
{
    use Helpers;

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param string $methodsStr
     * @return mixed
     */
    public function handle($request, Closure $next, $methodsStr)
    {
        $methods_to_check = explode("-", $methodsStr);
        $current_route_method = explode("@", $request->route()[1]["uses"])[1];

        if(!in_array($current_route_method, $methods_to_check) || (in_array($current_route_method, $methods_to_check) && auth()->check() && $this->superAdminCheck())) {
            return $next($request);
        }

        return response('Operation denied.', 401);
    }
}

Register this middleware in bootstrap/app.php in routeMiddleware() method as shown:

$app->routeMiddleware([
     'auth' => App\Http\Middleware\Authenticate::class,
     'super_admin_check' => App\Http\Middleware\SuperAdminMiddleware::class,
 ]);

To use this middleware in any controller just call it in the constructor and pass the action names that we need to apply this middlware in as a string separated by dashes after the “:”, for example to apply this middlware in methods (store, update) we use $this->middleware(‘super_admin_check:store-update’).

 

 

app/Http/Controllers/CategoriesController

<?php


namespace App\Http\Controllers;


use App\Models\Category;
use App\Models\CategoryFeature;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class CategoriesController extends Controller
{
    public function __construct()
    {
        $this->middleware('super_admin_check:store-update-destroy');
    }

    public function index(Request $request)
    {
        
    }

    public function store(Request $request)
    {

    }

    public function show($id)
    {

    }

    public function update(Request $request, $id)
    {

    }

    public function destroy($id)
    {

    }
}

This is our categories controller with it’s basic operations [CREATE, READ, UPDATE, DELETE], so we we will start by fetching all categories in the index() method.

 

Listing All Categories

Update the index() method with the below code:

public function index(Request $request)
    {
        $query = Category::with('parent');

        $categories = $this->filterAndResponse($request, $query);

        return response()->json(['categories' => $categories], 200);
    }

Here we are fetching all categories, then we call another method filterAndResponse() we will see it shortly that filters the result according to certain criteria, finally we return the response as json.

Let’s add this protected method filterAndResponse() in the bottom of the controller:

protected function filterAndResponse(Request $request, \Illuminate\Database\Eloquent\Builder $query)
{
        if ($request->filter_by_id) {
            $query->where('id', $request->filter_by_id);
        }

        if ($request->filter_by_title) {
            $query->where('title', 'like', "%" . $request->filter_by_title . "%");
        }

        if ($request->filter_by_parent_id) {
            $query->where('parent_id', $request->filter_by_parent_id);
        }

        $categories = $query->paginate(10);
        return $categories;
    }

 

 

Creating Categories

Creating new categories in the store() method so update the store() as shown below:

public function store(Request $request)
    {
        $validator = Validator::make($request->only('title'), [
            'title' => 'required'
        ]);

        if ($validator->fails()) {
            return response()->json(['success' => 0, 'message' => 'Please fix these errors', 'errors' => $validator->errors()], 500);
        }

        $category = new Category();

        $category->title = $request->input('title');
        $category->description = $request->input('description');
        $category->parent_id = $request->input('parent_id') != '' ? $request->input('parent_id') : null;
        $category->featured = $request->input('featured');
        $category->save();

        $this->insertFeatures($request, $category);

        return response()->json(['success' => 1, 'message' => 'Created successfully', 'category' => $category], 201);
    }

As you see in this code it’s essential to check for the super admin user to prevent normal users from manipulating system categories.This check is required in create, updating and delete operations.

Next we make a simple validation on the category title, then we create a new category model and bind the request parameters to it.

After saving the category we do insert the category features if found, this is the helper method insertFeatures(). Let’s add this method also at the bottom of the controller

protected function insertFeatures($request, $category) {
        if($request->has('features')) {
            foreach ($request->input('features') as $feature) {
                if(!empty($feature["field_title"])) {
                    $categoryFeature = new CategoryFeature();
                    $categoryFeature->field_title = $feature["field_title"];
                    $categoryFeature->field_type = $feature["field_type"];

                    $category->features()->save($categoryFeature);
                }
            }
        }
    }

This method takes an array of features and the category model, then we iterate over each feature, save each feature using the features() relationship we have added previously into category model.

 

Displaying Single Category

To display single category details, this is done in the show() method passing in the category id:

public function show($id)
    {
        $category = Category::with('parent', 'features')->findOrFail($id);

        return response()->json(['category' => $category], 200);
    }

 

 

Updating Categories

The update() method the same as the store() method except that here we update an existing model instead of creating a new one so modify the update() method as follows:

public function update(Request $request, $id)
    {
        $category = Category::with('parent')->findOrFail($id);

        $validator = Validator::make($request->only('title'), [
            'title' => 'required'
        ]);

        if ($validator->fails()) {
            return response()->json(['success' => 0, 'message' => 'Please fix these errors', 'errors' => $validator->errors()], 500);
        }

        $category->title = $request->input('title');
        $category->description = $request->input('description');
        $category->parent_id = $request->input('parent_id') != '' ? $request->input('parent_id') : null;
        $category->featured = $request->input('featured');
        $category->save();

        $category->features()->delete();

        $this->insertFeatures($request, $category);

        return response()->json(['success' => 1, 'message' => 'Updated successfully', 'category' => $category], 200);
    }

 

Deleting Categories

The destroy() method used to delete a category model

public function destroy($id)
    {
        $category = Category::findOrFail($id);

        $category->delete();

        return response()->json(['success' => 1, 'message' => 'Deleted successfully'], 200);
    }

That’s it our puzzle is now complete, but we need to add another operation which we will use in the Nuxtjs project to display all categories as an html tree. We will use this when creating new category and selecting the parent.

So i created this method which is a recursive method that takes the parent_id and each time i check if there is any children thereby calling it again. Let’s add it to the CategoriesController:

public function getCategoryHtmlTree(Request $request, $parent_id = null)
    {
        $query = $categories = Category::where('parent_id', $parent_id);

        if($request->except_id) {
            $query->where('id', '!=', $request->except_id)->get();
        }

        $categories = $query->get();

        foreach ($categories as $category) {
            echo '<option value="' . $category->id . '">' . str_repeat('-', Category::getCategoryLevel($category->id)) . ' ' .  $category->title . '</option>';

            if ($category->children->count() > 0) {
                $this->getCategoryHtmlTree($request, $category->id);
            }
        }
    }

This method() can be used in create and update screens and also in the list all categories page to filter categories by parent_id, here we are fetching the top parent categories, then while iterating over them we check if the current category children count greater than 0, then calling the method again passing in the new category parent id.

You may wonder that i make use of another static method getCategoryLevel(), this method used to append the category with dashes – according to the nesting level in the tree, let’s add this method to the Category model

app/Models/Category.php

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    public function features()
    {
        return $this->hasMany(CategoryFeature::class, 'category_id');
    }

    public function products()
    {
        return $this->hasMany(Product::class, 'product_id');
    }

    public function children()
    {
        return $this->hasMany(self::class, 'parent_id');
    }

    public function parent()
    {
        return $this->belongsTo(self::class, 'parent_id');
    }

    public static function getCategoryLevel($category_id, $level = 0)
    {
        $category = self::find($category_id);

        if(!is_null($category->parent_id)) {
            $level++;
            return self::getCategoryLevel($category->parent_id, $level);
        } else {
            return $level;
        }
    }
}

 

The Full CategoriesController Source

<?php


namespace App\Http\Controllers;


use App\Models\Category;
use App\Models\CategoryFeature;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class CategoriesController extends Controller
{
    public function __construct()
    {
        $this->middleware('super_admin_check:store-update-destroy');
    }

    public function index(Request $request)
    {
        $query = Category::with('parent');

        $categories = $this->filterAndResponse($request, $query);

        return response()->json(['categories' => $categories], 200);
    }

    public function store(Request $request)
    {
        $validator = Validator::make($request->only('title'), [
            'title' => 'required'
        ]);

        if ($validator->fails()) {
            return response()->json(['success' => 0, 'message' => 'Please fix these errors', 'errors' => $validator->errors()], 500);
        }

        $category = new Category();

        $category->title = $request->input('title');
        $category->description = $request->input('description');
        $category->parent_id = $request->input('parent_id') != '' ? $request->input('parent_id') : null;
        $category->featured = $request->input('featured');

        $category->save();

        $this->insertFeatures($request, $category);

        return response()->json(['success' => 1, 'message' => 'Created successfully', 'category' => $category], 201);
    }

    public function show($id)
    {
        $category = Category::with('parent', 'features')->findOrFail($id);

        return response()->json(['category' => $category], 200);
    }

    public function update(Request $request, $id)
    {
        $category = Category::with('parent')->findOrFail($id);

        $validator = Validator::make($request->only('title'), [
            'title' => 'required'
        ]);

        if ($validator->fails()) {
            return response()->json(['success' => 0, 'message' => 'Please fix these errors', 'errors' => $validator->errors()], 500);
        }

        $category->title = $request->input('title');
        $category->description = $request->input('description');
        $category->parent_id = $request->input('parent_id') != '' ? $request->input('parent_id') : null;
        $category->featured = $request->input('featured');

        $category->save();

        $category->features()->delete();

        $this->insertFeatures($request, $category);

        return response()->json(['success' => 1, 'message' => 'Updated successfully', 'category' => $category], 200);
    }

    public function destroy($id)
    {
        $category = Category::findOrFail($id);

        $category->delete();

        return response()->json(['success' => 1, 'message' => 'Deleted successfully'], 200);
    }

    public function getCategoryHtmlTree(Request $request, $parent_id = null)
    {
        $query = $categories = Category::where('parent_id', $parent_id);

        if($request->except_id) {
            $query->where('id', '!=', $request->except_id)->get();
        }

        $categories = $query->get();

        foreach ($categories as $category) {
            echo '<option value="' . $category->id . '">' . str_repeat('-', Category::getCategoryLevel($category->id)) . ' ' .  $category->title . '</option>';

            if ($category->children->count() > 0) {
                $this->getCategoryHtmlTree($request, $category->id);
            }
        }
    }

    /**
     * @param Request $request
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    protected function filterAndResponse(Request $request, \Illuminate\Database\Eloquent\Builder $query): \Illuminate\Contracts\Pagination\LengthAwarePaginator
    {
        if ($request->filter_by_id) {
            $query->where('id', $request->filter_by_id);
        }

        if ($request->filter_by_title) {
            $query->where('title', 'like', "%" . $request->filter_by_title . "%");
        }

        if ($request->filter_by_parent_id) {
            $query->where('parent_id', $request->filter_by_parent_id);
        }

        $categories = $query->paginate(10);
        return $categories;
    }

    protected function insertFeatures($request, $category) {
        if($request->has('features')) {
            foreach ($request->input('features') as $feature) {
                if(!empty($feature["field_title"])) {
                    $categoryFeature = new CategoryFeature();
                    $categoryFeature->field_title = $feature["field_title"];
                    $categoryFeature->field_type = $feature["field_type"];

                    $category->features()->save($categoryFeature);
                }
            }
        }
    }
}

 

Updating Routes

At this stage the missing thing is to add the routes to categories so open routes/web.php and update it as shown here:

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('/{id}', 'CategoriesController@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');
        });
    });
});

Note here that category endpoint will be /api/category. There are some operations that require user authentication like store, update, and delete, and the other operations like list, show, and the html tree doesn’t require user authentication. You can experiment with those endpoints in postman.

 

 

Continue to Part5: Displaying Categories in Nuxt Admin Panel

 

Published by WebMobTuts

Co-Founder of WebMobTuts.com. I am a Senior Software Developer focused mainly on web development with PHP and mysql, i have worked with many web tools and frameworks like Laravel, Codeigniter, and also client side frameworks like React, Vuejs and angular, I love learning new tools and techs , also love writing technical blog posts related to web development fields.

Leave a comment

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

Exit mobile version