Love Fuel?    Donate

FuelPHP Forums

Ask your question about FuelPHP in the appropriate forum, or help others by answering their questions.
Nestedset advanced breadcrumbs
  • Hi

    What is the best practice to create advanced nested set breadcrumbs?

    By saying 'advanced' I mean that it does not only return a simple path, like nestedset path() method does, but I want each crumb to be a link to the category browse method (below). Plus, what is important, I plan each crumb link to have a dropdown with subcategories of that cumb category.

    I'm currently loading a category by id, so I can't use any pre-defined URL path stored in table:

    function action_browse($id)
    $category = Model_Category::find($id);

    if ($category)
    // render path leading to $category

    What would be the best way to achieve this? What methods should I use, not to send many queries to the database?

    One idea I had is to use the $category->ancestors() method, but then I also need childrens of them, which results in tons of queries being sent.

    Second idea that came to my mind is to get top $category ancestor (array slice skips the dummy one):

    $firstAncestor = array_slice($category->ancestors()->get(), 1, 1);

    Then use cached dump_tree of the whole category table:

    $categories = Model_Category::forge()->roots()->get_one()->dump_tree();

    And that way, I can simply interate & make a recursive call to build HTML unordered list for the breadcrumbs, because I will have everything I need, starting from:

    Any idea how could I solve this properly? 
  • The model can't generate URI's, and imho it shouldn't. The reason for the path being there is exactly for this purpose, so you can generate it. It will be present in the result of dump_tree(), so it doesn't require additional queries. Same for the id, it's in the result too.

    As you can see in an example of our HTML generator ( we use a "type" field in our nested set table to indicate that kind of link it should be. Our environment is a bit complex, as we don't have fixed controllers, but our webengine has the entire app structure in the database, and we use controller methods per page widget. So we can also link to specific pages instead of a hardcoded path as well. And we can also store a url in the table, for example if you need a hardcoded URL to an external site as link.
  • My system is a bit different, since I'm using SEO-friendly urls, and these, has to be hardcoded (route), so basically to access a category the link is:


    or for subcategories:


    Thats why I don't really need a 'type' field, since categories URL will always be in the scope of a route and all I need to have a complete URL is the id and seo fields from the table.

    So yeah, I'm currently having a working prototype of breadcrumb generating function, however I have to call $category->ancestors()->get() which send lots of queries :-( so I decided to cache ancestors for every category id.

    Here is how I did:

    // Get ancestors list
    $ancestors = array_keys( $node->getAncestors() ); // cached version of ancestors()->get() 

    // Shift dummy one
    array_shift( $ancestors );

    $path = [];
    $categories = static::getCategories(); // cached version of Model_Category::forge()->roots()->get_one()->dump_tree( TRUE );

    foreach ( $ancestors as $i )
    $ancestor = $categories[ $i ] ?? FALSE;

    if ( $ancestor )
    $anchorAttr = [];
    $categories = $ancestor->children;

    $path .= '<li><a href="/c/' . $ancestor->id . '-' . $ancestor->name_seo . '">' . $ancestor->name . '</a>';

    What do you think about this approach? Could this be done any better? 
  • I don't see the relation between SEO friendly URI's and hardcoding. Our webengine uses the type field to distinguish between different types of URI's that it can generate. You don't need that, your code isn't generic.

    It was just an example explaining what was going on in that snippet.

    I still don't see why you can't use the result from dump_tree(). It gives you all nesting information, it gives you the correct path, and it gives you the category id.

    Loop over the result, diving into the children, until you find the id of the currently selected category. Alternatively, since you have the category in the URI, get it, use that to find() the category object (query 1 by PK), then use the model object to call path() (query 2 to depth+1, does get ancestors, which runs a query for each ancestor). You'll have to test what is faster, getting all categories in one query, or running depth + 1 queries.
  • SEO urls are static, thus they are somewhat hardcoded since we can't establish other relation for them until we edit the route, that does require code editing, so yeah, thats hardcoding until I change the behaviour of setting the route to any not requiring code touching solution, which I'm not currently interested.

    "I still don't see why you can't use the result from dump_tree(). It gives you all nesting information, it gives you the correct path, and it gives you the category id."

    As I said, that will require another set of recursive calls and such. While ancestors() method returns all parents to the current node in the db order, which is helpful and doesn't require many loops and recursive calls, to find a deeply nested children. Plus, what is the point of using path() in my case, when all it does is a string path, which is not helpful for me, since I need the HTML formatted path.

    "Loop over the result, diving into the children, until you find the id of the currently selected category. "

    Like I said before, I'm already having the category selected (look action_browse($id) method). Thats where I call $category->ancestors()->get() on to get a nice set of parents in a database order :-).

    Using the above described prototype, I was able to achieve this:


    So yeah, turns out if I want to achieve this:
    • HTML formatted path with children nodes on hover,
    • Not using many loops and recursive calls on each page load
    I have to use ancestors->get() method and simply cache them for each category ID, not to abuse the database I/O. Thats really simple solution, fortunately or not, requiring caching, but all I need to do then is to loop over 1-5 items (depends on nesting level of subcategory).

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

In this Discussion