Revisiting the multilevel menu in PHP with some additional code (as in… Bootstrap CSS)

(created at: May 07, 2014; last update: December 15, 2015)
It’s been almost a year since I’ve posted a tutorial (Multi-level menu in PHP – how to do it without killing yourself or the database in the process) about how do create a multi-level menu in PHP, but in all that time I’ve never seen anyone complaining about some of the weak points of my approach.

That is until, when I needed to use it, I realized that it added some additional non-needed html code. So, although it is the best way to create a multi-level menu without killing the database in the process, it still needed something.

So, what went wrong you may ask? Well… If you’ve followed the old tutorial, you may have seen that the functions added a ‘<ul></ul>’ at every list item, although there were no sub menu items in there.

So let’s start over.

I will again assume that you will have a database table from which you will retrieve the menu items. That table has at least the following columns: id, name, url, parent_id. Also, I found it useful to add a field named ‘order‘, in which I keep the position of the item inside the (sub-)menu.

What I do is retrieve the menu from the database by applying an ascending ORDER BY on the ‘order‘ field. This way I make sure that the items will be retrieved in the order that was meant for them to be displayed.

When you retrieve the menu items from the database, let’s presume the array will have the following structure:

$menu_items = array(
array(‘id’=>…, ‘parent_id’=>…, ‘name’=>…, ‘url’=>…),
array(‘id’=>…, ‘parent_id’=>…, ‘name’=>…, ‘url’=>…),
………
);

Hope you get the idea…

In order to work with the array we will use a recursive function (a function that calls on itself) on the array and return the result.

Now, let’s see the possibilities:

Simply create a multi-dimensional array

If you want to have something else than an list type of menu (I don’t know, maybe you just want to put the multi-level inside divs… who knows?…), you can use this function:

function ordered_list($array,$parent_id = 0)
{
  $temp_array = array();
  foreach($array as $element)
  {
    if ($element['parent_id'] == $parent_id)
    {
      $element['subs'] = ordered_list($array, $element['id']);
      $temp_array[] = $element;
    }
  }
  return $temp_array;
}

Now, by calling the function you will retrieve a multi-dimensional array representing the menu:

$the_menu = ordered_list($menu_items);
echo '<pre>';
print_r($the_menu);
echo '</pre>';

A HTML menu using <ul>s

Usually, when talking about menus, an unordered list is used. So, let’s create it:

function html_menu($array,$parent_id = 0,$parents = array())
    {
        if($parent_id==0)
        {
            foreach ($array as $element)
            {
                if (($element['parent_id'] != 0) && !in_array($element['parent_id'],$parents))
                {
                    $parents[] = $element['parent_id'];
                }
            }
        }
        $menu_html = '';
        foreach($array as $element)
        {
            if($element['parent_id']==$parent_id)
            {
                $menu_html .= '<li><a href="'.$element['url'].'">'.$element['title'].'</a>';
                if(in_array($element['id'],$parents))
                {
                    $menu_html .= '<ul>';
                    $menu_html .= html_menu($array, $element['id'], $parents);
                    $menu_html .= '</ul>';
                }
                $menu_html .= '</li>';
            }
        }
        $menu_html .= '';
        return $menu_html;
    }

As you can see, to avoid the problem with the empty <ul>s inside the item menus that didn’t have sub-menus, I had to iterate twice the array. Once for finding out what items are parents, and second to create the menu. This way, I could create a new undordered list inside the list item if there were sub-menus.

Also, this time I didn’t add the starting ul tags, this way permiting you to add another list items before or after our manu list. To output it you simply need to put those tags:

$the_menu = html_menu($menu_items);
echo '<ul>';
echo $the_menu;
echo '</ul>';

The Bootstrap kind of menu items

The Bootstrap framework is nice. So why not use it with our menu. To do this I had to make a few changes to the function:

function bootstrap_menu($array,$parent_id = 0,$parents = array())
    {
        if($parent_id==0)
        {
            foreach ($array as $element) {
                if (($element['parent_id'] != 0) && !in_array($element['parent_id'],$parents)) {
                    $parents[] = $element['parent_id'];
                }
            }
        }
        $menu_html = '';
        foreach($array as $element)
        {
            if($element['parent_id']==$parent_id)
            {
                if(in_array($element['id'],$parents))
                {
                    $menu_html .= '<li class="dropdown">';
                    $menu_html .= '<a href="'.$element['url'].'" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">'.$element['title'].' <span class="caret"></span></a>';
                }
                else {
                    $menu_html .= '<li>';
                    $menu_html .= '<a href="' . $element['url'] . '">' . $element['title'] . '</a>';
                }
                if(in_array($element['id'],$parents))
                {
                    $menu_html .= '<ul class="dropdown-menu" role="menu">';
                    $menu_html .= bootstrap_menu($array, $element['id'], $parents);
                    $menu_html .= '</ul>';
                }
                $menu_html .= '</li>';
            }
        }
        return $menu_html;
    }

Now, inside the Bootstrap styled navbar, you simply put the menu:

<nav class="navbar navbar-default navbar-fixed-top">
  <div class="container">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">The site</a>
    </div>
    <div id="navbar" class="collapse navbar-collapse">
      <ul class="nav navbar-nav">
        <?php
        $top_menu = bootstrap_menu($menu_items);
        echo $top_menu;
        ?>
      </ul>
    </div>
    <!--/.nav-collapse -->
  </div>
</nav>

Hope you’ve enjoyed it…

7 comments

  1. Hello,

    Thanks for the Bootstrap code but you forgot to call the function in the navbar.
    Also i have a question: i am a Php beginner and a i have tried but failed to retrieve a multidimensional array like your example from my Mysql database.
    I can pull all arrays out of the database with a loop but not as a multidimensional array, so i cant get it to work as it should be.
    Can you help me with that please?

    1. Thank you for noticing the error. Regarding your question, I don’t understand. You mean to ask me how to directly get a multidimensional array from MySQL? Because I don’t know how… That is why I created this tutorial: to show how you can transform a MySQL result into a multidimensional array. If you give me an example, maybe I can help. My email is avenir.ro@gmail.com.

  2. There is an error in the first code block for the function ordered_list: the recursive call should not have a dollar sign before it.

Leave a Reply

Your email address will not be published. Required fields are marked *

No spam? * Time limit is exhausted. Please reload CAPTCHA.