2

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!

  • Have you ever heard about ACL/RBAC? – Yang Mar 7 '14 at 12:08
  • Yeah, sure I have @djay, but I'm not exactly sure how I would go about implementing it? I guess that's what I've been trying to do with the whole if/else thing, but I don't know how to do it in a cleaner way. Thus the point of this question. – iLikeBreakfast Mar 10 '14 at 7:48
1

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.

0

I'd suggest writing a MenuFactory that takes a list of users/roles/permissions which in turn invokes any required builders to create the actual menu structure. This way the menu building is encapsulated, and the interface through the factory should be flexible enough to handle adding or removing users/roles in the future.

-1

It sounds like you have a few user types. Why don't you create a hierarchy of menu classes so at the top is an AbstractMenu and at the bottom are StudentMenu, TeacherMenu etc.? See also the accepted answer for this code review, which suggests to use an interface instead of an abstract class.

Besides that you may consider creating Menu and MenuFactory classes so that you could for example call MenuFactory->getStudentMenu() and obtain a concrete instance of Menu that suits a Student user. Then details of creating user-type-specific menus will be encapsulated in separate methods.

You can even combine these approaches and have static information populated in class constructor and dynamic information added by a factory.

  • Thanks for the answer. Firstly, I didn't downvote you, and second, I don't really see how that answers the question? I already know what to do, just not how to do it. If you know what I mean? And as an added question, why factory? It doesn't solve the problem of actually building the menu dynamically, which is my main objective here. – iLikeBreakfast Mar 11 '14 at 18:18
  • Thanks for your reply! Votes have been oscillating between +1 and -2 so I assume opinions diverged. It's a pity few actually cared to comment what was good or bad. I'll try to update my answer with some examples later today. They should explain the how part a bit better. – mkalkov Mar 12 '14 at 7:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.