Difference between #7 and #6 of Organize directories for applications with front-end and back-end using WebApplicationEnd behavior

unchanged
Title
Organize directories for applications with front-end and back-end using WebApplicationEnd behavior
unchanged
Category
Tutorials
unchanged
Tags
changed
Content
<a
href="http://www.yiiframework.com/doc/cookbook/33/">Previously</a>
there was described a way to build an application with front-end and back-end. I
would like to continue this theme and suggest another way to organize
directories using WebApplicationEnd behavior.

If you are not familiar with events & behaviors yet, then please, read <a
href="http://www.yiiframework.com/doc/cookbook/44/">this
tutorial</a> to get basic knowledge about using them.

First, create a new web application with yiic tool. I used an application
generated by Yii 1.1.1, but everything described below should work with other
versions of Yii Framework (probably with minor changes).

Now let's modify some directories' structure to separate front-end and back-end
related files.

Since application's ends usually use the same models, but different controllers
and views, we will separate them by creating two subdirectories under
protected/controllers and protected/views directories:

~~~
webroot/
    protected/
        components/
            Controller.php
            UserIdentity.php
        controllers/
            /front
                SiteController.php
            /back
                SiteController.php
        views/
            /front
                /layouts
                    column1.php
                    column2.php
                    main.php
                /site
                   /pages
                       about.php
                   contact.php
                   error.php
                   index.php
                   login.php
            /back
                /layouts
                    main.php
                /site
                    error.php
                    index.php
                    login.php
~~~

Front-end SiteController and all front-end views are files generated by yiic
tool. You have to create back-end SiteController and back-end views by yourself
(or just copy and modify front-end ones).

> Note: Since a new full path to front-end layouts is
"application.views.front.layouts", you have to edit
"column1.php" and "column2.php" files to render
"application.views.front.layouts.main" as a parent layout. Also set
Controller's (protected/components/Controller.php) layout property to 'column1'.

Now let's create different config files for both ends. Since these files usually
have much in common, we will "inherit" them from the main.php config:

<strong>webroot/protected/config/front.php:</strong>

~~~
[php]
return CMap::mergeArray(
    require(dirname(__FILE__).'/main.php'),
    array(
        // Put front-end settings there
        // (for example, url rules).
    )
);
~~~

<strong>webroot/protected/config/back.php:</strong>

~~~
[php]
return CMap::mergeArray(
    require(dirname(__FILE__).'/main.php'),
    array(
        // Put back-end settings there.
    )
);
~~~

By default, Yii will try to find controllers and views in protected/controllers
and protected/views directories respectively. We have to change this behavior
and force Yii to search controllers and views in the "back" or
"front" subdirectories depending on the currently running end.

Actually we can do it in the -end's config file by setting "viewPath"
and "controllerPath" properties, but what if we are going to have some
modules like News, Articles, etc.? We'll need to set these properties for them
too. We can also have some back-end modules which don't need such separation.

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

~~~
[php]
class WebApplicationEndBehavior extends CBehavior
{
    // Web application end's name.
    private $_endName;
    
    // Getter.
    // Allows to get the current -end's name
    // this way: Yii::app()->endName;
    public function getEndName()
    {
        return $this->_endName;
    }
    
    // Run application's end.
    public function runEnd($name)
    {
        $this->_endName = $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 main config file:

~~~
[php]
'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 we
can change modules' properties. Controllers and views paths are changed in the
attached handler "changeModulePaths".

If you have a module, which should use different controllers and views for
different ends, then just modify it's init() method:

~~~
[php]
protected function init()
{
    // ...    

    // We can configure our module depending on the value
    // of Yii::app()->endName.
    $this->foo = (Yii::app()->endName == 'front') ? 'bar1' : 'bar2';

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

Note that in this case the module's controllers and views paths must be
organized as shown before.

If a module doesn't need a separation to back-end and front-end controllers and
views, then just omit the onModuleCreate event's raising.

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

<strong>webroot/protected/components/BackEndController.php:</strong>

~~~
[php]
class BackEndController extends CController
{
    public $layout='layout_name';
    public $menu=array();
    public $breadcrumbs=array();

    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 must extend this
controller to perform access checking.

Everything's done. New index.php and backend.php files are:

<strong>webroot/index.php:</strong>

~~~
[php]
$yii = dirname(__FILE__).'/../yii/framework/yii.php';
$config = dirname(__FILE__).'/protected/config/front.php';

// Remove the following lines when in production mode
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', 3);

require_once($yii);
Yii::createWebApplication($config)->runEnd('front');
~~~

<strong>webroot/backend.php:</strong>

~~~
[php]
$yii = dirname(__FILE__).'/../yii/framework/yii.php';
$config = dirname(__FILE__).'/protected/config/back.php';

// Remove the following lines when in production mode
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', 3);

require_once($yii);
Yii::createWebApplication($config)->runEnd('back');
~~~


<strong>CRUD management as frontend and backend</strong>

For backend crud generation run url as,<br />
/backend.php?r=gii<br />
controller generate as : controllers\back\XyzController.php<br />
view files generate as : views\back\xyz\files.php<br />
<br />
For frontend crud generation run url as,<br />
/index.php?r=gii/crud<br />
controller generate as : controllers\front\XyzController.php<br />
view files generate as : views\front\xyz\files.php<br />


## Summary

The created behavior delivers us from specifying controllers and views paths for
the application and all it's modules by using runEnd() method and invoking the
onModuleCreate event in necessary places.

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

## Appendix A. How to use yiic tool.

You can easily generate models and views for the front-end the old way without
any changes. Just go to the application's webroot directory and execute the next
command:

~~~
protected/yiic shell
or
path/to/php.exe protected/yiic shell
~~~

All generated models will be saved under protected/models directory, and
controllers & views will be saved under protected/(controllers|views)/front
directory.

Generating controllers and views for the back-end is a little different. You
have to run yiic the next way:

~~~
protected/yiic shell backend.php
or
path/to/php.exe protected/yiic shell backend.php
~~~

Now all generated controllers and views will go to
protected/(controllers|views)/back directory.

## Appendix B. Back-end url rules.

Usually you don't need to setup url rules for the back-end, but if you want to
do this, then you'll have to modify .htaccess as follows:

~~~
AddDefaultCharset utf-8

Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on

# Make the 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
~~~

Then, in the back.php config file setup url manager component:

~~~
[php]
'urlManager'=>array(
    'urlFormat'=>'path',
    'showScriptName'=>false,
    'rules'=>array(
        'backend'=>'site/index',
        'backend/<_c>'=>'<_c>',
        'backend/<_c>/<_a>'=>'<_c>/<_a>',
    ),
),
~~~

Now you can generate beautiful urls using CHtml::link() method.

If you have a module (e.g. news), then you'll need to add 3 more rules
**before** existing ones:

~~~
[php]
'backend/news'=>'news',
'backend/news/<_c>'=>'news/<_c>',
'backend/news/<_c>/<_a>'=>'news/<_c>/<_a>',
~~~

Also, you still can add own rules:

~~~
[php]
'backend/news/<id:\d+>'=>'news/newsReport/update',
~~~

But make sure that you insert these rules before news rules containing
<_c> and <_a>. Otherwise the latter rules will be used instead.

Write new article
  • Written by: andy_s
  • Updated by: kiran sharma
  • Category: Tutorials
  • Yii Version: 1.1
  • Votes: +33
  • Viewed: 78,192 times
  • Created on: Feb 2, 2010
  • Last updated: Jun 27, 2012