Monday 22 April 2013

Yii MVC Architecture

 The MVC Architecture


Data flow diagram of MVC design pattern

The Model-View-Controller pattern defines the architecture on which an application using this model is designed and works. The image above shows the data flow between the three basic components of the pattern and the interactions with the user and the application itself (here in terms of database):

  • The user uses the controllers through what he sees on the view
  • The controller manipulates the data provided by the model
  • The model updates the view the user sees
  • Rinse and repeat

Each of the three components is made of one or more scripts (i.e.: PHP scripts, HTML and CSS files, JS scripts, etc.) usually residing on the server and being transferred on the client side via HTTP/S. It’s important to note here that the user can be a human being as well as another application (i.e.: think about web services).

YII's architecture is based on the MVC pattern therefore the structure is very similar, however can be confusing for a first timer to allocate each YII script/library to the right block and function. The following image should give you a better vision on how YII is structured and broken down in the MVC blocks

YII MVC Framework diagram

You can find more details on the implementation of the MVC pattern and best practices in YII by reading the easy-to-follow documentation on the YII website.

Customise the application

The customisation of the application can be performed in various way, you can:
  1. Download and add plug-in to the application
  2. Modify existing piece of code
  3. Create your own controllers and plug-in
What we'll do in this last part of the tutorial, is to modify existing piece of the code that has been automatically generated by YII. We will want to achieve the following:
  1. Enable the authentication through the database
  2. Align the password validation to a standard policy
  3. Modify the layout of the login screen.

Step Six – Enable the authentication through the database

The basic skeleton provided by the YII command line application, already includes a rudimentary authentication mechanism. This will check if the details entered matches the one specified in the controller. Now what we want to do is to modify the controller, so that the authentication method will check the data against the user table in the database.

The skeleton application uses the User Identity controller class for this purpose, we can find the file in WEBROOT\protected\components\UserIdentity.php. What we have to modify is the authenticate method, as shown in the code below:

class UserIdentity extends CUserIdentity
{
 
    private $_id;
 
    public function authenticate()
    {
        $username=strtolower($this->username);
        $user=User::model()->find('LOWER(username)=?',array($username));
        if($user===null)
            $this->errorCode=self::ERROR_USERNAME_INVALID;
        else if(!$user->validatePassword($this->password))
            $this->errorCode=self::ERROR_PASSWORD_INVALID;
        else
        {
            $this->_id=$user->id;
            $this->username=$user->username;
            $this->errorCode=self::ERROR_NONE;
        }
        return $this->errorCode==self::ERROR_NONE;
    }
 
  
    public function getId()
    {
        return $this->_id;
    }
}

You can see on line 8 the use of the ORM model for the user record, we access the user model and perform a search through the find method specifying the username.
$user=User::model()->find('LOWER(username)=?',array($username));

If the user is found then the password is validated (on line 9) using the method validatePassword
else if(!$user->validatePassword($this->password))

now this is a method you should add in the User’s model class (located in WEBROOT\protected\models\User.php) which will help to validate the password according to your need. Here is a very easy example
public function validatePassword($password)
 
 {
     return $this->hashPassword($password,$this->salt)===$this->password;
 }
 
  
 
 public function hashPassword($password,$salt)
 {
     return md5($salt.$password);
 }

During the registration phase we won't store the password in the database in plain text, but its hash instead and a randomly generated salt key. This is a common approach that enforce security and ensure we don't have access to such a sensitive personal user's data.

If everything is successful the user's ID will be returned back to the calling method.

In this step we have modified the User Identity component and the User's model class. Some of you may get confused, but it's really simple, the difference between these two script is that the former is like a plug-in used to aid in the authentication workflow, while the latter is used to represent the data related with the current user. You can use the User Identity multiple times depending on the various authentication methods you want to implement, while you will have only one User's model which is the currently logged user and all related data grabbed from the database's table.

Step Seven – Align the password validation to a standard policy

The password is one of the weakest point of any application, because it is the user's choice and this can be anything from a simple numeric sequence (i.e.: 12345) or a complex mix of letters, numbers and symbols (i.e.: aH72!k.). More likely the average user will chose something easy to remember and often something that has a meaning for him/her. This is called in IT jargon a weak password and it's easily broken by any good brute force or dictionary password hack.

Now any good developer should always introduce and reinforce password policies in their software. This is for the protection of the software, the infrastructure behind it and all the user's data. With your newly developed YII application you can easily achieve it.

YII has built-in validation rules which instruct YII to validate a specific input against a set of rules. Here is where you are going to implement your password policies.

It is important to understand that this is a very generic feature that can be applied to any parameter given in input to the application via the forms on the web pages; you need to be careful to implement the rules only where you need them and not everywhere.

Additionally rules implemented in forms models have no effect on record creation where the rules should be defined in the appropriate model. For example LoginForm model can have different rules than User model, where the former doesn't require any specific constraint for username and password and the latter requires minimum 8 chars long username and password.

If you want to add rules for user's registration, I suggest you implement it in the User's model by modifying the already built-in rules's method as shown in the code below

public function rules()
 
{
 
    // NOTE: you should only define rules for those attributes that
 
    // will receive user inputs.
 
    return array(
 
        array('username, password, salt, email', 'required'),
 
        array('username, password, salt, email', 'length', 'max'=>128),
 
        array('password','length','min'=>8,'max'=>12),
 
        array('profile', 'safe'),
 
        // The following rule is used by search().
 
        // Please remove those attributes that should not be searched.
 
        array('id, username, salt, email, profile', 'safe', 'on'=>'search'),
 
    );
 
}

you can add as many rules and validators as you want in your methods, here I’ve specified the fields: username, password, salt and email are required and a maximum length of 128 characters. I’ve also added an additional rule where the password’s length must be minimum 8 and maximum 12 characters.

Step Eight – Modify the layout of the user’s login screen

We have come so far touching various parts of the application but we never looked at the layout of our application's output. When you use the automatic YII generators you will get a basic and rather dull layout, which is OK while you are developing the application and focusing your efforts in functionalities. Now it is time to make our application beautiful, so you need to understand which scripts needs some modification.

Your main area of concern are the views, which as explained in the introduction of this post contains the structure of your web application in terms of look and feel. Views are a mix of PHP, HTML, CSS and JS; the first script you want to look at is the main layout which is applied throughout the application, it is located in WEBROOT\protected\views\layout\main.php. I strongly suggest you have a peek at all files contained in the WEBROOT\protected\views folder, you will find one folder for each of your controller and one file for each of the actions contained in your controller which requires user's iteraction.

I'm going to show you now how to modify one of these files, let's have a look at the login form located at WEBROOT\protectd\views\site\login.php

The first block of code will define via PHP the title of the page and will use a very nice but useful facilitator to show the whereabout in form of breadcrumbs
<?php
$this->pageTitle=Yii::app()->name . ' - Login';
 
$this->breadcrumbs=array(
    'Login'
);
 
?>

This is quite straight forward, the next block will define the form itself, as you can see from the code below there is a mixture of HTML and PHP code. Remember that you can write in the view script whatever you want and it will be displayed, if you enter any PHP code it will be interpreted.
<h1>Login</h1>
 
<pre>Please fill out the following form with your login credentials:</pre>
 
<div class="form">
<?php
$form=$this->beginWidget('CActiveForm', array(
 
 'id'=>'login-form',
 
 'enableClientValidation'=>true,
 
 'clientOptions'=>array(
 
 'validateOnSubmit'=>true,
 
 ),
 
)); ?>

let's spend few words on the form's definition code itself; You have two options at this stage, you can develop your own form validation/interaction or let YII do the work. I suggest the latter as you will have consistency across all your scripts, and a code easy to maintain and improve. You can achieve this through the usage of Widgets, which are on their own controllers and views but packaged to be used as “utility” code (there are a lot of widgets available on the YII website).

In this instance I have used the CActiveForm widget which will create a form in our view, it accepts several parameters so that you can customise it as much as you want. Here you can see that I've enabled the validation at client level, this mean that validation rules defined in the LoginForm controller will be used and related errors/warnings messages presented to the user on the web page while iterating with the form.

Validation Rule in action

Now we have to enter the input forms for each of the fields we want the user’s input, if you use the widget you will have to call in your code three different methods from the widget object as shown below.
</pre>
 
<div class="row"><?php echo $form->labelEx($model,'username'); ?>
 
 <?php echo $form->textField($model,'username'); ?>
 
 <?php echo $form->error($model,'username'); ?></div>
 
<pre>

The name of the methods give you an idea of what they do, the important thing is to note that those methods uses the main model object as first property in order to link with the data model and display the correct information and implement the correct validation rule (if needed). The second property is the name of the column as specified in your model, hence in your data dictionary. In this specific case we will have in output the label, the text field and a space reserved to show the errors if any. The last step is to add the buttons to submit the form and close the form itself. Once again this is easy to achieve, we will be using the CHTML core helper class to implement the button as this is not part of the form widget. The last bit is to invoke the method endWidget()which will close the form and add any final bit of code needed for the form to work.
</pre>
 
<div class="row buttons"></div>
 
<pre><?php $this->endWidget(); ?>

1 comment:

more info said...

waoooooooo nice.

Yii Interview Question