codeigniter-modular-extensions-hmvc

Support development of Modular Extensions – HMVC

Modular Extensions – HMVC

Modular Extensions makes the CodeIgniter PHP framework modular. Modules are groups of independent components,
typically model, controller and view, arranged in an application modules sub-directory that can be dropped into other CodeIgniter applications.

HMVC stands for Hierarchical Model View Controller.

Module Controllers can be used as normal Controllers or HMVC Controllers and they can be used as widgets to help you build view partials.

Features:

All controllers can contain an $autoload class variable, which holds an array of items to load prior to running the constructor.
This can be used together with module/config/autoload.php, however using the $autoload variable only works for that specific controller.

<?php     
class Xyz extends MX_Controller 
{
    $autoload = array(
        'helper'    => array('url', 'form'),
        'libraries' => array('email'),
    );
}

The Modules::$locations array may be set in the application/config.php file. ie:

<?php
$config['modules_locations'] = array(
    APPPATH.'modules/' => '../modules/',
);

Modules::run() output is buffered, so any data returned or output directly from the controller is caught and
returned to the caller. In particular, $this->load->view() can be used as you would in a normal controller, without the need for return.

Controllers can be loaded as class variables of other controllers using $this->load->module(‘module/controller’);
or simply $this->load->module(‘module’); if the controller name matches the module name.

Any loaded module controller can then be used like a library, ie: $this->controller->method(), but it has access to its own models and libraries independently from the caller.

All module controllers are accessible from the URL via module/controller/method or simply module/method if the module and controller names match.
If you add the _remap() method to your controllers you can prevent unwanted access to them from the URL and redirect or flag an error as you like.

Notes:

To use HMVC functionality, such as Modules::run(), controllers must extend the MX_Controller class.

To use Modular Separation only, without HMVC, controllers will extend the CodeIgniter Controller class.

You must use PHP5 style constructors in your controllers. ie:

<?php
class Xyz extends MX_Controller 
{
    function __construct()
    {
        parent::__construct();
    }
}

Constructors are not required unless you need to load or process something when the controller is first created.

All MY_ extension libraries should include (require) their equivalent MX library file and extend their equivalent MX_ class

Each module may contain a config/routes.php file where routing and a default controller can be defined for that module using:

<?php
$route['module_name'] = 'controller_name';

Controllers may be loaded from application/controllers sub-directories.

Controllers may also be loaded from module/controllers sub-directories.

Resources may be cross loaded between modules. ie: $this->load->model(‘module/model’);

Modules::run() is designed for returning view partials, and it will return buffered output (a view) from a controller. The syntax for using modules::run is a URI style segmented string and unlimited variables.

<?php
/** module and controller names are different, you must include the method name also, including 'index' **/
modules::run('module/controller/method', $params, $...);

/** module and controller names are the same but the method is not 'index' **/
modules::run('module/method', $params, $...);

/** module and controller names are the same and the method is 'index' **/
modules::run('module', $params, $...);

/** Parameters are optional, You may pass any number of parameters. **/

To call a module controller from within a controller you can use $this->load->module() or Modules::load()
and PHP5 method chaining is available for any object loaded by MX.
ie: $this->load->library(‘validation’)->run().

To load languages for modules it is recommended to use the Loader method which will pass the active module
name to the Lang instance; ie: $this->load->language(‘language_file’);

The PHP5 spl_autoload feature allows you to freely extend your controllers, models and libraries from
application/core or application/libraries base classes without the need to specifically include or require
them.

The library loader has also been updated to accommodate some CI 1.7 features: ie Library aliases are
accepted in the same fashion as model aliases, and loading config files from the module config directory
as library parameters (re: form_validation.php) have beed added.

$config = $this->load->config(‘config_file’), Returns the loaded config array to your variable.

Models and libraries can also be loaded from sub-directories in their respective application directories.

When using form validation with MX you will need to extend the CI_Form_validation class as shown below,

<?php
/** application/libraries/MY_Form_validation **/ 
class MY_Form_validation extends CI_Form_validation 
{
    public $CI;
}

before assigning the current controller as the $CI variable to the form_validation library.
This will allow your callback methods to function properly. (This has been discussed on the CI forums also).

<?php
class Xyz extends MX_Controller 
{
    function __construct()
    {
        parent::__construct();

        $this->load->library('form_validation');
        $this->form_validation->CI =& $this;
    }
}

View Partials

Using a Module as a view partial from within a view is as easy as writing:

<?php echo Modules::run('module/controller/method', $param, $...); ?>

Parameters are optional, You may pass any number of parameters.

Modular Extensions installation

  1. Start with a clean CI install
  2. Set $config[‘base_url’] correctly for your installation
  3. Access the URL /index.php/welcome => shows Welcome to CodeIgniter
  4. Drop Modular Extensions third_party files into the CI 2.0 application/third_party directory
  5. Drop Modular Extensions core files into application/core, the MY_Controller.php file is not required unless you wish to create your own controller extension
  6. Access the URL /index.php/welcome => shows Welcome to CodeIgniter
  7. Create module directory structure application/modules/welcome/controllers
  8. Move controller application/controllers/welcome.php to application/modules/welcome/controllers/welcome.php
  9. Access the URL /index.php/welcome => shows Welcome to CodeIgniter
  10. Create directory application/modules/welcome/views
  11. Move view application/views/welcome_message.php to application/modules/welcome/views/welcome_message.php
  12. Access the URL /index.php/welcome => shows Welcome to CodeIgniter

You should now have a running Modular Extensions installation.

Installation Guide Hints:

-Steps 1-3 tell you how to get a standard CI install working – if you have a clean/tested CI install, skip
to step 4.

-Steps 4-5 show that normal CI still works after installing MX – it shouldn’t interfere with the normal CI
setup.

-Steps 6-8 show MX working alongside CI – controller moved to the “welcome” module, the view file remains
in the CI application/views directory – MX can find module resources in several places, including the application directory.

-Steps 9-11 show MX working with both controller and view in the “welcome” module – there should be no
files in the application/controllers or application/views directories.

FAQ

Q. What are modules, why should I use them?

A. (http://en.wikipedia.org/wiki/Module)

(http://en.wikipedia.org/wiki/Modular_programming)

(http://blog.fedecarg.com/2008/06/28/a-modular-approach-to-web-development)

Q. What is Modular HMVC, why should I use it?

A. Modular HMVC = Multiple MVC triads

This is most useful when you need to load a view and its data within a view. Think about adding a shopping
cart to a page. The shopping cart needs its own controller which may call a model to get cart data.
Then the controller needs to load the data into a view. So instead of the main controller handling the
page and the shopping cart, the shopping cart MVC can be loaded directly in the page.
The main controller doesn’t need to know about it, and is totally isolated from it.

In CI we can’t call more than 1 controller per request. Therefore, to achieve HMVC, we have to simulate
controllers. It can be done with libraries, or with this “Modular Extensions HMVC” contribution.

The differences between using a library and a “Modular HMVC” HMVC class is:
1. No need to get and use the CI instance within an HMVC class
2. HMVC classes are stored in a modules directory as opposed to the libraries directory.

Q. Is Modular Extensions HMVC the same as Modular Separation?

A. Yes and No. Like Modular Separation, Modular Extensions makes modules “portable” to other installations. For example, if you make a nice self-contained model-controller-view set of files you can bring that MVC into another project by copying just one folder – everything is in one place instead of spread around model, view and controller folders.

Modular HMVC means modular MVC triads. Modular Separation and Modular Extensions allows related
controllers, models, libraries, views, etc. to be grouped together in module directories and used
like a mini application. But, Modular Extensions goes one step further and allows those modules to
“talk” to each other. You can get controller output without having to go out through the http interface
again.

codeigniter: extending common controller

The key is using the native autoload as explained in his post:

/*
| -------------------------------------------------------------------
|  Native Auto-load
| -------------------------------------------------------------------
| 
| Nothing to do with cnfig/autoload.php, this allows PHP autoload to work
| for base controllers and some third-party libraries.
|
*/
function __autoload($class)
{
    if(strpos($class, 'CI_') !== 0)
    {
        @include_once( APPPATH . 'core/'. $class . EXT );
    }
}

NOTE

As a note, you’ll want to put all of your “base” controllers in the core folder for CI2+

Print all Controllers file in codeigniter

$controllers = array();
$this->load->helper('file');

// Scan files in the /application/controllers directory
// Set the second param to TRUE or remove it if you
// don't have controllers in sub directories
$files = get_dir_file_info(APPPATH.'controllers', FALSE);

// Loop through file names removing .php extension
foreach (array_keys($files) as $file)
{
$controllers[] = str_replace(EXT, '', $file);
}
print_r($controllers);

Sitemap generation with Codeigniter

You can use this code:

controllers/seo.php

Class Seo extends CI_Controller {

    function sitemap()
    {

        $data = "";//select urls from DB to Array
        header("Content-Type: text/xml;charset=iso-8859-1");
        $this->load->view("sitemap",$data);
    }
}

views/sitemap.php

<?= '<?xml version="1.0" encoding="UTF-8" ?>' ?>

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc><?= base_url();?></loc> 
        <priority>1.0</priority>
    </url>

    <!-- My code is looking quite different, but the principle is similar -->
    <?php foreach($data as $url) { ?>
    <url>
        <loc><?= base_url().$url ?></loc>
        <priority>0.5</priority>
    </url>
    <?php } ?>

</urlset>

add line to config/routes.php

$route['seo/sitemap\.xml'] = "seo/sitemap";

How to: Multi-site CodeIgniter Set-up

A few people have asked me recently about setting up CodeIgniter to run accross multiple domains based on the same codebase. This can be handy for sites than run different databases for different geographical areas, all of which need the same code but different content.

To get this working I took a little code from PyroCMS and modded a previous article “How to: Support multiple production environments in CodeIgniter” and found a relatively simple solution.

Setting the base URL

The first step of getting CodeIgniter working anywhere automatically is curing it of it’s most pointless configuration setting. It seems CodeIgniter would like to be told where it is, which really doesn’t need to happen. We could solve this in many ways, but instead of extending or replacing any core code, I would preffer to put a little snippet of code into the main application/config/config.php. Enter this code into the file and it will automatically support pretty much any kind of URL.

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

/*
|--------------------------------------------------------------------------
| Base Site URL
|--------------------------------------------------------------------------
|
| URL to your CodeIgniter root. Typically this will be your base URL,
| WITH a trailing slash:
|
| http://www.your-site.com/
|
*/

if(isset($_SERVER['HTTP_HOST']))
{
    $config['base_url'] = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on' ? 'https' : 'http';
    $config['base_url'] .= '://'. $_SERVER['HTTP_HOST'];
    $config['base_url'] .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
}

else
{
    $config['base_url'] = 'http://localhost/';
}

Setting a SITE constant

So the website URL is now set and links are fully working around the site. Next we need a way to work out throughout our code which site is currently being used. Amongst other things, this will help us with selecting the right database settings later.

/*
|--------------------------------------------------------------------------
| Site
| Set a constant for whichever site you happen to be running, if its not here
| it will fatal error.
|--------------------------------------------------------------------------
*/
switch($_SERVER['HTTP_HOST'])
{
    case 'example.com':
    case 'www.example.com':
        define('SITE', 'example');
    break;
    
    case 'example2.com':
    case 'www.example2.com':
        define('SITE', 'example2');
    break;
    
    default:
        define('SITE', 'default');
    break;
}

Domain based database settings

Now that CodeIgniter has its links working and it knows what site it is trying to run, it needs to know the database configuration for this domain. To do that, we can break down our config into domain specific “Database groups”.

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
/*
| -------------------------------------------------------------------
| DATABASE CONNECTIVITY SETTINGS
| -------------------------------------------------------------------
| This file will contain the settings needed to access your database.
|
| For complete instructions please consult the "Database Connection"
| page of the User Guide.
|
| -------------------------------------------------------------------
| EXPLANATION OF VARIABLES
| -------------------------------------------------------------------
|
| ['hostname'] The hostname of your database server.
| ['username'] The username used to connect to the database
| ['password'] The password used to connect to the database
| ['database'] The name of the database you want to connect to
| ['dbdriver'] The database type. ie: mysql.  Currently supported:
     mysql, mysqli, postgre, odbc, mssql
| ['dbprefix'] You can add an optional prefix, which will be added
|     to the table name when using the  Active Record class
| ['pconnect'] TRUE/FALSE - Whether to use a persistent connection
| ['db_debug'] TRUE/FALSE - Whether database errors should be displayed.
| ['active_r'] TRUE/FALSE - Whether to load the active record class
| ['cache_on'] TRUE/FALSE - Enables/disables query caching
| ['cachedir'] The path to the folder where cache files should be stored
|
| The $active_group variable lets you choose which connection group to
| make active.  By default there is only one group (the "default" group).
|
*/

$active_record = TRUE;

$db['default']['hostname'] = "localhost";
$db['default']['username'] = "";
$db['default']['password'] = "";
$db['default']['database'] = "";
$db['default']['dbdriver'] = "mysql";
$db['default']['dbprefix'] = "";
$db['default']['pconnect'] = TRUE;
$db['default']['db_debug'] = TRUE;
$db['default']['cache_on'] = FALSE;
$db['default']['cachedir'] = "";
$db['default']['char_set'] = "utf8";
$db['default']['dbcollat'] = "utf8_general_ci";

// example
$db['example']['hostname'] = "localhost";
$db['example']['username'] = "root";
$db['example']['password'] = "";
$db['example']['database'] = "example";
$db['example']['dbdriver'] = "mysql";
$db['example']['dbprefix'] = "";
$db['example']['active_r'] = TRUE;
$db['example']['pconnect'] = TRUE;
$db['example']['db_debug'] = TRUE;
$db['example']['cache_on'] = FALSE;
$db['example']['cachedir'] = "";
$db['example']['char_set'] = "utf8";
$db['example']['dbcollat'] = "utf8_general_ci";

// Example 2
$db['example2']['hostname'] = "localhost";
$db['example2']['username'] = "root";
$db['example2']['password'] = "root";
$db['example2']['database'] = "testfoo";
$db['example2']['dbdriver'] = "mysql";
$db['example2']['dbprefix'] = "";
$db['example2']['active_r'] = TRUE;
$db['example2']['pconnect'] = TRUE;
$db['example2']['db_debug'] = TRUE;
$db['example2']['cache_on'] = FALSE;
$db['example2']['cachedir'] = "";
$db['example2']['char_set'] = "utf8";
$db['example2']['dbcollat'] = "utf8_general_ci";

// Check the configuration group in use exists, if not use the default
$active_group = (defined('SITE') && array_key_exists(SITE, $db)) ? SITE : 'default';

?>

The little snippet of code at the bottom will check to see if the SITE constant you have set matches up with a database group. If it doesn’t, it will use the default configuration group.

Your CodeIgniter set-up should now work with any domain you happen to point to it. You even run simple little if(SITE == ‘example2’) checks anywhere within your code to do special code for a certian site, although I would not reccomend you doing this too heavily.

Multi Level Subfolder Controller in CodeIgniter

Well, this is my first post so be gentle to me :) .

Recently i’m working on a web based application project using CodeIgniter framework. Problem arises when my boss wants this web application to have more than two level subfolder in controller folder (for example: /controller/company_a/admin/, /controller/company_a/reports/) for organizational purpose.

As the default CodeIgniter version 1.7.2 set up, controllers can’t have multi level subfolder, so I need to find a way to provide the request.

After a little bit of Googling and searching on CodeIgniter forums, I found this thread http://codeigniter.com/forums/viewthread/85554/

There’s a solution for this problem on that thread but i don’t like the idea hacking the CI core so I modified it. So here’s the solution:

  1. Copy this code into your CI application library folder (the default is /system/application/libraries), and name it as MY_Router.php
    Class MY_Router extends CI_Router
    	{
    	    Function MY_Router()
    	    {
    	        parent::CI_Router();
    	    }
    	 
    	    function _validate_request($segments)
        	{
    	        if (file_exists(APPPATH.'controllers/'.$segments[0].EXT))
    	        {
    	            return $segments;
    	        }
    	 
    	        if (is_dir(APPPATH.'controllers/'.$segments[0]))
    	        {
    	            $this->set_directory($segments[0]);
    	            $segments = array_slice($segments, 1);
    	 
    	            /* ----------- ADDED CODE ------------ */
    	 
    	            while(count($segments) > 0 && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]))
    	            {
    	                // Set the directory and remove it from the segment array
    	            $this->set_directory($this->directory . $segments[0]);
    	            $segments = array_slice($segments, 1);
    	            }
    	 
    	            /* ----------- END ------------ */
    	 
    	            if (count($segments) > 0)
    	            {
    	                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].EXT))
    	                {
    	                    show_404($this->fetch_directory().$segments[0]);
    	                }
    	            }
    	            else
    	            {
    	                $this->set_class($this->default_controller);
    	                $this->set_method('index');
    	 
    	                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT))
    	                {
    	                    $this->directory = '';
    	                    return array();
    	                }
    	 
    	            }
    	 
    	            return $segments;
    	        }
    	 
    	        show_404($segments[0]);
    	    }
    	}