wim91's avatar
Level 2

How to get a collection of the latest relationships with high nesting depth?

I created a category table recursively, meaning that in one table, subcategories reference the parent. This resulted in a high degree of nesting. A product belongs to a child category, and using a recursive method in the model, I retrieve all the nested relationships. I need to get a collection of the last relationship in the chain, but I'm getting "null." Could you tell me how to do this?

Here's the code.

/** class Product extends Model **/
public function category() {
      return $this->belongsTo(Category::class, 'category_id');
  }

/** class Category extends Model **/
public function parentCategories()
    {
        return $this->belongsTo(Category::class, 'category_id')->with('parentCategories');
    }
/** Controller **/

$product = Product::with('category.parentCategories')->find(2);
$parent_cat = $product->category->parent_categories;
dd($parent_cat); // null

1 like
2 replies
Jsanwo64's avatar

Add this to your Category model:

public function getRootCategory()
{
    $category = $this;

    while ($category->parentCategories) {
        $category = $category->parentCategories;
    }

    return $category;
}

Then do this

$product = Product::with('category.parentCategories')->find(2);

$rootCategory = $product->category->getRootCategory();

dd($rootCategory);

or do this, Recursive Function (Functional Style)

If you prefer recursion

public function getRootCategory()
{
    if (!$this->parentCategories) {
        return $this;
    }

    return $this->parentCategories->getRootCategory();
}

But i suspect you may be having a column naming confusion. check your columns again though

imranbru's avatar

A couple of things to check here. $product->category->parent_categories is likely returning null because either that specific category has no parent, or you're running into a casing issue (try $product->category->parentCategories). Also, keep in mind belongsTo returns a single model, not a Collection.

Since you are already eager-loading the entire chain using ->with('parentCategories'), you can traverse the loaded models in memory to find the root recursively without running N+1 database queries.

Add this accessor to your Category model:

public function getRootCategoryAttribute()
{
    return $this->parentCategories ? $this->parentCategories->root_category : $this;
}

Then in your controller:

$product = Product::with('category.parentCategories')->find(2);

$rootCategory = $product->category->root_category; 

Keep Remind Please While a recursive with() works for shallow relationships, it fires a new database query for every level of depth and will quickly chew up memory. If your nesting is truly deep, I highly recommend using recursive CTEs. Drop in the staudenmeir/laravel-adjacency-list package—it compiles tree traversals into a single, highly optimized SQL query and is pretty much the standard for deep nesting in Laravel.

Please or to participate in this conversation.