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!
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
(
)
)
)
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;
}
Multi-dimensional tree and an unordered HTML list generator
Recursively build a tree from a multi-dimensional array.
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>
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
Related
Hallöschen everyone,
i'm having a hierarchical array which i'm getting from jsTree when I copy+paste a node and I want to insert the new tree branch into database.
My table has an adjacency list structure with the three fields 'id','text','parent_id'.
The structure can be like below or more nested. I did't figured out how to keep track of the parent so to know which children belong to which parent as I walk the array recursively.
What could be the cleanest approach?
Array(
[id] => 1
[text] => 'a'
[children]
=>Array(
[0] => Array
(
[id] => 2
[text] => 'b'
[children] => []
)
[1] => Array
(
[id] => 3
[text] => 'c'
[children] => Array(
[0] => Array(
[id] => 4
[text] =>''
[children]=>[]
)
)
)
)
)
So the final sollution that works for me is this one
function insertRows($data, $parent_id)
{
foreach ($data as $key => $value) {
if(isset($value['text']))
// here insert the the $value['text'] and get the generated $id
if (isset($value['children'])) {
insertRows($value['children'], $id);
}
}
}
To duplicate the dataset try this:
function insertRows($array, $parentId = null)
{
/** #var PDO $pdo */
global $pdo;
foreach ($array as $key => $value) {
// Add text and parent id to table.
$pdo->query('INSERT INTO table SET text = "' . $value['text'] . '", parent_id = ' . $parentId);
if (is_array($value['children'])) {
insertRows($value['children'], $pdo->lastInsertId());
}
}
}
Its untested an similar to pseudocode. Its more a hint.
You can have third index as parent_id and for root element make parent_id as 0 and in thier respective children put parent_id as id of parent.
for root element :
Array(
[id] => 1,
[text] => 'a',
[parent_id] => 0
);
for 1st child it would be like
Array(
[id] => 2,
[text] => 'a',
[parent_id] => 1
);
I've got a dynamic form that allows a user to create as many form elements as they need -- then submit them. For this, I have prepared the input names as arrays like
<input name="title[]" ...
and posting them gives me output like
Array
(
[day] => 0
[project_id] => 5
[submit] => publish
[id] => Array
(
[0] => 4
[1] => 5
)
[title] => Array
(
[0] => Step 1
[1] => Step 2
)
[max_vol] => Array
(
[0] => 2
[1] => 3
)
[description] => Array
(
[0] => df dofi dofidfoi
[1] => dfvpdofvdpfo osd pod
)
)
I've created something that allows me to just grab the post arrays like so
foreach( $_POST as $post_key ) {
// ignore non-array post variables
if( is_array( $post_key ) ) {
foreach( $post_key as $form_value ) {
echo "$form_value\n";
}
}
}
/* ouputs...
4
5
Step 1
Step 2
2
3
df dofi dofidfoi
dfvpdofvdpfo osd pod
*/
which nicely sorts the non-arrays from the arrays, but I can't figure out how to take this variable number of created form elements and prepare them into an array variable that looks something like...
Array
(
[0] => Array
(
'id' => 4, 'title' => 'Step 1', 'max_vol' => '2', 'description' => 'df dofi dofidfoi'
),
[1] => Array
(
'id' => 5, 'title' => 'Step 2', 'max_vol' => '3', 'description' => 'dfvpdofvdpfo osd pod'
),
// could be more or less elements...
);
(I will be eventually passing these arrays to a MySQL query builder function).
Thanks.
How about creating a variable that is outside the scope of the foreach loop
$results = array();
foreach( $_POST as $post_key=>$post_value ) {
// ignore non-array post variables
if( is_array( $post_value ) ) {
foreach( $post_value as $form_key=>$form_value ) {
if (!isset($results[$form_key]))
{
$results[$form_key] = array();
}
$results[$form_key][$post_key] = $form_value;
}
}
}
// results is your array variable
print_r($results);
Iterate over some significant $_POST-array key, for example - id and get the values from other $_POST-arrays with the same index:
$values = array();
foreach ($_POST['id'] as $k => $v) {
$values[] = array(
'id' => $v,
'title' => $_POST['title'][$k],
'max_vol' => $_POST['max_vol'][$k],
'description' => $_POST['description'][$k],
);
}
print_r($values);
I have a flat array of categories. All categories have ID, Parent_id and Name.
The root categories have Parent_id equal to null. They have subcategories that might have them as their parents and their might be subsubcategories that have subcategories as parents. (their might be a lot of levels).
I can get all categories with a single query into a flat array. What I need is - If I take a category (or a subcaegory), how can I get a list of all nested categories (subcategories, subsubcategories) that are included in this category? Got stack with that problem :(
Array looks like this:
Array
(
[0] => Array
(
[pk_i_id] => 2
[fk_i_parent_id] =>
[i_expiration_days] => 30
[i_position] => 0
[b_enabled] => 1
[b_price_enabled] => 1
[s_icon] =>
)
[1] => Array
(
[pk_i_id] => 4
[fk_i_parent_id] =>
[i_expiration_days] => 30
[i_position] => 6
[b_enabled] => 1
[b_price_enabled] => 1
[s_icon] =>
)
[2] => Array
(
[pk_i_id] => 12
[fk_i_parent_id] =>
[i_expiration_days] => 60
[i_position] => 11
[b_enabled] => 1
[b_price_enabled] => 1
[s_icon] =>
)
[3] => Array
(
[pk_i_id] => 13
[fk_i_parent_id] => 108
[i_expiration_days] => 30
[i_position] => 0
[b_enabled] => 1
[b_price_enabled] => 1
[s_icon] =>
)
You can use recursion. It will be looks like this:
function outTree(array $tree, $parentId = null) {
echo '<ul>';
foreach ($tree as $row) {
if ($row['fk_i_parent_id'] == $parent_id) {
echo '<li>' . $row['pk_i_id'];
echo outTree($tree, $row['pk_i_id']);
echo '</li>';
}
}
echo '</ul>';
}
I am returning a list of pages and their parent pages from a MySQL database and putting all results into an array as follows where every result is an array which includes the parent, name and id of the forum (the key of array pages is also the same as page id).
For the sake of the model and the applicaiton, there are some other parameters.
"root pages" have a parent of 0
there are no orphaned pages
so, the MySQL query will return this dataset.
pages=>
[1] => array(id=>1,
parent=>0,
name=>Hello World)
[2] => array(id=>1,
parent=>1,
name=>Child of Hello World)
[3] => array(id=>1,
parent=>0,
name=>Brother of Hello World)
[4] => array(id=>4,
parent=>2,
name=Grand-child of Hello World)
[6] => array(id=>6,
parent=>4,
name=Great-grand-child of Hello World)
i would then like to transform the array into something that looks like this
pages=>
[1] => id=>1,
name=>Hello World
children=>
[2] => id=>1
name=>Child of Hello World
children=>
[4] =>
id=>4
name=> Grand-child of Hello World)
children=>
[6] =>
id=>6
name=> Great-grand-child of Hello World
children= null
[3] => array(id=>1,
name=>Brother of Hello World
children=>null
So basically, i want to turn a linear array into a nested multidimensional array so that i can print my sitemap.
it needs to be a recursive solution. there are over 700 pages and up to 5 or 6 levels.
i only want to do 1 mysql query. not 700 so please dont give me a mysql based solution.
Here is a quick recursive function that builds a tree. Note that it's not great (one reason is because it doesn't get rid of items that have been already added to the tree, so each time you recurse it goes thru the entire list) - but it should work enough to get you started.
function buildTree($itemList, $parentId) {
// return an array of items with parent = $parentId
$result = array();
foreach ($itemList as $item) {
if ($item['parent'] == $parentId) {
$newItem = $item;
$newItem['children'] = buildTree($itemList, $newItem['id']);
$result[] = $newItem;
}
}
if (count($result) > 0) return $result;
return null;
}
$myTree = buildTree($myArray, 0);
<?php
$pages = array();
$pages[1] = array('id' => 1, 'parent' => 0, 'name' => 'Hello World');
$pages[2] = array('id' => 1, 'parent' => 1, 'name' => 'Child of Hello World');
$pages[3] = array('id' => 1, 'parent' => 0, 'name' => 'Brother of Hello World');
$pages[4] = array('id' => 4, 'parent' => 2, 'name' => 'Grand-child of Hello World');
$pages[6] = array('id' => 6, 'parent' => 4, 'name' => 'Great-grand-child of Hello World');
$children = array();
foreach($pages as $key => $page){
$parent = (int)$page['parent'];
if(!isset($children[$parent]))
$children[$parent] = array();
$children[$parent][$key] = array('id' => $page['id'], 'name' => $page['name']);
}
$new_pages = recursive_append_children($children[0], $children);
function recursive_append_children($arr, $children){
foreach($arr as $key => $page)
if(isset($children[$key]))
$arr[$key]['children'] = recursive_append_children($children[$key], $children);
return $arr;
}
print_r($new_pages);
?>
Outputs:
Array
(
[1] => Array
(
[id] => 1
[name] => Hello World
[children] => Array
(
[2] => Array
(
[id] => 1
[name] => Child of Hello World
[children] => Array
(
[4] => Array
(
[id] => 4
[name] => Grand-child of Hello World
[children] => Array
(
[6] => Array
(
[id] => 6
[name] => Great-grand-child of Hello World
)
)
)
)
)
)
)
[3] => Array
(
[id] => 1
[name] => Brother of Hello World
)
)
As one of the steps toward a greater website redesign I am putting the majority of the content of our website into html files to be used as includes. I am intending on passing a variable to the PHP template page through the URL to call the proper include.
Our website has many programs that each need an index page as well as about 5 sub-pages. These program pages will need a menu system to navigate between the different pages.I am naming the pages pagex_1, pagex_2, pagex_3, etc. where "pagex" is descriptive of the page content.
My question is, what would be the best way to handle this menu system? Is there a way to modify the initial variable used to arrive at the index page to create links in the menu to arrive at the other pages?
Thanks for any help!
I'd store the menu structure in an associative PHP array like so:
$menu = array(
'index'=>array(
'title'=>'Homepage',
'include'=>'home.html',
'subitems'=>array()
),
'products' => array(
'title'=>'Products',
'include'=>'products.html',
'subitems'=>array(
'product1'=>array(
...
),
'product2'=>array(
...
),
'product3'=>array(
...
)
)
)
);
Make that array available to the template script as well (e.g. by include/require). Then use delimiters in the variable to identify the menu item (like a file system does with folders and files) and therefore the resulting page to be included:
'index' would identify the index page
'products' would identifiy the products start page
'products/product1' would identify the product 1 subpage inside the products page
... and so on.
// Menu Array multidimensional
$menu = array(
'index' => array(
'title' => 'Homepage',
'include' => 'home.html',
'subitems' => array()
),
'products' => array(
'title' => 'Products',
'include' => 'products.html',
'subitems' => array(
'product1' => array(
'title' => 'product1',
'include' => 'product1.html',
'subitems' => array(
'product1.2' => array(
'title' => 'product1.2',
'include' => 'product1.2.html',
'subitems' => array()
)
)
),
'product2' => array(
'title' => 'product2',
'include' => 'product2.html',
'subitems' => array()
),
'product3' => array(
'title' => 'product3',
'include' => 'product31.html',
'subitems' => array()
),
'product4' => array(
'title' => 'product4',
'include' => 'product4.html',
'subitems' => array()
)
)
)
);
// Make menu
function makeMenu($menu_array,$is_sub = false,$list=['ul','li']){
$attr = (!$is_sub) ? ' class="menu"' : ' class="submenu"';
$child = NULL;
$menu = "<{$list[0]}{$attr}>";
foreach($menu_array as $id => $items){
foreach($items as $key => $val){
if( is_array($val) ) {
if ( !empty($val) ) $child = makeMenu($val,true,$list);
} else {
$$key = $val;
}
}//foreach
$menu .= "<{$list[1]}>";
$menu .= ''.$title.'';
$menu .= $child; // Sub Menu
$menu .= "</{$list[1]}>";
unset($url, $text, $sub);
}//foreach
$menu .= "</{$list[0]}>";
return $menu;
}
// retrieve the desired page with the shaft
function makeSubMenu($menu, $key) {
return makeMenu(array($menu[$key]));
}
// chain Article
function chainItem(array $array, $unset = '' ){
$ds = array();
if ( is_array($array) )
foreach((array)$unset as $arr) unset($array[$arr]);
foreach($array as $key=>$value)
if (!is_array($value)) $ds[$key] = $value;
return $ds;
}
// Build walk recursive -> single array insert database MySql
function walkRecursive($array, $ordId = true, $unset=[], $children = 'children', $i = 1, $parent = 0, &$res = [] ) {
if ( !is_array($array) ) return array();
foreach(array_values($array) as $key=>$arr) {
$das = array(
'id' => $id = $ordId ? $arr['id'] : $i++,
'parent' => $parent
);
$res[] = array_merge($das,chainItem($arr,$unset));
if( isset($arr[$children]) ) {
walkRecursive($arr[$children], $ordId, $unset, $children, $i, $id, $res );
}
}
return $res;
}
echo makeMenu($menu);
// Result of the print
<ul class="menu">
<li>Homepage</li>
<li>Products
<ul class="submenu">
<li>product1
<ul class="submenu">
<li>product1.2</li>
</ul>
</li>
<li>product2
<ul class="submenu">
<li>product1.2</li>
</ul>
</li>
<li>product3
<ul class="submenu">
<li>product1.2</li>
</ul>
</li>
<li>product4
<ul class="submenu">
<li>product1.2</li>
</ul>
</li>
</ul>
</li>
</ul>
// Build walk recursive -> single array insert database MySql
header("Content-type: text/plain");
print_r( walkRecursive($menu,false,['parent','id'],'subitems') );
// Print array -> Result:
Array
(
[0] => Array
(
[id] => 1
[parent] => 0
[title] => Homepage
[include] => home.html
)
[1] => Array
(
[id] => 2
[parent] => 0
[title] => Products
[include] => products.html
)
[2] => Array
(
[id] => 3
[parent] => 2
[title] => product1
[include] => product1.html
)
[3] => Array
(
[id] => 4
[parent] => 3
[title] => product1.2
[include] => product1.2.html
)
[4] => Array
(
[id] => 4
[parent] => 2
[title] => product2
[include] => product2.html
)
[5] => Array
(
[id] => 5
[parent] => 2
[title] => product3
[include] => product31.html
)
[6] => Array
(
[id] => 6
[parent] => 2
[title] => product4
[include] => product4.html
)
)