Build a Menu with Recursive Functions

To continue my recent obsession with array handling, I want to spend this week talking about recursive functions and their application in dealing with arrays.

The goal of our exercise today is to build a menu, complete with sub-menu, all based off one function that runs recursively.

What Is Recursion?

In computer science, recursion is a concept in which a function can call itself. This is similar to the idea of looping in PHP, but it provides an opportunity, in the case of array handling, to add more fine-grain control into a program.

On a basic level, recursion can be illustrated with the following code:

function plusOne($x)
{
	if($x<10)
	{
		echo ++$x, "<br />";
		plusOne($x);
	}
	else
	{
		echo 'Finished! <br />';
	}
}

Calling this function will result in the following output:

1
2
3
4
5
6
7
8
9
10
Finished!

Obviously, the above could have been accomplished very simply with a loop, but it shows the basic idea behind recursion.

Constructing a Menu

In order to write our recursive array-handling function, let’s first define the array we’ll be using. To build a menu, we’ll need an element for each menu item that contains information about each item.

NOTE: Bear in mind that this example is very bare-bones, but should extend easily to include further information about your menu, including classes, IDs, or any other attributes you would want to add.

The Basic Array Structure

To start, let’s build a very basic menu. We’ll pretend we have a site that needs About Us, Blog, Links, and Contact pages. To create this, our array might look like this:

$menu = array(
	'about' => array(
		'display' => 'About Us'
	),
	'blog' => array(
		'display' => 'Read Our Blog'
	),
	'links' => array(
		'display' => 'Recommended Links'
		)
	),
	'contact' => array(
		'display' => 'Contact Us'
	)
);

Above, we’ve given each menu item an array key that easily defines it, and in the case of this example, will serve as the item’s URL unless it is otherwise defined.

Adding Sub-Menus

Let’s say that under our Recommended Links item, we need to add a sub-menu that holds links for a Products and a Services page. Additionally, the Services page needs a sub-menu that leads the user to Local and Online services.

To accomplish this, we’ll add another array element to our “links” element that contains an array holding information about our sub-menus:

	'links' => array(
		'display' => 'Recommended Links',
		'sub' => array(
			'products' => array(
				'display' => 'High-Quality Products',
				'url' => 'links/#products'
			),
			'services' => array(
				'display' => 'Helpful Services',
				'url' => 'links/#services',
				'sub' => array(
					'local' => array(
						'display' => 'Local Services',
						'url' => 'links/#services_local'
					),
					'online' => array(
						'display' => 'Online Services',
						'url' => 'links/#services_online'
					)
				)
			)
		)
	)

Notice that we included an element called “sub” that holds an array with two elements: “products” and “services”. Also, note that the “services” element has a “sub” element as well, containing an array with two elements: “local” and “online”.

We’ve also added a “url” element to our sub-menu items. This is so that we can have more control over where the menu is sending our users.

Turning the Array into a Usable Menu

With our array created, we need to write a function that will crawl through the array and build HTML markup based on the information supplied to it. To start, we need to define our function, which we’ll call buildMenu().

function buildMenu($menu_array, $is_sub=FALSE)
{
	/*
	 * If the supplied array is part of a sub-menu, add the
	 * sub-menu class instead of the menu ID for CSS styling
	 */
	$attr = (!$is_sub) ? ' id="menu"' : ' class="submenu"';
	$menu = "<ul$attr>n"; // Open the menu container

	// Create menu HTML here...

	/*
	 * Close the menu container and return the markup for output
	 */
	return $menu . "</ul>n";
}

We’ll be passing two parameters to the function, $menu_array and $is_sub. The first, obviously, is the array containing the array of menu items. The second is a flag that will let our function know if it is creating a sub-menu. This flag is to allow us to add an attribute to the menu HTML for easier CSS styling.

Our function will return a string, $menu, that will contain the HTML markup to display our menu. Since the standard convention for marking up menus is to use the <ul> tag, that’s what we’ll be doing here.

Step 1: Loop Through the Elements

After checking the $is_sub flag and determining whether or not we’re dealing with a sub-menu, we can loop through our elements. Because we’re dealing with multi-dimensional arrays, we’ll have to nest loops, which sounds complex, but is actually pretty simple once you see it in action.

The way we’ll be constructing our loops will follow a pattern like this: for each element in the array, run a loop to extract information out of the nested array.

In theory, the code will look like this:

function buildMenu($menu_array, $is_sub=FALSE)
{
	/*
	 * If the supplied array is part of a sub-menu, add the
	 * sub-menu class instead of the menu ID for CSS styling
	 */
	$attr = (!$is_sub) ? ' id="menu"' : ' class="submenu"';
	$menu = "<ul$attr>n"; // Open the menu container

	/*
	 * Loop through the array to extract element values
	 */
	foreach($menu_array as $id => $properties) {

		/*
		 * Because each page element is another array, we
		 * need to loop again. This time, we save individual
		 * array elements as variables, using the array key
		 * as the variable name.
		 */
		foreach($properties as $key => $val) {

			/*
			 * If the array element contains another array,
			 * call the buildMenu() function recursively to
			 * build the sub-menu and store it in $sub
			 */

			// Code here (see Step 2)...

			/*
			 * Otherwise, set $sub to NULL and store the
			 * element's value in a variable
			 */

			// Code here (see Step 2)...
		}

		/*
		 * If no array element had the key 'url', set the
		 * $url variable equal to the containing element's ID
		 */

		// Code here (see Step 3)...

		/*
		 * Use the created variables to output HTML
		 */

		// Code here (see Step 3)...

		/*
		 * Destroy the variables to ensure they're reset
		 * on each iteration
		 */

		// Code here (see Step 3)...

	}

	/*
	 * Close the menu container and return the markup for output
	 */
	return $menu . "</ul>n";
}

The comments above frame out the steps we need to take in order to loop through the array and generate our markup.

Step 2: Extracting Array Data

Now that our loops are set up, we need to write the code that will process the array elements as they are looped.

Our first step is to determine whether or not the value of the current element is an array. If so, we want to call buildMenu() again. The reason for this is that by doing so, we create another unordered list within our menu that we can store in a variable ($sub) for output with the parent element’s markup (thus generating valid XHTML markup).

If the current element is not an array, we use a programming trick called variable variables to dynamically name a variable with the element key (i.e. 'display' => 'About Us' is processed as $display = 'About Us';).

function buildMenu($menu_array, $is_sub=FALSE)
{
	/*
	 * If the supplied array is part of a sub-menu, add the
	 * sub-menu class instead of the menu ID for CSS styling
	 */
	$attr = (!$is_sub) ? ' id="menu"' : ' class="submenu"';
	$menu = "<ul$attr>n"; // Open the menu container

	/*
	 * Loop through the array to extract element values
	 */
	foreach($menu_array as $id => $properties) {

		/*
		 * Because each page element is another array, we
		 * need to loop again. This time, we save individual
		 * array elements as variables, using the array key
		 * as the variable name.
		 */
		foreach($properties as $key => $val) {

			/*
			 * If the array element contains another array,
			 * call the buildMenu() function recursively to
			 * build the sub-menu and store it in $sub
			 */
			if(is_array($val))
			{
				$sub = buildMenu($val, TRUE);
			}

			/*
			 * Otherwise, set $sub to NULL and store the
			 * element's value in a variable
			 */
			else
			{
				$sub = NULL;
				$$key = $val;
			}
		}

		/*
		 * If no array element had the key 'url', set the
		 * $url variable equal to the containing element's ID
		 */

		// Code here (see Step 3)...

		/*
		 * Use the created variables to output HTML
		 */

		// Code here (see Step 3)...

		/*
		 * Destroy the variables to ensure they're reset
		 * on each iteration
		 */

		// Code here (see Step 3)...

	}

	/*
	 * Close the menu container and return the markup for output
	 */
	return $menu . "</ul>n";
}

Step 3: Generate HTML and Clean Up

Our final step is to use the information we’ve extracted to generate our markup.

To start, we need to verify that a URL was supplied, and if not, substitute the top-most element’s key as the URL. This provides a shortcut for menu items that don’t need fancy URLs (i.e. http://example.com/about/) by allowing us to omit redundant information.

To accomplish this, we simply check that a variable was created called $url, and if not, we create it and set it to the value of $id that was extracted in our outermost loop.

With our URL determined, we’re ready to generate markup. This is a fairly straightforward step, as we’re just using the URL and display name extracted from our array to create an <li> tag.

With our menu item generated and added to the $menu variable, we can safely unset the variables and start our next iteration of the outermost loop. Unsetting the variables prevents issues with the $url variable storing the original default URL and breaking our menu.

The final function looks like this:

	/*
	 * Loop through the array to extract element values
	 */
	foreach($menu_array as $id => $properties) {

		/*
		 * Because each page element is another array, we
		 * need to loop again. This time, we save individual
		 * array elements as variables, using the array key
		 * as the variable name.
		 */
		foreach($properties as $key => $val) {

			/*
			 * If the array element contains another array,
			 * call the buildMenu() function recursively to
			 * build the sub-menu and store it in $sub
			 */
			if(is_array($val))
			{
				$sub = buildMenu($val, TRUE);
			}

			/*
			 * Otherwise, set $sub to NULL and store the
			 * element's value in a variable
			 */
			else
			{
				$sub = NULL;
				$$key = $val;
			}
		}

		/*
		 * If no array element had the key 'url', set the
		 * $url variable equal to the containing element's ID
		 */
		if(!isset($url)) {
			$url = $id;
		}

		/*
		 * Use the created variables to output HTML
		 */
		$menu .= "<li><a href="$url">$display</a>$sub</li>n";

		/*
		 * Destroy the variables to ensure they're reset
		 * on each iteration
		 */
		unset($url, $display, $sub);

	}

The Generated Menu

When we run our function with the array we created earlier, we’ll see the following output:

Our function was able to create a menu with two sub-levels (and it will go even deeper than that if necessary). The benefit of a function like this is that it allows us to forego the annoying task of editing menu HTML for every new client that we sign up, rather enabling us to quickly define an array that will be parsed into HTML quickly and easily.

Summary

Recursive functions are a great way to overcome complex tasks, especially when handling complex arrays such as our menu above. Taking a little time to get the hang of them can go a long way toward eliminating long strings of spaghetti code that muddies up our scripts.

How do you build your client menus? Do you have any clever ways to use recursion? Let me know in the comments!