One more way to organize directories for applications with front-end and back-end.

Previously there was described a way to build an application with front-end and back-end parts. I would like to continue this theme and suggest another way to organize directories. One can find the way described below a bit sophisticated, but it suits all my needs when I am building an application having a module structure.

Since application ends usually use the same models, but different controllers and views, we will create separate controllers and views folders for all ends. Since we have two ends, let's name folders "front" and "back":

webroot/
    protected/
        controllers/
            /front
                SiteController.php
            /back
                SiteController.php
        views/
            /front
                /layouts
                    main.php
                /site
                    index.php
                   contact.php
                   about.php
            /back
                /layouts
                    main.php
                /site
                    index.php
                    login.php

Front-end and back-end should use different config files, but since both files usually have much in common, we will create a "base" config file with shared settings:

webroot/protected/config/base.php:

// This config contains common settings for other config files.
 
return array(
    'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
    'name'=>'My Web Application',
 
    'sourceLanguage'=>'ru',
    'language'=>'ru',
 
    // preloading 'log' component
    'preload'=>array('log'),
 
    // autoloading model and component classes
    'import'=>array(
        'application.models.*',
        'application.components.*',
    ),
 
    'modules'=>array(
        'News',
    ),
 
    // application components
    'components'=>array(
 
        'db'=>array(
            'connectionString'  =>  'mysql:host=localhost;dbname=mydb',
            'username'          =>  'root',
            'password'          =>  '',
            'charset'           =>  'utf8',
        ),
 
        'errorHandler'=>array(
            // use 'site/error' action to display errors
            'errorAction'=>'site/error',
        ),
 
        'log'=>array(
            'class'=>'CLogRouter',
            'routes'=>array(
                array(
                    'class'=>'CFileLogRoute',
                    'levels'=>'error, warning',
                ),
            ),
        ),
    ),
);

webroot/protected/config/frontend.php:

return CMap::mergeArray(
    require(dirname(__FILE__).'/base.php'),
    array(    
        'components'=>array(
 
            'urlManager'=>array(
                'urlFormat'=>'path',
                'showScriptName'=>false,
                'urlSuffix'=>'',
                'rules'=>array(
                    // News.
                    'news'=>'News/newsReport/list',
                    'news/<id:\d+>'=>'News/newsReport/show',
                ),
            ),
        ),
    )
);

webroot/protected/config/backend.php:

return CMap::mergeArray(
    require(dirname(__FILE__).'/base.php'),
    array(
        'components'=>array(
 
            'user'=>array(
                'loginUrl'=>array('site/login'),
                // enable cookie-based authentication
                'allowAutoLogin'=>true,
            ),
        ),
    )
);

The main inconvenience now is that we have to define controllerPath and viewPath for our application and for all modules, which have different logic for different ends.

Here comes the Yii magic. In the webapp/protected/components folder create a file "WebApplicationEndBehavior.php" with the following contents:

class WebApplicationEndBehavior extends CBehavior
{
    // Web application end's name.
    private $_endName;
 
    // Getter.
    // Allows to get the current end name in this way: Yii::app()->endName;
    public function getEndName()
    {
        return $this->_endName;
    }
 
    // Run application's end.
    public function runEnd($name)
    {
        $this->_endName = $name; // Set end's name.
 
        // Attach the changeModulePaths event handler and raise it.
        $this->onModuleCreate = array($this, 'changeModulePaths');
        $this->onModuleCreate(new CEvent($this->owner));
 
        $this->owner->run(); // Run application.
    }
 
    // This event should be raised when CWebApplication or CWebModule instances are being initialized.
    public function onModuleCreate($event)
    {
        $this->raiseEvent('onModuleCreate', $event);
    }
 
    // onModuleCreate event handler.
    // A sender must have controllerPath and viewPath properties.
    protected function changeModulePaths($event)
    {
        $event->sender->controllerPath .= DIRECTORY_SEPARATOR.$this->_endName;
        $event->sender->viewPath .= DIRECTORY_SEPARATOR.$this->_endName;
    }
}

Now add some lines to the base config file:

'behaviors'=>array(
    'runEnd'=>array(
        'class'=>'application.components.WebApplicationEndBehavior',
    ),
),

Now our application has a new method runEnd (to run one of the application's ends) and a new event onModuleCreate. By raising this event from a web module (CWebApplication or CWebModule instance) we can change it's properties. Controller and view paths are changed in the attached handler changeModulePaths.

Now go to any module, which should use different controllers and views for different ends, and modify it's init() method:

protected function init()
{
    // ...    
 
    // Depending on the value of Yii::app()->endName we can configure our module in different ways.
    $this->newsPerPage = (Yii::app()->endName == 'front') ? 5 : 15;
 
    // Raise onModuleCreate event.
    Yii::app()->onModuleCreate(new CEvent($this));
}

Note that the module’s controllers and views paths must be organized as shown before.

Finally, let's protect back-end part by creating a parent controller for all back-end controllers:

webroot/protected/components/BackEndController.php:

class BackEndController extends CController
{
    public function filters()
    {
        return array(
            'accessControl',
        );
    }
 
    public function accessRules()
    {
        return array(
            array('allow',
                'users'=>array('*'),
                'actions'=>array('login'),
            ),
            array('allow',
                'users'=>array('@'),
            ),
            array('deny',
                'users'=>array('*'),
            ),
        );
    }
}

webroot/protected/controllers/back/SiteController.php and modules’ back-end controllers should extend this controller to perform access checking. SiteController's actionLogin displays the login page. Probably you'll need a special "lightweight" layout for this page. Just put it into the webroot/protected/views/back/layouts/login.php and write in the action: $this->layout = 'login';

Everything’s done. New index.php and backend.php files look like:

webroot/index.php:

define('YII_DEBUG', true);
 
$config = './protected/config/frontend.php';
require_once '../yii/framework/yii.php';
Yii::createWebApplication($config)->runEnd('front');

webroot/backend.php:

define('YII_DEBUG', true);
 
$config = './protected/config/backend.php';
require_once '../yii/framework/yii.php';
Yii::createWebApplication($config)->runEnd('back');

Actually this scheme of organizing directories does not differ from the previous one considerably. Here we just don't create a new subdirectory under the protected directory to store back-end files.

The main novelty is the created behavior, which delivers us from specifying controllers and views paths in application's and modules' configs. Instead, we use runEnd($name) method.

Also modules became more self-sufficient and can be easily integrated with the existing front-end and back-end layouts.

Total 9 comments:

#1065
Doude about rewrite url
by Shankar at 2:55pm on February 3, 2010.

Hey good way organize the directorys, very good. But i have the doude about how i can to use the mod_rewrite with the backend and frontend.

The first is the file .htaccess, and next how i will to create the respectives URL in the backend and frontend.

Once again I thank you for attention and solution for this organization.

#1067
htaccess & url manager
by andy_s at 2:49pm on February 3, 2010.

.htaccess file may look like:

Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on

# Make backend accessible via url: http://site/backend.
RewriteRule ^backend backend.php

# If a directory or a file exists, use it directly.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# Otherwise forward it to index.php.
RewriteRule . index.php

Usually you don't need to rewrite urls in the backend. But if you want to, then add urlManager component to the backend config file (see frontend config). It can look like:

'urlManager'=>array(
    'urlFormat'=>'path',
    'showScriptName'=>false,
    'urlSuffix'=>'',
    'rules'=>array(
        'backend'=>'site/index',
        // News.
        'backend/news'=>'News/newsReport/list',
        'backend/news/'=>'News/newsReport/update',
    ),
),

To generate appropriate links use CHtml::link method. For example:

CHtml::link('News', array('News/newsReport/list'));
#1072
Thank you
by Shankar at 2:00am on February 4, 2010.

Thank you man.

#1073
Thanks for sharing this
by atrandafir at 2:47am on February 5, 2010.

Thank you, I think i'll use this for a project to have a better structure.

#1087
Not working :(
by emix at 2:49am on February 9, 2010.

Everything looks fine - but - I have rewritten my application to look like above and now I cannot login :(

#1088
to emix
by andy_s at 2:37am on February 9, 2010.

The login part is not described here particularly. Please, reference the guide: http://www.yiiframework.com/doc/guide/topics.auth

(you'll need create UserIdentity class)

#1089
to andy_s
by emix at 2:26pm on February 9, 2010.

I had a fully working application and have rewritten it to allow more front ends. After followed above steps I cannot log in. The script does not work and I don't know what's going on :(

#1090
to emix #2
by andy_s at 2:58pm on February 9, 2010.

Maybe you forgot to add a user component to a front-end config (if you want to allow users log in in the front-end)?

#1247
doubt
by isreal at 3:41am on March 9, 2010.

what I dont understand is why and where should I put this code:

protected function init() { // ...

// Depending on the value of Yii::app()->endName we can configure our module in different ways.
$this->newsPerPage = (Yii::app()->endName == 'front') ? 5 : 15;

// Raise onModuleCreate event.
Yii::app()->onModuleCreate(new CEvent($this));

}

besides this, this is an excellent idea, I like it...

Your Comment:

You may enter comment using Markdown syntax.

Please login with your forum account.
Note: you must have at least ONE forum post with your account.