Chris Gmyr
Developer, entrepreneur, drummer, biker, dog owner, husband, and proud dad. Loves Laravel and coffee

Dynamically load modules, models, and configurations in Yii

09/12/2011

As I stated in a previous post I have started to play around with the Yii Framework, 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:

  • CodeIgniter 2.0.3
  • HMVC
  • One main controller called “admin” to handle a few functions/pages
  • Everything else in application/modules

In routes.php I had:

<?php
$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. On the front end there would be something like domain.com/users/view/1 which would go to application/modules/users/controller/users.php

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:
<?php
$config = array(
    'components' => array(
        'urlManager' => array(
            'urlFormat' => 'path',
            'showScriptName' => false,
            'rules' => array(
                //admin rules
                '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:
<?php
$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);

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