Posts Issued in August, 2014

This post covers my notes on Adding an RSS Web Feed (chapter 9) in Yii from the book "Web Application Development with Yii and PHP" by Jeffrey Winesett about learning Yii by taking a step-by-step approach to building a Web-based project task tracking system from conception through production deployment - software development life cycle (SDLC) issue-management application.

Feature planning

  • Use Zend Framework in the Yii application
  • Creating a new action in a controller class to respond to the feed request and return the appropriate data in an RSS format
  • Altering our URL structure for ease of use
  • Adding our newly created feed to both the project listings page as well as to each individual project details page
/**
     * Uses Zend Feed to return an RSS formatted comments data feed
     */
    public function actionFeed()
    {
        if(isset($_GET['pid'])) 
        {
            $comments = Comment::model()->with(array(
                                'issue'=>array(
                                    'condition'=>'project_id=:projectId', 
                                    'params'=>array(':projectId'=>intval($_GET['pid'])),
                                )))->recent(20)->findAll();
 
        }
        else   
            $comments = Comment::model()->recent(20)->findAll();  
 
        //convert from an array of comment AR class instances to an name=>value array for Zend
        $entries=array(); 
 
        foreach($comments as $comment)
        {
 
            $entries[]=array(
                    'title'=>CHtml::encode($comment->issue->name),     
                    'link'=>CHtml::encode($this->createAbsoluteUrl('issue/view',array('id'=>$comment->issue->id))),  
                    'description'=> $comment->author->username . ' says:<br>' . $comment->content,
                    'lastUpdate'=>strtotime($comment->create_time),   
                    'author'=>CHtml::encode($comment->author->username),
             );
        }  
 
        //now use the Zend Feed class to generate the Feed
        // generate and render RSS feed
        $feed=Zend_Feed::importArray(array(
             'title'   => 'Trackstar Project Comments Feed',
             'link'    => $this->createAbsoluteUrl(''),
             'charset' => 'UTF-8',
             'entries' => $entries,      
         ), 'rss');
 
        $feed->send();
 
    }
 
/*ProjectController::actionIndex() method.
Alter that method as follows:*/
public function actionIndex()
{
$dataProvider=new CActiveDataProvider ('Project');
Yii::app()->clientScript->registerLinkTag(
'alternate',
'application/rss+xml',
$this->createUrl('comment/feed'));
$this->render('index',array(
'dataProvider'=>$dataProvider,
));
}
 
/*similar change to add this link to a specific project details page. The rendering of these pages is handled by the ProjectController::actionView() method.*/
 
    /**
     * Displays a particular model.
     * @param integer $id the ID of the model to be displayed
     */
    public function actionView($id)
    {
        $issueDataProvider=new CActiveDataProvider('Issue', array(
            'criteria'=>array(
                'condition'=>'project_id=:projectId',
                'params'=>array(':projectId'=>$this->loadModel($id)->id),
            ),
            'pagination'=>array(
                'pageSize'=>1,
            ),
         ));
 
        Yii::app()->clientScript->registerLinkTag(
            'alternate',
            'application/rss+xml',
            $this->createUrl('comment/feed',array('pid'=>$this->loadModel($id)->id)));
 
 
        $this->render('view',array(
            'model'=>$this->loadModel($id),
            'issueDataProvider'=>$issueDataProvider,
        ));
 
    }

Download source code of chapter 9

Chapter 10 covers: Adding Layout (looking good)

Download source code of chapter 10

Chapter 11 covers: Using Yii Modules

Download source code of chapter 11

Chapter 12 covers: Production Readiness

Download source code of chapter 12

This post covers my notes on Adding User Comments (chapter 8) in Yii from the book "Web Application Development with Yii and PHP" by Jeffrey Winesett about learning Yii by taking a step-by-step approach to building a Web-based project task tracking system from conception through production deployment - software development life cycle (SDLC) issue-management application.

  • Comments design & develop
  • Creating the widget RecentComments

The following is a list of high-level tasks need to complete:

  • Design and create a new database table to support comments, Yii AR class.
  • Add a form directly to the issue details page to allow users to submit comments.
  • Display a list of all comments associated with an issue directly on its details page.
  • Take advantage of Yii widgets to display a list of the most recent comments on the projects listing page.
<?php
 
class m120921_015630_c reate_u ser_c omments_table extends CDbMigration
{
    public function up()
    {
        //create the issue table
        $this->createTable('tbl_comment', array(
            'id' => 'pk',
            'content' => 'text NOT NULL',
            'issue_id' => 'int(11) NOT NULL',
            'create_time' => 'datetime DEFAULT NULL',
            'create_user_id' => 'int(11) DEFAULT NULL',
            'update_time' => 'datetime DEFAULT NULL',
            'update_user_id' => 'int(11) DEFAULT NULL',
         ), 'ENGINE=InnoDB');
 
        //the tbl_comment.issue_id is a reference to tbl_issue.id 
        $this->addForeignKey("fk_comment_issue", "tbl_comment", "issue_id", "tbl_issue", "id", "CASCADE", "RESTRICT");
 
        //the tbl_issue.create_user_id is a reference to tbl_user.id 
        $this->addForeignKey("fk_comment_owner", "tbl_comment", "create_user_id", "tbl_user", "id", "RESTRICT", "RESTRICT");
 
        //the tbl_issue.updated_user_id is a reference to tbl_user.id 
        $this->addForeignKey("fk_comment_update_user", "tbl_comment", "update_user_id", "tbl_user", "id", "RESTRICT", "RESTRICT");
    }
 
    public function down()
    {
        $this->dropForeignKey('fk_comment_issue', 'tbl_comment');
        $this->dropForeignKey('fk_comment_owner', 'tbl_comment');
        $this->dropForeignKey('fk_comment_update_user', 'tbl_comment');
        $this->dropTable('tbl_comment');
    }
 
 
}
 
//relations OK, add to Issue comment relation
public function relations()
{
return array(
'requester' => array(self::BELONGS_TO, 'User', 'requester_id'),
'owner' => array(self::BELONGS_TO, 'User', 'owner_id'),
'project' => array(self::BELONGS_TO, 'Project', 'project_id'),
'comments' => array(self::HAS_MANY, 'Comment', 'issue_id'),
'commentCount' => array(self::STAT, 'Comment', 'issue_id'),
);
}
/*This establishes the one-to-many relationship between an issue and comments. It
also defines a statistical query to allow us to easily retrieve the total comment count
for any given issue instance*/
 
//add to IssueController:
    protected function createComment($issue)
    {
        $comment=new Comment;  
        if(isset($_POST['Comment']))
        {
            $comment->attributes=$_POST['Comment'];
            if($issue->addComment($comment))
            {
                Yii::app()->user->setFlash('commentSubmitted',"Your comment has been added." );
                $this->refresh();
            }
        }
        return $comment;
    }

Creating the widget RecentComments

<?php
/**
 * RecentCommentsWidget is a Yii widget used to display a list of recent comments 
 */
class RecentCommentsWidget extends CWidget
{
    private $_comments;  
    public $displayLimit = 5;
    public $projectId = null;
 
    public function init()
    {
        if(null !== $this->projectId)
            $this->_comments = Comment::model()->with(array('issue'=>array('condition'=>'project_id='.$this->projectId)))->recent($this->displayLimit)->findAll();
        else
            $this->_comments = Comment::model()->recent($this->displayLimit)->findAll();
    }  
 
    public function getData()
    {
        return $this->_comments;
    }
 
    public function run()
    {
        // this method is called by CController::endWidget()    
        $this->render('recentCommentsWidget');
    }
}
 
//protected/components/views/recentCommentsWidget.php
 
<ul>
    <?php foreach($this->getData() as $comment): ?>  
        <div class="author">
            <?php echo $comment->author->username; ?> added a comment.
        </div>
        <div class="issue">      
           <?php echo CHtml::link(CHtml::encode($comment->issue->name), array('issue/view', 'id'=>$comment->issue->id)); ?>
        </div>
 
    <?php endforeach; ?>
</ul>

Download source code of chapter 8

This post covers my notes on User Access Control (chapter 7) in Yii from the book "Web Application Development with Yii and PHP" by Jeffrey Winesett about learning Yii by taking a step-by-step approach to building a Web-based project task tracking system from conception through production deployment - software development life cycle (SDLC) issue-management application.

  • RBAC

Yii provides both a simple access control filter as well as a more sophisticated Role Based Access Control (RBAC) implementation as a means to help us address our user authorization requirements. Three roles: project owner has all administrative access to the project, project member has some administrative access, project reader has read-only access.

There are two methods relevant to access control in the ProjectController class: filters() and accessRules().

Access rules can be defined using a number of context parameters:

  • Controllers: Specifies an array of controller IDs to which the rule should apply.
  • Roles: Specifies a list of authorization items (roles, operations, and permissions) to which the rule applies.This makes use of the RBAC feature we will be discussing in the next section.
  • IPs: Specifies a list of client IP addresses to which this rule applies.
  • Verbs: Specifies which HTTP request types (GET, POST, and so on) apply to this rule.
  • Expression: Specifies a PHP expression whose value indicates whether or not the rule should be applied.
  • Actions: Specifies the action method, by use of the corresponding action ID, to which the rule should match.
  • Users: Specifies the users to which the rule should apply. The current application user's name attribute is used for matching: *: any user, ?: anonymous users, @: authenticated users. If no users are specified, the rule will apply to all users. The access rules are evaluated one by one in the order they are specified.

Ee could make changes, three times, in each of our project, issue, and user controller class files. Or add the necessary method to our base controller class. Open up protected/components/Controller.php and add the following method:

public function accessRules()
    {
        return array(
            array('allow',  // allow all users to perform 'index' and 'view' actions
                'controllers'=>array('issue','project','user'),
                'actions'=>array('index','view', 'addUser'),
                'users'=>array('@'),
            ),
            array('allow', // allow authenticated user to perform 'create' and 'update' actions
                'controllers'=>array('issue','project','user'),
                'actions'=>array('create','update'),
                'users'=>array('@'),
            ),
            array('allow', // allow admin user to perform 'admin' and 'delete' actions
                'controllers'=>array('issue','project','user'),
                'actions'=>array('admin','delete'),
                'roles'=>array('admin'),
            ),
            array('deny',  // deny all users
                'controllers'=>array('issue','project','user'),
                'users'=>array('*'),
            ),
        );
    }

Before we can establish an authorization hierarchy, assign users to roles, and perform access permission checking, we need to configure the authorization manager application component, authManager. This component is responsible for storing the permission data and managing the relationships between permissions. It also provides the methods to check whether or not a user has access to perform a particular operation. Yii provides two types of authorization managers CPhpAuthManager and CDbAuthManager. CPhpAuthManager uses a PHP script file to store the authorization data. CDbAuthManager, stores the authorization data in a database. The authManager is configured as an application component. Configuring the authorization manager consists simply of specifying which of these two types to use and then setting its initial class property values.

If use the database implementation for application. in main configuration file, protected/config/main.php, and add the following to the application components array:

// application components
'components'=>array('authManager'=>array(
'class'=>'CDbAuthManager',
'connectionID'=>'db',
),

CDbAuthManager class uses database tables to store the permission data. It expects a specific schema. That schema is identified in the framework file YiiRoot/framework/web/auth/schema.sql. It is a simple, yet elegant, schema consisting of three tables, AuthItem, AuthItemChild, and AuthAssignment.

Run

yiic migrate create create_rbac_tables

This will gen:

<?php
 
class m120619_015239_create_rbac_tables extends CDbMigration
{
    public function up()
    {
        //create the auth item table
        $this->createTable('tbl_auth_item', array(
            'name' => 'var char(64) NOT NULL',
            'type' => 'integer NOT NULL',
 
            'description' => 'text',
            'bizrule' => 'text',
            'data' => 'text',
            'PRIMARY KEY (`name`)',
        ), 'ENGINE=InnoDB');
 
        //create the auth item child table
        $this->createTable('tbl_auth_item_child', array(
            'parent' => 'var char(64) NOT NULL',
            'child' => 'var char(64) NOT NULL',
            'PRIMARY KEY (`parent`,`child`)',
        ), 'ENGINE=InnoDB');
 
        //the tbl_auth_item_child.parent is a reference to tbl_auth_item.name 
        $this->addForeignKey("fk_auth_item_child_parent", "tbl_auth_item_child", "parent", "tbl_auth_item", "name", "CASCADE", "CASCADE");
 
        //the tbl_auth_item_child.child is a reference to tbl_auth_item.name 
        $this->addForeignKey("fk_auth_item_child_child", "tbl_auth_item_child", "child", "tbl_auth_item", "name", "CASCADE", "CASCADE");
 
        //create the auth assignment table
        $this->createTable('tbl_auth_assignment', array(
            'itemname' => 'var char(64) NOT NULL',
            'userid' => 'int(11) NOT NULL',
            'bizrule' => 'text',
            'data' => 'text',
            'PRIMARY KEY (`itemname`,`userid`)',
        ), 'ENGINE=InnoDB');
 
        //the tbl_auth_assignment.itemname is a reference 
        //to tbl_auth_item.name 
        $this->addForeignKey(
            "fk_auth_assignment_itemname", 
            "tbl_auth_assignment", 
            "itemname", 
            "tbl_auth_item", 
            "name", 
            "CASCADE", 
            "CASCADE"
        );
 
        //the tbl_auth_assignment.userid is a reference 
        //to tbl_user.id 
        $this->addForeignKey(
            "fk_auth_assignment_userid", 
            "tbl_auth_assignment", 
            "userid", 
            "tbl_user", 
            "id", 
            "CASCADE", 
            "CASCADE"
        );
 
    }
 
    public function down()
    {
        $this->truncateTable('tbl_auth_assignment');
        $this->truncateTable('tbl_auth_item_child');
        $this->truncateTable('tbl_auth_item');
        $this->dropTable('tbl_auth_assignment');
        $this->dropTable('tbl_auth_item_child');
        $this->dropTable('tbl_auth_item');
    }
 
}

Open up /protected/config/main.php, and add the table name specification to the authManager component:

// application components
'components'=>array('authManager'=>array(
'class'=>'CDbAuthManager',
'connectionID'=>'db',
'itemTable' =>'tbl_auth_item',
'itemChildTable' =>'tbl_auth_item_child',
'assignmentTable' =>'tbl_auth_assignment',
),

The following diagram displays the basic hierarchy we define:

following diagram

As an example of using the API, the following code creates a new role and a new operation, and then adds the relationship between the role and the permission:

$auth=Yii::app()->authManager;
$role=$auth->createRole('owner');
$auth->createOperation('createProject','create a new project');
$role->addChild('createProject');

The RBAC framework in Yii does not have anything built-in that we can take advantage of to meet this requirement. The RBAC model is only intended to establish relationships between roles and permissions. It does not know (nor should it) anything about our TrackStar projects. In order to achieve this extra dimension to our authorization hierarchy, we need to alter our database structure to contain an association between a user, project, and role.

Table tbl_project_user_assignment is used to join many2many relatin between Users&Projects, add Roles in it:

<?php
 
class m120620_020255_add_role_to_tbl_project_user_assignment extends CDbMigration
{
    public function up()
    {
        $this->addColumn('tbl_project_user_assignment', 'role', 'var char(64)');
        //the tbl_project_user_assignment.role is a reference to tbl_auth_item.name 
        $this->addForeignKey('fk_project_user_role', 'tbl_project_user_assignment', 'role', 'tbl_auth_item', 'name', 'CASCADE', 'CASCADE');
    }
 
    public function down()
    {
        $this->dropForeignKey('fk_project_user_role', 'tbl_project_user_assignment');
        $this->dropColumn('tbl_project_user_assignment', 'role');
    }
 
}

We need to add the public method to the Project AR class that will take in a role name and a user ID and create the association between role, user, and project. In protected/models/Project.php file and add the following method:

/**
     * Assigns a user, in a specific role, to the project
     * @param int $userId the primary key for the user
     * @param string $role the role assigned to the user for the project    
     */
    public function assignUser($userId, $role)
    {
        $command = Yii::app()->db->createCommand();
        $command->insert('tbl_project_user_assignment', array(
            'role'=>$role,
            'user_id'=>$userId,
            'project_id'=>$this->id,
        ));
    }
    /**
     * Removes a user from being associated with the project
     * @param int $userId the primary key for the user
     */
    public function removeUser($userId)
    {
        $command = Yii::app()->db->createCommand();
        $command->delete('tbl_project_user_assignment', 'user_id=:userId AND project_id=:projectId', array(':userId'=>$userId,':projectId'=>$this->id));
    }
    /**
     * Determines whether or not the current application user is in the role for the project
     * @param string $role the role assigned to the user for the project 
     * @return boolean whether or not the user is in the role for this project  
     */
    public function allowCurrentUser($role)
    {
        $sql = "S E L E C T * FROM tbl_project_user_assignment WHERE project_id=:projectId AND user_id=:userId AND role=:role";
        $command = Yii::app()->db->createCommand($sql);
        $command->bindValue(":projectId", $this->id, PDO::PARAM_INT);
        $command->bindValue(":userId", Yii::app()->user->getId(), PDO::PARAM_INT);
 
 
        $command->bindValue(":role", $role, PDO::PARAM_STR);
        return $command->e xecute()==1 ? true : false;
    }

Adding users to projects:

  • Add a public static method called getUserRoleOptions() to the Project model class that returns a valid list of role options using the auth manager's getRoles() method.
  • Add a new public method called isUserInProject($user) to the Project model class to determine if a user is already associated with a project.
  • Add a new form model class called ProjectUserForm, extending from CFormModel for a new input form model. Add to this form model class three attributes, namely $username, $role, and $project. Also add validation rules to ensure that both the username and the role are required input fields, and that the username should further be validated via a custom verify() class method. This verify method should attempt to create a new UserAR class instance by finding a user matching the input username. If the attempt was successful, it should continue to associate the user to a project using the assignUser($userId, $role)method. Also associate the user to the role in our RBAC hierarchy implemented earlier in this chapter. If no user was found matching the username, it needs to set and return an error. (If needed, review the LoginForm::authenticate()method as an example of a custom validation rule method.)
  • Add a new view file under views/project called adduser.php to display our new form for adding users to projects. This form only needs two input fields, username and role. The role should be a drop-down choice listing.
  • Add a new controller action method called actionAdduser() to the ProjectController class and alter its accessRules() method to ensure it is accessible by authenticated members. This new action method is responsible for rendering the new view to display the form and handle the post back when the form is submitted.
//Projects.php - model add:
    /**
     * Returns an array of available roles in which a user can be placed when being added to a project
     */
    public static function getUserRoleOptions()
    {
        return CHtml::listData(Yii::app()->authManager->getRoles(), 'name', 'name');    
    } 
 
    /* 
     * Determines whether or not a user is already part of a project
     */
    public function isUserInProject($user) 
    {
        $sql = "SELECT user_id FROM tbl_project_user_assignment WHERE project_id=:projectId AND user_id=:userId";
        $command = Yii::app()->db->createCommand($sql);
        $command->bindValue(":projectId", $this->id, PDO::PARAM_INT);
        $command->bindValue(":userId", $user->id, PDO::PARAM_INT);
        return $command->e xecute()==1;
    }
 
//ProjectUserForm.php
<?php
/**
 * ProjectUserForm class.
 * ProjectUserForm is the data structure for keeping
 * the form data related to adding an existing user to a project. It is used by the 'Ad-duser' action of 'ProjectController'.
 */
class ProjectUserForm extends CFormModel
{
    /**
     * @var string username of the user being added to the project
     */
    public $username;
 
    /**
     * @var string the role to which the user will be associated within the project
     */
    public $role; 
 
    /**
     * @var object an instance of the Project AR model class
     */ 
    public $project;
 
    private $_user;
 
    /**
     * Declares the validation rules.
     * The rules state that username and password are required,
     * and password needs to be authenticated using the verify() method
     */
    public function rules()
    {
        return array(
            // username and role are required
            array('username, role', 'required'),
            //username needs to be checked for existence 
            array('username', 'exist', 'className'=>'User'),
            array('username', 'verify'),
        );
    }
 
 
    /**
     * Authenticates the existence of the user in the system.
     * If valid, it will also make the association between the user, role and project
     * This is the 'verify' validator as declared in rules().
     */
    public function verify($attribute,$params)
    {
        if(!$this->hasErrors())  // we only want to authenticate when no other input errors are present
        {
            $user = User::model()->findByAttributes(array('username'=>$this->username));
            if($this->project->isUserInProject($user))
            {
                $this->addError('username','This user has already been added to the project.'); 
            }
            else
            {
                $this->_user = $user;
            }
        }
    }
 
    public function assign()
    {
        if($this->_user instanceof User)
        {
 
            //assign the user, in the specified role, to the project
            $this->project->assignUser($this->_user->id, $this->role);  
            //add the association, along with the RBAC biz rule, to our RBAC hierarchy
            $auth = Yii::app()->authManager; 
            if(!$auth->isAssigned($this->role, $this->_user->id))
            {
                $bizRule='return isset($params["project"]) && $params["project"]->allowCurrentUser("'.$this->role.'");';  
                $auth->assign($this->role,$this->_user->id, $bizRule);
            }
            return true;
        }
        else
        {
            $this->addError('username','Error when attempting to assign this user to the project.'); 
            return false;
        }
 
    }
 
    /**
     * Generates an array of usernames to use for the autocomplete
     */
    public function createUsernameList()
    {
        $sql = "SELECT username FROM tbl_user";
        $command = Yii::app()->db->createCommand($sql);
        $rows = $command->queryAll();
        //format it for use with auto complete widget
        $usernames = array();
        foreach($rows as $row)
        {
            $usernames[]=$row['username'];
        }
        return $usernames;
 
    }
}
 
//ProjectControoller - addUser:
    /**
     * Provides a form so that project administrators can
     * associate other users to the project
     */
    public function actionAdduser($id)
    {
        $project = $this->loadModel($id);
        if(!Yii::app()->user->checkAccess('createUser', array('project'=>$project)))
        {
            throw new CHttpException(403,'You are not authorized to perform this action.');
        }
 
        $form=new ProjectUserForm; 
        // collect user input data
        if(isset($_POST['ProjectUserForm']))
        {
            $form->attributes=$_POST['ProjectUserForm'];
            $form->project = $project;
            // validate user input  
            if($form->validate())  
            {
                if($form->assign())
                {
                    Yii::app()->user->setFlash('success',$form->username . " has been added to the project." ); 
                    //reset the form for another user to be associated if desired
                    $form->unsetAttributes();
                    $form->clearErrors();   
                }
            }
        }
        $form->project = $project;
        $this->render('adduser',array('model'=>$form)); 
    }
 
//protected/views/project/adduser.php:
<?php
$this->pageTitle=Yii::app()->name . ' - Add User To Project';
$this->breadcrumbs=array(
    $model->project->name=>array('view','id'=>$model->project->id),
    'Add User',
);
$this->menu=array(
    array('label'=>'Back To Project', 'url'=>array('view','id'=>$model->project->id)),
);
?>
 
<h1>Add User To <?php echo $model->project->name; ?></h1>
 
<?php if(Yii::app()->user->hasFlash('success')):?>
     <div class="successMessage">
          <?php echo Yii::app()->user->getFlash('success'); ?>
     </div>
<?php endif; ?>
 
<div class="form">
<?php $form=$this->beginWidget('CActiveForm'); ?>
 
    <p class="note">Fields with <span class="required">*</span> are required.</p>
 
    <div class="row">
        <?php echo $form->labelEx($model,'username'); ?>
        <?php 
        $this->widget('zii.widgets.jui.CJuiAutoComplete', array(
            'name'=>'username',
            'source'=>$model->createUsernameList(),
            'model'=>$model,
            'attribute'=>'username',
            'options'=>array(
                'minLength'=>'2',
            ),
            'htmlOptions'=>array(
                'style'=>'height:20px;'
            ),
        ));
        ?>
        <?php echo $form->error($model,'username'); ?>
    </div>
 
    <div class="row">
        <?php echo $form->labelEx($model,'role'); ?>
        <?php echo $form->dropDownList($model,'role', Project::getUserRoleOptions()); ?>
        <?php echo $form->error($model,'role'); ?>
    </div>
 
 
    <div class="row buttons">
        <?php echo CHtml::submitButton('Add User'); ?>
    </div>
 
<?php $this->endWidget(); ?>
</div>

Download source code of chapter 7

This post covers my notes on User Management and Auth (chapter 6) in Yii from the book "Web Application Development with Yii and PHP" by Jeffrey Winesett about learning Yii by taking a step-by-step approach to building a Web-based project task tracking system from conception through production deployment - software development life cycle (SDLC) issue-management application.

  • Component behavior
  • Hash the password
  • Yii authentication model

Behaviors in Yii are classes implementing the IBehavior interface, and whose methods can be used to extend the functionality of components by being attached to the component, rather than the component explicitly extending the class. Behaviors can be attached to multiple components and components can attach multiple behaviors. => achieve a kind of multiple inheritance for our Yii component classes.

Rather than just adding the logic directly to our User model class, is because our other model classes, Issue and Project, also need this same logic.

In order for a component to use the methods of a behavior, the behavior has to be attached to the component. :

$component->attachBehavior($name, $behavior);

Zii extension library, already has a ready-made behavior that will update date-time columns, create_time and update_time, which we have on each of our underlying tables. This behavior is called CTimestampBehavior.

protected/
models/User.php:
public function behaviors()
{
return array(
'CTimestampBehavior' => array(
'class' => 'zii.behaviors.CTimestampBehavior',
'createAttribute' => 'create_time',
'updateAttribute' => 'update_time',
'setUpdateOnCreate' => true,
),
);
}

This is great, but we need to repeat this in our other model classes. We could duplicate the behaviors() method in each one. Alternatively, we could put this in a common base class and have each of our model classes extend this new base class. Extending the existing behavior to add this extra functionality would probably make the most sense in a real-world application; however, to demonstrate another approach, let's tap into the active record beforeSave event, and do this in a common base class from which all of our AR model classes can extend. This way, we exposure to a couple of different approaches and have more options to choose from when building other own Yii applications.

So, we need to create a new base class for our AR model classes. We'll also make this new class abstract since it should not be instantiated directly.

//TrackStarAR
<?php
abstract class TrackStarActiveRecord extends CActiveRecord
{
     /**
     * Prepares create_user_id and update_user_id attributes before saving.
     */
 
    protected function beforeSave()
    {
 
        if(null !== Yii::app()->user)
            $id=Yii::app()->user->id;
        else
            $id=1;
 
        if($this->isNewRecord)
            $this->create_user_id=$id;
 
        $this->update_user_id=$id;
 
        return parent::beforeSave();
    }
 
    /**
     * Attaches the timestamp behavior to update our create and update times
     */
    public function behaviors() 
    {
        return array(
            'CTimestampBehavior' => array(
                'class' => 'zii.behaviors.CTimestampBehavior',
                'createAttribute' => 'create_time',
                'updateAttribute' => 'update_time',
                'setUpdateOnCreate' => true,
            ),
        );
    }
 
}
 
/*then we change
class User extends CActiveRecord
{
…}
to
class User extends TrackStarActiveRecord
{
…}*/

For example, we see lines like the following in our create and update controller actions for AR models:

$model->attributes=$_POST['User'];

This is doing a mass assignment of all of the model attributes from the posted form fields. As an added security measure, this only works for attributes that have validation rules assigned for them. You can use the CSafeValidator as a way to mark model attributes that don't otherwise have any validation rules as being safe for this mass assignment. Since these fields are not going to be filled in by the user, and we don't need them to be massively assigned, we can remove the rules. Open up protected/models/User.php and in the rules() method, remove the following two rules:

array('create_user_id, update_user_id', 'numerical',
'integerOnly'=>true),
array('last_login_time, create_time, update_time', 'safe'),

The removal of the rule for the last_login_time attribute above was intentional.

Hash the password This time we'll override the afterValidate() method and apply a basic one-way hash to the password after we validate all the input fields, but before we save the record. User AR class - add the following to the bottom :

/**
     * apply a hash on the password before we store it in the database
     */
    protected function afterValidate()
    {   
        parent::afterValidate();
        //ensure we don't have any other errors
        if(!$this->hasErrors())
            $this->password = $this->hashPassword($this->password);                     
    }
 
    /**
     * Generates the password hash.
     * @param string password
     * @return string hash
     */
    public function hashPassword($password)
    {
        return md5($password);
    }
 
    /**
     * Checks if the given password is correct.
     * @param string the password to be validated
     * @return boolean whether the password is valid
     */
    public function validatePassword($password)
    {
        return $this->hashPassword($password)===$this->password;
    }

Yii authentication model

Central to the Yii authentication framework is an application component called user, configuration can be seen in the protected/config/main.php file, under the components array element:

'user'=>array(
// enable cookie-based authentication
'allowAutoLogin'=>true,
),

The following sequence diagram depicts the class interaction that occurs during a successful login from the time the form is submitted. diagram yii class interaction

Replace the contents of component UserIdentity.php with the following code:

<?php
 
/**
 * UserIdentity represents the data needed to identity a user.
 * It contains the authentication method that checks if the provided
 * data can identity the user.
 */
 
class UserIdentity extends CUserIdentity
{
    private $_id;
 
    public function authenticate()
    {
        $user=User::model()->find('LOWER(username)=?',array(strtolower($this->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->setState('lastLogin', date("m/d/y g:i A", strtotime($user->last_login_time)));
            $user->saveAttributes(array(
                'last_login_time'=>date("Y-m-d H:i:s", time()),
            ));
            $this->errorCode=self::ERROR_NONE;
        }
        return $this->errorCode==self::ERROR_NONE;
    }
 
    public function getId()
    {
        return $this->_id;
    }
}

And since User model class will do the actual password validation, add the following method to User model class:

/**
* Checks if the given password is correct.
* @param string the password to be validated
* @return boolean whether the password is valid
*/
public function validatePassword($password)
{
return $this->hashPassword($password)===$this->password;
}

Download source code of chapter 7

This post covers my notes on Managing Issues (chapter 5) in Yii from the book "Web Application Development with Yii and PHP" by Jeffrey Winesett about learning Yii by taking a step-by-step approach to building a Web-based project task tracking system from conception through production deployment - software development life cycle (SDLC) issue-management application.

  • S c h e m a DB & migrations (atomic)
  • UI for forms
  • Filter - Enforcing a project context
  • Views - get data from other models

The following figure outlines a basic entity-relationship between the users, projects, and issues. Projects can have zero to many users. A user needs to be associated with at least one project but can be associated with many. Issues belong to one and only one project, while projects can have from zero to many issues. Finally an issue is assigned to (or requested by) one single user. s

Run

yiic migrate create create_issue_user_and_assignment_tables

implemented the safeUp() and safeDown() methods:

<?php
 
class m120511_173401_create_issue_user_and_assignment_tables extends CDbMigration
{
    // Use safeUp/safeDown to do migration with transaction
    public function safeUp()
    {
        //create the issue table
        $this->createTable('tbl_issue', array(
            'id' => 'pk',
            'name' => 'string NOT NULL',
            'description' => 'text',
            'project_id' => 'int(11) DEFAULT NULL',
            'type_id' => 'int(11) DEFAULT NULL',
            'status_id' => 'int(11) DEFAULT NULL',
            'owner_id' => 'int(11) DEFAULT NULL',
            'requester_id' => 'int(11) DEFAULT NULL',
            'create_time' => 'datetime DEFAULT NULL',
            'create_user_id' => 'int(11) DEFAULT NULL',
            'update_time' => 'datetime DEFAULT NULL',
            'update_user_id' => 'int(11) DEFAULT NULL',
         ), 'ENGINE=InnoDB');
 
        //create the user table
        $this->createTable('tbl_user', array(
            'id' => 'pk',
            'username' => 'string NOT NULL',
            'email' => 'string NOT NULL',
            'password' => 'string NOT NULL',
            'last_login_time' => 'datetime DEFAULT NULL',
            'create_time' => 'datetime DEFAULT NULL',
            'create_user_id' => 'int(11) DEFAULT NULL',
            'update_time' => 'datetime DEFAULT NULL',
            'update_user_id' => 'int(11) DEFAULT NULL',
         ), 'ENGINE=InnoDB');
 
        //create the assignment table that allows for many-to-many relationship between projects and users
        $this->createTable('tbl_project_user_assignment', array(
            'project_id' => 'int(11) DEFAULT NULL',
            'user_id' => 'int(11) DEFAULT NULL',
            'PRIMARY KEY (`project_id`,`user_id`)',
         ), 'ENGINE=InnoDB');
 
        //foreign key relationships
 
        //the tbl_issue.project_id is a reference to tbl_project.id 
        $this->addForeignKey("fk_issue_project", "tbl_issue", "project_id", "tbl_project", "id", "CASCADE", "RESTRICT");
 
        //the tbl_issue.owner_id is a reference to tbl_user.id 
        $this->addForeignKey("fk_issue_owner", "tbl_issue", "owner_id", "tbl_user", "id", "CASCADE", "RESTRICT");
 
        //the tbl_issue.requester_id is a reference to tbl_user.id 
        $this->addForeignKey("fk_issue_requester", "tbl_issue", "requester_id", "tbl_user", "id", "CASCADE", "RESTRICT");
 
        //the tbl_project_user_assignment.project_id is a reference to tbl_project.id 
        $this->addForeignKey("fk_project_user", "tbl_project_user_assignment", "project_id", "tbl_project", "id", "CASCADE", "RESTRICT");
 
        //the tbl_project_user_assignment.user_id is a reference to tbl_user.id 
        $this->addForeignKey("fk_user_project", "tbl_project_user_assignment", "user_id", "tbl_user", "id", "CASCADE", "RESTRICT");
 
    }
 
    public function safeDown()
    {
        $this->truncateTable('tbl_project_user_assignment');
        $this->truncateTable('tbl_issue');
        $this->truncateTable('tbl_user');
        $this->dropTable('tbl_project_user_assignment');
        $this->dropTable('tbl_issue');
        $this->dropTable('tbl_user');
    }
 
}

Here we have implemented the safeUp() and safeDown() methods rather than the standard up() and down() methods. **Doing this runs these statements in a database transaction with the intent that they are committed or rolled back as a single unit. **

In Issue model changed a few lines:

class Issue extends CActiveRecord
{
    const TYPE_BUG=0;
    const TYPE_FEATURE=1;
    const TYPE_TASK=2;
 
    const STATUS_NOT_STARTED=0;
    const STATUS_STARTED=1;
    const STATUS_FINISHED=2;

Look at the relations:

public function relations()
    {
        // NOTE: you may need to adjust the relation name and the related
        // class name for the relations automatically generated below.
        return array(
            'requester' => array(self::BELONGS_TO, 'User', 'requester_id'),
            'owner' => array(self::BELONGS_TO, 'User', 'owner_id'),
            'project' => array(self::BELONGS_TO, 'Project', 'project_id'),
        );
    }

Adding the issue type drop-down. Normally, gen views look like:

<div class="row">
<?php echo $form->labelEx($model,'type_id'); ?>
<?php echo $form->textField($model,'type_id'); ?>
<?php echo $form->error($model,'type_id'); ?>
</div>
 
//replace the line $form->textField with :
<?php echo $form->dropDownList($model,'type_id', $model-
>getTypeOptions()); ?>

It should be noted that Yii framework base classes make use of the PHP _get "magic" function. This allows us, in our child classes, to write methods such as getTypeOptions() and reference those methods as class properties, using the syntax ->typeOptions. So we could have also used the equivalent syntax when requesting our issue type options array $model->typeOptions.

When uksin drop-down fields, it is good practice to also add a range validation to rules() method to ensure that the submitted value falls within the range of the values allowed by the drop-down. The CRangeValidator attribute, which uses an alias of in, is a good choice to use for defining this validation rule. So we could define such a rule as follows:

array('type_id', 'in', 'range'=>self::getAllowedTypeRange()),
//add a method to return an array of our allowed numerical type values
 
    public function getTypeOptions()
    {
        return array(
            self::TYPE_BUG=>'Bug',
            self::TYPE_FEATURE=>'Feature',
            self::TYPE_TASK=>'Task',
        );
    }

Fixing the owner and requester fields

Another problem we notice with the issue creation form is that the owner and requester fields are also freeform text-input fields. However, we know that these are integer values in the issue table that hold foreign key identifiers to the id column of the tbl_user table. One more problem - need to manage issues within the context of a specific project. That is, a specific project should be chosen before you are able to create a new issue. Currently, application does not enforce this workflow.

Enforcing a project context to ensure that a valid project context is present before we allow access to managing the issues. To do this, we are going to implement what is called a filter. A filter in Yii is a bit of code that is configured to be executed either before or after a controller action is executed. One common example is if we want to ensure that a user is logged in prior to executing a controller action method.

//Defining filters in IssueController.php
    public function filterProjectContext($filterChain)
    {   
        //set the project identifier based on either the GET input 
        //request variables   
        if(isset($_GET['pid']))
            $this->loadProject($_GET['pid']);   
        else
            throw new CHttpException(403,'Must specify a project before performing this action.');
 
        //complete the running of other filters and execute the requested action
        $filterChain->run(); 
    }

We need to add our new filter to this configuration array. To specify that our new filter should be applied to the create action, alter the IssueController::filters() method by adding code

public function filters()
    {
        return array(
            'accessControl', // perform access control for CRUD operations
            'projectContext + create index admin', //check to ensure valid project context
        );
    }

The filters() method should return an array of filter configurations. The previous code returns a configuration that specifies that the projectContext filter, which is defined as a method within the class, should be applied to the actionCreate() method. The configuration syntax allows for the "+" and "-" symbols to be used to specify whether a filter should or should not be applied. If filter to be applied to all the actions except the actionUpdate() and actionView() action methods, we could specify:

return array(
'projectContext - update, view' ,
);

We'll add a project property to the controller class itself. We'll then use a q uerystring parameter in our URLs to indicate the project identifier.

P rivate $ _p r o j e c t = n u l l; //containing the associated Project model instance.
 
    /**
     * Protected method to load the associated Project model class
     * @param integer projectId the primary identifier of the associated Project
     * @return object the Project data model based on the primary key 
     */
    protected f unction loadProject($projectId)  
    {
        if($this->_project===n u l l)
        {
        if($this->_project===n u l l)
        {
            $this->_project=Project::model()->findByPk($projectId);
            if($this->_project===null)
            {
                T h r o w n e w CHttpException(404,'The requested project does not exist.'); 
            }
        }
 
        return $this->_project; 
    }

With this in place, if attempt to create a new issue by clicking on the Create Issue you should see an "Error 403".

Resulted menu:

$this->menu=array(
array('label'=>'List Project', 'url'=>array('index')),
array('label'=>'Create Project', 'url'=>array('create')),
array('label'=>'Update Project', 'url'=>array('update',
'id'=>$model->id)),
array('label'=>'Delete Project', 'url'=>'#', 'linkOptions'=>array('s
ubmit'=>array('delete','id'=>$model->id),'confirm'=>'Are you sure you
want to delete this item?')),
array('label'=>'Manage Project', 'url'=>array('admin')),
array('label'=>'Create Issue', 'url'=>array('issue/create',
'pid'=>$model->id)),
);

So alter the IssueController::actionCreate() method as the following highlighted code suggests:

public function actionCreate()
{
$model=new Issue;
$model->project_id = $this->_project->id;

With these in place, we can easily access all of the issues and/or users associated with a project with incredibly easy syntax. For example:

//instantiate the Project model instance by primary key:
$project = Project::model()->findByPk(1);
//get an array of all associated Issue AR instances
$allProjectIssues = $project->issues;
//get an array of all associated User AR instance
$allUsers = $project->users;
//get the User AR instance representing the owner of
//the first issue associated with this project
$ownerOfFirstIssue = $project->issues[0]->owner;

Open up the view file containing the input form elements /protected/views/issue/_form.php, and find the two text-input field form element definitions for owner_id and requester_id and replace it with the following code:

<?php echo $form->textField($model,'owner_id'); ?>
//with this:
<?php echo $form->dropDownList($model,'owner_id', $model->project-
>getUserOptions()); ?>
//and also replace this line:
<?php echo $form->textField($model,'requester_id'); ?>
//with this:
<?php echo $form->dropDownList($model,'requester_id', $model->project-
>getUserOptions()); ?>

Altering the project controller - actionView() method in the ProjectController class to display a list of the issues associated with a specific project:

public function actionView($id)
    {
        $issueDataProvider=new CActiveDataProvider('Issue', array(
            'criteria'=>array(
                'condition'=>'project_id=:projectId',
                'params'=>array(':projectId'=>$this->loadModel($id)->id),
            ),
            'pagination'=>array(
                'pageSize'=>1,
            ),
         ));
 
        $this->render('view',array(
            'model'=>$this->loadModel($id),
            'issueDataProvider'=>$issueDataProvider,
        ));
 
    }

Altering view.php and add this to the bottom of that file:

<br />
<h1>Project Issues</h1>
<?php $this->widget('zii.widgets.CListView', array(
'dataProvider'=>$issueDataProvider,
'itemView'=>'/issue/_view',
)); ?>
 
// and the last thing - /protected/views/issue/_view.
php file that we specified as a layout template for each issue. Alter the entire contents
of that file to be the following:
<div class="view">
<b><?php echo CHtml::encode($data->getAttributeLabel('name')); ?>:</
b>
<?php echo CHtml::link(CHtml::encode($data->name), array('issue/
view', 'id'=>$data->id)); ?>
<br />
<b><?php echo CHtml::encode($data->getAttributeLabel('descripti
on')); ?>:</b>
<?php echo CHtml::encode($data->description); ?>
<br />
<b><?php echo CHtml::encode($data->getAttributeLabel('type_id'));
?>:</b>
<?php echo CHtml::encode($data->type_id); ?>
<br />
<b><?php echo CHtml::encode($data->getAttributeLabel('status_id'));
?>:</b>
<?php echo CHtml::encode($data->status_id); ?>
</div>

To display the username of the owner and requester (originally in _view "not set" is value) User class instances, change CDetailView configuration to the following (using relations of Issue model access data):

<?php $this->widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>array(
'id',
'name',
'description',
array(
'name'=>'type_id',
'value'=>CHtml::encode($model->getTypeText())
),
array(
'name'=>'status_id',
'value'=>CHtml::encode($model->getStatusText())
),
array(
'name'=>'owner_id',
'value'=>isset($model->owner)?CHtml::encode($model->owner-
>username):"unknown"
),
array(
'name'=>'requester_id',
'value'=>isset($model->requester)?CHtml::encode($model-
>requester->username):"unknown" ),
),
)); ?>

With this in place, the associated project will be loaded and available for use. Let's use it in our IssueController::actionIndex() method. Alter that method to be:

public function actionIndex()
{
$dataProvider=new CActiveDataProvider('Issue', array(
'criteria'=>array(
'condition'=>'project_id=:projectId',
'params'=>array(':projectId'=>$this->_project->id),
),
));
$this->render('index',array(
'dataProvider'=>$dataProvider,
));
}

Then we need to add to our criteria in the Issue::search() model class method.

public function search()
{
// Warning: Please modify the following code to remove attributes
that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id);
$criteria->compare('name',$this->name,true);
$criteria->compare('description',$this->description,true);
$criteria->compare('type_id',$this->type_id);
$criteria->compare('status_id',$this->status_id);
$criteria->compare('owner_id',$this->owner_id);
$criteria->compare('requester_id',$this->requester_id);
$criteria->compare('create_time',$this->create_time,true);
$criteria->compare('create_user_id',$this->create_user_id);
$criteria->compare('update_time',$this->update_time,true);
$criteria->compare('update_user_id',$this->update_user_id);
$criteria->condition='project_id=:projectID';
$criteria->params=array(':projectID'=>$this->project_id);
return new CActiveDataProvider(get_class($this), array(
'criteria'=>$criteria,
));
}

Download source code of chapter 5

This post covers my notes on Project CRUD (chapter 4) in Yii from the book "Web Application Development with Yii and PHP" by Jeffrey Winesett about learning Yii by taking a step-by-step approach to building a Web-based project task tracking system from conception through production deployment - software development life cycle (SDLC) issue-management application.

  • Yii migration utility
  • predefined validator classes and aliases

At the end of our efforts in this chapter, our application should allow users to create new projects, select from a list of existing projects, update/edit existing projects, and delete existing projects.

The Yii migration utility is a console command that we use with the yiic command-line tool. As a console command, it uses a configuration file specific to console commands, which, by default, is protected/config/console.php.

Creating a migration takes the general form of:

yiic migrate create <name>

The result: file php with migration

Should be mentioned that Yii provides many predefined validator classes and provides aliases with which to reference these when defining rules (array validation rules for model attributes - public function rules()). The complete list of predefined validator class aliases as of Yii Version 1.1.12 is as follows:

  • boolean: Alias of CBooleanValidator, validates the attribute that contains either true or false
  • captcha: Alias of CCaptchaValidator, validates the attribute value that is same as the verification code displayed in a CAPTCHA
  • compare: Alias of CCompareValidator, compares two attributes and validates they are equal
  • email: Alias of CEmailValidator, validates the attribute value that is a valid e-mail address
  • date: Alias of CDateValidator, validates the attribute value that is a valid date, time, or date-time value
  • default: Alias of CDefaultValueValidator, assigns a default value to the attributes specified
  • exist: Alias of CExistValidator, validates the attribute value against a specified table column in a database
  • file: Alias of CFileValidator, validates the attribute value that contains the name of an uploaded file
  • filter: Alias of CFilterValidator, transforms the attribute value with a specified filter
  • in: Alias of CRangeValidator, validates if the data is within a prespecified range of values, or exists within a specified list of values
  • length: Alias of CStringValidator, validates whether the length of the attribute value is within a specified range
  • match: Alias of CRegularExpressionValidator, uses a regular expression to validate the attribute value
  • numerical: Alias of CNumberValidator, validates whether the attribute value is a valid number
  • required: Alias of CRequiredValidator, validates whether the attribute value is empty or not
  • type: alias of CTypeValidator, validates whether the attribute value is of a specific data type
  • unique: Alias of CUniqueValidator, validates that the attribute value is unique, and is compared against a database table column
  • url: Alias of CUrlValidator, validates whether the attribute value is a valid URL

Download source code of chapter 4

This post covers my notes on Intro and Tests (chapters 1-3) in Yii from the book "Web Application Development with Yii and PHP" by Jeffrey Winesett about learning Yii by taking a step-by-step approach to building a Web-based project task tracking system from conception through production deployment - software development life cycle (SDLC) issue-management application.

  • philosophy
  • DRY & MVC
  • Unit tests
  • Functional test

Yii also embraces a convention over configuration philosophy, which contributes to its ease of use. This means that Yii has sensible defaults for almost all the aspects that are used for configuring your application. Following the prescribed conventions, you can write less code and spend less time developing your application. However, Yii does not force your hand. It allows you to customize all of its defaults and makes it easy to override all of these conventions.

Yii is also designed to help you with DRY development. DRY stands for Don't Repeat Yourself, a key concept of agile application development. All Yii applications are built using the Model-View-Controller (MVC) architecture. Yii enforces this development pattern by providing a place to keep each piece of your MVC code. This minimizes duplication and helps promote code reuse and ease of maintainability. The less code you need to write, the less time it takes to get your application to market. The easier it is to maintain your application, the longer it will stay on the market.

Typically in an MVC architecture, the model is responsible for maintaining the state, and should encapsulate the business rules that apply to the data that defines this state. A model in Yii is any instance of the framework class CModel or its child class. A model class is typically comprised of data attributes that can have separate labels (something user friendly for the purpose of display), and can be validated against a set of rules defined in the model. The data that makes up the attributes in the model class could come from a row of a database table or from the fields in a user input form. Yii implements two kinds of models, namely the form model (a CFormModel class) and active record (a CActiveRecord class). The class CFormModel represents a data model that collects HTML form inputs. It encapsulates all the logic for form field validation, and any other business logic that may need to be applied to the form field data. Active Record (AR) is a design pattern used to abstract database access in an objectoriented fashion. Each AR object in Yii is an instance of CActiveRecord or its child class, which wraps a single row in a database table or view, that encapsulates all the logic and details around database access, and houses much of the business logic that is required to be applied to that data.

Typically the view is responsible for rendering the user interface, often based on the data in the model. A view in Yii is a PHP script that contains user interface-related elements, often built using HTML, but can also contain PHP statements.

The controller is our main director of a routed request, and is responsible for taking user input, interacting with the model, and instructing the view to update and display appropriately.

Web applications need the data that is held in the persistent database storage to be mapped to in-memory class properties that define the domain objects. Object-relational mapping (ORM) libraries provide this mapping of database tables to domain object classes. Much of the code that deals with ORM is about describing how fields in the database correspond to properties in our in-memory objects, and is tedious and repetitive to write. Luckily, Yii comes to the rescue and saves us from this repetition and tedium by providing an ORM layer in the form of the Active Record (AR) pattern. Yii provides great support for database programming. Yii's Data Access Objects (DAO) are built on top of the PHP Data Objects (PDO) extension (http://php.net/pdo). This is a database abstraction layer that enables the application to interact with the database through a database-independent interface. All the supported database management systems (DBMS) are encapsulated behind a single uniform interface. In this way, the code can remain database independent and the applications developed using Yii DAO can easily be switched to use a different DBMS without the need for modification.

Unit tests are the tests that focus on the smallest units within a software application. In an object-oriented application, such as a Yii web application, the smallest units are the public methods that make up the interfaces to the classes. Unit tests should focus on one single class and not require other classes or objects to run it. Their purpose is to validate that a single unit of code is working as expected.

Functional tests focus on testing the end-to-end feature functionality of the application. These tests exist at a higher level than the unit tests and typically require multiple classes or objects to run. Their purpose is to validate that a given feature of the application is working as expected.

As of version 1.1, Yii is tightly integrated with the PHPUnit (http://www.phpunit. de/) and Selenium Remote Control (http://seleniumhq.org/projects/remotecontrol/) testing frameworks.

When used the yiic webapp console command to create new web application, files relevant to writing and executing automated tests are the following: trackstar/ Contains all the files listed in the file/directory column protected/ Protected application files tests/ Tests for the application fixtures/ Database fixtures functional/ Functional tests unit/ Unit tests report/ Coverage reports bootstrap.php The script executed at the very beginning of the tests phpunit.xml/ The PHPUnit configuration file WebTestCase.php/ The base class for web-based functional tests

First test ever:

//DbTest.php
<?php
class DbTest extends CTestCase
{  
     public function testConnection()
     {
        $this->assertNotNull(Yii::app()->db->connectionString);  
     } 
 
}

Here we have added a fairly trivial test. The assertTrue() method, which is a part of PHPUnit, is an assertion that will pass if the argument passed to it is true, and it will fail if it is false. In this case, it is testing if true is true. Change the assertEquals(true) statement in the testConnection() test method to:

$this->assertNotNull(Yii::app()->db->connectionString);

Configured database connection as an application component named db, Yii::app()->db should return an instance of the CDbConnection class. If the application failed to establish a database connection, this test would return an error. Since the test still passes, we can move forward with the confidence that the database connection is set up properly.

Generated automatically functional test SiteController:

<?php
 
class SiteTest extends WebTestCase
{
    public function testIndex()
    {
        $this->open('');
        $this->assertTextPresent('Welcome');
    }
 
    public function testContact()
    {
        $this->open('?r=site/contact');
        $this->assertTextPresent('Contact Us');
        $this->assertElementPresent('name=ContactForm[name]');
 
        $this->type('name=ContactForm[name]','tester');
        $this->type('name=ContactForm[email]','tester@example.com');
        $this->type('name=ContactForm[subject]','test subject');
        $this->click("//input[@value='Submit']");
        $this->waitForTextPresent('Body cannot be blank.');
    }
 
    public function testLoginLogout()
    {
        $this->open('');
        // ensure the user is logged out
        if($this->isTextPresent('Logout'))
            $this->clickAndWait('link=Logout (demo)');
 
        // test login process, including validation
        $this->clickAndWait('link=Login');
        $this->assertElementPresent('name=LoginForm[username]');
        $this->type('name=LoginForm[username]','demo');
        $this->click("//input[@value='Login']");
        $this->waitForTextPresent('Password cannot be blank.');
        $this->type('name=LoginForm[password]','demo');
        $this->clickAndWait("//input[@value='Login']");
        $this->assertTextNotPresent('Password cannot be blank.');
        $this->assertTextPresent('Logout');
 
        // test logout process
        $this->assertTextNotPresent('Login');
        $this->clickAndWait('link=Logout (demo)');
        $this->assertTextPresent('Login');
    }
}

Download source code of chapter 1-2, chapter 3

In this assignment, you will be designing and implementing MapReduce algorithms for a variety of common data processing tasks. Problem 6: Assume you have two matrices A and B in a sparse matrix format, where each record is of the form i, j, value. Design a MapReduce algorithm to compute matrix multiplication: A x B

Map Input

The input to the map function will be matrix row records formatted as lists. Each list will have the format [matrix, i, j, value] where matrix is a string and i, j, and value are integers.

The first item, matrix, is a string that identifies which matrix the record originates from. This field has two possible values:

    ‘a’ indicates that the record is from matrix A

    ‘b’ indicates that the record is from matrix B

Reduce Output

The output from the reduce function will also be matrix row records formatted as tuples. Each tuple will have the format (i, j, value) where each element is an integer.

You can test your solution to this problem using matrix.json:

    python multiply.py matrix.json

You can verify your solution against multiply.json.

import MapReduce
import sys
 
"""
Word Count Example in the Simple Python MapReduce Framework
"""
 
mr = MapReduce.MapReduce()
 
# =============================
# Do not modify above this line
 
def mapper(record):
    # key: document identifier
    # value: document contents
    matrix, row, col, value = record
    for n in range(5):
        if matrix == 'a':
            cell = (row,n)
            matr = 'L'
            col_row = col
        else:
            cell = (n,col)
            matr = 'R'
            col_row = row
        mr.emit_intermediate(cell, (matr, col_row, value))
    #key = (cell, matrix, col_row, value)
 
 
def reducer(key, list_of_values):
    # key: word
    # value: list of occurrence counts
    left_matrix  = [(item[1],item[2]) for item in list_of_values if item[0] == 'L' ]
    right_matrix = [(item[1],item[2]) for item in list_of_values if item[0] == 'R' ]
 
    result = 0
 
    for item_L in left_matrix:
        for item_R in right_matrix:
            if item_L[0] == item_R[0] :
                result += item_L[1] * item_R[1]
 
    #mr.emit((key[0], item[2], result))
    if result != 0:
        mr.emit((key[0],key[1], result))  
 
# Do not modify below this line
# =============================
if __name__ == '__main__':
  inputdata = open(sys.argv[1])
  mr.e xecute(inputdata, mapper, reducer)

In this assignment, you will be designing and implementing MapReduce algorithms for a variety of common data processing tasks. Problem 5: Consider a set of key-value pairs where each key is sequence id and each value is a string of nucleotides, e.g., GCTTCCGAAATGCTCGAA.... Write a MapReduce query to remove the last 10 characters from each string of nucleotides, then remove any duplicates generated.

Map Input

The input is a 2 element list: [sequence id, nucleotides]

sequence id: Unique identifier formatted as a string

nucleotides: Sequence of nucleotides formatted as a string Reduce Output

The output from the reduce function should be the unique trimmed nucleotide strings.

You can test your solution to this problem using dna.json:

    python unique_trims.py dna.json

You can verify your solution against unique_trims.json.

import MapReduce
import sys
 
"""
Word Count Example in the Simple Python MapReduce Framework
"""
 
mr = MapReduce.MapReduce()
 
# =============================
# Do not modify above this line
def mapper(record):
    # key: document identifier
    # value: document contents
    trim_nucleotid = record[1][:-10]
    mr.emit_intermediate(trim_nucleotid, 1 )
 
def reducer(trim_nucleotid, list_of_values):
    # key: word
    # value: list of occurrence counts
    #mr.emit((person,len(list_of_values)) )
    mr.emit(trim_nucleotid)
 
 
# Do not modify below this line
# =============================
if __name__ == '__main__':
  inputdata = open(sys.argv[1])
  mr.e xecute(inputdata, mapper, reducer)

In this assignment, you will be designing and implementing MapReduce algorithms for a variety of common data processing tasks. Problem 4 The relationship "friend" is often symmetric, meaning that if I am your friend, you are my friend. Implement a MapReduce algorithm to check whether this property holds. Generate a list of all non-symmetric friend relationships.

Map Input

The input is a 2 element list: [personA, personB]

personA: Name of a person formatted as a string

personB: Name of one of personA’s friends formatted as a string

This implies that personB is a friend of personA, but it does not imply that personA is a friend of personB. Reduce Output

The output should be the (person, friend) and (friend, person) tuples for each asymmetric friendship.

Note however that only one of the (person, friend) or (friend, person) output tuples will exist in the input. This indicates friendship asymmetry.

You can test your solution to this problem using friends.json:

    python asymmetric_friendships.py friends.json

You can verify your solution against asymmetric_friendships.json.

import MapReduce
import sys
 
"""
Word Count Example in the Simple Python MapReduce Framework
"""
 
mr = MapReduce.MapReduce()
 
# =============================
# Do not modify above this line
def mapper(record):
    # key: document identifier
    # value: document contents
    person = record[0]
    #mr.emit_intermediate(person,1)
    pair = tuple(sorted(record))
    mr.emit_intermediate(pair , 1)
 
def reducer(pair, list_of_values):
    # key: word
    # value: list of occurrence counts
    #mr.emit((person,len(list_of_values)) )
    if len(list_of_values) == 1:
        mr.emit((pair[0], pair[1]) )
        mr.emit((pair[1], pair[0]) )   
 
# Do not modify below this line
# =============================
if __name__ == '__main__':
  inputdata = open(sys.argv[1])
  mr.e xecute(inputdata, mapper, reducer)

Go to page: