Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

kickthemooon's avatar

Categories Tree View?

so i wanna display my categories as a tree view.

My eloquent logic is that the category model has category_id property and the relationship:

public function subcategory() {
        $this->belongsTo('App\Category');
    }

so its in relationship with it self, essentially forever alone :D

So I am satisfied with the logic now how would I represent this with li in my view? any ideas?

0 likes
17 replies
askaoru's avatar

interesting.. Maybe something like this?

// Controller
 $categories =  App\Category::where('category_id', null) ->get();

// View
<ul>
@foreach($categories as $category)
        <li>{{ $category->name }}
            @if(count( $category->subcategory) > 0 )
                <ul>
                @foreach($category->subcategory as $subcategory)
                    <li>{{ $subcategory->name }}</li>
                @endforeach 
                </ul>
            @endif
        </li>                   
@endforeach
</ul>

Really interesting theory there :P

4 likes
kickthemooon's avatar

@askaoru Ill try your code and let you know if it works. From first glance it looks very promising. Thanks.

1 like
igorblumberg's avatar

You could have a query scope to return only the "root" categories (category where category_id = 0 or null).

public function scopeRoot($query)
    {
        return $query->where('category_id', 0); //or null, nut sure how you are handling
    }   

Then on your controller

$root_elements = Category::with("subcategory")->root()->get();  
//pass this variable to your view

and on your view

@foreach($root_elements as menu)
<li>{{menu.name}}
    <ul>
    @foreach(menu.subcategory as submenu)
    <li> {{submenu.name}}</li>
    @endforeach 
    </ul>
@endforeach 

You could keep nesting foreachs if you have more then 2 levels. And you could validate if a menu has submenus before adding the UL, but overall that's how I would approach it.

2 likes
zanakka's avatar

I'm using a helper function to generate and display category tree view in blade files.

// app/Http/helpers.php
if (! function_exists('generateCategoryLists')) {
    function generateCategoryLists(array $elements, $parentId = 0,$indent = 0) {
        
        foreach ($elements as $key => $element) {
            if ($element['parent_id'] == $parentId) {
                
                echo '<li>' . $element['category_name'] . '</li>;
                
                $children = generateCategoryLists($elements, $element['id'],$indent + 1);
            }
        }
    }
}
// controller
$categories = Category::select('id', 'parent_id', 'category_name')->get()->toArray();
// create.balde.php
<ul>
    {!! generateCategoryLists($categories, $parentId=0, $indent=0) !!}
</ul>

Category Table Structure

id, parent_id, category_name

1 like
kickthemooon's avatar

@askaoru With the first code you suggested I get an error:

ErrorException in Model.php line 2717:
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation (View: C:\wamp\www\azurtours\resources\views\admin\Offers\add.blade.php)

any ideas? I like your code the most because it has the least lines :) but I will try also @igorblumberg and @kyawzinwin suggestions

ChristopherSFSD's avatar

Does your BelongsTo specify which column points to the parent category? For example I have a similar situation where I have component "classes". Each component class potentially has a parent component class. My belongsTo relationship in my ComponentClass model looks like this ...

public function ParentClass() { return $this->belongsTo('ComponentClass', 'parent_class_id'); }
1 like
kickthemooon's avatar

maybe thats something, i mean I have the category_id column so i thought if i do belongsTo it would assume that the parent column is category_id.

I have only one class not 2, and this:

class Category

...

public function subcategory() {
        $this->belongsTo('App\Category');
    }

is referencing itself

intossh@gmail.com's avatar

I was dealing with the same few weeks ago. What I actually used were two packages:

laravel-nestedset which nicely handles categories, they are easy to add and modify. You can also get children, descendants, and so on, this one is great for backend side of this.

Then for displaying the menu (ul) and getting currently active category, I used this package:

laravel-menu

I wrote an 'adapter' so the laravel-menu can get correct data from the database and create the actual menu based on category tree and active category. Laravel menu itself handles active category based on the route used and adds 'active' class to the specific 'li' If you want I could provide the 'adapter', however its pretty messy right now :)

thomaskim's avatar

@kickthemooon You are forgetting the return statement.

public function subcategory() {
    return $this->belongsTo('App\Category');
}
1 like
askaoru's avatar

@kickthemooon i tested everything out. And instead of making it belongsTo, changing it hasMany works better.
And as @thomaskim said above, you forgot to 'return' the relationship which gave you that first error

These were what I did.

// Category.php , the model

    public function subcategory()
    {
        return $this->hasMany(Category::class);
    }

// The route (you can put this on the controller)
Route::get('category', function() {
        $categories = \App\Category::where('category_id', null)->get();
        return view('category', compact('categories'));
    });

// The view (same code as above)
<ul>
@foreach($categories as $category)
        <li>{{ $category->name }}
            @if(count( $category->subcategory) > 0 )
                <ul>
                @foreach($category->subcategory as $subcategory)
                    <li>{{ $subcategory->name }}</li>
                @endforeach 
                </ul>
            @endif
        </li>                   
@endforeach
</ul>

The database
db
And the result
result

Extra: as @igorblumberg 's example, you might want to eager load this to reduce the number of your queries incase you have a lot categories/subcategories.

$categories = \App\Category::where('category_id', null)->with('subcategory')->get();
2 likes
zanakka's avatar

Hi @kickthemooon,

I think I forgot to add <ul> inside the recursive function. Can you try with the following edited function?

// app/Http/helpers.php
if (! function_exists('generateCategoryLists')) {
    function generateCategoryLists(array $elements, $parentId = 0,$indent = 0) {
        
        foreach ($elements as $key => $element) {
            if ($element['parent_id'] == $parentId) {
                echo '<ul>';
                echo '<li>' . $element['category_name'] . '</li>;
                echo '</ul>';
                $children = generateCategoryLists($elements, $element['id'],$indent + 1);
            }
        }
    }
}
1 like
kerby's avatar

In addition to @askaoru 's note - If you have more than one level of nesting you should extract partial and you it recursively

category.partial.blade.php:

<ul>
@foreach($categories as $category)
        <li>{{ $category->name }}
            @if(count( $category->subcategory) > 0 )
        @include('category.partial', ['categories' => $category->subcategory])
            @endif
        </li>                   
@endforeach
</ul>
2 likes

Please or to participate in this conversation.