Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I'm trying to build a recursive menu which would look something like this when finished, with an infinite amount of nested items:

Homepage
Services
  Members
  Brokers
  Insurers
Products

My data is being linked together by a simple parent_id column in my table.

I have currently managed to grab all the pages in the table and add them into an array based on their parent_id (see below).

Array
(
    [0] => 0-0-1 0-1-2 0-2-12 0-3-27 0-4-28 0-5-29 0-6-30 0-7-31 0-8-33 
    [2] => 2-0-3 2-0-6 2-0-7 2-0-8 
    [3] => 3-0-4 3-0-5 
    [8] => 8-0-9 8-0-10 
    [12] => 12-0-13 12-0-20 
    [13] => 13-0-14 13-1-15 13-2-17 13-3-16 13-4-19 13-5-18 13-9-34 
    [20] => 20-0-21 20-1-22 20-2-24 20-3-23 20-4-26 20-5-25 20-6-11 
)

This is formatted as [parent_id] => parent_id-sort_order-id parent_id-sort_order-id and so on.

My code so far is as follows:

$sqlGetDropdownPages = "SELECT * FROM `cms_staticPages` ORDER BY `sortOrder` ASC";
$qryGetDropdownPages = mysql_query($sqlGetDropdownPages) or die(mysql_error());
$resGetDropdownPages = mysql_fetch_assoc($qryGetDropdownPages);
$totGetDropdownPages = mysql_num_rows($qryGetDropdownPages);

do {    

    $pageArray[$resGetDropdownPages['parent_id']] .= $resGetDropdownPages['parent_id'].'-'.$resGetDropdownPages['sortOrder'].'-'.$resGetDropdownPages['id'].' ';

} while($resGetDropdownPages = mysql_fetch_assoc($qryGetDropdownPages));

ksort($pageArray);

foreach($pageArray as $i => $value) {

    $explodePages = explode(' ',$value);

    foreach(array_filter($explodePages) as $i2 => $page) {

        $pageInfoExplode = explode('-',$page);

        $getPageInfo = mysql_fetch_assoc(mysql_query("SELECT * FROM `cms_staticPages` WHERE `id` = '".$pageInfoExplode[2]."'"));

        echo $getPageInfo['identifier'].'<br />';

    }

}

Ran into a brick wall at this point and can't figure out what to do next.

I tried to explain this as well as I possibly can so if any clarification is needed just ask.

How to achieve this?

share|improve this question
4  
I would suggest that, instead of space-delimiting hyphen-delimited entries, to build a multi-dimensional array structure with string indices. This may keep you from going insane (no promises, though). –  watcher May 28 at 15:47
    
You should seriously consider using a nested set. There's an excellent article on how to implement them with MySQL here (scroll past the adjacency list section). It might be a little trickier to wrap your head around but it's a far better way to store a tree in an RDBMS like MySQL –  Bojangles May 28 at 15:57
    
@Bojangles this does seem like a pretty good idea, but I would need this to work for a select menu. I am attempting something similar to WordPress pages whereby you can add new pages under a parent. Would this be relatively easy to implement? –  Scott Bowers May 28 at 17:20

1 Answer 1

up vote 1 down vote accepted

I would index all the menu items (pages) by id first. In the second loop associate menu items with their parents and keep track of top level items.

One of the ways I would do it (Render method is just an example):

$sqlGetDropdownPages = "SELECT * FROM `cms_staticPages` ORDER BY `sortOrder` ASC";
$qryGetDropdownPages = mysql_query($sqlGetDropdownPages) or die(mysql_error());

//indexing pages by id
while ($pageRow = mysql_fetch_assoc($qryGetDropdownPages)) {
    $menuItems[$pageRow['id']] = new MenuItem($pageRow);
}

//associating pages with parents
foreach ($menuItems as $menuItem) {
    if ($menuItem->hasParent()) {
        $menuItem->addToParent($menuItems);
    } else {
        $topLevel[] = $menuItem;
    }
}

//$topLevel is an array of menuItems without parents
$render = '<ul>';
foreach ($topLevel as $menuItem) {
    $render .= $menuItem->render(true);
}
$render .= '</ul>';

MenuItem class would look something like this

class MenuItem {

    /**
     * @var array
     */
    private $data;

    /**
     * @var MenuItem[]
     */
    private $children = array();

    /**
     * @var MenuItem
     */
    private $parent;

    /**
     * @param array $data
     */
    public function __construct($data) {
        $this->data = $data;
    }

    /**
     * Finds the parent in the collection and adds itself to it
     * @param MenuItem[] $menuItems
     */
    public function addToParent($menuItems) {
        if (isset($menuItems[$this->data['parent_id']])) {
            $this->parent = $menuItems[$this->data['parent_id']];
            $this->parent->addChild($this);
        }
    }

    /**
     * @return bool
     */
    public function hasParent() {
        return empty($this->data['parent_id']);
    }

    /**
     * @param MenuItem $child
     */
    public function addChild(MenuItem $child) {
        $this->children[] = $child;
    }

    /**
     * Renders the menu with ul li
     * @param bool $withChildren
     * @return string
     */
    public function render($withChildren = false) {
        $render = '<li>' . $this->data['identifier'] . '</li>';

        if ($withChildren) {
            $render .= '<ul>';
            foreach ($this->children as $child) {
                $render .= $child->render(true);
            }
            $render .= '</ul>';
        }

        return $render;
    }

}
share|improve this answer
    
Thanks for this! I roughly understand how this works (I've never delved this far into PHP before). However, when I echo $render all I get is: <ul></ul> –  Scott Bowers May 28 at 17:49
    
what's in the $topLevel array? Do the var_dump($topLevel) after the second loop. –  nikola May 28 at 17:59
    
Thanks for the speedy reply :) It's NULL –  Scott Bowers May 28 at 18:01
    
How about $menuItems? –  nikola May 28 at 18:02
    
How do you store the pages with no parent? Is the parent_id null in the db for those records? –  nikola May 28 at 18:03

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.