HierarchicalMVC

Derek Jones edited this page Jul 5, 2012 · 7 revisions
Clone this wiki locally

Category:Extensions | Category:Extensions::HMVC

HierarchicalMVC

HierarchicalMVC makes CodeIgniter modular. The idea behind an hmvc is that controllers can now be used to help you build view partials. Modules here are groups of independent CI controllers that can be dropped into CodeIgniter applications (be it directly in other controllers or views).

HMVC stands for Hierarchical Model View Controller.

Background

When developing a web application often comes the need to create individual modules without adding further complexity to the page controller, like menus. I have a set of menus that are created form the database. One approach into solving this problem is to add code on every controller (or into a library method or helper) to create the menu structure and pass it to a view.

But what if you would like caching for the menu? Menus don't change often, so as CI does not support partial caching you need to create the menu every time.

One solution would be to use Modular_Extensions_-_HMVC. The idea of the HierarchicalMVC is based on this project. Some of the problems I found after using Modular Extensions is that it does not support caching and it is somehow complicated to set up, too many files/folders for the features provided. But one of the biggest problems is that it uses the same instances of output library and others so if you do $this->output->cache(1); on the main page controller and the imported controller happens to use caching also, $this->output->cache(5); the values will be overwritten, and weird things could come along.

Another solution would be to use a dedicated page/controller that just outputs the menu and then use the Fragment_Caching to load the menu. This method is very clean and simple to use. The problems I found with this approach is that it takes way more time to do an http local request than recreate the menu all the time. Even if it's a slow method it helps a lot at the way you structure the entire web application, and (in my opinion) helping in using a modular approach on MVC pattern.

HierarchicalMVC Features

  • Very simple to set up (only one library class) and to use (as easy as Fragment_Caching)
  • Permits loading of modules/view partials that use controllers
  • Makes use of fast caching when the called controller is set up to use caching

Installing and Using HierarchicalMVC

To install HierarchicalMVC, just add the HierarchicalMVC.php file provided below in application/libraries To use the library, just load and call the controller with it's parametters as you would normally do in the browser url:


$this->load->library('HierarchicalMVC');
$this->hierarchicalmvc->get_page('welcome');

This line of code loads the contents of the default "welcome" controller that comes with CI at the position wher the line was called.

application/libraries/HierarchicalMVC.php


<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 /**
  * HierarchicalMVC Class
  *
  * Loads views and controllers content
  *
  * @author    Dan Mihai Ile (mihai007)
  * @link      http://codeigniter.com/wiki/HierarchicalMVC/
  * @version   1.0 - (20/12/2009)
  */
class HierarchicalMVC extends CI_Loader
{

  /**
   * Load page
   *
   * This is a generic loader that get's a page inside the application structure
   * exactly as if it were acessed by the browser (making an actual http request
   * to localhost). If the page was cached previously, it will output from cache
   * file directly without making the http request instead.
   *
   * @access  public
   * @param   the path to the controller
   * @param   bool
   * @return  string the loaded controller
   */
  function get_page($path, $return = FALSE)
  {
    // get the CI instance
    $CI =& get_instance();

    // get the uri to be used to access by http at localhost
    $uri = $CI->config->item('base_url').
        $CI->config->item('index_page').
        "/$path";

    // complete path to the cache folder        
    $cache_path = ($CI->config->item('cache_path') == '') ? BASEPATH.'cache/' : $CI->config->item('cache_path');

    // holder for the page output
    $cache = '';

    ob_start();
    if (is_dir($cache_path) AND is_really_writable($cache_path))
    {
      // compute the complete cache path for the page specified by $path
      // the same way it is done at Output library (/system/library/Output.php->_display_cache)
      $cache_file = md5($uri);
      $filepath = $cache_path.$cache_file;

      if (@file_exists($filepath) AND $fp = @fopen($filepath, FOPEN_READ))
      {
        flock($fp, LOCK_SH);

        if (filesize($filepath) > 0)
        {
          $cache = fread($fp, filesize($filepath));
        }
        flock($fp, LOCK_UN);
        fclose($fp);

        // Strip out the embedded timestamp        
        if ( ! preg_match("/(\d+TS--->)/", $cache, $match))
        {
          // reset the content cache unusable
          $cache = '';
        }

        // Has the file expired? If so we'll delete it.
        if (time() >= trim(str_replace('TS--->', '', $match['1'])))
        {         
          @unlink($filepath);
          log_message('debug', "Cache file has expired. File deleted (check made at loading an hmvc)");
          // reset the content cache unusable
          $cache = '';
        } else {
          // Display the cache
          log_message('debug', "HierarchicalMVC is loading from cache.");
          echo str_replace($match['0'], '', $cache);
        }
      }
    }

    if ($cache == '') {
      // noting loaded from cache, make the sloow http request...
      log_message('debug', "HierarchicalMVC is loading using (slow) http request method.");
      readfile('http://localhost'.$uri);
    }

    log_message('debug', 'HierarchicalMVC loaded: '.$path);

    // Return the file data if requested
    if ($return === TRUE)
    {
      $buffer = ob_get_contents();
      @ob_end_clean();
      return $buffer;
    }

    /*
     * TODO comment imported from function _ci_load($_ci_data)
     *
     * Flush the buffer... or buff the flusher?
     *
     * In order to permit views to be nested within
     * other views, we need to flush the content back out whenever
     * we are beyond the first level of output buffering so that
     * it can be seen and included properly by the first included
     * template and any subsequent ones. Oy!
     *
     */    
    if (ob_get_level() > $this->_ci_ob_level + 1)
    {
      ob_end_flush();
    }
    else
    {
      // PHP 4 requires that we use a global
      global $OUT;
      $OUT->append_output(ob_get_contents());
      @ob_end_clean();
    }
  }
    
  // --------------------------------------------------------------------
}


/* End of file HierarchicalMVC.php */
/* Location: ./system/application/library/HierarchicalMVC.php */

HierarchicalMVC Speed

So how fast is it and what are the performance drawbacks? To get an idea on the speed improvements that this library could bring over the Fragment_Caching approach I downloaded CI 1.7.3 and while in the default controller I made some benchmarks using ab command in Ubuntu Linux (ApacheBench) on my laptop:


/* Requests per second using different approaches */
'A) Welcome page cached:'                               345.25
'B) Welcome page no-cache:'                             191.09
'C) Welcome page with 2 views:'                         187.17
'D) Welcome + library load:'                            179.22
'E) Welcome and second cached controller + library:'    175.56
'F) Welcome and second cached controller, no library:'  116.92
'G) Welcome and second controller, no library:'         108.57
'H) Welcome and second controller + library:'           92.10

/*
 + library: means uses HierarchicalMVC
no library: means uses Fragment Caching approach
*/

/*
All tests were made using 2 controllers (the default welcome controller and copy of it)
By this we show the actual time taken by the CI internals and library processing and not
some dumb database fetch.
 */

So what are the conclusions of this small test?

  • Try to not use Fragment_Caching or HierarchicalMVC that much if the included modules/view partials do not use cache as it will slow the overall page rendering time (seen in test G and H).
  • If the included modules/view partials use cache, HierarchicalMVC can really boost the page rendering time quite a lot (from 116 without the library on test F to 175 requests per second with the library on test E)