Dynamically load modules, models, and configurations

Hey everyone,

I just started getting into Yii this week, and I really love it so far. I’m moving over from CodeIgniter, and even though the 2 frameworks are very different, I haven’t had too much trouble getting up and going with it. I have built a pretty large CMS on top of CI and I’m now working on porting that over to Yii. I’m trying to mimic as much as I had before without affecting the workflow that I had previously. So far so good.

What I had in CodeIgniter:

In routes.php I had:


$route['admin/logout'] = 'admin/logout';

$route['admin/forgot'] = 'admin/forgot';

$route['admin/dashboard'] = 'admin/dashboard';

$route['admin/([a-zA-Z_-]+)/(:any)'] = '$1/admin/$2';

$route['admin/([a-zA-Z_-]+)'] = '$1/admin/index';

$route['admin'] = 'admin'; 

So if you go to domain.com/admin/users you would be in application/modules/users/controller/admin.php and anything like domain.com/admin/users/edit/1/ would work and find it’s place.

Now moving over to Yii…

  1. Create "admin" controller in Gii with dashboard, logout, and forgot actions

  2. Create "user" module in Gii

  3. Create the following urlManager rules in /protected/config/main.php:


'admin/<action:(dashboard|forgot|logout)>' => 'admin/<action>',

'admin/<module:\w+>/<action:\w+>/<id:\d+>' => '<module>/admin/<action>',

'admin/<module:\w+>/<action:\w+>' => '<module>/admin/<action>',

'admin/<module:\w+>' => '<module>/admin',

  1. Instead of just returning the config array, assign it to $config, and at the bottom of main.php enter:

return $config;

Your basic webapp should work as it did before at this point.

  1. In /protected/modules/user/controllers/DefaultController.php have something like:

<?php


class DefaultController extends Controller {


    public function actionIndex() {

        $this->render('index');

    }

    

    public function actionCreate() {

        die('this is default create');

    }

    

    public function actionEdit($id) {

        die('this is default edit = '.$id);

    }


}

  1. Copy that file and rename it "AdminController.php", then just change the "default" text to "admin":

<?php


class AdminController extends Controller {


    public function actionIndex() {

        $this->render('index');

    }

    

    public function actionCreate() {

        die('this is admin create');

    }

    

    public function actionEdit($id) {

        die('this is admin edit = '. $id);

    }


}

  1. Copy and rename /protected/modules/user/views/default to /protected/modules/user/views/admin for your admin only views.

  2. Create a /protected/modules/user/config directory and add a main.php file with something like:


<?php


$module_name = basename(dirname(dirname(__FILE__)));

$default_controller = 'default';


return array(

    'import' => array(

        'application.modules.' . $module_name . '.models.*',

    ),

    

    'modules' => array(

        $module_name => array(

            'defaultController' => $default_controller,

        ),

    ),

    

    'components' => array(

        'urlManager' => array(

            'rules' => array(

                $module_name . '/<action:\w+>/<id:\d+>' => $module_name . '/' . $default_controller . '/<action>',

                $module_name . '/<action:\w+>' => $module_name . '/' . $default_controller . '/<action>',

            ),

        ),

    ),

);

and you can add more rules and config settings if you want…

  1. Go back to your /protected/config/main.php file and right before you return $config, add this:

$modules_dir = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR;

$handle = opendir($modules_dir);

while (false !== ($file = readdir($handle))) {

    if ($file != "." && $file != ".." && is_dir($modules_dir . $file)) {

        $config = CMap::mergeArray($config, require($modules_dir . $file . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'main.php'));

    }

}

closedir($handle);

which will scan your modules directory and automatically add your module config to the main application config array.

Now you can have nice admin URLs that correspond with your modules: domain.com/admin, domain.com/admin/dashboard, domain.com/admin/user/create, domain.com/admin/user/edit/1 , domain.com/user/create, domain.com/user/edit/1 (instead of domain.com/user/default/edit/1 - you do not want "default" in the URL)

Note 1: You want to make sure you load the URL rules at the bootstrap so your whole application knows to redirect your module to the “default” controller without stating it. If you use the UserModule.php init() it will NOT work since you will already have to be in the module to load those rules, by that time it’s too late and you will get an error.

Note 2: You will obviously not want to use domain.com/user/create or domain.com/user/edit/1 on the front end of your site. I just put those as examples to show that everything is working.

Note 3: By adding the import statement for each module model you can now access all of your models throughout your whole application and other modules like:


$model = User::model()->findByPK(1);

Question: Is this the best way to handle what I wanted to accomplish? Since I’m still new I want to make sure I caught everything that I needed to.

I hope this helps some people who are looking to move over to Yii from CodeIgniter.

-Chris

Because this topic is highly rated in Google I decided to assist you with my solution: as I recently encountered same problem to dynamically load modules, I finally found this elegant solution. Hope it will work for some of you as it did to me.




require_once($yii);


class ExtendableWebApp extends CWebApplication {

	protected function init() {

		// this example dynamically loads every module which can be found

		// under `modules` directory

		// this can be easily done to load modules

		// based on MySQL db or any other as well

		foreach (glob(dirname(__FILE__).'/protected/modules/*', GLOB_ONLYDIR) as $moduleDirectory) {

			$this->setModules(array(basename($moduleDirectory)));

		}

		return parent::init();

	}

}


$app=new ExtendableWebApp($config);

$app->run();



@emix : looks good. Where to put this ?

Modify index.php

My few cents:

‘page’ and ‘admin’ are two different modules.

If i need any controller from the ‘page’ module inside the ‘admin’ module, I just extend it.

Supposing application.modules.page.controllers.PageController exists, application.modules.admin.controllers.PagesController becomes




<?php


Yii::import('application.modules.page.controllers.*');

Yii::import('application.modules.page.models.*');


class PagesController extends PageController {


    public function getViewFile($viewName) {

        $parentController = Awecms::getControllerIdFromClassName(get_parent_class());

        return Yii::app()->getModule('page')->getViewPath() . '/' . 'page' . '/' . $viewName . '.php';

    }


}



The beforeControllerAction() method in AdminModule.php takes care of authorization and the layout.




public function beforeControllerAction($controller, $action) {

        // this method is called before any module controller action is performed

        if (!Yii::app()->getModule('user')->isAdmin()) {

            throw new CHttpException(403, 'Action is forbidden.');

        }

        $controller->layout = 'main';

        return true;

    }



Now you can access /admin/pages to manage your pages.

The getViewFile() method of the controller is overwriten so that views from page module is used.