4

I am trying to figure out the best way to write a PHP function that will recursively build a multi-dimensional array with an unknown number of sublevels from a mysql table. Its purpose is to create a data structure which can be looped through to create a navigation menu on a website, with each menu item possibly having a submenu with child menu items.

The fields of note in the table are:
int ItemID
int ParentID
varchar ItemText
text ItemLink
tinyint HasChildren

So an example of a returned array from the function would be:

$menuItems = 
    array(
        itemID# => 
            array(
                'ItemText' => 'Home',
                'ItemLink' => 'index.php',
                'Children' => array(
                        itemID# => array (
                            'ItemText' => 'Home Sub 1',
                            'ItemLink' => 'somepage.php',
                            'Children' => 0
                        ),
                        itemID# => array (
                            'ItemText' => 'Home Sub 2',
                            'ItemLink' => 'somepage2.php',
                            'Children' => 0
                        ),
                    )
            ),
        itemID# => 
            array(
                'ItemText' => 'Contact',
                'ItemLink' => 'contact.php',
                'Children' => 0
            )
        )
    );

Would greatly appreciate if someone could point me in the right direction to accomplish this. Thanks!

4 Answers 4

6

Not too hard. What you do is store the menu items in an array where you can look them up by ID. Then you iterate over the menu items and if they have a non-null ParentID you add them to their parent's list of children. Then you remove all the children from the master list so you have only top-level items left.

Code:

<?php
$menuItems = array
(
    1 => array
    (
        'ItemText' => 'Home',
        'ItemLink' => 'index.php',
        'ParentID' => null,
    ),

    2 => array
    (
        'ItemText' => 'Home Sub 1',
        'ItemLink' => 'somepage.php',
        'ParentID' => 1,
    ),

    3 => array
    (
        'ItemText' => 'Home Sub 2',
        'ItemLink' => 'somepage2.php',
        'ParentID' => 1,
    ),

    4 => array
    (
        'ItemText' => 'Contact',
        'ItemLink' => 'contact.php',
        'ParentID' => null,
    ),
);

// Each node starts with 0 children
foreach ($menuItems as &$menuItem)
    $menuItem['Children'] = array();

// If menu item has ParentID, add it to parent's Children array    
foreach ($menuItems as $ID => &$menuItem)
{
    if ($menuItem['ParentID'] != null)
        $menuItems[$menuItem['ParentID']]['Children'][$ID] = &$menuItem;
}

// Remove children from $menuItems so only top level items remain
foreach (array_keys($menuItems) as $ID)
{
    if ($menuItems[$ID]['ParentID'] != null)
        unset($menuItems[$ID]);
}

print_r($menuItems);
?>

Output:

Array
(
    [1] => Array
        (
            [ItemText] => Home
            [ItemLink] => index.php
            [ParentID] => 
            [Children] => Array
                (
                    [2] => Array
                        (
                            [ItemText] => Home Sub 1
                            [ItemLink] => somepage.php
                            [ParentID] => 1
                            [Children] => Array
                                (
                                )

                        )

                    [3] => Array
                        (
                            [ItemText] => Home Sub 2
                            [ItemLink] => somepage2.php
                            [ParentID] => 1
                            [Children] => Array
                                (
                                )

                        )

                )

        )

    [4] => Array
        (
            [ItemText] => Contact
            [ItemLink] => contact.php
            [ParentID] => 
            [Children] => Array
                (
                )

        )

)
Sign up to request clarification or add additional context in comments.

3 Comments

This looks to be just what i need, I appreciate it John. And just to make sure I'm seeing this right, this solution will work for an unlimited number of sublevels right? Looks like it does, just making sure though as my example data only has one sublevel.
Nevermind, just tested it with a more complex set of test data and it worked great :)
This worked flawlessly and with extreme efficiency. Many thanks for putting it out there.
3

Have a function that calls itself every time it gets an array element. As in:

Your function is called to display a node. Then it checks if the node its calling from has a sub menu, and if does, it calls itself again. And the process repeats until it dies out, and all the previous function calls return.

void printData($mysql_table_node){
    if($mysql_table_node.has_node()){
        for($i = 0; $i < $mysqql_table_node.num_nodes()){
            printData($mysql_table_node->own_node);
        }
    }
        return;
}

2 Comments

I'm understanding your logic, but I think your level of abstraction in the solution is a little over my head. How would i go about creating these node objects and the method that checks if the node object has nodes? I apologize if my own question is nonsensical :)
An easy way would be to have flags on each data variable. If the node has a sub level, then the flag is incremented the number of times that it has sublevels. And if that number is greater than 0, then it has sub levels in it.
3

Multi-dimensional tree and an unordered HTML list generator

  1. Recursively build a tree from a multi-dimensional array.
  2. Generate a multi-dimensional HTML list code, from the tree (1).

You can't never know how many dimensions is in the given list of items. Each element can have a son->Grandson->Great grandson an so on.

So, you must use a recursive function to generate a multi-dimensional list.

Here is the code:

<?php

$categories = array(
    '1'=>   array('name'=>'one','parent'=>null),
    '2'=>   array('name'=>'two','parent'=>null),
    '20'=>  array('name'=>'twenty','parent'=>'2'),
    '21'=>  array('name'=>'twenty one','parent'=>'2'),
    '210'=> array('name'=>'two hundred and ten',    'parent'=>'21'),
    '211'=> array('name'=>'two hundred and eleven', 'parent'=>'21'),
    '212'=> array('name'=>'two hundred and twelve', 'parent'=>'21')
);

$tree=Menu::CreateTree($categories);
print_r($tree);
Menu::GenerateMenuHtmlCode($tree);

class Menu
{   
    public static function GenerateMenuHtmlCode($tree)
    {
        echo '<ul>';
        foreach ($tree as $key=>$value)
        {
             echo "<li>".$value['name'];
             if(!empty($value['sons'])) 
                 self::GenerateMenuHtmlCode($value['sons']);
             echo "</li>";
        }
        echo '</ul>';
    }

    public static function CreateTree($categories)
    {
        $tree=array();
        self::AddElement(&$categories,&$tree,null);
        return $tree;
    }

    private function AddElement($categories,&$tree,$parent)
    {
        foreach ($categories as $key=>$value)
        {
            if($value['parent']==$parent)
            {
                $tree[$key]=$categories[$key];
                $tree[$key]['sons']=array();
                self::AddElement($categories,&$tree[$key]['sons'],$key);
            }
            if(empty($tree['sons'])) unset ($tree['sons']);
        }
        unset($categories[$parent]);
        return ;
    }
}
?>

The result:

Array
(
    [1] => Array
        (
            [name] => one
            [parent] => 
            [sons] => Array()
        )

    [2] => Array
        (
            [name] => two
            [parent] => 
            [sons] => Array
                (
                    [20] => Array
                        (
                            [name] => twenty
                            [parent] => 2
                            [sons] => Array()
                        )

                    [21] => Array
                        (
                            [name] => twenty one
                            [parent] => 2
                            [sons] => Array
                                (
                                    [210] => Array
                                        (
                                            [name] => two hundred and ten
                                            [parent] => 21
                                            [sons] => Array()
                                        )

                                    [211] => Array
                                        (
                                            [name] => two hundred and eleven
                                            [parent] => 21
                                            [sons] => Array()
                                        )

                                    [212] => Array
                                        (
                                            [name] => two hundred and twelve
                                            [parent] => 21
                                            [sons] => Array()
                                        )
                                )
                        )
                )
        )
)

and:

<ul>
    <li>one</li>
    <li>two
        <ul>
            <li>twenty</li>
            <li>twenty one
                <ul>
                    <li>two hundred and ten</li>
                    <li>two hundred and eleven</li>
                    <li>two hundred and twelve</li>
                </ul>
            </li>
        </ul>
    </li>
</ul>

Comments

2

I know this is a late reply but I found this script above and it was fantastic. I ran into an issue though with it unsetting my children because of the way the ItemID was working, when I ran it with a similarly designed table to the OP. To get around this, and given the amount of RAM in most web servers should be able to handle this, I've taken John Kugelman's great example and modified it slightly. Instead of having to apply children to all of the items and then unsetting them after, I create a new array and build it all in one

Code:

$new_array = array();
foreach ($menuItems as $key => &$menuItem) {
    if (($menuItem['ParentID'] != NULL) && ($menuItem['ParentID'] != '')) {
        $new_array[$menuItem['ParentID']]['Children'][$menuItem['ItemID']] = &$menuItem;
    } else {
        $new_array[$menuItem['ItemID']] = &$menuItem;
    }
}

print_r($new_array);

Hope this helps someone else because the above certainly helped me

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.