Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added automatic hierarchical module loading

Signed-off-by: dchill42 <dchill42@gmail.com>
  • Loading branch information...
commit 75d08d61bb1def5aa4ed434a2e78b116c0884edc 1 parent 2ecf791
Darren Hill authored
21 application/config/config.php
View
@@ -248,6 +248,27 @@
/*
|--------------------------------------------------------------------------
+| Module Directory Path
+|--------------------------------------------------------------------------
+|
+| Where to look for HMVC modules. The default is APPPATH/modules/.
+| Paths may be absolute, relative to the PHP includes path, or relative to
+| APPPATH.
+|
+| Empty path directories will not be searched, but subdirectories may be
+| recursed with URI segments when routing or leading subdirectories when
+| calling Loader directly. For example, if the module path is APPPATH/modules/,
+| then "/foo/bar/baz" may map to any of these Controllers:
+| APPPATH/modules/controllers/foo.php
+| APPPATH/modules/foo/controllers/bar.php
+| APPPATH/modules/foo/bar/controllers/baz.php
+| APPPATH/modules/foo/bar/baz/controllers/[default].php
+|
+*/
+$config['module_path'] = array('modules');
+
+/*
+|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
111 system/core/Loader.php
View
@@ -88,6 +88,13 @@ class CI_Loader {
protected $_ci_mvc_paths = array();
/**
+ * List of module paths to load models/viewers/controllers from
+ *
+ * @var array
+ */
+ protected $_ci_module_paths = array();
+
+ /**
* List of cached variables
*
* @var array
@@ -174,6 +181,30 @@ public function __construct()
$this->_ci_mvc_paths = array(APPPATH => TRUE);
}
+ // Get module path
+ $paths = $this->CI->config->item('module_path');
+ if ($paths)
+ {
+ // Validate each path and add to list
+ foreach ( (array) $paths as $path)
+ {
+ // Groom and resolve path against includes
+ $path = CodeIgniter::resolve_path($path);
+
+ // If path isn't absolute or resolved against includes, try it against the app path
+ foreach (array($path, $this->_ci_app_path.ltrim($path, '\/')) as $dir)
+ {
+ // Make sure it's a directory with contents (not just '.' and '..')
+ if (is_dir($dir) && count(scandir($dir)) > 2)
+ {
+ // Add to paths and move on to next path
+ $this->_ci_module_paths[] = $dir;
+ break;
+ }
+ }
+ }
+ }
+
log_message('debug', 'Loader Class Initialized');
}
@@ -432,19 +463,43 @@ public function model($model, $name = '', $db_conn = FALSE)
}
// Search MVC paths for model
+ $found = FALSE;
$model = strtolower($model);
$file = 'models/'.$path.$model.'.php';
- foreach ($this->_ci_mvc_paths as $mod_path => $view_cascade)
+ foreach (array_keys($this->_ci_mvc_paths) as $mod_path)
{
// Check each path for filename
- if ( ! file_exists($mod_path.$file))
+ if (file_exists($mod_path.$file))
{
- continue;
+ // Include source and mark found
+ include($mod_path.$file);
+ $found = TRUE;
+ break;
}
+ }
- // Include source and instantiate object
- require_once($mod_path.$file);
+ // Do we need to keep looking?
+ if ( ! $found)
+ {
+ // Search module paths for model
+ $file = $path.'models/'.$model.'.php';
+ foreach ($this->_ci_module_paths as $mod_path)
+ {
+ // Does the model exist in the module?
+ if (file_exists($mod_path.$file))
+ {
+ // Include source and mark found
+ include($mod_path.$file);
+ $found = TRUE;
+ break;
+ }
+ }
+ }
+ // Did we find the model?
+ if ($found)
+ {
+ // Instantiate object
$model = ucfirst($model);
$this->CI->$name = new $model();
$this->_ci_models[] = $name;
@@ -876,6 +931,22 @@ public function remove_package_path($path = '', $remove_config_path = TRUE)
// --------------------------------------------------------------------
/**
+ * Get Module Paths
+ *
+ * Return a list of all module paths to check within mvc paths for
+ * subdirectories containing models, views, and controllers
+ *
+ * @return array Module paths
+ */
+ public function get_module_paths()
+ {
+ // Just return module paths
+ return $this->_ci_module_paths;
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
* Include a file from package paths
*
* This function includes a prefixed subclass file if found, and its base file
@@ -991,6 +1062,36 @@ protected function _ci_load($_ci_data)
break;
}
}
+
+ // Did we find it?
+ if ( ! $_ci_exists)
+ {
+ // Is a subdirectory included?
+ $_ci_subdir = '';
+ if (($_ci_slash = strrpos($_ci_file, '/')) !== FALSE)
+ {
+ // The path is in front of the last slash
+ $_ci_subdir = substr($_ci_file, 0, ++$last_slash);
+
+ // And the file name behind it
+ $_ci_file = substr($_ci_file, $_ci_slash);
+ }
+ unset($_ci_slash);
+
+ // Search module paths for view
+ $_ci_viewpath = $_ci_subdir.'views/'.$_ci_file;
+ foreach ($this->_ci_module_paths as $_ci_mod_path)
+ {
+ // Does the view exist in the module?
+ if (file_exists($_ci_mod_path.$_ci_viewpath))
+ {
+ // Set path, mark existing, and quit
+ $_ci_path = $_ci_mod_path.$_ci_viewpath;
+ $_ci_exists = TRUE;
+ break;
+ }
+ }
+ }
}
}
78 system/core/Router.php
View
@@ -294,6 +294,84 @@ public function validate_route($route)
}
}
+ // Search for controller in modules hierarchy
+ // If the module path is APPPATH/modules/, "/foo/bar/baz" may map to:
+ // APPPATH/modules/controllers/foo.php
+ // APPPATH/modules/foo/controllers/bar.php
+ // APPPATH/modules/foo/bar/controllers/baz.php
+ // APPPATH/modules/foo/bar/baz/controllers/[default].php
+ $seg_ct = count($route);
+ foreach ($this->CI->load->get_module_paths() as $path)
+ {
+ // Does the requested controller exist in the base folder?
+ if (file_exists($path.'controllers/'.$route[0].'.php'))
+ {
+ // Found it - append method if missing
+ if ($seg_ct < 2)
+ {
+ $route[] = 'index';
+ }
+
+ // Prepend path and empty directory and return
+ return array_merge(array($path, ''), $route);
+ }
+
+ // Is there a module sub-folder?
+ $sub = '';
+ for ($seg = 0; $seg < $seg_ct; ++$seg)
+ {
+ // Add segment to subdirectory path and check for controllers
+ $sub .= $route[$seg].'/';
+ if (is_dir($path.$sub.'controllers/'))
+ {
+ // Found a module - is there a controller name?
+ if ($seg_ct > $seg + 1)
+ {
+ // Yes - get class and method
+ $class = $route[$seg + 1];
+ $method = $seg_ct > $seg + 2 ? $route[$seg + 2] : 'index';
+ }
+ else
+ {
+ // Get default controller segments
+ $default = $this->_default_segments();
+ if (empty($default))
+ {
+ // No default controller to apply - carry on
+ unset($default);
+ continue;
+ }
+
+ // Get class and method
+ $class = array_shift($default);
+ $method = array_shift($default);
+ }
+
+ // Does the requested controller exist in the module?
+ if (file_exists($path.$sub.'controllers/'.$class.'.php'))
+ {
+ // Found it - assemble segments
+ if ($seg_ct <= $seg + 1)
+ {
+ $route[] = $class;
+ }
+ if ($seg_ct <= $seg + 2)
+ {
+ $route[] = $method;
+ }
+ if (isset($default) && count($default) > 0)
+ {
+ $route = array_merge($route, $default);
+ }
+
+ // Prepend path and return
+ array_unshift($route, $path.$sub);
+ return $route;
+ }
+ }
+ }
+ }
+
// If we got here, no valid route was found
return FALSE;
}
23 tests/codeigniter/core/Router_test.php
View
@@ -27,7 +27,8 @@ public function test_validate_route()
$alt_dir = 'alternative';
$alt_root = $this->ci_vfs_mkdir($alt_dir);
$alt_path = $this->ci_vfs_path($alt_dir.'/');
- $this->ci->load->paths = array($alt_path, $this->ci_app_path);
+ $this->ci->load->packages = array($alt_path, $this->ci_app_path);
+ $this->ci->load->modules = array();
// Create controller in app path
$dir = 'controllers';
@@ -115,7 +116,8 @@ public function test_default_routing()
// Mock loader with package paths
$this->_mock_loader();
- $this->ci->load->paths = array($this->ci_app_path);
+ $this->ci->load->packages = array($this->ci_app_path);
+ $this->ci->load->modules = array();
// Set up routes config with default
$stack = array('main', 'handler');
@@ -169,7 +171,8 @@ public function test_query_routing()
// Mock loader with package paths
$this->_mock_loader();
- $this->ci->load->paths = array($this->ci_app_path);
+ $this->ci->load->packages = array($this->ci_app_path);
+ $this->ci->load->modules = array();
// Set up routes config
$routes = array('default_controller' => '');
@@ -211,7 +214,8 @@ public function test_remap_routing()
// Mock loader with package paths
$this->_mock_loader();
- $this->ci->load->paths = array($this->ci_app_path);
+ $this->ci->load->packages = array($this->ci_app_path);
+ $this->ci->load->modules = array();
// Set up routes config
$route = 'main/path';
@@ -245,7 +249,8 @@ public function test_error_route()
{
// Mock loader with package paths
$this->_mock_loader();
- $this->ci->load->paths = array($this->ci_app_path);
+ $this->ci->load->packages = array($this->ci_app_path);
+ $this->ci->load->modules = array();
// Do we get FALSE with no error route?
$this->assertFalse($this->router->get_error_route('error_override'));
@@ -332,7 +337,8 @@ public function test_set_overrides()
$alt_dir = 'alternative';
$alt_root = $this->ci_vfs_mkdir($alt_dir);
$alt_path = $this->ci_vfs_path($alt_dir.'/');
- $this->ci->load->paths = array($alt_path, $this->ci_app_path);
+ $this->ci->load->packages = array($alt_path, $this->ci_app_path);
+ $this->ci->load->modules = array();
// Set up routes config
$routes = array('default_controller' => '');
@@ -404,7 +410,10 @@ private function _mock_loader()
$class = 'Router_Loader';
if ( ! class_exists($class))
{
- eval('class '.$class.' { public $paths; public function get_package_paths() { return $this->paths; } }');
+ $code = 'class '.$class.' { public $packages; public $modules; '.
+ 'public function get_package_paths() { return $this->packages; } '.
+ 'public function get_module_paths() { return $this->modules; } }';
+ eval($code);
}
$this->ci->load = new $class();
}
Please sign in to comment.
Something went wrong with that request. Please try again.