Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP / MySQL build tree menu

Tags:

php

mysql

tree

I am trying to build an un-oredered list menu tree from my database in PHP and MySQL.

I have an array of page objects I am returning from the db. Each page object has parent_id attribute, which is set to null if it doesn't have a parent. Here's what the page objects look like:

page object
  id
  title
  parent_id

If possible I would like to not do it recursively and only hit the database once, since I am going to be building the menu on almost every request. I want to create a function that I can just pass my array of objects to and it will return the html list.

like image 874
imns Avatar asked Dec 10 '10 22:12

imns


2 Answers

I like @mario's solution, and have improved on it with the prevention of the excess <ul>. I would just recommend doing an ORDER BY on your SQL query to get the menu in the order you want (might even recommend a weight/sequence column be added to the schema.

Data setup:

$menu = array( // Presumed to have been coming from a SQL SELECT, populated for demo.
  array('id'=>1,'title'=>'Menu 1',          'parent_id'=>null),
  array('id'=>2,'title'=>'Sub 1.1',         'parent_id'=>1),
  array('id'=>3,'title'=>'Sub 1.2',         'parent_id'=>1),
  array('id'=>4,'title'=>'Sub 1.3',         'parent_id'=>1),
  array('id'=>5,'title'=>'Menu 2',          'parent_id'=>null),
  array('id'=>6,'title'=>'Sub 2.1',         'parent_id'=>5),
  array('id'=>7,'title'=>'Sub Sub 2.1.1',   'parent_id'=>6),
  array('id'=>8,'title'=>'Sub 2.2',         'parent_id'=>5),
  array('id'=>9,'title'=>'Menu 3',          'parent_id'=>null),
);

Handling:

function has_children($rows,$id) {
  foreach ($rows as $row) {
    if ($row['parent_id'] == $id)
      return true;
  }
  return false;
}
function build_menu($rows,$parent=0)
{  
  $result = "<ul>";
  foreach ($rows as $row)
  {
    if ($row['parent_id'] == $parent){
      $result.= "<li>{$row['title']}";
      if (has_children($rows,$row['id']))
        $result.= build_menu($rows,$row['id']);
      $result.= "</li>";
    }
  }
  $result.= "</ul>";

  return $result;
}
echo build_menu($menu);

Output:

<ul>
  <li>Menu 1<ul>
    <li>Sub 1.1</li>
    <li>Sub 1.2</li>
    <li>Sub 1.3</li>
  </ul></li>
  <li>Menu 2<ul>
    <li>Sub 2.1<ul>
      <li>Sub Sub 2.1.1</li>
    </ul></li>
    <li>Sub 2.2</li>
  </ul></li>
  <li>Menu 3</li>
</ul>
like image 157
Brad Christie Avatar answered Nov 05 '22 08:11

Brad Christie


Instead of querying the database recursively, you can just pull out all entries and make the output function recursive. It's often as trivial as:

function print_list($array, $parent=0) {
    print "<ul>";
    foreach ($array as $row) {
        if ($row->parent_id == $parent) {
            print "<li>$row->title";
            print_list($array, $row->id);  # recurse
            print "</li>";
    }   }
    print "</ul>";
}

It's only important to nest <ul>s into <li>. Or just use HTML and leave out the closing </li>.

Actually this prints too many <ul>s, so I would check for existence of sublevels and avoid printing it directly.

like image 3
mario Avatar answered Nov 05 '22 08:11

mario