09-23-2015, 03:46 AM (This post was last modified: 09-25-2015, 05:11 AM by Martin7483.)
Hi everyone!
I would like to share my approach of adding HMVC to CodeIgniter. I call it Simple HMVC
Simple HMVC is an extension for CodeIgniter 3.x, but should also work in 2.x
It makes it possible to use Controller in Controller calls. Direct routing to a HMVC module is not support for one simple reason. I don't believe HMVC should support direct routing.
In my opinion I think HMVC is a collection of small independent applications within a lager MVC application. CodeIgniter is that lager MVC application. Simple HMVC makes it possible to use module based applications by only extending the CodeIgniter core Loader. My approach also keeps the loosely coupled system of CodeIgniter intact. The freedom you have in your regular controllers, libraries, models, views and helpers is extended to Simple HMVC. Systems like Modular Extensions – HMVC by WireDesignz make you use a stricter form of coding for your modules.
How to install Simple HMVC
Step 1
Create a MY_Loader.php file in the directory ./application/core/MY_loader.php If you already have this file edit this file by adding the methods shown in the code below
PHP Code:
<?php if (! defined('BASEPATH')) exit('No direct script access allowed');
/** * My Loader * * This class extends the Crossfire core Loader. It adds the methods for * using Simple HMVC in your CodeIgniter 3.x projects. It also adds * a method for using views stored in your libraries directory * * @author Martin Langenberg */
class MY_Loader extends CI_Loader {
public function __construct() { log_message('debug', "Class ".get_class($this)." Initialized.");
parent::__construct(); }
/** * Set the view path for a library * * Prepends a library view path to the view paths array. * Enables using views from within a library. This helps keep * related files in the same parent directory * * @param string name of the library */ public function library_view($library, $view_cascade = TRUE) { $path = APPPATH.'libraries/'.$library.'/'; $this->_ci_view_paths = array($path.'views/' => $view_cascade) + $this->_ci_view_paths; }
/** * Load module and execute the called method * * @param string $module * @param string $method * @param array $params * @return mixed, the output of the called controller/method combination */ public function module($module, $method = NULL, $params = array() ) { // Define the base path to the modules directory define('MOD_BASE_PATH', APPPATH.'modules/');
// Has the Controller already been loaded? No, then load it if( ! property_exists($CI, $controller) ) { $this->_ci_module_paths($dir_path, TRUE); // Include the file log_message('debug', "The module {$controller} has been loaded."); require_once $controller_path;
// Auto load config file for the loaded controller $config = NULL; if( $config_path = set_realpath($dir_path.'/config/'.$controller.'.php', TRUE) ) { require_once $config_path; }
// Create the new Object of the required module and register as loaded $object = ucfirst($controller).'_Controller'; $app = new $object($config); $this->_modules[$controller] = $app;
// Add a reference to the CI super object easy use later on if needed $CI->{$controller} = $this->_modules[$controller]; } else { log_message('debug', "The controller {$controller} of module {$dir} has already been loaded. Second attempt ignored"); }
// Execute called method if set if( ! is_null($method) ) { // Check if called method exists if( ! method_exists($app, $method) ) { $application['error_type'] = "Unknow method {$method}()"; $application['error'] = "The method <strong>\"{$method}\"</strong> does not exist in the called controller <strong>\"{$controller}\"</strong> of the requested module <strong>\"{$dir}\"</strong>"; return $this->_module_error($module, $application); } // Run and return return $CI->{$controller}->$method($params); }
// Return the controller object; return $this->_modules[$controller]; }
/** * Add module paths to the corresponding paths arrays * * This method only loads the paths of a given module. * Makes it possible to use any module resource in a second HMVC module * without the overhead of the complete module controller * * @param string $module */ public function module_paths($module) { // Get the CI Super object $CI =& get_instance();
// Load main Controller or sub controller? $application = $this->_load_module($module);
// Are we calling a sub controller? if( ($last_slash = strrpos($module, '/') ) !== FALSE) { // The dir is in front of the last slash $dir = str_replace('/', '', substr($module, 0, $last_slash + 1) );
// And the module name behind it $controller = substr($module, $last_slash + 1); } else { $dir = $module; $controller = $module; }
if( $dir_path = set_realpath(MOD_BASE_PATH.$dir, TRUE) ) { if($controller_path = set_realpath(MOD_BASE_PATH.$dir.'/controllers/'.$controller.'.php', TRUE) ) { return array('dir'=>$dir,'dir_path'=>$dir_path,'controller'=>$controller, 'controller_path'=>$controller_path); } else { $error_type = "Unknow controller {$controller}"; $error = "The controller <strong>\"{$controller}\"</strong> for the requested module <strong>\"{$dir}\"</strong> does not exist"; } } else { $error_type = "Unknow controller {$dir}"; $error = "The requested module <strong>\"{$dir}\"</strong> does not exist"; }
Step 2 Create a directory named modules in your CodeIgniter application directory → ./application/modules
Step 3 Create the file Base_module.php in the directory ./application/modules/Base_module.php. Place the code shown below in this file. Keep adding shared code for modules if required to this file
PHP Code:
<?php if (! defined('BASEPATH')) exit('No direct script access allowed');
/** * Simple HMVC Base module * * This class is the base for Simple HMVC modules. It is auto loaded when requesting a * Simple HMVC module. Extend your own module controller from this base the have access * to the base functions that you add your self * * @author Martin Langenberg */
/** * __get * * Enables the use of CI super-global without having to define an extra variable. * * @access public * @param $var * @return mixed */ public function __get($var) { return get_instance()->$var; }
}
Step 4 Create a module directory structure as follows in ./application/modules/:
your_module – The name of your new module
config
controllers
helpers
libraries
models
views
Step 5 Create a controller with the same name as your directory your_module in the controllers directory of the module. You can also add sub controllers. Extend controllers from the Base_module class if you wish to use the shared code in your controller
Step 6 Build your module application as you would build a small CodeIgniter project!
Error display Create a view file in ./application/views/errors/html/error_module.php and add the following to it
Code:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
?>
This will load a controller named your_module within the module your_module and call the method index within the controller. The output of the method index will be returned.
The same call, but now added params are passed to the index method of the called module controller. The params must be an array. How your controller futher handles it is up to you.
Once loaded a module can also be accessed via it's CI super object alias like so:
PHP Code:
$this->your_controller->some_method();
Module resources can be reused by another module without the need of loading the complete module. You can do this by loading the paths only
PHP Code:
$this->load->module_paths('some_other_module');
Using config files In the config directory of the module you can add config files for controllers and libraries. Just use the same filename for both controllers and libraries so that config files are auto loaded. Remember to add $config = null to the contructors of the classes
All required files have been added as an attachment. I would like to hear what the CI community thinks of my approach
I don't have an extended autoload method, and for my own use I won't need one. But what you can do is create a module_loader.php in ./application/libraries and autoload that. Within this module autoloader you can then load any module or module components you want.
(09-24-2015, 01:35 PM)skunkbad Wrote: This looks pretty cool. How would you autoload a module or module component?
I have added an example Module_loader and have updated the zip file attached to the original post.
The Module_loader.php
PHP Code:
<?php if (! defined('BASEPATH')) exit('No direct script access allowed');
/** * Simple HMVC Autoloader * * Simple HMVC does not extend the core autoload function. To autoload modules or any module components * you can load them in this library and have CI auto load this library. * It's a simple workaround and like Simple HMVC does not replace or effect core CI code * * NOTE: When auto loading, only load the module as an object so it is available through * the CI super object as $this->your_module->some_method(); Calling a method through this * library will only complicate your code ;) * * @author Martin Langenberg */
class Module_loader {
// Set to TRUE to autoload based on route private $load_by_route = TRUE;
/* The CI uri segments * You can use this to base auto loading on a specific route */ private $segments = array();
if($this->load_by_route) { $this->segments = $this->uri->segments; // No segments available, set as home if( ! array_key_exists(1, $this->segments) ) { $this->segments[1] = 'home'; } }
$this->_autoload(); }
/** * __get * * Enables the use of CI super-global without having to define an extra variable. * * @access public * @param $var * @return mixed */ public function __get($var) { return get_instance()->$var; }
/** * _autoload * * Auto loads any module or module components you want */ private function _autoload() {
if($this->load_by_route) {
// Load these modules based on route switch ( $this->segments[1] ) { case 'home': // Load a module main controller object $this->load->module('your_module');
// Load a module sub controller object $this->load->module('your_module/sub_controller');
// Load module component(s) $this->load->module_paths('some_module'); // Load the components you want like loading any available CI resource $this->load->library('some_module_library'); $this->load->helper('some_module_helper'); $this->load->model('som_module_model');
(09-25-2015, 06:54 AM)mwhitney Wrote: I may have missed it somewhere, but don't you need to load the path helper before you can use set_realpath() in your loader?
Yes, you do and I auto load it. And I have replaced it with my own version of set_realpath() in a MY_path_helper.php file. The file is included in zip file. I should of mentioned it in the beginning.
I'm confused. Other than the terminology of the entry class being a "controller" instead of a "library", how is this different than using Packages in CodeIgniter?
If you can't route to it then it seems to be basically a package, which exists in CI and allows for any of the standard CI file types except the controller. The only difference that I can see would be that you don't have to grab the CI instance with get_instance() since you're extending from the CI_Controller?