Build Category đa cấp trong Laravel

Sau 1 thời gian nghỉ code để tập trung làm việc cho công ty đa quốc gia trong chuyên ngành đa cấp và bây giờ ngay lúc này đây tôi đã quay trở lại và đồi bại hơn xưa với chủ đề Category đa cấp trong Laravel
Về hướng giải quyết có nhiều bạn sẽ nghĩ đến việc dùng đệ quy để làm . Đệ quy là phương pháp không được khuyến khích dùng cho lắm vì nó rất hao tốn tài nguyên. Vậy có cách nào khác tối ưu hơn để giải quyết việc này?
Hướng tôi đang muốn đề cập cho các bạn sau đây là dùng tree - tức là cấu trúc database sẽ theo cấu trúc cây nhị phân - 1 phương pháp khác để giải quyết bài toán này đó là ứng dụng của mô hình nested set model để xây dựng danh mục đa cấp, mô hình tạm thời nó sẽ thế này:

  • Ở đây Clothing là gốc và nó sẽ có mẹ tức parent_id là null, _lft = 1, rgt = 20, việc sắp xếp này ko phải ko theo quy luật nào, mà là từ trái qua phải, rồi từ dưới luồn lách lên trên rồi lại từ trái qua phải dưới lên trên ( ^^) cho đến hết tức là Sun Dresses và vòng về Clothing. Nói đến đây tôi tự cảm thấy nó khá loàng ngoàng nên quyết định quay lại với chủ đề chính:
    Tôi đang dùng thư viện này để build
    https://github.com/lazychaser/laravel-nestedset
    Bạn nào thấy bài viết có ích hãy Donate cho tôi và tôi sẽ chuyển khoản lại cho tác giả của lib trên.
composer require kalnoy/nestedset

migrate cho nó

public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->increments('id');
            $table->string("name");
            NestedSet::columns($table);
            $table->timestamps();
        });
    }

bạn nhớ use thêm

use Kalnoy\Nestedset\NestedSet;

seed db cho em nó, phần này tôi lấy trong soucre code của ổng viết lib bên trên

public function run()
    {
        $data =  array(
            array('id' => 1, 'name' => 'store', '_lft' => 1, '_rgt' => 20, 'parent_id' => null, 'category_type' => 1),
                array('id' => 2, 'name' => 'notebooks', '_lft' => 2, '_rgt' => 7, 'parent_id' => 1, 'category_type' => 1),
                    array('id' => 3, 'name' => 'apple', '_lft' => 3, '_rgt' => 4, 'parent_id' => 2, 'category_type' => 2),
                    array('id' => 4, 'name' => 'lenovo', '_lft' => 5, '_rgt' => 6, 'parent_id' => 2, 'category_type' => 1),
                array('id' => 5, 'name' => 'mobile', '_lft' => 8, '_rgt' => 19, 'parent_id' => 1, 'category_type' => 1),
                    array('id' => 6, 'name' => 'nokia', '_lft' => 9, '_rgt' => 10, 'parent_id' => 5, 'category_type' => 1),
                    array('id' => 7, 'name' => 'samsung', '_lft' => 11, '_rgt' => 14, 'parent_id' => 5, 'category_type' => 1),
                        array('id' => 8, 'name' => 'galaxy', '_lft' => 12, '_rgt' => 13, 'parent_id' => 7, 'category_type' => 2),
                    array('id' => 9, 'name' => 'sony', '_lft' => 15, '_rgt' => 16, 'parent_id' => 5, 'category_type' => 1),
                    array('id' => 10, 'name' => 'lenovo', '_lft' => 17, '_rgt' => 18, 'parent_id' => 5, 'category_type' => 1),
            array('id' => 11, 'name' => 'store_2', '_lft' => 21, '_rgt' => 22, 'parent_id' => null, 'category_type' => 1),
        );
        \DB::table('categories')->insert($data);
    }

Model vẫn như thường lệ và bạn thêm

use Kalnoy\Nestedset\NodeTrait;

và có thêm vào function tùy biến name và set lại parent id

public function getLftName()
    {
        return '_lft';
    }

    public function getRgtName()
    {
        return '_rgt';
    }

    public function getParentIdName()
    {
        return 'parent_id';
    }

    // Specify parent id attribute mutator
    public function setParentAttribute($value)
    {
        $this->setParentIdAttribute($value);
    }

Tiếp đến sẽ là CategoryController

public function index()
    {
        $categories = Category::get()->toTree();
        $data = [
            'categories' => $categories
        ];
        return view('admin.category.index', $data);
    }

dữ liệu sẽ build ra dạng thế này, bạn tùy biến if else dưới view

public function store(CreateCategoryRequest $request)
    {
        if ($request->id) {
            $target =  Category::find($request->id);
            if($target) {
                $node = new Category([
                    'name' => $request->name
                ]);
                $node->appendToNode($target)->save();
            }
        } else {
            $category = Category::create([
                'name' => $request->name,
            ]);
        }

        return response()->json([
                'result' => 'OK',
            ], 200);
    }

Ở hàm store bạn phải phân biệt được việc tạo 1 node gốc mới (tức là parent_id = null) hay là thêm 1 node cho 1 node đã có (tức là parent_id = nút mẹ)
2 case đó nằm trong if else bên trên

public function update(UpdateCategoryRequest $request)
    {
        $category = Category::find((int)$request->id);
        if($category) {
            $category->name = $request->name;
            $category->save();
            return response()->json([
                'result' => 'OK',
            ], 200);
        }
    }

Việc update cần có id nên trong UpdateCategoryRequest bạn khai báo
idrequired
Phần view tôi đang dùng thư viện này để build ra cây
https://dbushell.com/Nestable/
Bài viết tiếp theo nếu cố gắng sẽ làm về phần move kéo thả để cắt di chuyển 1 node. Hẹn gặp lại mọi người.

P/s:
Nếu có vẫn gặp vướng mắc trong khi thực hành hoặc có bất kì thắc mắc về PHP, Laravel hay muốn tìm hiểu thêm các thông tin hữu ích, các bạn hãy tham gia group Laravel VietNam để được các admin và thành viên support trực tiếp vấn đề mà bạn gặp phải nhé.