leart Posted May 9, 2010 Share Posted May 9, 2010 Hey guys, I'm pretty sure this is a common topic and so I was wondering if there's a common/design pattern solution to this problem. The problem - my web application navigation is split into Sections and it's modules so it's a 2-levels navigation. For example, there's Management, and inside it: Users, Groups etc. I have chosen to go with CodeIgniter framework and I was wondering about how to support that? If it wasn't made clear until now then the importance of the navigation isn't only to draw HTML links but rather, each module in a section would have to know which is it's parent section (for the Users module it'll be the Management section). And also, each section and module would need to have attributes like 'active' (for css markup) and such as 'location' for the link to redirect somewhere (to the controller of the module for example). Hoping to get your help on this... Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/ Share on other sites More sharing options...
ignace Posted May 9, 2010 Share Posted May 9, 2010 1) You could use the Singleton pattern to create a Menu object, which you would be able to call inside your module to add a navigation element. Although due to it's global nature it is not encouraged. class Menu { private static $instance = null; private function __construct() {} private function __clone() {} public static function getInstance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } } $menu = Menu::getInstance(); Sources: http://en.wikipedia.org/wiki/Singleton_pattern 2) Pass the $module structure to a $menu object. This uses the Composite pattern and is presumably the most encouraged method. $menu = new Menu($moduleComposite); interface MenuInterface {} class Menu { public function __construct(MenuInterface $a) {/*allows other objects to be passed besides the composite*/} } Sources: http://en.wikipedia.org/wiki/Composite_pattern Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/#findComment-1055450 Share on other sites More sharing options...
leart Posted May 9, 2010 Author Share Posted May 9, 2010 Thanks for the quick reply. My 'requirements' for the Sections -> Modules hierarchy is that it be dynamic, meaning, it would be easy to add/remove either. So for Sections, if each of those is a class, then it's easy to just add a new class to form a new Section. For Modules, which essentially encompass the actual functionality I have chosen to implement HMVC so that each Module contains it's own controllers/ views/ models/ etc... and it's easy to add/remove that. Is that falling well with the Composite design pattern? While new to that, I'm trying to digest how it all fits together in actual implementation. I'm not really following on your example to 'pass the $module structure to the $menu object' $menu = new Menu($moduleComposite); You didn't mean to pass the actual module controller, right? So we have another entity (if so to speak) which would describe the module then? As I described above - the Modules as implemented in the HMVC way, will each have a details.xml where I can have a 'parent' element to point to it's parent section. Hope you could shed some more light on this and some more of your thoughts and suggestions. Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/#findComment-1055471 Share on other sites More sharing options...
ignace Posted May 9, 2010 Share Posted May 9, 2010 The $moduleComposite is an "array" which $menu iterates over and builds the menu. Saying that you may want to take a look at the Builder Pattern. This has ofcourse the disadvantage of building the entire composite tree (instantiating every section and it's containing modules) on the other hand it could aswell write the info to a XML file and read later on by a class that builds the Menu from the XML source. I have no idea of how your application is architect of course, but in order to build your Menu you will have to know all available links for the Sections and their containing Modules or you'll end up with a half menu. You could of course also just add it to a database when the module is installed. Afterwards you read out the database table and build (and Cache, the menu is not that important to query the database on every request) the Menu. Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/#findComment-1055480 Share on other sites More sharing options...
leart Posted May 9, 2010 Author Share Posted May 9, 2010 It's somewhat hard for me to see the "big picture" of how all of this comes together. Ignace, I really appreciate the patience and advise. Any chance you are familiar with the CodeIgniter framework? I could maybe pass some code of how I approached things until now (not really a whole lot of progress but I'm sure it'll give a better understanding). Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/#findComment-1055528 Share on other sites More sharing options...
ignace Posted May 10, 2010 Share Posted May 10, 2010 I could maybe pass some code of how I approached things until now Don't say it, do it! What will my reply now be? Sure? And you'll have to wait even longer for a response. Efficiency is key Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/#findComment-1055703 Share on other sites More sharing options...
leart Posted May 10, 2010 Author Share Posted May 10, 2010 Don't say it, do it! What will my reply now be? Sure? And you'll have to wait even longer for a response. Efficiency is key Cool, thanks for baring with me Ok so, in terms of controller's inheritance what I have is: MY_Controller -> Management_Section -> Users The MY_Controller class does some high level things like setting the the template's layout, views, etc... it also gets the available sections (Management, Reports, etc) which I'll explain on them next.... class MY_Controller extends Controller { // the data member keeps views-related information protected $data; function __construct() { parent::Controller(); // load modules, set template views, partials, etc... $this->load->model('modules_m'); } protected function _get_sections() { $sections = array(); foreach(glob(APPPATH . 'sections/*.php') as $filename) { $section = pathinfo($filename, PATHINFO_FILENAME); require_once($filename); // perform PHP introspection to get class attributes information $classVars = get_class_vars($section); $sections[$classVars['sectionSettings']['name']] = $classVars['sectionSettings']; } return $sections; } } Each section is a class in the sections/ directory. I decided to do that instead of something drastically simple like array("Management", "Reports) because I wanted it to be somewhat dynamic. Like, if in the next update of the software I need another section I can just drop a class to that sections/ directory. <?php class Management_Section extends MY_Controller { public static $sectionSettings = array( "name" => "Management", "priority" => 0, "enabled" => true, "active" => false, "location" => 'users/users', ); function __construct() { parent::__construct(); $this->setSectionSettings(array("active"=>true)); $this->data->view['modules'] = $this->modules_m->get_modules(); $this->data->view['sections'] = $this->_get_sections(); } function _getSectionModules() { $modules = $this->data->view['modules']; foreach($modules as $index => $module) { if ($module['section'] == self::$sectionSettings['name']) { $sectionModules[$module['name']] = $module; } } return $sectionModules; } function getSectionName() { return self::$sectionSettings['name']; } protected function setSectionSettings($setting) { foreach($setting as $key => $value) self::$sectionSettings[$key] = $value; } } This is the Users module. To "manage" the modules I have a model which scans the modules/ directory and parses a details.xml file which has some general info as well as information like section => "Management" <?php class Users extends Management_Section { public static $moduleSettings = array("name" => "Users", "active" => false, "location" => 'users/users', ); function Users() { parent::__construct(); parent::setSectionSettings(array("active"=>true)); } function index() { $data = array(); $this->data->title = "My Real Title"; $this->data->view['sectionModules'] = $this->_getSectionModules(); $this->data->view['sectionModules'][self::$moduleSettings['name']]['active'] = true; $this->template->build('base/body', $this->data); } } To sum it up - this code as it is works ok. Though I don't know if I'm making design mistakes which I will later regret and since I am now re-designing a spaghetti code into an OOP manner, I want it to be good so I have the time to spare on designing it as best I can. One of my thoughts on a possible "mistake" with that code is that maybe Users should be inheriting from a modules class though I can't do multiple inheritance and inherit from both Modules and a section like I do right now so... I don't know. tell me what you think :-) Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/#findComment-1055708 Share on other sites More sharing options...
ignace Posted May 10, 2010 Share Posted May 10, 2010 You are mixing Controller & Model logic, and I was on the right track in my second post. You basically want to build the navigation menu and mark certain items as active. Take a look at an example implementation over at Zend framework. Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/#findComment-1055747 Share on other sites More sharing options...
leart Posted May 10, 2010 Author Share Posted May 10, 2010 You are mixing Controller & Model logic, and I was on the right track in my second post. Yeah, I actually thought of that too, to take out the _get_sections out of the MY_Controller and instead have a model to deal with the sections and load that model up in the MY_Controller. So, a couple of questions: 1. is your suggestion towards the composite/builder fits in my current implementation where I have a class for each section and I am building the menu array like that? 2. is it better to have my mvc hierarchy such as: MY_Controller -> Module -> Users instead of MY_Controller -> Management_Section -> Users ? Thanks Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/#findComment-1055753 Share on other sites More sharing options...
ignace Posted May 10, 2010 Share Posted May 10, 2010 QA #1 Yes, you will need those patterns but not in the way I envisioned. For an example implementation I advice you to take a look at the link I provided in my previous post (Zend framework). QA #2 Your menu and your controller hierarchy are completely decoupled, they have no business with each other. Plus, your menu can also contain external links. Therefor it would be best to store your menu hierarchy in a database table, read out the table and build the menu. Apply logic to indicate active items. Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/#findComment-1055762 Share on other sites More sharing options...
leart Posted May 11, 2010 Author Share Posted May 11, 2010 Alright so this is what I've come up with. I'm actually feeling pretty ok with it but I'd appreciate your feedback and comments: Hierarchy is now: MY_Controller -> MY_Module -> Users So the menu is completely decoupled. Users is a module which inherits from a parent general module class (MY_Module) which inherits from the controller class. Now on to the code: Nothing new in the MY_Controller except I am now loading the Menu model require_once("MY_Module.php"); class MY_Controller extends Controller { // the data member keeps views-related information protected $data; function __construct() { parent::Controller(); $this->load->model('modules_m'); // load the modules model $this->load->model('menu'); // load the menu model // controller set layout/view/general stuff... } } The Menu model loads all the sections which still remain classes for now and gets the section's info via variable introspection. /* * Loop through all sections and include them */ foreach(glob(APPPATH . 'sections/*.php') as $filename) { require_once($filename); } class Menu extends Model { private $sections; private $modules; public function __construct() { parent::__construct(); $this->modules = $this->modules_m->get_modules(); $this->createSections(); } public function getSections() { if ($this->sections === null) $this->createSections(); return $this->sections; } public function setSectionActive($sectionName) { $this->sections[$sectionName]['active'] = true; } public function setModuleActive($sectionName, $moduleName) { $this->sections[$sectionName]['active'] = true; $this->sections[$sectionName]['modules'][$moduleName]['active'] = true; } public function createSections() { $this->sections = null; foreach(glob(APPPATH . 'sections/*.php') as $filename) { $section = pathinfo($filename, PATHINFO_FILENAME); require_once($filename); // perform PHP introspection to get class attributes information $classVars = get_class_vars($section); // build an array of sections with key being the section name pointing // to an array with the section attributes. $sectionName = $classVars['sectionSettings']['name']; $this->sections[$sectionName] = $classVars['sectionSettings']; // each section has a modules attribute which is a pointer to an array of // modules $this->sections[$sectionName]['modules'] = $this->getSectionModules($sectionName); } } function getSectionModules($sectionName) { $sectionModules = array(); foreach($this->modules as $index => $module) { if ($module['section'] == $sectionName) { $sectionModules[$module['name']] = $module; } } return $sectionModules; } } The MY_Module class: class MY_Module extends MY_Controller { protected $menuItems; protected $moduleData; function __construct($moduleClass) { parent::__construct(); $this->moduleData = $this->modules_m->get($moduleClass); $this->menu->setModuleActive( $this->moduleData['section'], $this->moduleData['name'] ); // sets up the menu tree array which is later passed-on to the view $this->data->view['sections'] =& $this->menu->getSections(); } } class Users extends MY_Module { function Users() { $moduleClass = basename(__FILE__, ".php"); parent::__construct($moduleClass); //var_dump($this->data->view['sections']['Management']); } function index() { $data = array(); $this->data->title = "Test Title"; $this->template->build('base/body', $this->data); } } What do you think? Quote Link to comment https://forums.phpfreaks.com/topic/201168-how-to-implement-navigation-for-a-web-app/#findComment-1056369 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.