Php – Design for creating a menu based on user type

design-patternsobject-oriented-designPHP

Problem
I am building a PHP application which has a main menu, which in turn contains various "sections" or "subheadings" with various links. There are several user types such as SuperAdmin, Admin, Teacher, Student, etc, and each type should see a potentially different menu structure. It seems that my client would like to add more types down the line.

Currently I have a huge if/elseif/… block that checks what user type you are and creates an array with arrays for all the sections, containing yet more arrays for each link (these arrays contain the url, class and caption of the url). It's about 700 lines long at the moment. As you can see, this is becoming quite complex and tiresome to get working. So what I've attempted to do is use some design patterns (of which my knowledge is not that amazing).

Failed attempts
Firstly I attempted to create a few Builder implementations: one for the link itsself, one for the "section", and finally one for the menu itself. Then I attempted implementing a Strategy pattern to check current user type, and add links to sections and these sections to the menu. The final attempt was to serialize this menu object to the DB and just read it from there (as some user types can have dynamically configured sections/links)

The real question
I have no clue whether my rationale is even remotely correct in this regard, because I've created and completely deleted all my code about 4 times now as it becomes complex to comprehend just what must go where. So I guess my actual question is this: If you had to build this kind of implementation for menus (or anything where multiple objects are built into another object, and multiple objects of that type are built into yet another object), how would you go about it? What patterns would be useful and how would one structure them to work together without killing your brain?

Thanks!

Best Answer

PHP Dynamic and Recursive Menu System

When building the dynamic menu at my previous job, for our CMS, we had one class that dynamicly builds/prints the menu based on the menu object.

View output example of the code below

Since you're kind of new, I don't suggest making any overkill menu, but try to keep it as simple as possible.

I suggest making a class for the menu, where you send/store the menu array. This way you can make other objects unlock/lock different menu objects, or even send a sub-menu to that menu class (depending on how complex you want to make it).

This allows you to dynamicly build the menu as you run through the system code, and print the menu when your code is finished running. That way you can put the user based menu items in each section of the code, which handles that part.

For example, when running the system, make a function/method that checks the user access and for simple solutions, have hard-coded arrays with menu objects for each user access, or if going hardcore, build the menu arrays dynamicly. That's up to you.

<?php
// Let's create the child menu array for the "Home" menu structure
$home[] = array('TITLE' => 'Home 1',
                'URL' => '/home-1-url-here',
                'CSS_CLASS' => 'sub-menu',
                'CHILDREN' => $home1Child);

$home[] = array('TITLE' => 'Home 2',
                'URL' => '/home-2-url-here',
                'CSS_CLASS' => 'sub-menu');

// Now we create the whole home array, with the children
$homeMenu = array('CSS_CLASS' => 'top-css-class',
                 'TITLE' => 'Home',
                 // If the top menu item is just a link, specify an URL
                 'URL' => '', 
                 // Or if it has children pass them on
                 'CHILDREN' => $home);

// Let's create the child menu array for the "Gallery" menu structure
$gallery[] = array('TITLE' => 'Gallery 1',
                'URL' => '/gallery-1-url-here',
                'CSS_CLASS' => 'sub-menu');

$gallery2Child[] = array('TITLE' => 'Gallery 2.1',
                'URL' => '/gallery-2.1-url-here',
                'CSS_CLASS' => 'sub-menu-child');
$gallery2Child[] = array('TITLE' => 'Gallery 2.2',
                'URL' => '/gallery-2.2-url-here',
                'CSS_CLASS' => 'sub-menu-child');

$gallery[] = array('TITLE' => 'Gallery 2',
                'URL' => '/gallery-2-url-here',
                'CSS_CLASS' => 'sub-menu',
                'CHILDREN' => $gallery2Child);

// Now we create the whole gallery array, with the children
$galleryMenu = array('CSS_CLASS' => 'top-css-class',
                 'TITLE' => 'Gallery',
                 // If the top menu item is just a link, specify an URL
                 'URL' => '', 
                 // Or if it has children pass them on
                 'CHILDREN' => $gallery);

$menu = new menu;
// Store the menu items we want in the menu
$menu->storeItem($homeMenu);
$menu->storeItem($galleryMenu);
// Print the menu
echo $menu->buildMenu();

If you want to make sub-menus aswell, make the menu class work recursively.

class menu {
   private $mainMenuClass = 'main-menu-class';
   private $parentClass = 'parent-menu-class';
   private $menu;

   public function storeItem($menuItem, $parentClass = '') { // If no $parentClass is specified, pick the default one
      // This stores the menu in an array
      $this->menu[] = $menuItem;
   }

   public function buildMenu() {
      // Let's break down the menu array, and start building it
      $html = '<ul class="'. $this->mainMenuClass .'">'. PHP_EOL;
      foreach($this->menu as $parentClass => $item) {
         $html .= '<li class="'. $item['CSS_CLASS'] .'"><a href="'. $item['URL'] .'">'. $item['TITLE'] .'</a>'. PHP_EOL;
         // Check if it's a recursive sub-menu (with children)
         // Now we send the children array to the buildChildrenItems() method
         $html .= $this->buildChildrenItems($item['CHILDREN']);
         $html .= '</li>'. PHP_EOL;
      }
      $html .= '</ul>'. PHP_EOL;
   return $html;
   }

   public function buildChildrenItems($menuSection) {
      // Put the recursive logics here so we can keep looping
      $html = '<ul class="'. $this->parentClass .'">'. PHP_EOL;
      foreach($menuSection as $item) {
          $html .= '<li class="'. $item['CSS_CLASS'] .'"><a href="'. $item['URL'] .'">'. $item['TITLE'] .'</a>'. PHP_EOL;
          // Now here is where the recursive magic happends
          // If the child item has even more children, call this method once again for those children
          $html .= ( isset($item['CHILDREN']) && is_array($item['CHILDREN']) ) ? $this->buildChildrenItems($item['CHILDREN']) : ''; 
          $html .= '</li>'. PHP_EOL;
      }
      $html .= '</ul>'. PHP_EOL;
   return $html;
   }
}

?>

From here you just design the menu as you want, using the CSS classes.

If you want to make it recursive (with dynamic sub-menus), then simply make the recursive logics before calling the buildChildrenItems() method. This is included in the code example above.

For more complex recursive menus, try these arrays.

$home1Child[] = array('TITLE' => 'Home 1.1',
                'URL' => '/home-1.1-url-here',
                'CSS_CLASS' => 'sub-menu-child');

$home121Child[] = array('TITLE' => 'Home 1.2.1',
                    'URL' => '/home-1.2.1-url-here',
                    'CSS_CLASS' => 'sub-menu-child');

    $home121Child[] = array('TITLE' => 'Home 1.2.2',
                    'URL' => '/home-1.2.2-url-here',
                    'CSS_CLASS' => 'sub-menu-child');

$home1Child[] = array('TITLE' => 'Home 1.2',
                'URL' => '/home-1.2-url-here',
                'CSS_CLASS' => 'sub-menu-child',
                'CHILDREN' => $home121Child);

$home1Child[] = array('TITLE' => 'Home 1.3',
                'URL' => '/home-1.3-url-here',
                'CSS_CLASS' => 'sub-menu-child');

$home[] = array('TITLE' => 'Home 1',
                'URL' => '/home-1-url-here',
                'CSS_CLASS' => 'sub-menu',
                'CHILDREN' => $home1Child);

$home[] = array('TITLE' => 'Home 2',
                'URL' => '/home-2-url-here',
                'CSS_CLASS' => 'sub-menu');

$home[] = array('TITLE' => 'Home 3',
                'URL' => '/home-3-url-here',
                'CSS_CLASS' => 'sub-menu');

// Now we create the whole home array, with the children

$homeMenu = array('CSS_CLASS' => 'top-css-class',
                 'TITLE' => 'Home',
                 // If the top menu item is just a link, specify an URL
                 'URL' => '', 
                 // Or if it has children pass them on
                 'CHILDREN' => $home);

// Let's create the child menu array for the "Gallery" menu structure
$gallery[] = array('TITLE' => 'Gallery 1',
                'URL' => '/gallery-1-url-here',
                'CSS_CLASS' => 'sub-menu');

$gallery2Child[] = array('TITLE' => 'Gallery 2.1',
                'URL' => '/gallery-2.1-url-here',
                'CSS_CLASS' => 'sub-menu-child');
$gallery2Child[] = array('TITLE' => 'Gallery 2.2',
                'URL' => '/gallery-2.2-url-here',
                'CSS_CLASS' => 'sub-menu-child');

$gallery[] = array('TITLE' => 'Gallery 2',
                'URL' => '/gallery-2-url-here',
                'CSS_CLASS' => 'sub-menu',
                'CHILDREN' => $gallery2Child);

// Now we create the whole gallery array, with the children
$galleryMenu = array('CSS_CLASS' => 'top-css-class',
                 'TITLE' => 'Gallery',
                 // If the top menu item is just a link, specify an URL
                 'URL' => '', 
                 // Or if it has children pass them on
                 'CHILDREN' => $gallery);

View output example of the code above

Finally, in the different classes for example, let's say the Gallery. Create the gallery menu array, check what kind of access the user has, and build it properly. Then you send the gallery menu array to the menu class, as shown above.

This concept is also good for future expansions, so when adding a new class/section on the website, let's say a guestbook. All the menu logics are built in that class and just sent to the dynamic menu class which builds it.

I do not recommend putting all the menu logics inside one big menu file, but as you run the system code, process each section and build the menu object as you go.

Hope this helps you on your way!

PS. If you have alot of user access types, and want to keep a clean code, put the menu arrays for each user access in seperate php files. (user_type_member.php or user_type_admin.php).

In the php code, where the arrays are written, just make a user type check, and call the according php file to load the array for that user.

switch($userType) {
   default: 
   case 'visitor': include('user_type_visitor.php'); break;
   case 'member': include('user_type_member.php'); break;
   case 'admin': include('user_type_admin.php'); break;
}

Regards, Andreas.

Related Topic