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

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 

Back to Contents

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.

Back to Contents

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);

Back to Contents

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...]
)                

Back to Contents

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);
}

Back to Contents

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;
}    

Back to Contents

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.


Back to Contents

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.

Back to Contents

developed by the lab