Working with Multidimensional Arrays

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
s992
Forum Contributor
Posts: 124
Joined: Wed Oct 27, 2010 3:06 pm

Working with Multidimensional Arrays

Post by s992 »

I'm having trouble wrapping my head around this problem.

I'm trying to write some code to generate dynamic navigation which will have an unlimited number of subnavigation levels. The way I'm currently approaching this is with a multi-dimensional array of navigation items. This is a simplified example of the array:

Code: Select all

$navigation = array(
	'link'	=>	'#',
	'title'	=>	'Top Level Navigation',
	array(
		'link'	=>	'#',
		'title'	=>	'SubNav1',
		array(
			'link'	=>	'#',
			'title'	=>	'SubSubNav1')
	)
);
As you can see, child elements are arrays underneath parent elements. I'd like this to go on as deep as possible, but I have no clue how to process the final array. An endless string of foreachs? Any advice would be fantastic!
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Re: Working with Multidimensional Arrays

Post by John Cartwright »

I would probably use a key to identify child elements, and recursively iterate to build your html using the following structure.

Code: Select all

$navigation = array(
     array(
           'link'  =>      '#',
           'title' =>      'Top Level Navigation',
           'children' => array(
              array(
                 'link'  =>      '#',
                 'title' =>      'SubNav1',
                 'children' => array(
                    array(
                       'link'  =>      '#',
                       'title' =>      'SubSubNav1'
                    )
                 )
              )
           )
      )
);

function buildNav($nav) 
{
   foreach ($nav as $value) {
      $html .= '<a href="'. $value['link'] .'">'. $value['title'] .'</a>';
      if (isset($value['children']) && count($value['children'])) {
         $html .= buildNav($value['children']);
      }
   }

   return $html;
}

echo buildNav($navigation);
You will obviously need to modify the buildNav() to properly build the html, but you should get the idea.
s992
Forum Contributor
Posts: 124
Joined: Wed Oct 27, 2010 3:06 pm

Re: Working with Multidimensional Arrays

Post by s992 »

Hey John, thanks - worked like a charm!

Now, I do have another question. I thought I had my array creation set up properly but it looks like I do not - my nav is only going two levels deep. Here's the function I've got currently(this is in CodeIgniter, but it shouldn't make a difference):

Code: Select all

	protected function _get_nav_array() {
		
		// SELECT slug, id, parent_id, title FROM nav WHERE display = 1
		$sql = $this->CI->db->select('slug, id, parent_id, title')->where('display',1)->get('nav');
		
		// Turn the result into an array
		$arr = $sql->result_array();
		
		// Put a placeholder value in the [0] index of the array because
		// everything was offset by 1
		array_unshift($arr,'placeholder');
		
		// Loop through the array
		foreach($arr as $item) {
			
			// parent_id = 0 indicates that it is a top-level nav element
			// and nothing further is required
			if($item['parent_id'] != 0) {
				
				// Example: $array[1]['children'][0] = $item
				$arr[$item['parent_id']]['children'][] = $item;
				
				// Pop that off the end of the array so we don't have duplicate values
				array_pop($arr);
			}
		}
		
		// Shift the placeholder index back off of the top
		array_shift($arr);
		
		// Just a setter.
		$this->_set_nav_array($arr);
	}
So the problem, which I believe lies in my use of array_pop(), is that any element that has a parent_id of another subnav element gets destroyed at some point during the process. Here's an example of what my table looks like:

Code: Select all

id 	parent_id 	title 		slug 		display
1 	0 		About Us 	about 		1
2 	0 		Demo Page 	demo-page 	1
3 	1 		Subnav1 	sub 		1
4 	1 		Subnav2 	subnav 		1
5 	2 		Other Subnav 	subnavv 	1
6 	3 		SubSubNav 	subsub 		1
Everything is displaying as expected except for the final entry on the table which has parent_id set to 3. Any suggestions?
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Re: Working with Multidimensional Arrays

Post by John Cartwright »

Like I showed you in my example, you need to recursively call the function and pass the child elements to the parent. You can do this without recursion, but it's the simplest way :D
s992
Forum Contributor
Posts: 124
Joined: Wed Oct 27, 2010 3:06 pm

Re: Working with Multidimensional Arrays

Post by s992 »

Hey John, thanks again. I managed to track down this question on StackOverflow that got me pointed in the right direction. The final function, in case anyone ever runs into this problem:

Code: Select all

	protected function _get_nav_array() {
		
		// SELECT slug, id, parent_id, title FROM nav WHERE display = 1
		$sql = $this->CI->db->select('slug, id, parent_id, title')->where('display',1)->get('nav');
		
		// Turn the result into an array
		$arr = $sql->result_array();
		
		// Put a placeholder value in the [0] index of the array because
		// everything was offset by 1, thanks to MySQL starting IDs at
                // 1 and PHP starting array indexes at 0
		$placeholder = array('id' => NULL, 'parent_id' => NULL, 'title' => NULL, 'slug' => NULL);
		array_unshift($arr,$placeholder);
		
		// Loop through the array, finding parent_id and assigning sub-nav
		// items as necessary
		foreach($arr as $id => &$item) {
			if($item['parent_id'] != NULL) {
				$arr[$item['parent_id']]['children'][$id] = &$item;
			}
		}
		
		// Get rid of all the left over items that were duplicated during the
		// previous foreach loop
		foreach(array_keys($arr) as $id) {
			if($arr[$id]['parent_id'] != NULL) {
				unset($arr[$id]);
			}
		}
		// Shift the placeholder index back off of the top
		array_shift($arr);
		
		// Just a setter.
		$this->_set_nav_array($arr);
	}
Post Reply