In this article, we can will build navigation menus that will allow our users to navigate through our application. We will develop a menu generator, that builds the menus for us, and also checks the logged in User’s permissions, and only shows pages and links relevant to that user. We will begin by creating a Path() object, which will store details about the user’s current location within our Application, or their Path from the root of our application.

<?PHP	// models/path.class.php
	
class Path{
	
	public $Path;
	
	private $PageStore;
	private $CurrentController;
	private $CurrentMethod;

	public function __construct($PageStore, $CurrentController, $CurrentMethod){
		$this->PageStore = $PageStore;
		$this->CurrentController = $CurrentController;
		$this->CurrentMethod = $CurrentMethod;
		$this->getPath();
	}
	
	public function getPath(){
		$Path = array();
		
		$CurrentPage = $this->PageStore->getPage($this->CurrentController, $this->CurrentMethod);
		
		$Page = $Path[] = $CurrentPage;
		while($Page->Parent != 'root'){
			$Page = $this->PageStore->getParent($Page);
			$Path[] = $Page;
		}
		$this->Path = array_reverse($Path);
		return $this->Path;
	}
	
}
	
?>

Our Path() object will store a public $Path variable which will contain the full menu path of the currently viewed page. For example, if the User was on the Edit User page, the Path might be Settings->Users->Edit User. This information will be very useful when we are generating menus later on.

In our constructor method, we ask that a PageStore and a CurrentController, and CurrentMethod be provided. The PageStore will allow us to find information relating to each of our pages, and the CurrentController and CurrentMethod values will enable us to locate the page we are currently on. The constructor method simple assigns these values to the variables we have set up to hold them, and then calls the getPath() method.

The getPath() method first creates an empty array, which will give us somewhere to store our path information, and then finds the current page’s information from our PageStore.

Next, we add the CurrentPage to our $Path array, and also set it as the starting point for our search. We then start a while loop, that will continue until the currently searched page’s parent value is ‘root’. This means we will search through the page tree until we reach the root. For each page that we need to search, we obtain the parent page object, add it to our $Path array, and then search for this new page’s parent. Once we have reached the root, the $Path array should now contain all of the pages from the current page to it’s root parent. In our earlier example, the array would contain the Edit User page object, the Users page object and the Settings page object. These items, would also be in reverse order, ie from Edit User to Settings. We want to be able to view the path from the root down to the current page later, so a simple array_reverse will sort our array correctly. We then set the public $Path variable to the value of our array for use later.

Now in order to obtain a $Path object, we simply need to create a new Path object, and provide a PageStore and the CurrentController and CurrentMethod. We can simplify this by using our dependency injection container, and setting the values of CurrentController and CurrentMethod in our App class.

<?PHP	// core/container.class.php -- additions only --
	
	public static function newPath($CurrentController = null, $CurrentMethod = null){
		if($CurrentController == null){
			$CurrentController = self::$CurrentController;
		}
		if($CurrentMethod == null){
			$CurrentMethod = self::$CurrentMethod;
		}
		
		return new Path(self::newPageStore(), $CurrentController, $CurrentMethod);
	}

?>
	
<?PHP	// core/app.class.php -- updates only --
	private function routeRequest(){
		if(isset($this->URL[0])){
			if(class_exists($this->URL[0])){
				$this->Controller = $this->URL[0];
				parent::$CurrentController = $this->URL[0];
			}
		}
		$ControllerName = 'new'.$this->Controller;
		$this->Controller = self::$ControllerName();
		
		if(isset($this->URL[1])){
			if(method_exists($this->Controller, $this->URL[1])){
				$this->Method = $this->URL[1];
				parent::$CurrentMethod = $this->URL[1];
			}
		}
		
		$this->Params = $this->URL ? array_values($this->URL) : [];
	}
?>

In the app.class.php file, you will see we have updated the routeRequest() method. The only real changes we have made here are to set parent::$CurrentController and parent::$CurrentMethod when we route the request. This makes the CurrentController and CurrentMethod values available to our Container class for use when building a Path object.

In the container.class.php file we have added a newPath() method, which gives the ability to set a controller and a path. The method checks to see if a controller and a path were set, and if not, it will use the values set in the Container’s CurrentController and CurrentMethod variables by default. Once we have determined the necessary variable contents, we can then return a new Path object which will already contain the $Path variable with our Path data.

Now that we have access to our current location in the application, we can look at how we can generate a menu. We can do this by creating a new Menu model class.

<?PHP	// models/menu.class.php

class Menu {
	
	private $PageStore;
	private $Path;
	
	public $Menu;
	
	public function __construct($Level, $PageStore, $Path){
		$this->PageStore = $PageStore;
		$this->Path = $Path;
				
		$this->Menu = $this->generateMenu($Level);
	}
	
	public function generateMenu($Level){
		$LevelAvailable = false;

		//Check if level is available
		if(sizeof($this->Path->Path) >= $Level){
			$Level = $Level - 1;
			
			//Get the path page at this level
			$CurrentItem = $this->Path->Path[$Level];
			$ParentKey = $CurrentItem->Parent;
			//Level Available to true
			$LevelAvailable = true;
		}
		
		//Check if the level being requested is a child menu of the current page.
		$ChildMenuLevel = sizeof($this->Path->Path) + 1;
		if($ChildMenuLevel == $Level){
			//If it is the child menu of the current page being requested, set the parent to this page's ID
			$CurrentItem = $this->Path->Path[$Level - 2]; //Level requested is 1 deeper than the current item, plus the array offset = 2
			$ParentKey = $CurrentItem->getKeyId();
			$LevelAvailable = true;
		}

		if($LevelAvailable){
			$Menu = '';
			//Get all items at this level
			$Directory = $this->PageStore->getPageList();
			$Items = $this->PageStore->findChildrenByParentKey($Directory, $ParentKey);
			if(sizeof($Items) >= 1){
				foreach($Items AS $Item){
					if(in_array($Item->getKeyId(), App::$UserPermissions)){
						$Attributes = '';
						if($Item->getKeyId() == $CurrentItem->getKeyId()){
							$Attributes = 'class="current"';
						}
						$Menu .= '<a href="/'.$Item->Controller.'/'.$Item->Method.'" alt="'.$Item->MenuTitle.'" '.$Attributes.'><div id="menuitem">'.$Item->MenuTitle.'</div></a>';
					}
				}
			} else{
				$Menu = false;
			}
			
		} else{
			
			$Menu = false;
		}
		
		return $Menu;
		
	}
	
}
	
?>

Our Menu class required we provide a $Level, a PageStore object and a $Path object. The constructor then stores the PageStore and Path, and calls the generateMenu() method. The $Level mentioned here refers to the menu level requested. In our example above, the menu that the Settings page would appear on would be level 1, or the main menu. The User’s page would appear on level 2, and the Edit User page would appear on level 3. We can use this one method to find each level of menu, depending on which is required.

The generateMenu() method first sets a default value of false to $LevelAvailable. We will then test to see if the level requested is actually available in our page structure. This will prevent errors should we accidentally look for level 100 or even if we look for level 3 where we only have 2 levels available. We test this in 2 steps. First, is the size of our $Path array bigger than the level requested? If so, we know the level is available, as our current page is on the same or a deeper level. If this test passes, we can set this level’s current page to the page stored in our path at this level. This will allow us to change styling for the current pages in the menu later on. In our example, we need to mark the Settings, Users, Edit User pages as current. We also set our $LevelAvailable variable to true in order to continue processing the generation of the menu.

Our next test for $LevelAvailable is to see if the menu being requested is the child menu of the current page. For example, if we were on the Settings->Users page, we need to be able to see the third level menu in order to be able to select Edit User. We do this by adding 1 to the size of the current path, and comparing the value to the $Level requested. If they are equal, then we have identified the case we have described. We then find the Key of the current page, in order to use that to search for child pages. Again, we set $LevelAvailable to true.

Now that we have determined the level is available, and we have found the parent Key to use to find other pages for the menu, we can begin generating the menu. First, we set an empty string to which we can add the HTML required to display the menu items. We then obtain a full page list by using the PageStore’s getPageList() method. We can then find the items with the correct parent key by using the PageStore’s getChildrenByParentKey() method and providing the PageList and the ParentKey. We now have a list of all pages for our menu stored in the $Items variable. We now loop through each Item, test to see if the item is marked as our CurrentItem, and if so add a “class=current” attribute to the item. We then add HTML to our $Menu variable that contains the information for the current menu item. Here we are essentially creating an a href link to our page using the Controller and Method and wrapping the link’s text in a CSS div so that we can style the menu item using CSS later.

If we determine that the level is not available, or the search for pages returns no results, we set our $Menu to false, and finally return our generated $Menu to the constructor method which stores the menu in the public $Menu variable.

So how do we now use the Menu class to generate our Menu’s? We will look at this in more detail in the next article when we look at building a user interface for our Application. But here is a simple example. I have updated our index.php file, which is the starting point for our application, and added three levels of Menu (as this project will most likely contain 3 navigation levels).

<?PHP	// index.php
include_once '../vendor/autoload.php';
include_once 'core/password.php';
//Set Custom Session Handler
App::setSessionHandler();
?>
<html>
	<head>
		<title>A SaaS Experience</title>
		<link rel="stylesheet" href="/public/zebra_form.css">
		<script src="/public/jquery.js"></script>
		<script src="/public/zebra_form.js"></script>
	</head>
	
	<body>

<?PHP
	
$App = new App($_SERVER['REQUEST_URI']);

//Add items before content render, eg menu/header
if($App->requestIsInternal()){
		$Menu = App::newMenu(1);
		echo '<p>Menu1: '.$Menu->Menu;		
		
		$Menu1 = App::newMenu(2);
		echo '<p>Menu2: '.$Menu1->Menu;		
		
		$Menu2 = App::newMenu(3);
		echo '<p>Menu3: '.$Menu2->Menu;	
}

// Render page content
$App->render();


//Add items after content render, eg footer


?>

	</body>
</html>
	
<?PHP	// core/app.class.php -- updates only --
		private function routeRequest(){
		if(isset($this->URL[0])){
			if(class_exists($this->URL[0])){
				$this->Controller = $this->URL[0];
				parent::$CurrentController = $this->URL[0];
			}
		}
		$ControllerName = 'new'.$this->Controller;
		$this->Controller = self::$ControllerName();
		
		if(isset($this->URL[1])){
			if(method_exists($this->Controller, $this->URL[1])){
				$this->Method = $this->URL[1];
				parent::$CurrentMethod = $this->URL[1];
			}
		}
		
		$this->Params = $this->URL ? array_values($this->URL) : [];
	}
	
	public function render(){
		call_user_func([$this->Controller, $this->Method], $this->Params);
	}

You will see that right after we create our $App object, which controls and runs our entire application, we have added a quick test to see if the current page is an internal page. We do this, as we do not want our menu’s showing on external pages like our login screen or our signup screen. If the App object tells us the request is internal, we can add our menu’s to the screen by using our dependency injection container to request a newMenu() and providing the menu level. We can then echo the menu to the screen. In the next article, we will look at styling and fitting the menu in with our user interface, but for now we should get a nice list of our pages at each menu level.

Note, there is also a small change to our App class’s routeRequest() method. Previously we included the call_user_func() method call in our routeRequest() method, which loads the content of the current page. This has been moved into a render() method within the App class, and is now called in the index.php file to allow us to add our menu before the page content is rendered.

I have included an image below of how the menu’s currently appear for a 3 level path. Remember, we will make these look more appealing later!

The image above represents the menu’s you would see if you were on the UserCategories page, or the Edit User Categories or User Category Permissions pages.

In the next article, we will begin building a more appealing user interface for our framework and application, and define our headers, footers, give our menu’s some style and purpose, and provide a workspace to display our page content properly.