Default Controller in Sub Directory

Alright, so I’ve been using Yii for a fair amount of time now and have finally hopped into the community :] Prior to my stint in PHP I was programming in Scala - it’s rather difficult to be a member of many different active communities at once. So, hi Yii :D

Anyway, Given the following URL: /admin/users Yii first looks for a folder in the controllers folder called ‘admin’ and if it exists, looks for a controller within admin called ‘UsersController’. Great!

What I want to do is create a folder called ‘/admin’ and have, say, a default controller that would be called if that directory exists - but no controller is matched. For example => ‘/admin’ => ‘/admin/main’ … now I already understand that this can be done in URL rules and that given this particular logic it wouldn’t be too expensive to run - but the simple matter is that redefining the search logic would be yield better performance.

I figure this will mean extending an object - anyone have any thoughts on the matter? I want to do this a dozen times, ( so the URL rules become even more expensive )

/admin => /admin/MainController.php ( multiple admin controllers )

/admin/logs => /admin/logs/MainController.php ( multiple log controllers )

/admin/stats/ => /admin/stats/MainController.php ( multiple stat controllers )

Basically - if anyone has worked with Drupal, I am kind of building that stereotypical admin splash page where I’m going to run checkAccess on a few sections like checkAccess(‘statAdmin’), checkAccess(‘logAdmin’) - these are very broad examples, but there’d be a few levels of adminship in my project that would build a different admin screen each time.

Thank you for your time!! ( Hint: perhaps making this a high level setting would be awesome too. I’m willing to contribute if anyone finds value )

to summarize. Instead of calling /admin/main or admin/logs/main I would like to create this implicitly. It just brings my level of programming service to the client up a notch by hiding some magic and making urls just work.

You can create a ‘admin’ module: module

The url to call a modules controller action: /moduleid/controllerid/actionid

  1. create your admin folder under: protected/modules/admin

  2. add the module file: protected/modules/admin/AdminModule.php





class AdminModule extends CWebModule

{

  public $defaultController = 'main';

  

  public $mySettings; //custom properties you can config in config/main.php

}




and register the module in config/main.php




  'modules'=>array(

        'admin'=>array(

           'mySettings'=12345

          )

        ) 



  1. add your controllers:

    protected/modules/admin/controllers/MainController

    protected/modules/admin/controllers/LogsController

    protected/modules/admin/controllers/StatsController

    protected/modules/admin/controllers/UsersController

Now you can call:

/admin = the actionIndex method of the modules defaultController (=MainController)

     (you can change the default action by 



  class AdminController extends Controller {

  public $defaultAction = 'manage';

   ....




[/code]


/admin/users/manage = the usercontrollers actionManage from the module 'admin'





- In addition you can use [b]'nested modules'[/b] too by adding modules within your admin-module 




protected/modules/admin/modules/reports


Add: protected/modules/admin/modules/reports/ReportsModule.php with defaultController='view'

 

add a ViewController: protected/modules/admin/modules/reports/controllers/ViewController.php

...


Now you can call: [b]/admin/reports/report1[/b] (calls actionReport1 from the ViewController) or [b]/admin/reports/view/report1[/b]




- [b]Use the 'controllerMap' in config/main.php build you own urls[/b]


[code]

 'controllerMap' => array(

		'users'=>'application.modules.admin.controllers.UsersController',




Now you can call /users (the defaultAction from the UsersController)

or /users/manage

or /admin/users/manage

but - important - this will not be the same:

If you ‘expose’ a controller from a module like above (url: /users) the AdminModule.php (init …) will not be executed/initialized and the module property of the controller will be null. So you can’t use $this->module->mySettings in a module controller.

If called via the url ‘/admin/users’ $this->module in the controller is the AdminModule and the module will be initialized.

So you have to do a workaround (maybe in a basic AdminBasicConroller):




class AdminBasicController extends Controller {


	/**

	 * Have to assign the module in __construct

	 * if loaded directly from controllerMap

	 *

	 * @param string $id

	 * @param string $module

	 */

	public function __construct($id, $module = null)

	{

		if (!isset($module))

			$module = Yii::app()->getModule('admin');


		parent::__construct($id, $module);

	}

}






class AdminController extends AdminBasicController {

...

}




Maybe this is worth a wiki-tutorial…

Really? It’s that easy, huh? Wow, thank you for taking the time out to describe how this is done. It makes sense and now I’ve learned something new!

re: wiki. Yes, and don’t just stop there. Yii should put a section in the request dispatching section linking to that wiki. Where it attempts to resolve the request to a controller - you should say ‘if you’d like to do something like this, please see this section.’

The documentation and the community would benefit greatly from something that simple.

Again, thanks! Hopefully I can share my knowledge in the coming future.

Quick follow up question.

The directions worked well, and I created

/protected/modules/admin/controllers/MainController

and

/protected/modules/admin/views/main/index.php

But index.php is not able to ‘automagically’ absorb the layout styling of

/protected/views/layouts/main.php

I figured this much and am not expecting it to automatically work without come configuration - so using your method, how would I be able to get my views to use the existing layout files ?

Also:

How do you manage your URLs in modules?

So, you have a ‘splash page’ of options

I want:

/admin/users

/admin/logs

/admin/x

/admin/y

CHtml::link(‘Users’, array( ‘/admin/users’ ) )

Seems to be the only way, but this seems very coupled to the URL

unlike doing it like this:

array( ‘users’ )

So, for logs I would have

/admin/logs/error/

/admin/logs/etc/

Any thoughts on url management for deep module nesting ?

1. By default Yii searches for the view in the modules viewpath: views/layouts/… within the module.

See creating modules

But this should work:

Set the defaultLayout in the /protected/modules/admin/controllers/MainController.php (or your BaseController)




 

 public $layout='//layouts/main'; //= protected/views/layouts/main.php



or you can override the getViewPath() method of the controller.

2. CHtml::link(‘Users’, array( ‘/admin/users’ ))

It’s ok (for me), because you know you are working in the admin-area.

So you know, that the AdminModule::init() always will be called for all controller actions in the module and you can prepare a lot in this method (loading informations from a config file, checking rights, set themes, languages…).

As a simple example:

Instead of public $mySettings in my AdminModule.php from above you can use a property ‘allowedIp’:




 

class AdminModule extends CWebModule

{

  public $defaultController = 'main';

  

  public $allowedIps = array('127.0.0.1'); 

}


public function init() 

{

  $ip = Yii::app()->getRequest()->userHostAddress;


  if(!empty($this->allowedIps) && !in_array($ip,$this->allowedIps)

    throw new CHttpException(400, 'Not found');

  

  parent::init();

}






You can set the allowedIp in your config/main.php.

This is only a simple solution, I would implement this with ‘’: $allowedIps = array('84.10.10.’,…);

You can copy/modify the code from Yii / controller rules for an implementation.

Another (maybe better) solution to restrict the ips is to implement the access-rules in the ‘AdminBaseController’ of the module (all controllers in the module should extend this basecontroller):




public function accessRules() {

        return array(

            

            array('allow', 

                'users' => array('admin'),

                'ips'=> $this->module->allowedIps,

            ),

            array('deny', // deny all users

                'users' => array('*'),

            ),

        );

    }




If you want to use CHtml::link(‘Users’, array(‘users’)) you have to config the ‘controllerMap’ as described above.

3. Don’t split up your module into a lot of nested (sub-)modules.

Use nested modules only if it’s an extra ‘addon’ to a existing module (maybe implemented later).

A module LogController with actionError() … etc. is ok.