Products in multilevel categories with a Many-To-Many relation

Let's say you are building an online shop, with Laravel, that requires products to be in multiple categories. Also those categories will have multiple subcategories. And the depth of those subcategories is not defined.

First of all we will create our migrations and models.

php artisan make:migration CreateProductsTable --create=products

Our products will be pretty simple. A product will have a name, price and description.

Schema::create('products', function (Blueprint $table) {
    $table->string('name', 255);

Now lets create our Product model:

php artisan make:model Product

And fill our $fillable:

protected $fillable = ['name', 'description', 'price'];

Next, we will create a migration for our categories:

php artisan make:migration CreateCategoriesTable --create=categories

A Category has a name and a field for sub_category:

Schema::create('categories', function (Blueprint $table) {
    $table->string('name', 255);

And again we are creating the model:

php artisan make:model Category

and add a fillable array:

protected $fillable = ['name', 'sub_category_id'];

And as I said in the introduction of this post, a produts can be added to multiple categories. Therefore, we will need a pivot table that links a product and a category.

php artisan make:migration CreateCategoryProductPivotTable --create=category_product
Schema::create('category_product', function (Blueprint $table) {

We are not going to need a model for this table, because we are going to use Laravel's ORM relations.

Now we are ready to run:

php artisan migrate

In this post I'm not covering the front end part of adding a category/product or editing them. So let's "seed" our database with records before adding the relations to our models and try fetching the data.

Let's first create the seeder for our categories table:

php artisan make:seeder CategoryTableSeeder

And for our products:

php artisan make:seeder ProductTableSeeder

Here's the code for the seeders:

Then run:

php artisan db:seed

Don't forget to add the seeders to the DatabaseSeeder class!

After seeding our tables we are ready to fetch some data.

Let's say we have a select in a form that adds our products to our database. We need to get all categories that do not have childs.

The following query does that:

$categories = Category::where('id', '!=', 'sub_category_id')->get();

So, we can use Eloquent's relationships to fetch categories with products and get product's category.

In our Category model we have to set:

public function products()
    return $this->belongsToMany(Product::class);

And in our Product model:

public function categories()
    return $this->belongsToMany(Category::class);

And we can access them as properties of the model:

$product = Product::find(1);
dd($product->categories); //This is going to be a collection of Category model


$category = Category::find(1);

Finally we are going to build a tree that we can use for generating navigation or anything related to our categories.

We will create two methods:

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

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

Let's create our views home.blade.php:

<!doctype html>
<html lang="en">

    <meta charset="UTF-8">
    <title>Laravel test</title>
@include('categories', ['categories' => $categories])

and categories.blade.php:

    @foreach($categories as $category)
        <li>{{ $category->name }}
                @include('categories', ['categories' => $category->children])

Here we are telling blade to treat the children as categories where they are passed through.

If you used the seeders provided in this post you will see a slight performance issue, because Eloquent makes lots of queries to our database in order to get the rows respectively for our models and related models.

This can be avoided by building a tree after fetching all the records, but this is out of the scope of this post. You can find many packages that can help you with this task. Even some designed specially to work with Laravel & Eloquent.

unsplash-logoPaul Jarvis provided this awesome picture!

Show Comments