The HTree Hierarchial Model is a data model for storing a hierarchial tree of information retrieved from SQL.
We needed a way to extract a table of information stored in a parent/child structure and create a tree-shaped array to store that information.
Hierarchial Model allows us to store a table of information with parent/children relationships in a PHP array structure.
About this release
This is our first release of this code but we have used it flawlessly in our own code so we would put it at a beta stage. If you do find any bugs/errors or have any suggestions please email them to us or comment on our blog post.
Contents
- Introduction
- SQL Data Structure
- Installation
- Basic Usage
- Hierarchial Model Array Structure
- Other Features and Techniques
- Advanced Usage: creating a single level array
- Sample Implementation
- HModel Methods
Introduction
We needed a class that would retrieve a block of data with parent/child relationships from MySQL and store it in an array which correctly represents the three dimensional nature of the parent/child relationships.
Essentially we want to turn this:
PK parent name ------------------------- 1 0 Animals 2 1 Cat
Into this:
Array
(
[0] => Array
(
[PK] => 1
[parent] => 0
[name] => Animals
[childs] => Array
(
[0] => Array
(
[PK] => 2
[parent] => 1
[name] => Cat
)
)
)
The requirements are that the class be able to handle any number of levels "deep", that it be able to return custom data for each element, and that it have methods for adding, deleting, enabling, disabling, and moving data within the same parent and between parents.
SQL Data Structure
SQL Schema
This shows an example database schema for a hierarchial model table.
CREATE TABLE `menu` ( `menu_PK` int(11) NOT NULL auto_increment, `menu_title` varchar(64) NOT NULL, `menu_parent` int(11) NOT NULL default '0', `menu_live` tinyint(4) NOT NULL default '0', `menu_order` int(11) NOT NULL, `menu_visible` tinyint(4) NOT NULL, `menu_image` varchar(32) NOT NULL, `menu_pageViews` int(11) NOT NULL, PRIMARY KEY (`menu_PK`) )
Sample Data
Here is a table of sample data to show you the parent/child relationship. As you can see, items are ordered within their parent category.
menu_PK menu_title menu_parent menu_live menu_order menu_visible menu_image menu_pageViews ------------------------------------------------------------------------------------------------------------ 1 Animals 0 1 1 1 animals.jpg 15 2 Cat 1 0 1 1 cat.jpg 12 3 Dog 1 1 2 1 dog.jpg 16 4 Horse 1 1 3 1 horse.jpg 9 5 Vegetables 0 1 2 1 veges.jpg 11 6 Carrot 5 1 1 1 carrot.jpg 13 7 Minerals 0 1 3 1 minerals.jpg 7 8 Copper 7 0 1 1 copper.jpg 14 9 Gold 7 1 2 1 gold.jpg 13
Represented as a tree, this would look like:
[0]
|
|--Animals
| |-- Cat
| |-- Dog
| `-- Horse
|--Vegetables
| `-- Carrot
`--Minerals
|-- Copper
`-- Gold
Installation
Copying required files
Simply copy the hierarchial_model.php file into the system/application/models folder within your CodeIgniter installation to complete the installation.
Basic HModel Usage
Usage Requirements
Loading the model
In the controller's construct function, load the Hierarchial Model model and give it the name "HModel".
$this->load->model('hierarchial_model', 'HModel');
Runtime Configuration
Configuration Example
Shown here is an example of the configuration directives required to use the HModel model. It is recommended that users call these in the controller's constructor to make the model available to all of a controller's functions.
$this->HModel->table = "menu";
$this->HModel->PK_field = "menu_PK";
$this->HModel->name_field = "menu_title";
$this->HModel->parent_field = "menu_parent";
$this->HModel->order_field = "menu_order";
$this->HModel->live_field = "menu_live";
$this->HModel->condition_field = "menu_visible";
$this->HModel->attribute_fields = array('menu_image','menu_pageViews');
$this->HModel->home_mode = "1";
Configuration Directives
$this->HModel->table
Defines the MySQL table that contains the hierarchial data.
$this->HModel->PK_field
Defines the field name that contains the Primary Key of the table.
$this->HModel->name_field
Defines the field name that contains the name of the menu item.
$this->HModel->parent_field
Defines the field name that contains the parent item's Primary Key (menu_PK).
$this->HModel->order_field
Defines the field name that indicates the order of the item within its category (menu_parent).
$this->HModel->live_field [OPTIONAL]
Defines the field name that indicates whether a menu item is "Live".
$this->HModel->condition_field [OPTIONAL]
Defines a field name that must contain a true value (1) for this item to be output.
$this->HModel->attribute_fields [OPTIONAL]
Contains an array of field names that should be added to the output array as additional information along with each item.
$this->HModel->home_mode [OPTIONAL]
Specifies that the menu be in "home mode", and treat the "Home" item specially.
Usage
Usage example
$menu = $this->HModel->get_items(); print_r($menu);
Hierarchial Model Array Structure
Returned Array
This shows a print_r() of the HModel array which is returned by the command $this->HModel->get_items();.
Array
(
[0] => Array
(
[id] => 1
[parent] => 0
[live] => 1
[menu_image] => animals.jpg
[menu_pageViews] => 15
[name] => Animals
[childs] => Array
(
[0] => Array
(
[id] => 2
[parent] => 1
[live] => 0
[menu_image] => cat.jpg
[menu_pageViews] => 12
[name] => Cat
)
[1] => Array
(
[id] => 3
[parent] => 1
[live] => 1
[menu_image] => dog.jpg
[menu_pageViews] => 16
[name] => Dog
)
[2] => Array
(
[id] => 4
[parent] => 1
[live] => 1
[menu_image] => horse.jpg
[menu_pageViews] => 9
[name] => Horse
)
)
)
[1] => Array
(
[id] => 5
[parent] => 0
[live] => 1
[menu_image] => veges.jpg
[menu_pageViews] => 11
[name] => Vegetables
[childs] => Array
(
[0] => Array
(
[id] => 6
[parent] => 5
[live] => 1
[menu_image] => carrot.jpg
[menu_pageViews] => 13
[name] => Carrot
)
)
)
[...etc...]
)
Other Features and Techniques
Home Mode Feature
One particular use for this style of hierarchial model is for managing the menu structure and content of a web site. In such a situation, it is often desirable to have a "Home" menu option, which appears in the data structure and whose parameters (such as image or content) can be edited, but which cannot be moved. "Home Mode" allows for such a menu item to exist. The "Home" menu item must have a menu_PK of '1'.
To put your menu into "home mode", simply specify the option in your controller's constructor when you configure the HModel module like so:
$this->HModel->home_mode = "1";
When in home mode, your first-level items still have a parent value of "0", so Home is not the parent of the items, but at the same level as the first-level items. Home should have a position value ('menu_order') of 1, which cannot be changed. The commands delete_item, move_item_up, move_item_down, move_item will not perform any action if passed the 'Home' item. The sample data for a menu in home mode might look like:
menu_PK menu_title menu_parent menu_live menu_order menu_visible menu_image menu_pageViews ------------------------------------------------------------------------------------------------------------ 1 Home 0 1 1 1 home.jpg 11 2 Animals 0 1 2 1 animals.jpg 15 3 Cat 1 0 1 1 cat.jpg 12
Caching Menu Results
Because generating a large hierarchial model with a lot of categories can result in a large number of MySQL queries, it is often desirable to cache the menu for front-end use so that viewers of the page receive a cached version of the menu. This can be done by implementing a simple function in your own model that serializes the menu and saves it to the database or to a file, and another which retrieves the cached menu and unserializes it before display. You will need to call save_menu_cache() after each menu management function, ie: add, delete, move. The following example shows what these functions might look like:
function save_menu_cache() {
// get hierarchial model and serialize it into a variable
$menu = serialize($this->HModel->get_items());
// save serialized menu to database
$this->db->where('cache_name', 'menu');
$this->db->set('cache_value', $menu);
$this->db->update('cache_table');
}
function load_menu_cache() {
// get the serialized menu from the database
$this->db->where('cache_name', 'menu');
$query = $this->db->get('cache_table');
$result = $query->row();
// unserialize data before returning array
return unserialize($result->cache_value);
}
Advanced Usage: creating a single level array
Case Study: Two-level Menu Editor
Outputting Flat Data
While a tree-shaped array of data is handy, sometimes we prefer to deal with a "flat" array in which all the children are at the same level as the parents, such as we might when outputting a list of menu rows for management. In this case study, we wish to use HModel to manage a web-site menu structure with two levels: the parent categories, and sub-categories. We want all the menu items structured in a flat array one after another, with the "sub-categories" indented or in a smaller font to indicate their depth. The data might be output like this:
Animals - Cat - Dog - Horse Vegetables - Carrot Minerals - Copper - Gold
Retrieving Flat Arrays
As well as flattening the array, we will add a little more metadata at this point.
We drop the 'parent' parameter as we no longer need it.
The new value 'isSub' indicates whether the item is sub-item and should be drawn differently.
The new value 'hasChildren' indicates whether an item has any sub-items
The new values 'upArrow' and 'downArrow' indicate whether an arrow should be drawn to allow the item to be moved up and down.
Array
(
[0] => Array
(
[id] => 1
[live] => 1
[name] = Animals
[menu_image] => animals.jpg
[menu_pageViews] => 15
[hasChildren] => 1
[isSub] = 0
[upArrow] = 0
[downArrow] = 1
}
[1] => Array
(
[id] => 2
[live] => 0
[name] = Cat
[menu_image] => cat.jpg
[menu_pageViews] => 12
[isSub] = 1
[upArrow] = 0
[downArrow] = 1
}
[...etc...]
}
Converting "tree" array to "flat" array
This function iterates through the "tree" array produced by HModel and creates a 2 dimensional "flat" array. It would go into a new model such as "menu_model".
The purple code specifies that the "Home" item (PK 1) is not movable.
The green code sets the hasChildren parameter if the item has children.
The blue code sets the upArrow and downArrow parameters depending on whether the item is already at the top or bottom of its category using the $this->HModel->_is_item_at_top() and $this->HModel->_is_item_at_bottom() functions.
function get_flat_array() {
// get the "tree" array using HModel
$menuArray = $this->HModel->get_items();
$menu = array();
$x = 0;
// for each top level menu item in the $menuArray
for ($i=0; $i<count($menuArray); $i++) {
// set all of the properties
$menu[$x]['name'] = $menuArray[$i]['name'];
$menu[$x]['id'] = $menuArray[$i]['id'];
$menu[$x]['isSub'] = 0; // add new property 'isSub'
$menu[$x]['live'] = $menuArray[$i]['live'];
$menu[$x]['menu_image'] = $menuArray[$i]['menu_image'];
$menu[$x]['menu_pageViews'] = $menuArray[$i]['menu_pageViews'];
// if there are children present in array 'childs'
if (isset($menuArray[$i]['childs'])) {
$menu[$x]['hasChildren'] = 1;
}
// if we are in "home mode"
if (isset($this->HModel->home_mode)) {
// if item is the "Home" item, it cannot be moved.
if ($menu[$x]['pk'] != 1) {
// If item is at the top, it doesn't get an up arrow
// we are in home mode and this is a top-level item so we pass 1 to ignore Home
if ($this->HModel->_is_item_at_top($menuArray[$i]['id'], 1)) {
$menu[$x]['upArrow'] = 0;
} else {
$menu[$x]['upArrow'] = 1;
}
// If item is at the bottom, it doesn't get a down arrow
if ($this->HModel->_is_item_at_bottom($menuArray[$i]['id'])) {
$menu[$x]['downArrow'] = 0;
} else {
$menu[$x]['downArrow'] = 1;
}
} else {
// "Home" item cannot be moved.
$menu[$x]['upArrow'] = 0;
$menu[$x]['downArrow'] = 0;
}
} else {
// If item is at the top, it doesn't get an up arrow
if ($this->HModel->_is_item_at_top($menuArray[$i]['id'])) {
$menu[$x]['upArrow'] = 0;
} else {
$menu[$x]['upArrow'] = 1;
}
// If item is at the bottom, it doesn't get a down arrow
if ($this->HModel->_is_item_at_bottom($menuArray[$i]['id'])) {
$menu[$x]['downArrow'] = 0;
} else {
$menu[$x]['downArrow'] = 1;
}
}
// If the item has children
if (isset($menuArray[$i]['childs']) && is_array($menuArray[$i]['childs'])) {
// for each of the children
for ($j=0; $j<count($menuArray[$i]['childs']); $j++) {
$x++;
$subitem = $menuArray[$i]['childs'][$j];
// set all of the properties
$menu[$x]['name'] = $subitem['name'];
$menu[$x]['id'] = $subitem['id'];
$menu[$x]['isSub'] = 1; // add new property 'isSub'
$menu[$x]['live'] = $subitem['live'];
$menu[$x]['menu_image'] = $subitem['menu_image'];
$menu[$x]['menu_pageViews'] = $subitem['menu_pageViews'];
// If item is at the top, it doesn't get an up arrow
if ($this->HModel->_is_item_at_top($subitem['id'])) {
$menu[$x]['upArrow'] = 0;
} else {
$menu[$x]['upArrow'] = 1;
}
// If item is at the bottom, it doesn't get a down arrow
if ($this->HModel->_is_item_at_bottom($subitem['id'])) {
$menu[$x]['downArrow'] = 0;
} else {
$menu[$x]['downArrow'] = 1;
}
}
$x++;
} else {
$x++;
}
}
// return the new menu array
return $menu;
}
Sample Implementation
Example HTML/CSS output from array
Here is some HTML/CSS output built by iterating through the "flat" array for each menu item. We use this as the menu editor in our CMS, Breeze.
Note how the arrows indicate an item's position in that level, or within that category. Compare this with the Sample Data table from earlier.

HModel's methods
Using HModel's methods directly
These are all of HModel's methods and how to use them for adding, deleting and moving items.
For more complete control over parameters, adding can be done by your own external functions, which may call on methods such as get_highest_in_category to determine the current highest item.
Common public methods
$this->HModel->add_item($item_name, $parent_PK)
Adds a new item with the given $item_name and the supplied parent value $parent_PK.
$this->HModel->delete_item($menu_PK)
Deletes the item with the given $menu_PK and moves all higher items in the same category up one.
$this->HModel->move_item($menu_PK, $parent_PK)
Moves the given menu item specified by $menu_PK and move it to the end of category specified by $parent_PK and move any higher items in the old category up one.
$this->HModel->toggle_live($menu_PK)
Toggles the "Live" status of the item with the given $menu_PK.
$this->HModel->move_item_up($menu_PK)
Moves an item up one position in the current category, and moves the previous item down.
$this->HModel->move_item_down($menu_PK)
Moves an item down one position in the current category, and moves the next item up.
Other methods
$this->HModel->is_item_live($menu_PK)
Returns true (1) if the item with given menu_PK is live.
$this->HModel->get_highest_in_category($parent_PK)
Returns the highest positioned item within the given parent's $menu_PK. Used to find the highest item in a category before adding a new item.
$this->HModel->_is_item_at_top($menu_PK)
Returns true (1) or false (0) if item is at the top of it's category. That is, if it's the lowest numbered item with a particular 'menu_parent'.
$this->HModel->_is_item_at_bottom($menu_PK)
Returns true (1) or false (0) if item is at the bottom of it's category. That is, if it's the highest numbered item with a particular 'menu_parent'.
$this->HModel->_get_parent($menu_PK)
Returns the 'menu_parent' (the parent item's menu_PK) value for a given menu item.
$this->HModel->_get_position($menu_PK)
Returns the order ('menu_order') of a given menu item within that category.