Laravel Eloquent: build categories tree in json format

Laravel Eloquent: build categories tree in json format

Often, online stores have products grouped by categories and subcategories, while the number of subcategories and their nesting can be unlimited. In this article, I will show you how you can use Eloquent to pass category nesting to the API to quickly display a tree.

First, let's create a migration, for example we will use a simple model of two fields - name and description:

class CreateListingCategoriesTable extends Migration

{

    public function up()

    {

        Schema::create('listing_categories', function (Blueprint $table) {

            $table->bigIncrements('id');

            $table->string('name');

            $table->longText('description')->nullable();

            $table->integer('parent_id')->nullable();

            $table->timestamps();

            $table->softDeletes();

        });

    }

}

To fix the nesting, we use the parent_id field, where the parent category will be NULL and the child category will reference the parent's id. In the picture for this post, you can see an example of the entered data with two parent categories and three-level nesting.

Next, we need to create a model app / Models / ListingCategory.php

final class ListingCategory extends \Parents\Models\Model

{

 

    public const RESOURCE_NAME = 'listing_categories';

 

    public const DOMAIN_NAME = 'Listings';

 

    public $table = 'listing_categories';

 

    protected $dates = [

        'created_at',

        'updated_at',

        'deleted_at',

    ];

 

    protected $fillable = [

        'name',

        'description',

        'parent_id',

        'created_at',

        'updated_at',

        'deleted_at',

    ];

 

    protected function serializeDate(DateTimeInterface $date): string

    {

        return $date->format('Y-m-d H:i:s');

    }

 

    /**

     * Get the index name for the model.

     */

    public function childs(): \Illuminate\Database\Eloquent\Relations\HasMany

    {

        return $this->hasMany(__CLASS__, 'parent_id', 'id') ;

    }

 

    /**

     * @return \Illuminate\Database\Eloquent\Relations\HasMany

     * @psalm-suppress InvalidReturnStatement

     * @psalm-suppress InvalidReturnType

     */

    public function childrenCategories(): \Illuminate\Database\Eloquent\Relations\HasMany

    {

        return $this->hasMany(__CLASS__, 'parent_id', 'id')->with('childs');

    }

}

 

Here we see two methods - childs, through which we can get the child category and childrenCategories - where we recursively get all the child related posts.

 

The Form Request for creating a category will look like this:

 

final class StoreListingCategoryRequest extends Request

{

    public function authorize(): bool

    {

        return $this->check('listing_category_create');

    }

 

    public function rules(): array

    {

        $rules = [

            'data.attributes.name' => [

                'string',

                'min:3',

                'max:190',

                'nullable',

            ],

            'data.attributes.description' => [

                'string',

                'nullable',

            ],

            'data.attributes.parent_id' => [

                'integer',

                'min:-2147483648',

                'max:2147483647',

                'nullable',

                'exists:Domains\Listings\Models\ListingCategory,id'

            ]

        ];

        return $this->mergeWithDefaultRules($rules);

    }

}

It is worth noting that the FormRequest in this example is configured to work according to the Json: Api standard.

Now let's create an entry point to get the category tree. To do this, insert the following in the api.php file:

 

Route::get('listing-categories/tree', [

    App\Http\Controllers\Api\V1\ListingCategoriesApiController::class,

    'tree'

])->name('listing-categories.tree');

 

In the controller, in the tree method, we need to get a list of categories and pass them to the transformer. To display the API, you use the fractal library:

 

    public function tree(IndexListingCategoriesRequest $request, GetCategoryTreeAction $action): ?JsonResponse

    {

        return fractal(

            $action(),

            new TreeCategoryTransformer(),

            new \League\Fractal\Serializer\JsonApiSerializer($this->getUrl())

        )->withResourceName(ListingCategory::RESOURCE_NAME)

            ->respondJsonApi();

    }

 

As an example of the $action class, you can pass a list of categories through the repository as follows:

 

ListingCategory::whereNull('parent_id')

            ->with('childrenCategories')

            ->get();

 

In this case, the transformer takes the following form:

 

final class TreeCategoryTransformer extends \Parents\Transformers\Transformer

{

    public function transform(ListingCategory $category): array

    {

        if ($category->childrenCategories) {

            $item = [

                'id' => $category->id,

                'name' => $category->name,

                'description' => $category->description,

                'parent_id' => $category->parent_id,

            ];

            /** @var ListingCategory $value */

            foreach ($category->childrenCategories as $value) {

                $item['children'][] = $this->transform($value);

            }

 

            return $item;

        }

 

        return [

            'id' => $category->id,

            'name' => $category->name,

            'description' => $category->description,

            'parent_id' => $category->parent_id,

        ];

    }

}

 

Ultimately, after accessing the entry point along the path api/v1/listing-categories/tree, we will get the following structure in json format:

 

{

  "data": [

    {

      "type": "listing_categories",

      "id": "1",

      "attributes": {

        "name": "Equipment",

        "description": "Super equipment",

        "parent_id": null

      },

      "links": {

        "self": "http:\/\/delta.test:8000\/listing_categories\/1"

      }

    },

    {

      "type": "listing_categories",

      "id": "2",

      "attributes": {

        "name": "Electronics",

        "description": "Super electro products",

        "parent_id": null,

        "children": [

          {

            "id": 3,

            "name": "Computers",

            "description": "Super computers and pcs",

            "parent_id": 2,

            "children": [

              {

                "id": 4,

                "name": "Computer Accessories",

                "description": "Keyboard, mouses etc",

                "parent_id": 3

              },

              {

                "id": 5,

                "name": "Computer Equipment",

                "description": "equipment for computers",

                "parent_id": 3

              }

            ]

          },

          {

            "id": 6,

            "name": "Mobile",

            "description": "Mobile phones",

            "parent_id": 2

          }

        ]

      },

      "links": {

        "self": "http:\/\/delta.test:8000\/listing_categories\/2"

      }

    }

  ]

}

 

Popular Posts

My most popular posts

Maximum productivity on remote job
Business

Maximum productivity on remote job

I started my own business and intentionally did my best to work from anywhere in the world. Sometimes I sit with my office with a large 27-inch monitor in my apartment in Cheboksary. Sometimes I’m in the office or in some cafe in another city.

Hello! I am Sergey Emelyanov and I am hardworker
Business PHP

Hello! I am Sergey Emelyanov and I am hardworker

I am a programmer. I am an entrepreneur in my heart. I started making money from the age of 11, in the harsh 90s, handing over glassware to a local store and exchanging it for sweets. I earned so much that was enough for various snacks.

Hire Professional CRM developer for $25 per hour

I will make time for your project. Knowledge of Vtiger CRM, SuiteCRM, Laravel, and Vue.js. I offer cooperation options that will help you take advantage of external experience, optimize costs and reduce risks. Full transparency of all stages of work and accounting for time costs. Pay only development working hours after accepting the task. Accept PayPal and Payoneer payment systems. How to hire professional developer? Just fill in the form

Telegram
@sergeyem
Telephone
+4915211100235