Modular CI - HMVC for CodeIgniter
Modular extensions for CodeIgniter have been around in some shape or form for quite some time: Modular Extensions, HMVC, Matchbox and Modular Separation.
While designing an application framework based on CodeIgniter, I needed a much more flexible system than what is on offer by the extensions mentioned above.
A module in my definition is a complete CodeIgniter mini-application.
It needs to support all elements you have in a regular application folder: controllers, models, views, libraries, helpers and config files. All these elements must be loadable using the CodeIgniter standard practices, automatic, and with a minimum of configuration. And this includes controllers loading other controllers, and models loading other models, to support true HMVC.
At the same time, the solution needs to support modulair routing. Not with a fixed format (for example the first URI segment has to be the module name), but using regular CI routing rules.
Also, I wanted as little impact on a standard CodeIgniter installation as possible, as every extension of a core library method might break some CodeIgniter functionality in the future. Some extensions however, are unavoidable. These are documented in detail below.
- Supports routing to module controllers
- Location of your modules is configurable at runtime
- Supports cross module calls, also to controller methods
- Uses standard CodeIgniter routing, no Router library modifications
- Introduces $this->load->module() via a Loader library extension
- Support for the default index() method and the _remap() method when routing
- Supports loading models without CI object assignment, to support ORM libraries
- Supports module controller return values for true HMVC (as suggested by n0xie)
- Allow overriding the module locations (p.e if you have a modules and a themes directory with a module structure)
- Allows loading your application folder as a module, so you can call these controllers too
- Supports language separation by module, to prevent duplicate language line keys
Just download the tarball or zipfile from bitbucket.org. Go to get source and select the desired archive format. After unpacking, copy the contents of the application folder to the application folder of your local CodeIgniter installation.
Note: Modular CI includes extensions to the Loader, Language, Form_validation, Model and Controller libraries. If your application already uses one of these, make sure you merge them manually so you don't lose your changes. Pay particular attention to methods you might have overloaded, that are also present in Modular CI. Modular CI also contains an example config/routes.php. If your application already has one defined, make sure not to overwrite it.
Note: Modular CI supports both CodeIgniter v1.7.2 and v2.0. This means that some files are present in multiple folders, due to the changes introduced in v2.0. If you are using v1.7.2, you can remove the application/core folder. If you are using v2.0, you can remove the MY_Loader, MY_Language, MY_Model and MY_Controller files from the application/libraries folder.
Modular CI comes with a config file in the application/config directory. You can use this configuration file to control the behaviour of certain Modular CI aspects. This file is called module.php, and contains the following configuration parameters:
Module self reference
The configuration value 'self' determines how module classes can be references from classes inside the module itself.
$config['self'] = FALSE;
The default value is FALSE, which means that you access the classes from inside exactly the same as from the outside, by the module name used when loading the module.
This is a consistent solution, but requires you to hardcode the module name in your code or use a trick like:
$thisclass = $this->__modulereference; $this->$thisclass->library->mod_library->lib_method();
If hardcoding is not an option, or you're not into tricks like this, you can define a global name for module self-reference:
$config['self'] = 'self';
This way, you can always refer to classes of the same module by the name 'self', independent on how the module was named, and/or which object name was given when the module was loaded. This allows you to rename your module without breaking your code.
Module language file loading
CodeIgniter allows you to store language strings in separate files, and load them using $this->lang->load(). All loaded strings are stored into a single one-dimensional array, which means that if you have multiple language files using the same array keys, the one loaded first will be overwritten by the one loaded later.
While this might be enough for small and monolithic applications, this might prove to be a problem when you start using modules, some of them might not be written by you. To deal with this issue, Modular CI supports a multi-dimensional language array, in which all language strings from one module are separately stored from another, or from language strings loaded from the application itself.
You control this behaviour with the following configuration variable:
$config['use_language_array'] = TRUE;
By default, Modular CI assumes that you want this enhanced behaviour. If you want to disable it, and use CodeIgniters default bahaviour, change this value to FALSE.
Start using Modular CI
By default, Modular CI expects modules to be installed in the directory ‘modules’, in your application folder. If you alter the location, you have to inform the loader that there is a different location. You do that via
// set the location of our modules (relative to APPPATH) $this->load->module_path( 'modules' ); // or use a fully qualified path $this->load->module_path( '/var/www/private/ci/modules' );
If you have modules in multiple locations, all you have to do is to alter the location by calling the module_path() method again, with the new location. A module remembers the location it is loaded from. After loading it, you can change the module path without affecting the access to module classes of already loaded modules.
Once the path is set, you can initialize a module by using:
// Make the "my_module" module available $this->load->module('my_module');
If you have long and complex module names, or if your module name is variable (for example because it is user defined), you can load the module with an alternative object name. This decouples the name of the module as it exists on disk from the objectname used in your code:
// Make the "my_module_with_a_long_name" module available as 'my_module' $this->load->module('my_module_with_a_long_name', 'my_module');
Or load a module from a specific location:
// Make the "user_selected_theme" theme module available as 'theme' $this->load->module('user_selected_theme', 'theme', APPPATH.'themes');
You can even load the application folder itself as a module, so you can load controllers in your application folder as module controllers:
// Load the application folder as $this->core module $this->load->module('application', 'core', FCPATH);
This is all you have to do. From this moment on, all files from this module can be accessed in a way similar to the way you would normally load them. We will describe every supported CodeIgniter file type in more detail below.
Note: When you use the loader library to load a model, library or module, the newly instantiated object will be assigned to all already loaded objects. Also the objects loaded from the application folder. This means that you are no longer required to use $CI =& get_instance() to access those objects from a library.
There is no need to specifically load a controller first.
Like with normal CodeIgniter controllers, if you don't specify the method you want to call, the _remap() method is called when it exists, and if not, the index() method is called.
// call the index() or _remap() method, pass variables to the controller constructor $this->my_module->controller->mod_controller( 'varX', 'varY', 'varZ' ); // call the index() or _remap() method, without passing variables $this->my_module->controller->mod_controller(); // this works too... $this->my_module->controller->mod_controller;
When you want to call a specific method of your module controller, just add it:
// call the method() method, pass variables to the controller constructor $this->my_module->controller->mod_controller( 'varX', 'varY', 'varZ' )->method(); // call the method() method, passing variables to the method $this->my_module->controller->mod_controller->method( 'varA', 'varB' );
Note: A controller is autoloaded when you first reference it. Module controllers are, like any other CodeIgniter class, loaded as singleton. This means that if you want to pass variables to the constructor, you have to do this when you first call the controller.
Note: If you use the included modulerouter controller to route a URI directly to a module controller, your module controller is called the same way a normal CodeIgniter controller is called. This means that if your controller contains a _remap() method, that method will be called with the requested method as a parameter. If no _remap() method exists, the requested method will be called, with the remaining URI segments passed as an array.
Modular CI includes a MY_Controller class. Make sure your module controllers always extend the MY_Controller class. You can use base classes if you want, as long as you make sure that these base classes extend MY_Controller. Also, Modular CI doesn't support autoloading of base classes. Remember to load them yourself before loading any controller that uses them. This particularly applies to when you use Modular CI controllers in 'routed mode'. In that case, you might want to preload your base controller in the modulerouter controller to make sure they are loaded.
Loading a library works similar to loading a controller. The only difference is that by default, no method is called when you don't specifically call one.
// load the module library and pass a config array to its constructor $this->my_module->library->mod_library( $config );
As with standard CodeIgniter libraries, if you load a module library without specifying a config array, the config folder of the module will be checked for a config file with the same name as the library. If it exists, it will be passed to the library constructor.
You call a library method the same way as you call a controller method:
// call the my_lib() method of the mod_library() $this->my_module->library->mod_library->my_lib( 'varA' );
It might start to sound boring, but guess what? Loading a model works similar to loading a controller or a library! As with a library, models don't have a concept of a default method.
// load the module model and pass a config array to its constructor $this->my_module->model->mod_model( $config );
You call a model method the same way as you call a controller or a library method:
// call the my_model() method of the mod_model() $result = $this->my_module->model->mod_model->my_model( 'varA' );
Modular CI models can also be used ORM style. This allows you to use module models with an ORM library like Datamapper DMZ.
// load the module model ORM style, and pass a value to its constructor $model = $this->my_module->model->mod_model( $id ); // call ORM methods in the module model $result = $model->select( $this )->where( $that )->get();
Modular CI includes a MY_Model class. Make sure your module models always extend the MY_Model class. You can use base classes if you want, as long as you make sure that these base classes extend MY_Model. Also, Modular CI doesn't support autoloading of base classes. Remember to load them yourself before loading any model that uses them.
As with any standard CodeIgniter application, a module can have it's own configuration files. You load it similar to a standard config file:
// load the my_config file from the my_module/config folder $this->my_module->config('my_config');
You can also decide not to merge the values from the configuration file with the application configuration, but return it instead. Like in standard CodeIgniter, you do this by passing TRUE as second parameter:
// load the my_config file from the my_module/config folder, and return its values $config = $this->my_module->config('my_config', TRUE);
You load a module view similarly to loading an application view, with the same parameters:
// load a module view $this->my_module->view('my_view', $data); // load a module view and have the result returned $output = $this->my_module->view('my_view', $data, TRUE);
If you use a template engine, and/or a third-party template parser, you need to be able to inform the method you use to parse your views which view file to load. For this purpose, every module has a method called 'viewpath':
// get the fully qualified path to the my_view viewfile $path = this->my_module->viewpath('my_view'); // and pass it on to my template engine $this->template->parse($path, $data);
This allows you to use a template and/or view parsing solution that is not Modular CI aware.
Note: Your template or parser library should be able to handle view file names that are fully qualified. Some third-party template engines or parsing solutions try to load views hardcoded from APPPATH.'views'. If this is the case, you need to alter the code of these solutions to do a file_exists() check first.
You're probably bored out of your skull right now, but don't worry, we're getting to the end of this readme. And yes, loading a language file from a module is similar to loading anything else:
// Load a module language file $this->my_module->lang('my_language');
If you have decided not to use the new multi-dimension language array feature, any standard CI function or method that interacts with language strings will work with the strings loaded from a module. If you do, Modular CI includes a Language library extension that provides support for this feature.
// Retrieve a language string from a my_module language file echo $this->lang->line('my_string', 'my_module');
Alternatively, you can use the line() method of the module lang method. Combined with the 'self' feature mentioned earlier that makes that you don't have to hardcode the module name:
// Retrieve a language string from a language file of the current module echo $this->self->lang->line('my_string');
You can even do the two in one go:
// Load a module language file and retrieve a language string $this->my_module->lang('my_language')->line('my_string');
Altough Modular CI has been designed to be used as an HMVC design pattern, you can also use it to divide your application into logical pieces, and make every module directory accessable from the URI like standard application controllers. For this purpose, a modulerouter controller is included in the package.
To be able to route to module controllers, only some configuration in your config/routes.php is required. The package contains an example file to show you how this is done.
As Modular CI is still under development (the current code is very stable, but new features are being added while we're developing ExiteCMS), there is no formal version number issued at this time.
Due to the fact that Modular CI is still under development, the source repository contains test code which I use for regression testing, and to give you an opportunity to see how it works, and what's possible.
You don't need these files when you want to use Modular CI in your application. In that case, you only need the following files: - application/config/module.php - application/config/modulerouter.php ( if you need module routing ) - application/core/MY_* ( if you are using CI 2.0 ) - application/libraries/Module.php - application/libraries/MY_Form_validation.php - application/libraries/MY_Controller.php ( if you are using CI 1.7.2 ) - application/libraries/MY_Model.php ( if you are using CI 1.7.2 ) - application/libraries/MY_Loader.php ( if you are using CI 1.7.2 ) - application/libraries/MY_Language.php ( if you are using CI 1.7.2 )
It is safe to delete all other files (note again that CI 1.7.2 uses the files in the libraries folder, CI 2.0 the ones in the core folder).
email: WanWizard [at] exitecms.org web: http://www.exitecms.org